2010-07-31 9 views
9

Ich versuche, Dekoratoren zu verwenden, um zu verwalten, wie Benutzer auf Ressourcen innerhalb einer Webanwendung (die auf Google App Engine läuft) zugreifen können oder nicht. Beachten Sie, dass ich Nutzern die Anmeldung mit ihren Google-Konten nicht gestatten. Daher ist es nicht möglich, bestimmte Zugriffsrechte für bestimmte Routen in app.yaml festzulegen.Python-Dekoratoren und Klassenvererbung

benutzte ich die folgenden Ressourcen:
- Bruce Eckel's guide to decorators
- SO : get-class-in-python-decorator2
- SO : python-decorators-and-inheritance
- SO : get-class-in-python-decorator

jedoch ein Ich bin immer noch verwirrt Bit ...

Hier ist mein Code! Im folgenden Beispiel ist current_user eine @property-Methode, die zur RequestHandler-Klasse gehört. Es gibt ein User (db.model) -Objekt zurück, das im Datenspeicher mit einer IntProperty() -Ebene gespeichert ist.

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

Allerdings verwendet meine Anwendung verschiedene Controller für verschiedene Arten von Ressourcen. Um den @requiredLevel Dekorateur in allen Unterklassen zu verwenden, muß ich es die übergeordnete Klasse bewegen (Request):

class RequestHandler(webapp.RequestHandler): 

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

Meine Idee ist es, den Dekorateur in allen Controller-Subklassen für den Zugriff auf den folgenden Code:

Ich glaube, ich habe gerade die Grenze meines Wissens über Dekoratoren und Klassenvererbung erreicht :). Irgendwelche Gedanken?

+1

Warum ist es eine Methode für die Klasse? Das wird nur dazu führen, dass Dinge kaputt gehen, und es funktioniert nur wie eine reguläre Funktion innerhalb der Klasse, in der es definiert wurde. Wenn Sie nicht auf 3.x sind, wird es wahrscheinlich in Ordnung funktionieren. –

+0

Der Dekorator ist eine Methode für die Klasse, weil ich noch nicht herausgefunden habe, wie man einen Dekorator als eine Klasse 1/die Argumente akzeptiert und 2/die Methoden auf die aktuelle Klasse selbst zugreifen kann. Hast du das gemeint? Da ich hauptsächlich Autodidakt bin, habe ich Probleme, Bruce Eckells Leitfaden, Dekorateure und Nachlässe zu verstehen. – jbmusso

+0

Sie könnten einfach die Funktion außerhalb der Klasse kopieren und einfügen, und es würde gut und normal funktionieren. Wäre das genug, um deine Frage zu beantworten? –

Antwort

4

Ihr ursprünglicher Code, mit zwei kleinen Verbesserungen, sollte auch funktionieren. Eine klassenbasierte Ansatz scheint eher schwergewichtigen für einen solchen einfachen Dekorateur:

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

Alternativ können Sie verwenden eine @staticmethod statt:

class RequestHandler(webapp.RequestHandler): 

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

Der Grund der ursprüngliche Code nicht funktioniert, ist, dass requiredLevel wurde als eine Instanzmethode angenommen, die zur Zeit der Klassenerklärung nicht zur Verfügung steht (wenn Sie die anderen Methoden dekorierten). Sie wird auch nicht vom Klassenobjekt verfügbar sein (indem Sie den Decorator auf Ihre RequestHandler-Basisklasse setzen) ist eine ausgezeichnete Idee, und der resultierende Decorator-Aufruf ist schön selbstdokumentierend).

Sie könnten daran interessiert sein, die Dokumentation über @classmethod und @staticmethod zu lesen.

Auch ein bisschen von vorformulierten ich in meinem Dekorateure setzen mag:

@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

Nach dem Durchstöbern von StackOverflow und sorgfältig lesen Bruce Eckel's guide to decorators, ich denke, ich fand eine mögliche Lösung.

Es geht um den Dekorateur als eine Klasse in der übergeordneten Klasse Umsetzung:

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 

Dies macht die Arbeit! Die Verwendung von f_args [0] scheint mir ein bisschen dreckig zu sein, ich werde diese Antwort bearbeiten, wenn ich etwas hübscher finde.

Dann können Sie Methoden in Subklassen die folgende Art und Weise dekorieren:

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 

Fühlen Sie sich frei zu äußern oder eine Verbesserung vorzuschlagen.

+0

könnten Sie 'wrapped_f (request_handler, * args, ** kwargs)' als Funktionssignatur verwenden. Außerdem müssen Sie die Decorator-Klasse nicht in Ihre RequestHandler-Klasse einfügen. Ich würde dies auf Modulebene setzen. – nils

+0

Danke für Ihren Kommentar! Ich glaube, Sie haben mir gerade erklärt, wie Vererbung anders funktioniert (und viel besser). Ich werde meinen Code entsprechend aktualisieren. – jbmusso