python-interface
python-interface copied to clipboard
TypeError: metaclass conflict with Django
I get the following error when using implements()
with a Django model.
class InstagramContentSource(ContentSource, implements(IContentSource)):
TypeError: metaclass conflict: the metaclass of a derived class must be a (non-strict) subclass of the metaclasses of all its bases
Interface
class IContentSource(Interface):
def get_id(self):
pass
Django Model
class ContentSource(models.Model, Logger):
...
Interface Implementation
class InstagramContentSource(ContentSource, implements(IContentSource)):
class Meta:
proxy = True
What implements(IFace)
actually does under the hood is construct a new instance of an ImplementsMeta
metaclass, which verifies the the implementation of your interface at type construction time.
One of the downsides of using a metaclass for this is that a given class can only have one metaclass. The error you're seeing there is telling you that both Django and Interface are trying to define a metaclass for your type, and python can't use both of them.
I can think of two ways that interface could potentially try to solve this problem.
- The simplest thing we could do would be to add a
check_implements
function that could be applied as a decorator to classes that can't useimplements
because they need another metaclass. The API for this would look something like:
@check_implements(IContentSource)
class InstagramContentSource(ContentSource):
...
This would work by doing the same checks that implements(IContentSource)
does currently. The downside of this API is that we wouldn't automatically apply interface checks to subclasses of InstagramContentSource
; you'd have to remember to manually decorate your subclasses.
- Add an enhanced version of
implements
named something likeimplements_with_metaclass
, that takes an existing metaclass (in this case, it would be the metaclass ofmodels.Model
), and dynamically generates a new metaclass that multiply-inherits from bothImplementsMeta
and the additional metaclass. Internally, this would look more or less like thiswith_metaclasses
function. The API for this would look something like:
meta = type(ContentSource)
class InstagramContentSource(ContentSource,
implements_with_meta(meta, IContentSource)):
class Meta:
proxy = True
That's a lot more verbose, and the implementation would be more complex, but the upside of this would be that implementation checks would continue to propagate through subclasses.
Here is another solution for this issue. Python 3.6 introduced a new method __init_subclass__
(the docs are here).
It is called automatically whenever a subclass is created and it gets in as a parameter the newly created subclass. So it has all the necessary data for performing the checks. Using this method, there is no need for a metaclass any more and thus the issue of metaclass conflicts will not arise.
I am using this approach in a similar project and are very happy with this new Python feature :)