gabn88 gabn88 - 4 months ago 32
Python Question

How to create dynamic methods with python?

For my project I need to dynamically create custom (Class) methods.

I found out it is not so easy in Python:

class UserFilter(django_filters.FilterSet):
'''
This filter is used in the API
'''
# legacy below, this has to be added dynamically
#is_field_type1 = MethodFilter(action='filter_field_type1')

#def filter_field_type1(self, queryset, value):
# return queryset.filter(related_field__field_type1=value)

class Meta:
model = get_user_model()
fields = []


But it is giving me errors (and headaches...). Is this even possible?

I try to make the code between #legacy dynamic

One option to do this I found was to create the class dynamically

def create_filter_dict():
new_dict = {}
for field in list_of_fields:

def func(queryset, value):
_filter = {'stableuser__'+field:value}
return queryset.filter(**_filter)

new_dict.update({'filter_'+field: func})

new_dict.update({'is_'+field: MethodFilter(action='filter_'+field)})

return new_dict


meta_model_dict = {'model': get_user_model(), 'fields':[]}
meta_type = type('Meta',(), meta_model_dict)

filter_dict = create_filter_dict()
filter_dict['Meta'] = meta_type
UserFilter = type('UserFilter', (django_filters.FilterSet,), filter_dict)


However, this is giving me

TypeError at /api/v2/users/
func() takes 2 positional arguments but 3 were given


Does anyone know how to solve this dilemma?

Answer

Exception Value: 'UserFilter' object has no attribute 'is_bound'

You are getting this error because the class methods you are generating, are not bound to any class. To bound them to the class, you need to use setattr()

Try this on a console:

class MyClass(object):
    pass

@classmethod 
def unbound(cls):
    print "Now I'm bound to ", cls


print unbound setattr(MyClass, "bound", unbound) 
print MyClass.bound 
print MyClass.bound()

Traceback: UserFilter = type('Foo', (django_filters.FilterSet, ), create_filter_dict().update({'Meta':type('Meta',(), {'model': get_user_model(), 'fields':[]} )})) TypeError: type() argument 3 must be dict, not None

Now, this is failing because dict.update() doesn't return the same instance, returns None. That can be fixed easily

class_dict = create_filter_dict()
class_dict.update({'Meta':type('Meta',(), {'model': get_user_model(), 'fields':[]})}
UserFilter = type('Foo', (django_filters.FilterSet, ), class_dict))

However, just look how messy that code looks. I recommend to you to try to be clearer with the code you write even if it requires to write a few extra lines. In the long run, the code will be easier to maintain for you and your team.

meta_model_dict = {'model': get_user_model(), 'fields':[]}
meta_type = type('Meta',(), meta_model_dict)

filter_dict = create_filter_dict()
filter_dict['Meta'] = meta_type
UserFilter = type('Foo', (django_filters.FilterSet,), filter_dict)

This code maybe not perfect but it is more readable than the original line of code you posted:

UserFilter = type('Foo', (django_filters.FilterSet, ), create_filter_dict().update({'Meta':type('Meta',(), {'model': get_user_model(), 'fields':[]})}))

And removes a complication on an already kinda difficult concept to grasp.

You might want to learn about metaclasses. Maybe you can overwrite the new method of a class. I can recommend you 1 or 2 posts about that.

Another option is that maybe you are not adding the filters correctly or in a way django doesn't expect? That would explain why you get no errors but none of your functions gets called.