3

Some background: We have a system for transactions where we devide the flow based upon the country the transaction bills to. We have a logging table that exists in 2 instances, one DB logging transactions to the EU, the other to anywhere else. We also have a test library that manages and hides the guts of working with the DB where roughly speaking each table is represented by a class. I have a class that represents the table, and the db session manager class has two members for each of the two instances of the class. What I want to do is create a generic 'meta dao' class that will take any arbitrary call to it, inspect the args, and based upon one of the input arguments, subsequently dispatch the call to the correct db instance-representing class instance. I initially thought about just overloading every method, but that's clunky and dirty.

I was looking at using __getattr__ to override the method lookup so that I could then call down to the correct instance based upon the name of the method __getattr__ recieves, but from what I understand, I can't inspect the incoming method arguments from within __getattr__, so I can't properly dispatch from within it in this case. Does anyone have any ideas of a different direction I can pursue, or a way to 'inspect' the arguments, not just the method name, from within __getattr__?

[edit] Here's a genericized version of what I'm talking about:

class BarBase(object):
    def __init__(self, x):
        self.x = x
    def do_bar(self, i):
        return self.x * i

class FooBar(BarBase):
    def __init__(self, x):
        super(FooBar, self).__init__(x)
    def do_foo(self, i):
        return self.x + i

class MetaFoo(object):
    def __init__(self, bar_manager):
        self.foo_manager = bar_manager
    #something here that will take an arbitrary methodname and args as
    #long as args includes a value named i, inspect i, and call
    #bar_manager.fooa.[methodname](args) if i < 10,
    #and bar_manager.foob.[methodname](args) if i >= 10

class BarManager(object):
    def __init__(self):
        self.bar_list = {}
    def __get_fooa(self):
        if 'fooa' not in self.bar_list.keys():
            self.bar_list['fooa'] = FooBar('a')
        return self.bar_list['fooa']
    fooa = property(__get_fooa)
    def __get_foob(self):
        if 'foob' not in self.bar_list.keys():
            self.bar_list['foob'] = FooBar('b')
        return self.bar_list['foob']
    foob = property(__get_foob)
    def __get_foo(self):
        if 'foo' not in self.bar_list.keys():
            self.bar_list['foo'] = MetaFoo(self)
        return self.bar_list['foo']
Silas Ray
  • 25,682
  • 5
  • 48
  • 63

3 Answers3

2

Somethings along these lines should work:

class ProxyCall(object):
   '''Class implementing the dispatch for a certain method call'''
   def __init__(self, proxy, methodname):
      self.proxy = proxy
      self.methodname = methodname

   def __call__(self, *p, **kw):
      if p[0] == "EU": # or however you determine the destination
         return getattr(self.proxy.EU, self.methodname)(*p, **kw);
      else:
         return getattr(self.proxy.OTHER, self.methodname)(*p, **kw);


class Proxy(object):
   '''Class managing the different "equivalent" instances'''
   def __init__(self, EU, OTHER):
      self.EU = EU
      self.OTHER = OTHER

   def __getattr__(self, name):
      if not hasattr(self.EU, name):
         # no such method
         raise AttributeError()
      else:
         # return object that supports __call__ and will make the dispatch
         return ProxyCall(self, name)

Then you would create the two instances and combine them in a proxy object:

eu = make_instance(...)
other = make_instance(...)
p = Proxy(eu, other)
p.somemethod(foo) 
sth
  • 222,467
  • 53
  • 283
  • 367
  • How does `ProxyCall` have access to `foo` passed in to `p.somemethod()` when all that is passed down to `ProxyCall` from `Proxy` is `name`? I appear to be somewhat out of my depth... It appears I'd be overriding __getattr__() in BarManager, correct? – Silas Ray Dec 14 '11 at 21:54
2

python decorators are your friend. You can do something like this

class MetaFoo(object):

    def overload(func):
        """
        we need to check a named variable so for simplicity just checking kwargs
        """
        def _wrapper(*args, **kwargs):
            if kwargs.get('i',0) < 10:
                # get func.func_name from foo and call it
                print "calling foo.",func.func_name
            else:
                print "calling bar.",func.func_name

            func(*args, **kwargs)

        return _wrapper

    @overload
    def func1(self, i):
        print "default functionality"


MetaFoo().func1(i=5)
MetaFoo().func1(i=10)

output:

calling foo. func1
default functionality
calling bar. func1
default functionality

If you have few methods to override you can individually apply decorator and even can pass parameters e.g. diff threshold to different methods, but if want to override all methods may be you can add a metaclass which overloads all method of given class, but in this case overriding __getattr__ as suggested by sth is a good alternative

Anurag Uniyal
  • 85,954
  • 40
  • 175
  • 219
  • From what I'm seeing (and it could be wrong :)), this appears to be dispatching to a specific function rather than an arbitrary function of a specific class instance. And as you say, I'd have to @overload every method in FooBar for it work wor, right? – Silas Ray Dec 14 '11 at 22:10
  • @sr2222 a) in the example I am just printing the function name, but based on function name you can dispatch to any arbitrary method of another object b) yes you can override each method manually or if you want write a metaclass or simple class wrapper which overloads all methods – Anurag Uniyal Dec 14 '11 at 22:31
0

Dispatching based on passed arguments is a two-step process:

  1. __getattr__ returns a proxy method
  2. python calls the proxy which then decides which real method to call

Here's an example:

from functools import partial

class TwoFold(object):
    EU = ('GERMANY','FRANCE','ITALY','GREECE',)
    def __getattr__(self, name):
        try:
            EU = object.__getattribute__(self, 'EU_' + name)
            Other = object.__getattribute__(self, 'Other_' + name)
        except AttributeError:
            raise AttributeError(
                "%r is missing an EU_%s or Other_%s" % (self, name, name)
                )
        judge = partial(self._judge, name, EU, Other)
        return judge
    def _judge(self, method_name, EU, Other, *args, **kwargs):
        if kwargs['country'].upper() in self.EU:
            method = EU
        else:
            method = Other
        return method(*args, **kwargs)
    def EU_log(self, tax, country):
        print "logging EU access for %s, tax rate of %r" % (country, tax)
    def Other_log(self, tax, country):
        print "logging non-EU access for %s, tax rate of %r" % (country, tax)

if __name__ == '__main__':
    test = TwoFold()
    test.log(7.5, country='France')
    test.log(10.1, country='Greece')
    test.log(8.9, country='Brazil')
    test.howsat('blah')

When run, this gives:

logging EU access for France, tax rate of 7.5
logging EU access for Greece, tax rate of 10.1
logging non-EU access for Brazil, tax rate of 8.9

Followed by:

Traceback (most recent call last):
  File "test.py", line 29, in <module>
    test.howsat('blah')
  File "test.py", line 10, in __getattr__
raise AttributeError("%r is missing an EU_%s or Other_%s" % (self, name, name))
AttributeError: <__main__.TwoFold object at 0x00B4A970> is missing an
    EU_howsat or Other_howsat

To make this work you would either have to always use the same keyword argument (and name it when you call the function) or always have the argument in the same position. Or you can make several different proxies for each style/category/whatever type of method.

Ethan Furman
  • 63,992
  • 20
  • 159
  • 237