Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Writing a generic getattr() and populate method parameters based on attr name

I am trying to create a generic Python class for a Pub/Sub type app, where the model definition specified three methods for each type of resource X that we have:

new_X
changed_X
deleted_X

I've abstracted the code to a single method, that accepts parameters about the type and action:

def _pub_wrapper(self, verb, obj_type, id_list):
    ids = [str(i) for i in id_list]
    self._pub(ids, '{0}.{1}'.format(verb,
                                    obj_type.lower()))

But this then requires me to hand-write each of the defined methods, albeit in one line, each:

def new_resources(self, id_list):
    self._pub_wrapper('new', 'resources', id_list)

def changed_resources(self, id_list):
    self._pub_wrapper('changed', 'resources', id_list)

I'm trying to find a better pattern, to abstract this even further, so that I don't have to hand-write these one-line methods. Because the method names map to the verb / type in my pub/sub system, I was thinking of something like (pseudo-code to follow):

def __getattr__(self, item):
    if name in [whitelist of approved methods]:
        return ??? Some sort of partially defined version of self._pub_wrapper, with verb and obj_type filled in from parsing item ???
    raise AttributeError()

This generic method would ideally catch calls like:

publisher.new_resources([])
publisher.new_items([])
publisher.new_banks([])

without me having to hand-code each of those methods...is there an elegant way to do this? I was thinking perhaps I could do it with a decorator around __getattr__, but not quite sure how to return a decorated method. Tried the following, but the self._pub_wrapper() method was never called.

def define_verb_and_type(*args, **kwargs):
    def wrap(func):
        def wrapped_func(*args):
            return func(*args, verb=kwargs['verb'], obj_type=kwargs['obj_type'])
        return wrapped_func
    return wrap


def __getattr__(self, item):
    if item in ['new_resources', 'changed_resources', 'deleted_resources']:
        verb = item.split('_')[0]
        obj_type = item.split('_')[-1]
        return define_verb_and_type(self._pub_wrapper, verb, obj_type)
    raise AttributeError
like image 375
user Avatar asked Oct 19 '22 05:10

user


1 Answers

I am not quite sure what your code actually is doing, but from a simplistic point of view I get that you want to catch generic calls, like publisher.new_resources( [] ), which automagically should generate a call to _pub_wrapper(new, resources, []) or one below _pub(...).

Here is a working example:

class Test:    
    def _pub_wrapper(self, verb, obj_type, id_list):
        print "  Called _pub_wrapper(" , verb, ", ", obj_type, ", ", id_list, ")"
        ids = [str(i) for i in id_list]
        self._pub(ids, '{0}.{1}'.format(verb,
                                        obj_type.lower()))

    def _pub(self, ids, something):
        print "    Called _pub( ", self, ", ", ids, ", ", something, ")"

    def __getattr__(self, item):
        verb = item.split('_')[0]
        obj_type = item.split('_')[-1]
        if verb in ['new', 'changed', 'deleted'] and \
           obj_type in ['resources', 'items', 'banks']:

            def wrapper(*args, **kwargs):
                print "Within wrapper: verb=", verb, ", obj_type=", obj_type, ", args=",  args, ", kwargs=", kwargs
                print "Within wrapper: ", verb, ", ", obj_type, ", ",  args
                return self._pub_wrapper(verb, obj_type, args[0])
            return wrapper
        raise AttributeError

    """        
    def __getattr__(self, item):
        if item in ['new_resources', 'changed_resources', 'deleted_resources', 'changed_banks']:
            verb = item.split('_')[0]
            obj_type = item.split('_')[-1]
            print verb, " vs ", obj_type
            def wrapper(*args):
                print "Within wrapper: ", verb, ", ", obj_type, ", ",  args[0]
                return self._pub_wrapper(verb, obj_type, args[0])
            return wrapper
        raise AttributeError
    """

    def fake_new_resources(self, id_list):
        self._pub_wrapper('new', 'resources', id_list)

t = Test()
print "Faking it... "
t.fake_new_resources([])

print "New tries"
t.new_resources([])
t.changed_banks([])
t.deleted_items(["hi", "bye"], 4, your=23, mine=42)

The output generated when running this are:

Faking it... 
  Called _pub_wrapper( new ,  resources ,  [] )
    Called _pub(  <__main__.Test instance at 0x1c366c> ,  [] ,  new.resources )
New tries
Within wrapper: verb= new , obj_type= resources , args= ([],) , kwargs= {}
  Called _pub_wrapper( new ,  resources ,  [] )
    Called _pub(  <__main__.Test instance at 0x1c366c> ,  [] ,  new.resources )
Within wrapper: verb= changed , obj_type= banks , args= ([],) , kwargs= {}
  Called _pub_wrapper( changed ,  banks ,  [] )
    Called _pub(  <__main__.Test instance at 0x1c366c> ,  [] ,  changed.banks )
Within wrapper: verb= deleted , obj_type= items , args= (['hi', 'bye'], 4) , kwargs= {'your': 23, 'mine': 42}
  Called _pub_wrapper( deleted ,  items ,  ['hi', 'bye'] )
    Called _pub(  <__main__.Test instance at 0x1c366c> ,  ['hi', 'bye'] ,  deleted.items )

Code comments

You were quite close, but had some issues related to your define_verb_and_type(), and the calling of this. I simplified this section a bit, and added loads of debug print statements. Currently I code only online, so I don't have a good debugger, and therefore I use print for debug.

One change I made, was that instead of testing for item in [ ... list ...], I split the item before, and tested for the actual verb and obj_type. You might even want to loose the test for the obj_type. Kept a working version testing against the list in a comment block.

One question I have related to your code, is in the _pub_wrapper where you use the id_list. I don't quite see what you are trying to achieve here, and kind of expected this either to be a instance variable somehow or to see additions/removal to this list.

I also added a new example to display both named and unnamed arguments. The former is present in the kwargs parameter, whilst the later are in the args parameter.

In other words, I'm not quite if I understood your question correctly, and what code you want us to review. But the code provided in my answer here, does provide the generic call approach you want and hopefully will help you progress in your coding project.

like image 62
holroy Avatar answered Oct 22 '22 02:10

holroy