[BangPypers] Adding mixing at run-time

Anand Chitipothu anandology at gmail.com
Thu Oct 9 13:44:30 CEST 2014


On Thu, Oct 9, 2014 at 11:30 AM, kracekumar ramaraju <
kracethekingmaker at gmail.com> wrote:

> On Wed, Oct 8, 2014 at 4:19 PM, Anand Chitipothu <anandology at gmail.com>
> wrote:
>
> > Hi,
> >
> > I'm working on a slightly large application and I'm trying to model it
> as a
> > some kind of plugin architecture so that it modular and I can enable or
> > take out features without lot of code changes.
> >
> > One of the issues I faced was that the wanted to add some methods to a
> > class only when a feature/plugin is enabled.
> >
> > Here is a simplified version of what I have:
> >
> > class Place(Mixable):
> >     def __init__(self, name):
> >         self.name = name
> >
> >     def get_users(self):
> >         return "users-of-{}".format(self.name)
> >
> >     def get_links(self):
> >         return "links-of-{}".format(self.name)
> >
> > There are couple of issues with this.
> >
> > 1. The logic for computing users or links doesn't really belong to this
> > file. I wanted to that in a separate module for modularity.
> >
> > 2. The Place class will become too large to manage over time.
> >
> > So, I came up with the following approach to address these issues.
> >
> > # place.py
> >
> > class Mixable(object):
> >     """Magic class to allow adding mixins to the class at run-time.
> >     """
> >     @classmethod
> >     def mixin(cls, mixin):
> >         """Decorator to add a mixin to the class runtime.
> >         """
> >         cls.__bases__ = cls.__bases__ + (mixin,)
> >
> > class Place(Mixable):
> >     def __init__(self, name):
> >         self.name = name
> >
> > # users.py
> > @Place.mixin
> > class UsersMixin(object):
> >     def get_users(self):
> >         return "users-of-{}".format(self.name)
> >
> >
> Is there any specific reason for using decorators ?
>

Just because I felt that provides a nice API.

I would have used a more explicit approach like the following, but I liked
the decorators one better.

def setup_plugin():
    register_place_plugin(UsersMixin)

> # links.py
> > @Place.mixin
> > class LinksMixin(object):
> >     def get_links(self):
> >         return "links-of-{}".format(self.name)
> >
> > p = Place('bangalore')
> > print(p.get_users())
> > print(p.get_links())
>
>
> I somehow feel this can lead to unexpected behaviour since Mixable classes
> are added at various files.
>

Yes, it requires some discipline.


> This pattern looks like global variable argument (global variables are
> bad). Say if Idecorate Foo with @Place.mixin by mistake, behaviour of the
> Foo will be available in Place class and leads to side effect.
>

Well that is a compromise. Same as Flask vs. Django URL routing.


>
> How about
>
> #dispatcher.py
> # import all the classes
>
> def get_place_class():
>       # Check for features
>       for cls in classes:
>            Place.__bases__ = Place.__bases__ + (cls,)
>       return Place
>

That should be setup_place_class() and that should be called only once. But
the decorators approach makes calling it twice impossible.


> > With this I was able to split the class into 3 files and now I have
> > flexibility of deciding which features enable from a config file.
> >
> > What do you guys think of this approach?
> >
>
> My only concern base classes are added in different files and difficult to
> remember.
>

Yes, it will become difficult if not used with care, but I think it can be
used very elegantly if enough care is given.

What I really want is multiple people work on separate parts of the system
without having to touch the core code.


> Is this a good practice?
> > Are there any know flaws in this approach?
>
> How did you solve that issue when you faced similar situation?
> >
> >
> Did I understand the problem correctly ?
>

 I think so.

Anand


More information about the BangPypers mailing list