Python декораторы и наследование классов - PullRequest
9 голосов
/ 31 июля 2010

Я пытаюсь использовать декораторы для управления тем, как пользователи могут или не могут получить доступ к ресурсам в веб-приложении (запущенном в Google App Engine). Обратите внимание, что я не разрешаю пользователям входить в систему со своими учетными записями Google, поэтому установка определенных прав доступа к определенным маршрутам в app.yaml не является опцией.

Я использовал следующие ресурсы:
- Руководство Брюса Экеля по декорациям
- SO: get-class-in-python-decorator2
- SO: Python-декораторы и наследование
- SO: get-class-in-python-decorator

Однако я все еще немного сбит с толку ...

Вот мой код! В следующем примере current_user - это метод @property, принадлежащий классу RequestHandler. Возвращает объект User (db.model), хранящийся в хранилище данных, с уровнем IntProperty ().

class FoobarController(RequestHandler):

    # Access decorator
    def requiredLevel(required_level):
        def wrap(func):
            def f(self, *args):
                if self.current_user.level >= required_level:
                    func(self, *args)
                else:
                    raise Exception('Insufficient level to access this resource') 
            return f
        return wrap

    @requiredLevel(100)
    def get(self, someparameters):
        #do stuff here...

    @requiredLevel(200)
    def post(self):
        #do something else here...

Однако мое приложение использует разные контроллеры для разных видов ресурсов. Чтобы использовать декоратор @requiredLevel во всех подклассах, мне нужно переместить его в родительский класс (RequestHandler):

class RequestHandler(webapp.RequestHandler):

    #Access decorator
    def requiredLevel(required_level):
        #See code above

Моя идея - получить доступ к декоратору во всех подклассах контроллера, используя следующий код:

class FoobarController(RequestHandler):

    @RequestHandler.requiredLevel(100)
    def get(self):
        #do stuff here...

Мне кажется, я только что достиг предела своих знаний о декораторах и наследовании классов :). Есть мысли?

Ответы [ 2 ]

4 голосов
/ 27 августа 2010

Ваш оригинальный код, с двумя небольшими изменениями, также должен работать.Подход на основе классов кажется довольно тяжелым для такого простого декоратора:

class RequestHandler(webapp.RequestHandler):

    # The decorator is now a class method.
    @classmethod     # Note the 'klass' argument, similar to 'self' on an instance method
    def requiredLevel(klass, required_level):
        def wrap(func):
            def f(self, *args):
                if self.current_user.level >= required_level:
                    func(self, *args)
                else:
                    raise Exception('Insufficient level to access this resource') 
            return f
        return wrap


class FoobarController(RequestHandler):
    @RequestHandler.requiredLevel(100)
    def get(self, someparameters):
        #do stuff here...

    @RequestHandler.requiredLevel(200)
    def post(self):
        #do something else here...

Альтернативно, вы могли бы использовать @staticmethod вместо:

class RequestHandler(webapp.RequestHandler):

    # The decorator is now a static method.
    @staticmethod     # No default argument required...
    def requiredLevel(required_level):

Причина, по которой исходный кодне сработало то, что requiredLevel, как предполагалось, был методом экземпляра, который не будет доступен во время объявления класса (когда вы декорировали другие методы), и при этом он не будет доступен из объекта класса (помещаяОтличная идея - использовать декоратор в базовом классе RequestHandler, и в результате вызов декоратора самодокументируется).

Вам может быть интересно прочитать документацию о @classmethod и @staticmethod.

Кроме того, я хотел бы добавить немного шаблонов в свои декораторы:

    @staticmethod
    def requiredLevel(required_level):
        def wrap(func):
            def f(self, *args):
                if self.current_user.level >= required_level:
                    func(self, *args)
                else:
                    raise Exception('Insufficient level to access this resource') 
            # This will maintain the function name and documentation of the wrapped function.
            # Very helpful when debugging or checking the docs from the python shell:
            wrap.__doc__ = f.__doc__
            wrap.__name__ = f.__name__
            return f
        return wrap
1 голос
/ 31 июля 2010

После копания в StackOverflow и внимательного прочтения Руководства Брюса Экеля по декораторам , я думаю, что нашел возможное решение.

Это включает в себя реализацию декоратора как класса в классе Parent:

class RequestHandler(webapp.RequestHandler):

    # Decorator class :
    class requiredLevel(object):
        def __init__(self, required_level):
            self.required_level = required_level

        def __call__(self, f):
            def wrapped_f(*f_args):
                if f_args[0].current_user.level >= self.required_level:
                    return f(*f_args)
                else:
                    raise Exception('User has insufficient level to access this resource') 
            return wrapped_f

Это делает работу! Использование f_args [0] мне кажется немного грязным, я отредактирую этот ответ, если найду что-нибудь красивее.

Затем вы можете декорировать методы в подклассах следующим образом:

FooController(RequestHandler):
    @RequestHandler.requiredLevel(100)
    def get(self, id):
        # Do something here

    @RequestHandler.requiredLevel(250)
    def post(self)
        # Do some stuff here

BarController(RequestHandler):
    @RequestHandler.requiredLevel(500)
    def get(self, id):
        # Do something here

Не стесняйтесь комментировать или предлагать улучшения.

...