A django model that subclasses an abc, gives a metaclass conflict - python

I have a following model and abstract base class
import abc
from django.db import models
class AbstractBase():
__metaclass__ = abc.ABCMeta
#abc.abstractmethod
def my_method(self):
return
class MyModel(models.Model, AbstractBase):
#abc.abstractmethod
def my_method(self):
return 1
But I am getting the following error.
metaclass conflict: the metaclass of a derived class must be a
(non-strict) subclass of the metaclasses of all its bases
I think the problem here is (As it is described here http://code.activestate.com/recipes/204197-solving-the-metaclass-conflict/) that two base class has two different metaclasses so python cannot decide which metaclass to use for child object.
In order to solve this I removed multiple inheritence and use following register method to register child class
abc.register(Child)
But I did not really like this approach since it looks like monkey patching.
Is there another way to solve this problem?
I try to assign Model metaclass to Child explicitly but it did not work.
I am not looking for a way to solve it by writing code. I think this must be solved by changing my class structure.

Apart from creating a new metaclass that inherits from both ABCMeta and ModelBase, or making ABCMeta inherit from ModelBase, there isn't much you can do.
However, possibly a different registration pattern might be appropriate? Maybe something like contrib.admin.autodiscover? Or a class decorator? Or a loop at the bottom of the .py file which calls register on the appropriate classes (ex, for var in globals().values(): if isinstance(var, type) and issubclass(var, AbastractBase): register(var))?
Edit: D'oh. I'd assumed that ABCMeta was an example, not ABCMeta. That's what I get for browsing StackOverflow on too little sleep.

Related

Understanding Abstract Base Classes in Python

I was reading about abstract base class and came across https://www.python-course.eu/python3_abstract_classes.php website. I got general idea about them but I found two statement contradictory of each other.
Subclasses of an abstract class in Python are not required to implement abstract methods of the parent class.
and
A class that is derived from an abstract class cannot be instantiated unless all of its abstract methods are overridden.
My understanding of first statement is, derived class are not required to implement abstract method of the parent class which is wrong. I made a sample program to check that.
from abc import ABC, abstractmethod
class AbstractClassExample(ABC):
#abstractmethod
def do_something(self):
print("Some implementation!")
class AnotherSubclass(AbstractClassExample):
def just_another_method(self):
super().do_something()
print("The enrichment from AnotherSubclass")
x = AnotherSubclass() # TypeError: Can't instantiate abstract class AnotherSubclass with abstract methods do_something
x.do_something()
I would like an explanation of what the first statement means(preferably with examples).
Your code demonstrates that the second statement is true. It doesn't show that the first statement is false.
In your code, you are trying to instantiate AnotherSubclass, which is not allowed because AnotherSubclass does not implement all the abstract methods. The second statement says this.
However, if you delete the last two lines, i.e. not instantiating AnotherSubclass, then your code will produce no errors when you try to run it. This shows that the first statement is true - subclasses of abstract classes that doesn't implement all its abstract methods are allowed to exist.
You can write another subclass of AnotherSubclass called YetAnotherClass, this time implementing the abstract method, and you will be able to instantiate YetAnotherClass. Note that your program now does something, and AnotherSubclass is still allowed to exist.

Using ABC, PolymorphicModel, django-models gives metaclass conflict

So far every other answer on SO answers in the exact same way: construct your metaclasses and then inherit the 'joined' version of those metaclasses, i.e.
class M_A(type): pass
class M_B(type): pass
class A(metaclass=M_A): pass
class B(metaclass=M_B): pass
class M_C(M_A, M_B): pass
class C:(A, B, metaclass=M_C): pass
But I don't know what world these people are living in, where they're constructing your own metaclasses! Obviously, one would be using classes from other libraries and unless you have a perfect handle on meta programming, how are you supposed to know whether you can just override a class's metaclass? (Clearly I do not have a handle on them yet).
My problem is:
class InterfaceToTransactions(ABC):
def account(self):
return None
...
class Category(PolymorphicModel, InterfaceToTransactions):
def account(self):
return self.source_account
...
class Income(TimeStampedModel, InterfaceToTransactions):
def account(self):
return self.destination_account
...
Which of course gives me the error: "metaclass conflict: the metaclass of a derived class must be a (non-strict) subclass of the metaclasses of all its bases"
I've tried many variations of the solution given above, the following does not work, gives the same error.
class InterfaceToTransactionsIntermediaryMeta(type(PolymorphicModel), type(InterfaceToTransactions)):
pass
class Category(PolymorphicModel, InterfaceToTransactions):
__metaclass__ = InterfaceToTransactionsIntermediaryMeta
...
Nor does putting anything inside the class Meta function. I've read every single other SO question on this topic, please don't simply mark it as duplicate.
-------------------Edited 1/8/18 after accepting the solution-------
Oddly enough, if I try to makemigrations with this new configuration (the one I accepted), it starts giving the metaclass error again, but it still works during runtime. If I comment out the metaclass parts then makemigrations and migrate, it will do it successfully, but then I have to put it back in there after migrating every time.
If you are using Python 3, you are trying to use your derived metaclass incorrectly.
And since you get "the same error", and not other possible, more subtle, error, I'd say this is what is happening.
Try just changing to:
class IntermediaryMeta(type(InterfaceToTransactions), type(PolymorphicModel)):
pass
class Category(PolymorphicModel, InterfaceToTransactions, metaclass=IntermediaryMeta):
...
(At least the ABCMeta class is guaranteed to work collaboratively using super, that is enough motive to place the classe it first on the bases )
tuple)
If that yields you new and improved errors, this means that one or both of those classes can't really collaborate properly due to one of several motives. Then, the way to go is to force your inheritance tree that depends on ABCMeta not to do so, since its role is almost aesthetical in a language where everything else is for "consenting adults" like Python.
Unfortunatelly, the way to that is to use varying methods of brute-force, from safe "rewritting everything" to monkey patching ABCMeta and abstractmethod on the place were "InterfaceToTransactions" is defined to simply do nothing.
If you need to get there, and need some help, please post another question.
Sorry - this is actually the major drawbacks of using metaclasses.
Unless django-polymorphic decides to inherit from abc.ABC this is going to be very difficult to achieve. A good solution would be to "manually" create your interface. For instance:
class InterfaceToTransactions:
def account(self):
raise NotImplementedError("Account method must be implemented.")
...
class Category(PolymorphicModel, InterfaceToTransactions):
def account(self):
return self.source_account
...
class Income(TimeStampedModel, InterfaceToTransactions):
def account(self):
return self.destination_account
...

