Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Python API design pattern question

Tags:

python

I often find I have class instances that are descendants of other class instances, in a tree like fashion. For example say I'm making a CMS platform in Python. I might have a Realm, and under that a Blog, and under that a Post. Each constructor takes it's parent as the first parameter so it knows what it belongs to. It might look like this:

class Realm(object):
    def __init__(self, username, password)

class Blog(object):
    def __init__(self, realm, name)

class Post(object);
    def __init__(self, blog, title, body)

I typically add a create method to the parent class, so the linkage is a bit more automatic. My Realm class might look like this:

class Realm(object):
    def __init__(self, username, password):
        ...
    def createBlog(self, name):
        return Blog(self, name)

That allows the user of the API to not import every single module, just the top level one. It might be like:

realm = Realm("admin", "FDS$#%")
blog = realm.createBlog("Kittens!")
post = blog.createPost("Cute kitten", "Some HTML blah blah")

The problem is those create methods are redundant and I have to pydoc the same parameters in two places.

I wonder if there's a pattern (perhaps using metaclasses) for linking one class instance to a parent class instance. Some way I could call code like this and have the blog know what it's parent realm is:

realm = Realm("admin", "FDS$#%")
blog = realm.Blog("Kittens!")
like image 239
jpsimons Avatar asked Mar 05 '11 18:03

jpsimons


1 Answers

You could use a common base class for the containers featuring an add() method

class Container(object):
    def __init__(self, parent=None):
        self.children = []
        self.parent = parent
    def add(self, child)
        child.parent = self
        self.children.append(child)
        return child

and make the parent parameter optional in the derived classes

class Blog(Container):
    def __init__(self, name, realm=None):
        Container.__init__(realm)
        self.name = name

Your code above would now read

realm = Realm("admin", "FDS$#%")
blog = realm.add(Blog("Kittens!"))
post = blog.add(Post("Cute kitten", "Some HTML blah blah"))

You wouldn't have any create...() methods any more, so no need to document anything twice.

If setting the parent involves more than just modifying the parent attribute, you could use a property or a setter method.

EDIT: As you pointed out in the comments below, the children should be tied to the parents by the end of the contstructor. The above approach can be modified to support this:

class Container(object):
    def __init__(self, parent=None):
        self.children = []
        self.parent = None
    def add(self, cls, *args)
        child = cls(self, *args)
        self.children.append(child)
        return child

class Realm(Container):
    def __init__(self, username, password):
        ...

class Blog(Container):
    def __init__(self, realm, name):
        ...

class Post(Container):
    def __init__(self, blog, title, body):
        ...

realm = Realm("admin", "FDS$#%")
blog = realm.add(Blog, "Kittens!")
post = blog.add(Post, "Cute kitten", "Some HTML blah blah")
like image 139
Sven Marnach Avatar answered Nov 07 '22 14:11

Sven Marnach