creating classes with mix-ins

S

samwyse

I'm writing a class that derives it's functionality from mix-ins.
Here's the code:

def boilerplate(what): # This used to be a decorator, but all of
the
##what = f.__name__ # function bodies turned out to be
'pass'.
'Validate the user, then call the appropriate plug-in.'
def template(self, which, username, password, *args):
if not self.security.isAuthorised(username, password,
which, what):
raise Exception('Unauthorised access')
return getattr(self.blog, what)(which, *args)
template.__name__ = what
template.__doc__ = getattr(self.blog, what).__doc__
return template

class MetaWeblog(object):
def __init__(self,
securityHandler=SimpleSecurityHandler,
blogHandler=SimpleBlogHandler):
self.security = securityHandler()
self.blog = blogHandler()
newPost = boilerplate('newPost')
editPost = boilerplate('editPost')
getPost = boilerplate('getPost')
# etc, etc, etc

I'd like to replace the method definitions with a loop:
for what in attr_list:
setattr(klass, what, boilerplate(what))

That begs the question of where I define 'klass' and 'attr_list'.
Should I use a class decorator, or a metaclass? In favor of
decorators is that I can see how to do it; in favor of a metaclass is
that I get to learn how to use them. ;-) What are the other pros and
cons for each choice?
 
S

samwyse

I'm writing a class that derives it's functionality from mix-ins.

While waiting, I gave a try at using class decorators. Here's what I
came up with:

def add_methods(*m_list, **kwds):
def wrapper(klass):
for m_name in m_list:
def template(self, which, username, password, *args):
if not self.security.isAuthorised(username, password,
which, m_name):
raise Exception('Unauthorised access')
return getattr(self.blog, m_name)(which, *args)
dotted_name = kwds.get('prefix', '') + m_name
template.__name__ = dotted_name
template.__doc__ = dotted_name
setattr(klass, dotted_name, template)
return klass
return wrapper

@add_methods('newPost', 'editPost', 'getPost', 'newMediaObject',
'getCategories', 'getRecentPosts',
prefix='metaWeblog.')
class MetaWeblog(object):
def __init__(self,
securityHandler=SimpleSecurityHandler,
blogHandler=SimpleBlogHandler):
self.security = securityHandler()
self.blog = blogHandler()

if __name__ == '__main__':
server = SimpleXMLRPCServer(("localhost", 8080))
server.register_instance(MetaWeblog())
server.register_introspection_functions()
server.serve_forever()

The problem that I'm having is that when I call newPost,
SimpleBlogHandler.getRecentPosts gets invoked. Apparently add_methods
isn't generating unique templates. I think I need to move 'template'
into another function, but I'm starting to wonder if metaclasses might
work better. Any ideas?
 
C

Carl Banks

I'm writing a class that derives it's functionality from mix-ins.
Here's the code:

    def boilerplate(what):   # This used to be a decorator, but all of
the
        ##what = f.__name__  # function bodies turned out to be
'pass'.
        'Validate the user, then call the appropriate plug-in.'
        def template(self, which, username, password, *args):
            if not self.security.isAuthorised(username, password,
which, what):
                raise Exception('Unauthorised access')
            return getattr(self.blog, what)(which, *args)
        template.__name__ = what
        template.__doc__ = getattr(self.blog, what).__doc__
        return template

    class MetaWeblog(object):
        def __init__(self,
                     securityHandler=SimpleSecurityHandler,
                     blogHandler=SimpleBlogHandler):
            self.security = securityHandler()
            self.blog = blogHandler()
        newPost = boilerplate('newPost')
        editPost = boilerplate('editPost')
        getPost = boilerplate('getPost')
        # etc, etc, etc

I'd like to replace the method definitions with a loop:
        for what in attr_list:
            setattr(klass, what, boilerplate(what))

That begs the question of where I define 'klass' and 'attr_list'.
Should I use a class decorator, or a metaclass?

