15

What is the best approach to take if you want to dynamically create and reference nested attributes?

I was writing a simple Flickr client, and wanted to match the documented API as closely as possible, without actually defining every method. For instance, to make a request to Flickr's flickr.people.getInfo API method:

flickr = Client()
data = flickr.people.getInfo(user_id='xxx')

In this case flickr.people.getInfo directly maps to the corresponding method in their API documentation. When called, people and getInfo are created as they are looked up, then the proper request to make is determined by the path to getInfo, which is people.getInfo. This is the approach I used:

class Attr(object):
    def __init__(self, client, name, parent):
        self._client = client
        self._name = name
        self._parent = parent

    def __getattr__(self, name):
        attr = Attr(self._client, name, self)
        setattr(self, name, attr)
        return attr

    def _get_path(self, path=None):
        if path:
            path = '.'.join((self._name, path))
        else:
            path = self._name
        if isinstance(self._parent, Attr):
            return self._parent._get_path(path)
        return path

    def __call__(self, *args, **kwargs):
        return self._client.execute_method(self._get_path(), *args, **kwargs)

class Client(object):
    def __getattr__(self, name):
        attr = Attr(self, name, None)
        setattr(self, name, attr)
        return attr

    def execute_method(self, method, *args, **kwargs):
        print method, args, kwargs

This works, but I'm curious if my approach to deal with nested attribute assignment/lookup can be improved, or if there are any errors lurking in wait, unbeknownst to me. In particular, I'm curious if there is a better way to figure out the "path" to a given attribute. For example, if I call Client().x.y.z(), x, y, z do not exist, and will be created one by one (as __getattr__ looks up a single attribute at a time). By the time z is called, I need to be able to discern that the path to z is x.y.z.

Zach Kelling
  • 52,505
  • 13
  • 109
  • 108
  • "nested" attributes are almost always handled with nested objects. Why are you parsing the name, when Python can handle this parsing for you? – S.Lott Jun 27 '11 at 20:48
  • I'm creating nested objects. Is there a simpler way to figure out the path to a nested attribute? – Zach Kelling Jun 27 '11 at 20:51
  • When you do `a.b.c`, Python gives `"b"` to `a` `__getattr__`. You don't need to reason out a "path" or anything like it. You just need to provide the proper instance of `b` at `a.b` and the proper instance of `c` and `b.c`. – S.Lott Jun 27 '11 at 20:53
  • I don't understand how that helps. If I call `client.x.y.z()`, and `x`, `y`, and `z` do not exist beforehand, I create them one at a time, since `__getattr__` only looks up a single attribute. Once `z` is returned, is there a way to discern that the path to it is `x.y.z`? – Zach Kelling Jun 27 '11 at 20:58
  • @zeekay: Please **update** the question with your example from the comments. It's hard to address this in comments. – S.Lott Jun 27 '11 at 21:00
  • 2
    @zeekay: Do existing libraries like Flipy not already offer this sort of thing? http://ianloic.com/2010/01/26/flipy/ – Thomas K Jun 27 '11 at 21:22
  • @Thomas Not that I've seen. Using `flipy` you'd do (in comparison to the above) `flickr.resturl(method='flickr.people.getInfo', user_id='xxx')`. This is fairly similar to how `Flickr.API` is used, also. – Zach Kelling Jun 27 '11 at 21:32
  • @zeekay: The example on the page I linked shows `me_info = flickr.people.getInfo(user_id=me.nsid)`. I haven't tested it, but... the guy who wrote it seems to think it works like that. ;-) – Thomas K Jun 27 '11 at 23:28
  • @Thomas Oh cool, you are right :) Flipy does something similar to what I was doing, it overrides `__getattr__` and returns a `Method` object. Each new `Method` obj is instantiated with the method name, so there is no silly looking up path stuff like with my approach. I'll answer the question with an updated example if no one else wants to bite. – Zach Kelling Jun 27 '11 at 23:55
  • @zeekay: Excellent, glad I could save you the effort of rewriting it ;-) – Thomas K Jun 28 '11 at 00:15
  • @Thomas Oh, I already finished! It was just a quick hack :) I was mostly curious about how I could improve the dynamic method calling bit (which flipy does nicely). – Zach Kelling Jun 28 '11 at 00:20

1 Answers1

7

Thanks to Thomas K for pointing out that flipy already does this (and seems like a nice library for interacting with flickr). A cleaner approach:

class Method(object):
    def __init__(self, client, method_name):
        self.client = client
        self.method_name = method_name

    def __getattr__(self, key):
        return Method(self.client, '.'.join((self.method_name, key)))

    def __call__(self, **kwargs):
        print self.method_name, kwargs

class Client(object):
    def __getattr__(self, key):
        return Method(self, key)

Et voilà:

>>> c = Client()  
>>> c.some.method(x=1, y=2)
some.method {'y': 2, 'x': 1}
Community
  • 1
  • 1
Zach Kelling
  • 52,505
  • 13
  • 109
  • 108