2010-07-31 25 views
9

Estoy intentando usar decoradores para gestionar la forma en que los usuarios pueden o no acceder a los recursos dentro de una aplicación web (ejecutándose en Google App Engine). Tenga en cuenta que no permitiré que los usuarios inicien sesión con sus cuentas de Google, por lo que establecer derechos de acceso específicos a rutas específicas dentro de app.yaml no es una opción.Decoradores de Python y herencia de clase

que utilizan los siguientes recursos:
- Bruce Eckel's guide to decorators
- SO : get-class-in-python-decorator2
- SO : python-decorators-and-inheritance
- SO : get-class-in-python-decorator

Sin embargo todavía estoy un poco confundido ...

Aquí está mi código! En el siguiente ejemplo, current_user es un método @property que pertenece a la clase RequestHandler. Devuelve un objeto User (db.model) almacenado en el almacén de datos, con un nivel 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... 

Sin embargo, mi aplicación utiliza diferentes controladores para diferentes tipos de recursos. Para utilizar el decorador @requiredLevel dentro de todas las subclases, tengo que moverlo a la clase padre (RequestHandler):

class RequestHandler(webapp.RequestHandler): 

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

Mi idea es tener acceso a la decoradora en todas las subclases de controlador utilizando el siguiente código:

class FoobarController(RequestHandler): 

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

Creo que acabo de llegar al límite de mi conocimiento sobre decoradores y herencia de clases :). Alguna idea ?

+1

¿Por qué es un método en la clase? Eso solo hará que las cosas se rompan, y solo funcionará como una función normal dentro de la clase en la que se definió. A menos que estés en 3.x, en cuyo caso probablemente funcione bien. –

+0

El decorador es un método en la clase porque aún no he calculado cómo codificar un decorador como una clase 1/que acepta argumento (s) y 2/que puede acceder a métodos para la clase actual. ¿Es esto lo que querías decir? Siendo en su mayoría autodidacta, tengo problemas para comprender completamente la guía, los decoradores y la herencia de Bruce Eckell. – jbmusso

+0

Podrías simplemente copiar y pegar la función fuera de la clase, y funcionaría bien y con normalidad. ¿Sería eso suficiente para responder a tu pregunta? –

Respuesta

4

Su código original, con dos pequeños retoques, también debería funcionar. Un enfoque basado en la clase parece más grueso y resistente para un simple decorador tales:

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

Alternativamente, se puede utilizar una vez @staticmethod:

class RequestHandler(webapp.RequestHandler): 

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

La razón del código original no funciona es que requiredLevel se supuso como un método de instancia, que no estará disponible en el momento de la declaración de clase (cuando estaba decorando los otros métodos), ni estará disponible desde el objeto de clase (poniendo el decorador en su clase base RequestHandler) es una excelente idea, y la llamada al decorador resultante está muy bien documentada).

Puede que le interese leer la documentación sobre @classmethod y @staticmethod.

También, un poco de texto modelo me gusta poner en mis 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 
1

Después de buscar en StackOverflow, y de leer cuidadosamente Bruce Eckel's guide to decorators, creo que encontré una posible solución.

Se trata de la aplicación de la decoradora como clase en la clase padre:

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 

Esto hace el trabajo! Usar f_args [0] me parece un poco sucio, voy a editar esta respuesta si encuentro algo más bonito.

A continuación, se puede decorar métodos de subclases de la siguiente manera:

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 

Siéntase libre de comentar o proponer una mejora.

+0

puede usar 'wrapped_f (request_handler, * args, ** kwargs)' como la firma de la función. Además, no es necesario colocar la clase de decorador en su clase RequestHandler. Yo pondría esto en el nivel de módulo. – nils

+0

¡Gracias por tu comentario! Creo que me acabas de hacer entender la forma en que la herencia funciona de manera diferente (y mucho mejor). Actualizaré mi código en consecuencia. – jbmusso

Cuestiones relacionadas