views:

78

answers:

2

Hi,

I am trying to write a base crud controller class that does the following:

class BaseCrudController:
    model = ""
    field_validation = {}
    template_dir = ""

    @expose(self.template_dir)
    def new(self, *args, **kwargs)
        ....

    @validate(self.field_validation, error_handler=new)
    @expose()
    def  post(self, *args, **kwargs):
        ...

My intent is to have my controllers extend this base class, set the model, field_validation, and template locations, and am ready to go.

Unfortunately, decorators (to my understanding), are interpreted when the function is defined. Hence it won't have access to instance's value. Is there a way to pass in dynamic data or values from the sub class?

For example:

class AddressController(BaseCrudController):
    model = Address
    template_dir = "addressbook.templates.addresses"

When I try to load AddressController, it says "self is not defined". I am assuming that the base class is evaluating the decorator before the sub class is initialized.

Thanks, Steve

+1  A: 

Perhaps using a factory to create the class would be better than subclassing:

def CrudControllerFactory(model, field_validation, template_dir):
    class BaseCrudController:
        @expose(template_dir)
        def new(self, *args, **kwargs)
            ....

        @validate(field_validation, error_handler=new)
        @expose()
        def  post(self, *args, **kwargs):
            ....

    return BaseCrudController
Andrew E. Falcon
interesting thought. but wouldn't this limited my ability to extend the actual controllers? my hope is that the base controller would help me handle all the crud cases, and allow me to add functionality to the controllers that extend the base class.
steve
you would have to subclass like `class ExtendedClass(CrudControllerFactor(model, field_validation, template_dir):`. This could be a problem if you wish to subclass the subclass.
Andrew E. Falcon
A: 

Unfortunately, decorators (to my understanding), are interpreted when the function is defined. Hence it won't have access to instance's value. Is there a way to pass in dynamic data or values from the sub class?

The template needs to be called with the name of the relevant attribute; the wrapper can then get that attribute's value dynamically. For example:

import functools

def expose(attname=None):
  if attname:
    def makewrapper(f):
      @functools.wraps(f)
      def wrapper(self, *a, **k):
        attvalue = getattr(self, attname, None)
        ...use attvalue as needed...
      return wrapper
    return makewrapper
  else:
    ...same but without the getattr...

Note that the complication is only because, judging from the code snippets in your Q, you want to allow the expose decorator to be used both with and without an argument (you could move the if attname guard to live within wrapper, but then you'd uselessly repeat the check at each call -- the code within wrapper may also need to be pretty different in the two cases, I imagine -- so, shoehorning two different control flows into one wrapper may be even more complicated). BTW, this is a dubious design decision, IMHO. But, it's quite separate from your actual Q about "dynamic data".

The point is, by using the attribute name as the argument, you empower your decorator to fetch the value dynamically "just in time" when it's needed. Think of it as "an extra level of indirection", that well-known panacea for all difficulties in programming!-)

Alex Martelli
I am able to do set the value as long as it's defined within the same file:template_dir = "address"@expose(template_dir)But when I try to do:@expose(self.template_dir)and define self.template_dir in the sub class, it says self is undefined.I have edited my Q with an example.
steve
@steve, of course: at the time the template executes there **is** no `self`. By passing the attribute name, as I explain, you get an extra level of indirection and completely solve the problem.
Alex Martelli
@alex: i think i got this now. so i will have to write another @expose to override the existing one. call @expose(attrname="template_dir"), and write a wrapper that will evaluate the value of template_dir?
steve
@steve, yes, roughly, but be careful about having several objects with the same barename in the same namespace: you can't! Python doesn't support C++-like "overload" (different functions with identical names distinguished by their kinds and numbers of arguments). You may want to use a different name for the different functionalities (it's much clearer than cramming many different functionalities into a single function).
Alex Martelli