2010-07-31 8 views
9

Sto cercando di utilizzare i decoratori per gestire il modo in cui gli utenti possono accedere o meno alle risorse all'interno di un'applicazione Web (in esecuzione su Google App Engine). Tieni presente che non autorizzo gli utenti ad accedere con i loro account Google, pertanto l'impostazione di diritti di accesso specifici a percorsi specifici all'interno di app.yaml non è un'opzione.Decoratori Python ed ereditarietà delle classi

ho usato le seguenti risorse:
- Bruce Eckel's guide to decorators
- SO : get-class-in-python-decorator2
- SO : python-decorators-and-inheritance
- SO : get-class-in-python-decorator

Tuttavia io sono ancora un po 'confuso ...

Ecco il mio codice! Nell'esempio seguente, current_user è un metodo @property che appartiene alla classe RequestHandler. Restituisce un oggetto User (db.model) memorizzato nel datastore, con un livello 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... 

Tuttavia, la mia applicazione utilizza controller diversi per diversi tipi di risorse. Per poter utilizzare il decoratore @requiredLevel all'interno di tutte le sottoclassi, ho bisogno di spostarlo nella classe genitore (RequestHandler):

class RequestHandler(webapp.RequestHandler): 

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

La mia idea è quella di accedere il decoratore in tutte le sottoclassi controller utilizzando il seguente codice:

class FoobarController(RequestHandler): 

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

Penso di aver appena raggiunto il limite delle mie conoscenze sui decoratori e sull'eredità di classe :). Qualche idea ?

+1

Perché è un metodo sulla classe? Ciò causerà solo la rottura delle cose e funzionerà solo come una funzione normale all'interno della classe in cui è stato definito. A meno che non si sia su 3.x, nel qual caso probabilmente funzionerà correttamente. –

+0

Il decoratore è un metodo sulla classe perché non ho ancora capito come codificare un decoratore come classe 1/che accetta argomenti e 2/che può accedere ai metodi della classe corrente stessa. È questo che intendevi? Essendo per lo più autodidatta, ho difficoltà a comprendere appieno la guida, i decoratori e l'eredità di Bruce Eckell. – jbmusso

+0

Si potrebbe semplicemente copiare e incollare la funzione al di fuori della classe e funzionerebbe correttamente. Basterebbe rispondere alla tua domanda? –

risposta

4

Anche il codice originale, con due piccoli ritocchi, dovrebbe funzionare. Un approccio basato su classi sembra piuttosto pesante per un semplice decoratore quali:

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

In alternativa, è possibile utilizzare un @staticmethod invece:

class RequestHandler(webapp.RequestHandler): 

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

Il motivo per il codice originale non ha funzionato è che requiredLevel è stato assunto come un metodo di istanza, che non sarà disponibile al momento della dichiarazione di classe (quando stavi decorando gli altri metodi), né sarà disponibile dall'oggetto classe (mettendo il decoratore sulla classe base RequestHandler è un'idea eccellente, e la chiamata decoratore risultante è ben autodocumentante).

Potrebbe essere interessato a leggere la documentazione su @classmethod e @staticmethod.

Inoltre, un po 'di boilerplate mi piace mettere nei miei decoratori:

@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

Dopo aver scavato attraverso StackOverflow e aver letto attentamente Bruce Eckel's guide to decorators, penso di aver trovato una possibile soluzione.

Essa implica l'attuazione del decoratore come classe nella classe principale:

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 

Questo fa il lavoro! L'utilizzo di f_args [0] mi sembra un po 'sporco, modificherò questa risposta se trovo qualcosa di più carino.

Poi si può decorare metodi in sottoclassi seguente modo:

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 

Sentitevi liberi di commentare o proporre un miglioramento.

+0

potresti usare 'wrapped_f (request_handler, * args, ** kwargs)' come firma della funzione. Inoltre non è necessario inserire la classe decoratore nella classe RequestHandler. Lo metterei a livello di modulo. – nils

+0

Grazie per il tuo commento! Penso che tu mi abbia appena fatto capire il modo in cui l'ereditarietà funziona in modo diverso (e molto meglio). Aggiornerò il mio codice di conseguenza. – jbmusso