Ray Salemi
Ray Salemi

Reputation: 5913

Dynamic Wrapper in Python

I'm looking to create a dynamic wrapper class that exposes the API calls from a provided object using data in the object.

Statically it looks like this:

 class Concrete:
     def __init__(self, data):
       self.data = data

     def print_data(self):
         print(self.data)


 class Wrapper:
     '''
     One day this will wrap a variety of objects. But today
     it can only handle Concrete objects.
     '''
     def wrap_it(self, concrete):
         self.cco = concrete  # concreteobject=cco

     def print_data(self):
         self.cco.print_data()


 cco = Concrete(5)
 wcco = Wrapper()
 wcco.wrap_it(cco)
 wcco.print_data()

Produces

 5

I'd like to figure out how to do the same thing but make wrap_it dynamic. It should search the concrete object find the functions, and create functions of the same name that call the same function in the concrete object.

I imagine that the solution involves inspect.signature or at least some use of *args and **kwargs, but I've not seen an example on how to put all this together.

Upvotes: 2

Views: 2083

Answers (2)

fferri
fferri

Reputation: 18940

You can use the __getattr__ magic method to hook getting undefined attributes, and forward them to the concrete object:

class DynamicWrapper():
    def wrap_it(self, concrete):
        self.cco = concrete

    def __getattr__(self, k):
        def wrapper(*args, **kwargs):
            print(f'DynamicWrapper calling {k} with args {args} {kwargs}')
            return getattr(self.cco, k)(*args, **kwargs)

        if hasattr(self.cco, k):
            return wrapper
        else:
            raise AttributeError(f'No such field/method: {k}')

cco = Concrete(5)
dwcco = DynamicWrapper()
dwcco.wrap_it(cco)
dwcco.print_data()

Upvotes: 3

AboodXD
AboodXD

Reputation: 158

Use the dir() function to get the attributes of the given object, check if they are callable and assign them to your wrapper, like this:

 class Wrapper:
     def wrap_it(self, objToWrap):
         for attr in dir(objToWrap):
             if not attr.startswith('__') and callable(getattr(objToWrap, attr)):
                 exec('self.%s = objToWrap.%s' % (attr, attr))

And now, for testing.

>>> cco = Concrete(5)
>>> wcco = Wrapper()
>>> wcco.wrap_it(cco)
>>> wcco.print_data()
5

Upvotes: 1

Related Questions