Reputation: 10956
In a Python library I'm using, I want to wrap the public methods of a class from the library. I'm trying to use a MetaClass to do this like so.
from functools import wraps
from types import FunctionType
from six import with_metaclass
def wrapper(func):
@wraps(func)
def wrapped(*args, **kwargs):
print("wrapped %s" % func)
return func(*args, **kwargs)
return wrapped
class MetaClass(type):
def __new__(mcs, classname, bases, class_dict):
library_class_dict = bases[0].__dict__
print(library_class_dict)
for attributeName, attribute in library_class_dict.items():
if type(attribute) == FunctionType and \
not attributeName.startswith('_'):
print("%s %s" % (attributeName, attribute))
attribute = wrapper(attribute)
library_class_dict[attributeName] = attribute
print(library_class_dict)
return type.__new__(mcs, classname, bases, class_dict)
# this is the class from the library that I cannot edit
class LibraryClass(object):
def library_method(self):
print("library method")
class Session(with_metaclass(MetaClass, LibraryClass)):
def __init__(self, profile, **kwargs):
super(Session, self).__init__(**kwargs)
self.profile = profile
When you put this into a Python file and run it, you get the error
TypeError: Error when calling the metaclass bases
'dictproxy' object does not support item assignment
I get that trying to assign directly to __dict__
is a bad idea. That's not what I want to do. I would much rather add the MetaClass to the LibraryClass but I'm not sure how.
I've been through the other StackOverflow questions regarding Python MetaClass programming but haven't come across any that try to add a MetaClass to a library class where you can't the source code.
Upvotes: 4
Views: 255
Reputation: 1121834
You can't assign to a dictproxy. Use setattr()
to set attributes on a class:
setattr(bases[0], attributeName, attribute)
However, you don't need a metaclass to do this, which is entirely overkill here. You can just do this on that base class, and do it once:
for attributeName, attribute in vars(LibraryClass).items():
if isinstance(attribute, FunctionType) and not attributeName.startswith('_'):
setattr(LibraryClass, attributeName, wrapper(attribute))
This just does it once, rather than every time you create a subclass of of LibraryClass
.
Upvotes: 4
Reputation: 85442
Essentially you want to do this:
LibraryClass.library_method = wrapper(LibraryClass.library_method)
for all methods automatically.
Using your code pieces:
from functools import wraps
from types import FunctionType
class LibraryClass(object):
def library_method(self):
print("library method")
def wrapper(func):
@wraps(func)
def wrapped(*args, **kwargs):
print("wrapped %s" % func)
return func(*args, **kwargs)
return wrapped
You can write a helper function that does that for all methods:
def wrap_all_methods(cls):
for name, obj in cls.__dict__.items():
if isinstance(obj, FunctionType) and not name.startswith('_'):
setattr(cls, name, wrapper(obj))
return cls
Now, wrap all methods:
LibraryClass = wrap_all_methods(LibraryClass)
Test if it works:
class Session(LibraryClass):
pass
s = Session()
s.library_method()
prints:
wrapped <function LibraryClass.library_method at 0x109d67ea0>
library method
The method is wrapped.
Upvotes: 0