lprsd
lprsd

Reputation: 87175

Python: How to enable the following API using class magic

Objective:

Given something like:

stackoverflow.users['55562'].questions.unanswered()

I want it converted into the following:

http://api.stackoverflow.com/1.1/users/55562/questions/unanswered

I have been able to achieve that, using the following class:

class SO(object):

    def __init__(self,**kwargs):
        self.base_url = kwargs.pop('base_url',[]) or 'http://api.stackoverflow.com/1.1'
        self.uriparts = kwargs.pop('uriparts',[])
        for k,v in kwargs.items():
            setattr(self,k,v)

    def __getattr__(self,key):
        self.uriparts.append(key)
        return self.__class__(**self.__dict__)        

    def __getitem__(self,key):
        return self.__getattr__(key)

    def __call__(self,**kwargs):
        return "%s/%s"%(self.base_url,"/".join(self.uriparts))

if __name__ == '__main__':
    print SO().abc.mno.ghi.jkl()
    print SO().abc.mno['ghi'].jkl()

#prints the following
http://api.stackoverflow.com/1.1/abc/mno/ghi/jkl
http://api.stackoverflow.com/1.1/abc/mno/ghi/jkl

Now my problem is I can't do something like:

stackoverflow = SO()
user1 = stackoverflow.users['55562']
user2 = stackoverflow.users['55462']
print user1.questions.unanswered
print user2.questions.unanswered

#prints the following
http://api.stackoverflow.com/1.1/users/55562/users/55462/questions/unanswered
http://api.stackoverflow.com/1.1/users/55562/users/55462/questions/unanswered/questions/unanswered

Essentially, the user1 and user2 refer to the same SO object, so it can't represent different users.

I have been thinking any pointers to do that would be helpful, because this additional level of functionality would make the API far more interesting.

Upvotes: 0

Views: 194

Answers (2)

capfredf
capfredf

Reputation: 397

IMHO, when you recreate a new stackoverflow object, you need to separate the arguments from old instance attributes with a deep copy

import copy
........    
def __getattr__(self,key):
    dict = copy.deepcopy(self.__dict__)
    dict['uriparts'].append(key)
    return self.__class__(**dict)
....

If you want more flexibility on the URI parts, an abstraction is needed for a cleaner design. For example:

class SOURIParts(object):
    def __init__(self, so, uriparts, **kwargs):
        self.so = so
        self.uriparts = uriparts
        for k,v in kwargs.items():
            setattr(self,k,v)

    def __getattr__(self,key):
        return SOURIParts(self.so, self.uriparts+[key])

    def __getitem__(self,key):
        return self.__getattr__(key)

    def __call__(self,**kwargs):
        return "%s/%s"%(self.so.base_url,"/".join(self.uriparts))

class SO(object):
    def __init__(self, base_url='http://api.stackoverflow.com/1.1'):
        self.base_url =  base_url

    def __getattr__(self,key):
        return SOURIParts(self, [])

    def __getitem__(self,key):
        return self.__getattr__(key)

I hope this helps.

Upvotes: 3

synthesizerpatel
synthesizerpatel

Reputation: 28036

You could override __getslice__(Python 2.7), or getitem()(Python3.x) and use a memorizing decorator so that if the slice you request (the userid) has already been looked up it would use cached results -- otherwise it could retrieve the results and populate the existing SO instance object.

However, I think a more OO way to solve the problem is make SO a pure lookup module that returns stack overflow user objects which would then have the deeper-digging lookups for profile details. But thats just me.

Upvotes: 0

Related Questions