Reputation: 9759
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
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