pydantic: Using custom metaclass raises metaclass conflict

Initial Checks

  • I have searched GitHub for a duplicate issue and I’m sure this is something new
  • I have searched Google & StackOverflow for a solution and couldn’t find anything
  • I have read and followed the docs and still think this is a bug
  • I am confident that the issue is with pydantic (not my code, or another library in the ecosystem like FastAPI or mypy)

Description

I am getting the following error:

TypeError: metaclass conflict: the metaclass of a derived class must be a (non-strict) subclass of the metaclasses of all its bases

Example Code

class MetaBase(type):
    def __new__(mcs, name, bases, attrs):
        cls = type.__new__(mcs, name, bases, attrs)
        return cls

class User(BaseModel, metaclass= MetaBase):
    id: int

Python, Pydantic & OS Version

pydantic version: 1.10.5
            pydantic compiled: True
                 install path: /Users/renato/Library/Caches/pypoetry/virtualenvs/apispec-plugins-c17nWIz5-py3.9/lib/python3.9/site-packages/pydantic
               python version: 3.9.12 (main, Apr  5 2022, 01:52:34)  [Clang 12.0.0 ]
                     platform: macOS-13.0-arm64-arm-64bit
     optional deps. installed: ['typing-extensions']

Affected Components

About this issue

  • Original URL
  • State: closed
  • Created a year ago
  • Comments: 15 (8 by maintainers)

Most upvoted comments

@codectl one thing that may work for you for this instead of using a custom metaclass is to override __init_subclass__, which was specifically introduced for the purpose of reducing the need for metaclasses in certain common situations.

So you could do something like:

_REGISTRY = {}

class RegisteredBaseModel(BaseModel):
    def __init_subclass__(cls, **kwargs):
        super().__init_subclass__(cls, **kwargs)
        _REGISTRY[cls.__name__] = cls


class User(RegisteredBaseModel):
    id: str
    ...

(Note: it might be a good idea to use cls.__qualname__ instead of cls.__name__, though __name__ may be good enough outside of pathological cases.)

@codectl Also, if you are willing to inherit from type(BaseModel), you might as well just import ModelMetaclass (which is exactly the same thing). It can be imported from pydantic.main:

from pydantic import BaseModel
from pydantic.main import ModelMetaclass

assert ModelMetaclass is type(BaseModel)  # no error gets raised

To be clear, unless @samuelcolvin were to say otherwise (and given his message above I strongly suspect he would not), I would not treat ModelMetaclass as a stable/public API, and would strongly encourage you to avoid modifying ModelMetaclass if you care about reducing the probability of your code breaking in future versions of pydantic. (@samuelcolvin I’m wondering if it might make sense to move the ModelMetaclass into an _internal module for this reason. Maybe it’s already “too public” from v1…)

The use case you’ve described of adding all subclasses to a registry is exactly the kind of thing __init_subclass__ was created for, I do think that approach should work for you.

@dmontagu’s solution looks great, I’d prefer not to make the metaclass public unless absolutely necessary.

ah, forgot to call super().__new__. Yes, you are right.

Then let’s wait for @samuelcolvin.