I'm trying to register all the resources that I defined with Flask-RESTFUL
using the registry pattern.
from flask_restful import Resource
class ResourceRegistry(type):
REGISTRY = {}
def __new__(cls, name, bases, attrs):
new_cls = type.__new__(cls, name, bases, attrs)
cls.REGISTRY[new_cls.__name__] = new_cls
return new_cls
@classmethod
def get_registry(cls):
return dict(cls.REGISTRY)
class BaseRegistered(object):
__metaclass__ = ResourceRegistry
class DefaultResource(BaseRegistered, Resource):
@classmethod
def get_resource_name(cls):
s = re.sub('(.)([A-Z][a-z]+)', r'\1-\2', cls.__name__)
return '/' + re.sub('([a-z0-9])([A-Z])', r'\1-\2', s).lower()
When the whole thing is launched I get the following:
TypeError: Error when calling the metaclass bases
metaclass conflict: the metaclass of a derived class must be a (non-strict) subclass of the metaclasses of all its bases
I've tried with layers of proxy classes but the result is still the same. So is there a way to register my resources using this pattern ?
A metaclass is the class used to build a class, which means that the very structure of a class - its internal __dict__
, for example, or the automatic delegation mechanism provided by __getattribute__
- are created into the class by the metaclass.
When you inherit from a parent class you are implicitly inheriting its metaclass, because you are basically just extending the structure of the parent class, which is in part provided by its metaclass.
So, when you inherit from a class you have either to keep the metaclass given you by your parent or define a new metaclass which is derived from that of your parent. This is true in the case of multiple inheritance as well.
An example of wrong Python code is
class ParentType(type):
pass
class Parent(metaclass=ParentType):
pass
class ChildType(type):
pass
class Child(Parent, metaclass=ChildType):
pass
This gives exactly the error you mention in your question. Making ChildType
a subclass of ParentType
solves the issue
class ParentType(type):
pass
class Parent(metaclass=ParentType):
pass
class ChildType(ParentType):
pass
class Child(Parent, metaclass=ChildType):
pass
The answer given by Dologan correctly addresses the problem creating a metaclass which inherits from both the metaclasses and then use it straightforwardly.
I would, if possible, avoid such a complex solution, however. Multiple inheritance is a bit difficult to manage - not a bad thing, just complex - and doing it in a metaclass could really result in something difficult to grasp.
It obviously depends on what you are trying to accomplish and how good you document and test the solution. If you want or need to solve it with metaclasses I would suggest to leverage the Abstract Base Classes (which are basically categories). ABCs contain the list of the registered classes in their _abc_registry
attribute, but there is no official method to get it, and it is better to avoid directly using it. You may however derive ABCMeta and make it keep a public registry with this code
import abc
class RegistryABCMeta(abc.ABCMeta):
def __init__(self, name, bases, namespace):
super().__init__(name, bases, namespace)
self.registry = []
def register(self, subclass):
super().register(subclass)
self.registry.append(subclass)
class RegistryABC(metaclass=RegistryABCMeta):
pass
You may now create categories just inheriting from RegistryABC
and using the register()
method:
class MyCategory(RegistryABC):
pass
MyCategory.register(tuple)
MyCategory.register(str)
print(MyCategory.registry)
and what you get here is [<class 'tuple'>, <class 'str'>]
.
Your DefaultResource
class seems to be inheriting from classes with two different metaclasses: BaseRegistered
(with metaclass ResourceRegistry
) and Resource
(with Flask's MethodViewType
metaclass).
This answer would suggest doing something like:
from flask.views import MethodViewType
class CombinedType(ResourceRegistry, MethodViewType):
pass
class BaseRegistered(object):
__metaclass__ = Combinedtype
And then proceed as before.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With