haki
haki

Reputation: 9759

Decorator to invoke instance method

I have a class A with method do_something(self,a,b,c) and another instance method that validates the input and check permissions named can_do_something(self,a,b,c).

This is a common pattern in my code and I want to write a decorator that accepts a validation function name and perform the test.

def validate_input(validation_fn_name):
        def validation_decorator(func):
            def validate_input_action(self,*args):
                error = getattr(self,validation_fn_name)(*args)
                if not error == True:
                    raise error
                else:
                    return func(*args)
            return validate_input_action
        return validation_decorator

Invoking the functions as follows

@validate_input('can_do_something')
def do_something(self,a,b,c):
   return a + b + c

Problem is that i'm not sure how to maintain self through out the validation function. I've used the validation fn name with getattr so the fn could be ran in the context of the instance but i cannot do that for func(*args).

So what is the proper way to achieve this ?

Thanks.

EDIT

So following @André Laszlo answer I realized that self is just the first argument so there is no need to use getattr at all but just pass on the *args.

def validate_input(validation_fn):
    def validation_decorator(func):
        def validate_input_action(*args):
            error = validation_fn(*args)
            if not error == True:
                raise error
            else:
                return func(*args)
        return validate_input_action
    return validation_decorator

Much more elegant and it also supports static methods as well.

Adding a static method to @André Laszlo example proves the decorator is working :

 class Foo(object):
    @staticmethod
    def validate_baz(a,b,c):
       if a > b:
          return ValueError('a gt b')

    @staticmethod
    @validate_input(Foo.validate_baz)
    def baz(a,b,c):
       print a,b,c

    >>> Foo.baz(1,2,3)
    1 2 3
    >>> Foo.baz(2,1,3)
    Traceback (most recent call last):
      File "<stdin>", line 1, in <module>
      File "<stdin>", line 6, in validate_input_action
    ValueError: a gt b

But, when i'm trying to do them same thing in a django model:

from django.db import models
from django.conf import settings

settings.configure()

class Dummy(models.Model):
    id = models.AutoField(primary_key=True)
    name = models.CharField(max_length=10)

    def can_say_name(self):
        if name is None:
            return Exception('Does not have a name')

    @validate_input(can_say_name)
    def say_name(self):
        print self.name

    @staticmethod
    def can_create_dummy(name):
        if name == 'noname':
            return Exception('No name is not a name !')

    @staticmethod
    @validate_input(Dummy.can_create_dummy)
    def create_dummy(name):
        return Dummy.objects.create(name=name)

I get the following :

NameError: name 'Dummy' is not defined

So what is the different between a django model and an Object in relation to this issue ?

Upvotes: 1

Views: 96

Answers (1)

Andr&#233; Laszlo
Andr&#233; Laszlo

Reputation: 15537

I think this does what you want:

def validate_input(validation_fn_name):
    def validation_decorator(func):
        def validate_input_action(self, *args):
            error = getattr(self, validation_fn_name)(*args)
            if error is not None:
                raise error
            else:
                arglist = [self] + list(args)
                return func(*arglist)
        return validate_input_action
    return validation_decorator

class Foo(object):

    def validate_length(self, arg1):
        if len(arg1) < 3:
            return ValueError('%r is too short' % arg1)

    @validate_input('validate_length')
    def bar(self, arg1):
        print "Arg1 is %r" % arg1


if __name__ == "__main__":
    f = Foo()
    f.bar('hello')
    f.bar('')

Output is:

Arg1 is 'hello'
Traceback (most recent call last):
  File "validator.py", line 27, in <module>
    f.bar('')
  File "validator.py", line 6, in validate_input_action
    raise error
ValueError: '' is too short

Updated answer

The error (NameError: name 'Dummy' is not defined) occurs because the Dummy class is not defined yet when the validate_input decorator gets Dummy as an argument. I guess this could have been implemented differently, but for now that's the way Python works. The easiest solution that I see is to stick to using getattr, which will work because it looks up the method at run time.

Upvotes: 2

Related Questions