Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

TypeError: metaclass conflict with Django #12

Open
bjorntheart opened this issue Jan 12, 2018 · 2 comments
Open

TypeError: metaclass conflict with Django #12

bjorntheart opened this issue Jan 12, 2018 · 2 comments

Comments

@bjorntheart
Copy link

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
@ssanderson
Copy link
Owner

ssanderson commented Jan 12, 2018

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.

  1. 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 use implements 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.

  1. Add an enhanced version of implements named something like implements_with_metaclass, that takes an existing metaclass (in this case, it would be the metaclass of models.Model), and dynamically generates a new metaclass that multiply-inherits from both ImplementsMeta and the additional metaclass. Internally, this would look more or less like this with_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.

@eeriksp
Copy link

eeriksp commented Apr 16, 2019

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 :)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

3 participants