Decoradores de Python e herança de classe

9

Estou tentando usar decoradores para gerenciar o modo como os usuários podem ou não acessar recursos em um aplicativo da web (em execução no Google App Engine). Observe que não estou permitindo que os usuários façam login com as Contas do Google. Por isso, a definição de direitos de acesso específicos para rotas específicas no app.yaml não é uma opção.

Eu usei os seguintes recursos:
- Guia de Bruce Eckel para decoradores
- SO: get-class-em-python-decorator2
- SO: python-decorators-and-inheritance
- SO: get-class-em-python-decorator

No entanto, ainda estou um pouco confuso ...

Aqui está o meu código! No exemplo a seguir, current_user é um método @property que pertence à classe RequestHandler. Ele retorna um objeto Usuário (db.model) armazenado no armazenamento de dados, com um nível 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...

No entanto, meu aplicativo usa diferentes controladores para diferentes tipos de recursos. Para usar o decorador @requiredLevel em todas as subclasses, eu preciso movê-lo para a classe pai (RequestHandler):

class RequestHandler(webapp.RequestHandler):

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

Minha ideia é acessar o decorador em todas as subclasses do controlador usando o seguinte código:

class FoobarController(RequestHandler):

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

Acho que acabei de chegar ao limite do meu conhecimento sobre decoradores e herança de classes :). Alguma idéia?

    
por jbmusso 31.07.2010 в 18:36
fonte

2 respostas

4

Seu código original, com dois pequenos ajustes, também deve funcionar. Uma abordagem baseada em classes parece bastante pesada para um decorador tão simples:

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...

Como alternativa, você pode usar um @staticmethod :

class RequestHandler(webapp.RequestHandler):

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

O motivo pelo qual o código original não funcionou é que requiredLevel foi considerado como um método de instância, que não estará disponível no momento da declaração de classe (quando você estava decorando os outros métodos), nem será disponível a partir do objeto de classe (colocar o decorador em sua classe base RequestHandler é uma excelente ideia, e a chamada do decorador resultante é bem auto-documentada).

Você pode estar interessado em ler a documentação sobre @classmethod e @staticmethod .

Além disso, eu gosto de colocar um pouco de clichê em meus decoradores:

    @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
    
por David Eyk 27.08.2010 / 17:46
fonte
1

Depois de vasculhar o StackOverflow e ler cuidadosamente o guia de Bruce Eckel para decoradores , acho que encontrou uma solução possível.

Envolve implementar o decorador como uma classe na classe pai:

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

Isso faz o trabalho! Usando f_args [0] parece um pouco sujo para mim, eu vou editar esta resposta se eu encontrar algo mais bonito.

Depois, você pode decorar métodos em subclasses da seguinte maneira:

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

Sinta-se à vontade para comentar ou propor um aprimoramento.

    
por jbmusso 31.07.2010 / 20:31
fonte