How can I use multiple inheritance with a metaclass?

Viewed 6079

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 ?

3 Answers

It doesn't need to be complex, but you will have to decide whether some ResourceMixins should be also registered in REGISTRY (here they will). In python 2 just use __metaclass__ I've spent few hours because i didn't save the code and was wondering what kind of sorcery is this. In my case i wanted to add extra method_decorators, but Resource class have it already declared so it's not possible to add it in attrs, but set on new_cls object. Also don't forget to call MethodViewType.__new__ (super(), not type).

from flask_restful import Resource
from flask.views import MethodViewType

class ResourceRegistry(MethodViewType):

    REGISTRY = {}

    def __new__(cls, name, bases, attrs):
        new_cls = super().__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 DefaultResource(Resource, metaclass=ResourceRegistry):

    @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()
Related