Here's the thing: unless you have advance knowledge of the methods
defined by self.blog, you can't get the attr_list at class definition
time, which means neither the metaclass nor the decorator would be a
good approach. If that's the case, you should define newPost,
editPost, and whatever other methods of self.blog as ordinary
attributes of self, within the init function. boilerplate would be
the same except you would pass self to it and allow template to use it
from its nested scope (it would no longer be an instance method since
it's an ordinary attribute).

If you do know what the methods of self.blog will be, then that's
where you get attr_list from. So, for instance, if blogHandler always
returns an object of type Blog, then you could inspect Blog's type
dict to see what methods are defined in it; in fact you probably want
to check the whole MRO for Blog, like this (untested):

attr_list = []
for cls in Blog.__mro__:
for value in cls.__dict__:
if is_wrapped_method(value):
attr_list.append(value)


A metaclass is probably overkill to assign the wrapped blog methods.
I probably wouldn't even bother with the decorator, and just write the
loop after the class definition. Then you can use MetaBlog directly
for klass.

class MetaBlog(object):
...

for what in attr_list:
setattr(MetaBlog, what, boilerplate(what))


If it were the kind of thing I found myself doing often I'd refactor
into a decorator.


Carl Banks
 
S

samwyse

Should I use a class decorator, or a metaclass?

Here's the thing: unless you have advance knowledge of the methods
defined by self.blog, you can't get the attr_list at class definition
time, which means neither the metaclass nor the decorator would be a
good approach.  If that's the case, you should define newPost,
editPost, and whatever other methods of self.blog as ordinary
attributes of self, within the init function.  boilerplate would be
the same except you would pass self to it and allow template to use it
from its nested scope (it would no longer be an instance method since
it's an ordinary attribute).

If you do know what the methods of self.blog will be, then that's
where you get attr_list from.  So, for instance, if blogHandler always
returns an object of type Blog, then you could inspect Blog's type
dict to see what methods are defined in it; in fact you probably want
to check the whole MRO for Blog, like this (untested):

attr_list = []
for cls in Blog.__mro__:
    for value in cls.__dict__:
        if is_wrapped_method(value):
            attr_list.append(value)

A metaclass is probably overkill to assign the wrapped blog methods.
I probably wouldn't even bother with the decorator, and just write the
loop after the class definition.  Then you can use MetaBlog directly
for klass.

class MetaBlog(object):
    ...

for what in attr_list:
    setattr(MetaBlog, what, boilerplate(what))

If it were the kind of thing I found myself doing often I'd refactor
into a decorator.

Unfortunately, 'boilerplate()' uses the handlers that I provide when
MetaBlog is instantiated. I tried the following, but it didn't work
(for reasons that were obvious in retrospect).

def instantiate_template(m_name, instance):
isAuthorised = instance.security.isAuthorised
method_to_wrap = getattr(instance.blog, m_name)
def template(self, which, username, password, *args):
if not isAuthorised(username, password, which, m_name):
raise Exception('Unauthorised access')
return method_to_wrap(which, *args)
template.__name__ = method_to_wrap.__name__
template.__doc__ = method_to_wrap.__doc__
return template

class MetaWeblog(object):
def __init__(self,
securityHandler=SimpleSecurityHandler,
blogHandler=SimpleBlogHandler):
self.security = securityHandler()
self.blog = blogHandler()
# from http://www.xmlrpc.com/metaWeblogApi
m_prefix = 'metaWeblog.'
m_list = ('newPost', 'editPost', 'getPost', 'newMediaObject',
'getCategories', 'getRecentPosts', )

# Here's where things fell apart
for m_name in m_list:
dotted_name = m_prefix + m_name
method = instantiate_template(m_name, self)
setattr(self, dotted_name, method)

So, I replaced that last for-loop with this:

# Since we're about to monkey-patch the class, we should
# make certain that all instances use the same handlers.
handlers = (self.security, self.blog)
try:
assert getattr(self.__class__, '_handlers') == handlers
except AttributeError:
for m_name in m_list:
dotted_name = m_prefix + m_name
method = instantiate_template(m_name, self)
setattr(self.__class__, dotted_name, method)
setattr(self.__class__, '_handlers', handlers)

This is good enough for now, since I can't conceive of a reason why
MetaBlog would be instantiated more than once. If it were, on the
other hand, it would probably be because you wanted to use different
handlers. In that case, I think I'd want to use a class factory,
something like this:
server.register_instance(
MetaWeblogFactory(securityHandler, blogHandler)()
)

Anyway, thanks for getting me over a conceptual hump.
 
C

Carl Banks

Unfortunately, 'boilerplate()' uses the handlers that I provide when
MetaBlog is instantiated.

In that case, the handler functions should be attributes of the
instance, not of the class. Do something like this:

class MetaBlog(object):
def __init__(self):
self.blog = blogHander()
for name in self.blog.get_methods():
setattr(self,name,self.boilerplate(name))

def boilerplate(self,name):
method = getattr(self.blog,name)
def template(*args):
self.do_verification_here()
method(*args)
return template


Carl Banks
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

No members online now.

Forum statistics

Threads
473,995
Messages
2,570,230
Members
46,817
Latest member
DicWeils

Latest Threads

Top