How is one meant to use the collections.abc classes with multiple inheritance?

I'm having an issue where I want to use the mixin methods from collections.abc.MutableSequence, but I also have to inherit from something else.
class Thing(urwid.Pile, collections.abc.MutableSequence):
...
I end up with
TypeError: metaclass conflict: the metaclass of a derived class must be
a (non-strict) subclass of the metaclasses of all its bases
How do I determine what's going on, and fix it? metaclass = ABCMeta doesn't do the trick, for what it's worth.
metaclass=ABCMeta is the problem. MutableSequence is using ABCMeta as its metaclass, Pile is using something else, hence the conflict.
What you can do is inherit from Pile and use MutableSequence.register(), like this:
class Thing(urwid.Pile):
...
collections.abc.MutableSequence.register(Thing)
You won't get an exception if your Thing does not implement all the required methods, however issubclass(Thing, MutableSequence) and isinstance(Thing(), MutableSequence) will return True.

How do I combine wxPython, abc, and a metaclass mixin?

I have a base class from which other classes should inherit:
class AppToolbar(wx.ToolBar):
''' Base class for the Canary toolbars '''
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
# ... a few common implementation details that work as expected...
self._PopulateToolbar()
self.Realize()
The base class does not (and cannot) implement _PopulateToolbar(); it should be an abstract method. As such, I figured using abc was a good plan, so I tried this:
class AppToolbar(wx.ToolBar, metaclass=abc.ABCMeta):
# ... as above, but with the following added
#abc.abstractmethod
def _PopulateToolbar():
pass
Perhaps unsurprisingly, attempting to run this led to TypeError: metaclass conflict: the metaclass of a derived class must be a (non-strict) subclass of the metaclasses of all its bases. I thought, "Oh, right, I'll just use a mixin":
class PopulateToolbarMixin(metaclass=ABCMeta):
#abstractmethod
def _PopulateToolbar(self):
pass
PopulateToolbarMixin.register(wx.ToolBar)
PopulateToolbarMixin.register(AppToolbar)
No change: still the same TypeError message. I suspect I'm missing something obvious with the use of ABCMeta here; this doesn't look like an error specific to wxPython. What am I doing wrong? Is there a better way to approach the same issue?
Edit: it has been pointed out to me in a conversation with a colleague that one cannot mix metaclasses. Since wx.ToolBar apparently derives from sip.wrappertype, it looks like there is no way to do this. What is another, still Pythonic way to handle the "abstract method" approach here?
In your first example, where you inherit from wx.ToolBar and abc.ABCMeta, you don't want AppToolbar to be a subclass of abc.ABCMeta, you want AppToolbar to be an instance of it. Try this:
class AppToolbar(wx.ToolBar, metaclass=abc.ABCMeta):
# ... as above, but with the following added
#abc.abstractmethod
def _PopulateToolbar():
pass
Though looking at this a bit closer, it seems that you can't define a subclass of wx.Toolbar with abc.ABCMeta as its metaclass, as wx.Toolbar is an instance of a metaclass other than bultins.type. You can, however, get abstract-like behavior out of AppToolbar._PopulateToolbar:
class AppToolbar(wx.ToolBar):
def _PopulateToolbar():
''' This is an abstract method; subclasses must override it. '''
raise NotImplementedError('Abstract method "_PopulateToolbar" must be overridden before it can be called.')

Django __getitem__ on model: metaclass conflict

In Django, I'd like to implement __getitem__ on a class level (so in the below example, I want to do Alpha['a']). I've found that I need a metaclass for this: just like it this needs to be implemented on a class to make it accessible on the instance, it must be implemented on a metaclass to use it on class level, as I understand it.
class AlphaMeta(type):
a = 7
def __getitem__(self, key):
return getattr(self, key)
class Alpha(models.Model):
value = models.CharField(max_length = 64, default = '')
__metaclass__ = AlphaMeta
print Alpha['a']
The problem is that I get the error below. It works fine if Alpha is a normal new-style class (class Alpha(object)), but for a more complex base it needs more. However, I don't unstand what it wants from me, as I don't understand what the metaclasses of all it's bases are.
metaclass conflict: the metaclass of a derived class must be a
(non-strict) subclass of the metaclasses of all it's bases
I'm very new to metaclasses; any hints are greatly appreciated!
EDIT: model fields go in Alpha rather than AlphaMeta
I would really suggest avoiding messing with the metaclass of models as you can easily run into some weird issues that are hard to debug. Anyway, if you still want to do this, the error message tells you what you need to do.
AlphaMeta needs to be a subclass of the metaclass of models.Model, which is django.db.models.base.ModelBase. So try
from django.db.models.base import ModelBase
class AlphaMeta(ModelBase):
…
You probably also want to call the superclass implementation in the case of a KeyError.

Categories

Resources