Hi! I need some input on my use of metaclasses since I'm not sure I'm using them in a pythonic and graceful manner. I'm very grateful for any tips, pointers and RTFMs I can get from you guys.
Below, you'll find some background info and an executable code example. In the code example I have two ways of doing the same thing. The problem is that the "Neat" version doesn't work, and the "Ugly" version that works gives me the creeps. The "Neat" version raises a TypeError when I try the multiple inheritance (marked with comment in the code): Traceback (most recent call last): File "/bioinfo/yohell/pybox/gridjs/gridjs-2.0/test.py", line 132, in ? class FullAPI(JobAPI, UserAPI, AdminAPI): File "/bioinfo/yohell/pybox/gridjs/gridjs-2.0/test.py", line 43, in __new__ return type.__new__(cls,classname,bases,classdict) TypeError: Error when calling the metaclass bases metaclass conflict: the metaclass of a derived class must be a (non-strict) subclass of the metaclasses of all its bases In the "Ugly" version, I'm changing the metaclass in the global scope between class definitions, and that gives me bad vibes. What should I do? Is there a way to fix my "Neat" solution? Is my "Ugly" solution in fact not so horrid as I think it is? Or should I rethink the whole idea? Or maybe stick with decorating manually (or in BaseAPI.__init__)? Sincere thanks for your time. Cheers! /Joel Hedlund Background ########## (feel free to skip this if you are in a hurry) I'm writing an XMLRPC server that serves three types of clients (jobs, users and admins). To do this I'm subclassing SimpleXMLRPCServer for all the connection work, and I was planning on putting the entire XMLRPC API as public methods of a class, and expose it to clients using .register_instance(). Each session is initiated by a handshake where a challenge is presented to the client, each method call must then be authenticated using certificates and incremental digest response. Each client type must be authenticated differently, and each type of client will also use a discrete set of methods. At first, I was planning to use method decorators to do the validation, and have a different decorator for each type of client validation, like so: class FullAPI: @valid_job_required def do_routine_stuff(self, clientid, response, ...): pass @valid_user_required def do_mundane_stuff(self, clientid, response, ...): pass @valid_admin_required def do_scary_stuff(self, clientid, response, ...): pass ... There will be a lot of methods for each client type, so this class would become monstrous. Therefore I started wondering if it weren't a better idea to put the different client APIs in different classes and decorate them separately using metaclasses, and finally bring the APIs together using multiple inheritance. This is what I had in mind: class BaseAPI(object): pass class JobApi(BaseAPI): pass class UserApi(BaseAPI): pass class AdminApi(BaseAPI): pass class FullApi(JobAPI, UserAPI, AdminAPI): pass Now, I'm having trouble implementing the metaclass bit in a nice and pythonic way. Code example ############ test.py ======================================================================= # Base metaclass for decorating public methods: from decorator import decorator @decorator def no_change(func, *pargs, **kwargs): return func(*pargs, **kwargs) class DecoratePublicMethods(type): """Equip all public methods with a given decorator. Class data members: decorator = no_change: <decorator> The decorator that you wish to apply to public methods of the class instances. The default does not change program behavior. do_not_decorate = []: <iterable <str>> Names of public methods that should not be decorated. multiple_decoration = False: <bool> If set to False, methods will not be decorated if they already have been decorated by a prior metaclass. decoration_tag = '__public_method_decorated__': <str> Decorated public methods will be equipped with an attribute with this name and a value of True. """ decorator = no_change do_not_decorate = [] multiple_decoration = True decoration_tag = '__public_method_decorated__' def __new__(cls,classname,bases,classdict): for attr,item in classdict.items(): if not callable(item): continue if attr in cls.do_not_decorate or attr.startswith('_'): continue if (not cls.multiple_decoration and hasattr(classdict[attr], cls.decoration_tag)): continue classdict[attr] = cls.decorator(item) setattr(classdict[attr], cls.decoration_tag, True) return type.__new__(cls,classname,bases,classdict) ## Authentication stuff: class AuthenticationError(Exception): pass import random @decorator def validate_job(func, self, id, response, *pargs, **kwargs): """Bogus authentiction routine""" if random.randint(0,3) / 3: raise AuthenticationError return func(self, id, response, *pargs, **kwargs) @decorator def validate_user(func, self, id, response, *pargs, **kwargs): """Bogus authentiction routine""" if random.randint(0,4) / 4: raise AuthenticationError return func(self, id, response, *pargs, **kwargs) @decorator def validate_admin(func, self, id, response, *pargs, **kwargs): """Bogus authentiction routine""" if random.randint(0,1): raise AuthenticationError return func(self, id, response, *pargs, **kwargs) ## Ugly (?) way that works: ## ----------------------------------------------------------- #class BaseAPI(object): # __metaclass__ = DecoratePublicMethods # #DecoratePublicMethods.decorator = validate_job # #class JobAPI(BaseAPI): # def do_routine_stuff(self, clientid, response, foo): # print "Routine stuff done." # #DecoratePublicMethods.decorator = validate_user # #class UserAPI(BaseAPI): # def do_mundane_stuff(self, clientid, response, moo): # print "Mundane stuff done." # #DecoratePublicMethods.decorator = validate_admin # #class AdminAPI(BaseAPI): # def do_scary_stuff(self, clientid, response, moose): # print "Scary stuff done." # #class FullAPI(JobAPI, UserAPI, AdminAPI): # pass # #a = FullAPI() ## ----------------------------------------------------------- ## Neat (?) way that doesn't work: ## ----------------------------------------------------------- class RequireJobValidation(DecoratePublicMethods): decorator = validate_job class RequireUserValidation(DecoratePublicMethods): decorator = validate_user class RequireAdminValidation(DecoratePublicMethods): decorator = validate_admin class BaseAPI(object): pass class JobAPI(BaseAPI): __metaclass__ = RequireJobValidation def do_routine_stuff(self, clientid, response, foo): print "Routine stuff done." class UserAPI(BaseAPI): __metaclass__ = RequireUserValidation def do_mundane_stuff(self, clientid, response, moo): print "Mundane stuff done." class AdminAPI(BaseAPI): __metaclass__ = RequireAdminValidation def do_scary_stuff(self, clientid, response, moose): print "Scary stuff done." print "OK up to here." ## FIXME: Illegal multiple inheritance. class FullAPI(JobAPI, UserAPI, AdminAPI): pass b = FullAPI() ## ----------------------------------------------------------- ======================================================================= Oh, and by the way - this example uses Michele Simionato's excellent decorator module, available from here: http://www.phyast.pitt.edu/~micheles/python/decorator.zip If you don't want to donwload it, for this example you can just substitute this: @decorator def foo(func, *pargs, **kwargs): print "Code goes here" return func(*pargs, **kwargs) for this: def foo(func): def caller(*pargs, **kwargs): print "Code goes here" return func(*pargs, **kwargs) return caller The difference is that @decorator preserves function signatures and such for decorated functions. Very neat. Thanks again for your time. /Joel -- http://mail.python.org/mailman/listinfo/python-list