简体   繁体   中英

Best way to subclass Flask pluggable views for extensible functionality

I'm building a webapp where different views will have different amounts of 'wrapped functionality' (eg authentication, logging/error handling, database access etc), and be able to easily share this functionality between views.

I'm thinking Pluggable Views would be good way to handle this, by repeatedly subclassing views to build up layers of functionality which wraps the main operation of the view.

However I'm struggling to work out the best way to implement this. I'm thinking of chaining up decorators but the inheritance doesn't seem to work well.

For example, a simplified view with some custom logging and error handling:

from flask.views import View

class LoggedView(View):
    def __init__(self,template):
         self.template=template

    #Decorator method for error handling and logging
    def log_view(self,view):
        def decorator(**kwargs):
            try:
                #Set up custom logging
                self.log = .....
                #Execute view
                return view(**kwargs)
            except CustomApplicationError as e:
                #Log and display error
                self.log.error(e)
                return render_template('error.html',error=str(e))
         return decorator

    decorators=[log_view]

    #This can be overridden for more complex views
    def dispatch_request(self):
        return render_template(self.template)

View can be used like:

app.add_url_rule('/index', view_func=LoggedView.as_view('index',template='index.html'))

Then if I wanted to build upon this view to also add user authentication:

class RestrictedView(LoggedView):

    #Decorator method for user access validation
    def validate_access(self,view):
        def decorator(**kwargs):
            g.user=session.get('user')
            if g.user is None:
                return redirect(url_for('login'))
            #Execute view
            return view(**kwargs)
         return decorator

    #How to add this functionality to the decorator chain? e.g. I dont think this works: 
    decorators.append(validate_access)

Then I want to repeat this subclassing to add further functionality such as database access

  • Is there a better way to achieve what I'm trying to do?
  • Does having the decorators as view methods make sense? Does using 'self' within the decorator work?

Any suggestions would be much appreciated!

decorators is a list, a mutable structure. You can't just append to it in a subclass. The name decorators is not defined in a subclass, and if you'd append to LoggedView.decorators you'd be appending to the wrong list!

You'd have to create a new list object in a subclass to mask the attribute on the base class; you can contruct one by concatenating to the base class sequence; I used tuples here to make this clearer:

class LoggedView(View):
    decorators = (log_view,)

class RestrictedView(LoggedView):
    decorators = LoggedView.decorators + (validate_access,)

Note that decorators are not methods , they are not bound to the view when they are applied, so there is no self argument.

If you need access to the view instance from a decorator, then don't use View.decorators , those decorate a simple function that when called creates the view before calling View.dispatch_request() on that view; it is this simple function that is returned when you call View.as_view() . On the other hand, if you need to be able to access the wrapper a decorator produces when registering the route or (in the other direction) when looking up the registered view for an endpoint, then using View.decorators is exactly correct.

You'd either decorate the methods directly (including dispatch_request() ) or implement your own mechanism in dispatch_request() :

import inspect

class LoggedView(View):
    method_decorators = (log_view,)

    #This can be overridden for more complex views
    def dispatch_request(self):
        # decorate methods
        cls = type(self)
        members = vars(type(self)).items()
        for name, object in members:
            if not inspect.isfunction(object):
                continue
            if name == 'dispatch_request':
                continue
            # add bound decorated functions to the view
            for d in self.method_decorators:
                setattr(self, name, d(object).__get__(self, cls))

        # dispatch
        return render_template(self.template)

This is the path that the Flask-RESTFul project uses to allow specifying decorators for all methods on a view with one line.

Then pull the self argument from the wrapper call arguments (but do pass it on to the wrapped function):

def log_view(view):
    def decorator(self, **kwargs):
        try:
            #Set up custom logging
            self.log = .....
            #Execute view
            return view(self, **kwargs)
        except CustomApplicationError as e:
            #Log and display error
            self.log.error(e)
            return render_template('error.html',error=str(e))
     return decorator

I'd define the decorator functions themselves outside of the view classes.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM