Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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.

like image 767
mastachimp Avatar asked Nov 16 '17 00:11

mastachimp


1 Answers

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.

like image 123
jsbueno Avatar answered Nov 15 '22 15:11

jsbueno