Лучший способ подкласса подключаемых представлений Flask для расширяемой функциональности

Я создаю веб-приложение, в котором разные представления будут иметь разное количество «обернутых функций» (например, аутентификация, ведение журнала/обработка ошибок, доступ к базе данных и т. д.) и смогут легко обмениваться этими функциями между представлениями.

Я думаю, что сменные представления были бы хорошим способом справиться с этим, многократно создавая подклассы представлений для создания уровней функциональности, которые обертывают основную работу представления.

Однако я изо всех сил пытаюсь найти лучший способ реализовать это. Я думаю о цепочке декораторов, но наследование, похоже, не работает.

Например, упрощенное представление с некоторыми настраиваемыми журналами и обработкой ошибок:

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)

Представление можно использовать как:

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

Затем, если бы я хотел использовать это представление, чтобы также добавить аутентификацию пользователя:

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)

Затем я хочу повторить этот подкласс, чтобы добавить дополнительные функции, такие как доступ к базе данных.

  • Есть ли лучший способ добиться того, что я пытаюсь сделать?
  • Имеет ли смысл использовать декораторы в качестве методов просмотра? Работает ли использование «я» в декораторе?

Любые предложения будут высоко ценится!


person Finn Andersen    schedule 03.08.2018    source источник


Ответы (1)


decorators — это список, изменяемая структура. Вы не можете просто добавить его в подкласс. Имя decorators не определено в подклассе, и если вы добавите LoggedView.decorators, вы добавите не тот список!

Вам придется создать новый объект списка в подклассе, чтобы замаскировать атрибут базового класса; вы можете создать его, соединившись с последовательностью базового класса; Я использовал кортежи здесь, чтобы сделать это более понятным:

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

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

Обратите внимание, что декораторы — это не методы, они не привязаны к представлению при применении, поэтому здесь нет аргумента self.

Если вам нужен доступ к экземпляру представления из декоратора, то не используйте View.decorators, они украшают простую функцию, которая при вызове создает представление перед вызовом View.dispatch_request() для этого представления; именно эта простая функция возвращается при вызове View.as_view(). С другой стороны, если вам нужно иметь доступ к обертке, которую создает декоратор при регистрации маршрута или (в другом направлении) при поиске зарегистрированного представления для конечной точки, то использование View.decorators совершенно правильно.

Вы бы либо декорировали методы напрямую (включая dispatch_request()), либо реализовали свой собственный механизм в 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)

Это путь, который проект Flask-RESTFul использует для указания декораторов для всех методов в представлении одной строкой.

Затем извлеките аргумент self из аргументов вызова оболочки (но передайте его обернутой функции):

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

Я бы определил сами функции декоратора вне классов представления.

person Martijn Pieters    schedule 03.08.2018
comment
Спасибо за ответ. Я подумал, что, возможно, декораторы как методы будут иметь доступ к себе из-за замыканий. Я не совсем уверен, что происходит в этом механизме запроса на отправку, похоже, что он каким-то образом привязывает методы класса к функции декоратора, но где происходит фактическое украшение? Могу ли я просто напрямую декорировать dispatch_request слоями упаковки и, возможно, использовать flask global g для доступа к вещам, которые мне нужны, в функциях декоратора? - person Finn Andersen; 04.08.2018
comment
@FinnAndersen: декораторы применяются к новой функции, которую возвращает View.as_view(); у этого есть то преимущество, что вы можете связаться с декораторами через API-интерфейсы Flask для конечных точек и маршрутизации. Если вам это не нужно, то можно применить декораторы к dispatch_request(). - person Martijn Pieters; 04.08.2018
comment
Разве as_view() не использует dispatch_request() для создания функции маршрутизации, поэтому нужно применить к ней какие-либо декораторы? - person Finn Andersen; 05.08.2018
comment
@FinnAndersen: нет, as_view() создает новую функцию, которая при вызове создает экземпляр класса представления и вызывает dispatch_request для этого нового экземпляра. Декораторы на dispatch_request работают нормально, просто они не регистрируются в качестве конечной точки в Flask. Иногда вам нужно последнее. - person Martijn Pieters; 05.08.2018
comment
Хорошо, спасибо за разъяснение. Что было бы преимуществом или вариантом использования, когда было бы полезно зарегистрировать декораторы в качестве конечной точки или найти зарегистрированное представление для конечной точки? - person Finn Andersen; 06.08.2018
comment
@FinnAndersen: скажем, вы хотите перечислить все представления, требующие входа в систему. Вы можете запросить у Flask все маршруты и проверить каждую зарегистрированную функцию (проверить имя или определенный атрибут и т. д.). Это не сработает, если вы декорировали dispatch_request(). - person Martijn Pieters; 06.08.2018
comment
Хорошо, спасибо, я пойду с этим сейчас и посмотрю, не столкнусь ли я с какими-либо проблемами - person Finn Andersen; 07.08.2018