2010-07-31 3 views
9

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

Я использовал следующие ресурсы:
- Bruce Eckel's guide to decorators
- SO : get-class-in-python-decorator2
- SO : python-decorators-and-inheritance
- 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... 

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

+1

Почему метод в классе? Это приведет к разрыву вещей, и оно будет работать только как регулярная функция внутри класса, в котором он был определен. Если вы не на 3.x, и в этом случае он, вероятно, будет работать нормально. –

+0

Декоратор - это метод класса, потому что я еще не понял, как закодировать декоратор как класс 1 /, который принимает аргументы (аргументы) и 2 /, которые могут обращаться к методам текущего класса. Это то, что вы имели в виду? Будучи в основном самоучкой, у меня возникли проблемы с полным пониманием руководства, декораторов и наследования Брюса Эккелла. – jbmusso

+0

Вы можете просто скопировать-вставить функцию вне класса, и она будет работать нормально и нормально. Этого будет достаточно, чтобы ответить на ваш вопрос? –

ответ

4

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

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

После того, как вы выкапываете StackOverflow и внимательно читаете Bruce Eckel's guide to decorators, я думаю, что нашел возможное решение.

Она включает в себя реализацию декоратор как класс в классе 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 

Прокоментируй или предложить улучшения.

+0

вы можете использовать 'wrapped_f (request_handler, * args, ** kwargs)' как подпись функции. Также нет необходимости помещать класс декоратора в ваш класс RequestHandler. Я бы поставил это на уровень модуля. – nils

+0

Спасибо за ваш комментарий! Я думаю, вы просто заставляли меня понимать, как наследование работает по-другому (и намного лучше). Я обновлю свой код соответствующим образом. – jbmusso