Search code examples
pythonclassdynamicmetaclasssqlmodel

Dynamically define class with inheritance and static attributes (sqlmodel / sqlalchemy metaclasses)


I have the following class definition that I would like to make dynamic:

class SQLModel_custom(SQLModel, registry=self.mapper_registry):
    metadata = MetaData(schema=self.schema)

I've tried something like that:

type('SQLModel_custom', (SQLModel, self.mapper_registry), {'metadata': MetaData(schema=self.schema)})

But this give me 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

Maybe the issue comes from the fact I'm not using registry= when defining parent classes in the dynamic version, but I don't see how I could acheive the same result. Any advice? Thank you!


Solution

  • There are two problems here:

    1. As the error message indicates, you are calling the wrong metaclass. SQLModel itself is not constructed with type, but some other subclass of type. While the rules for determining the correct metaclass can be complicated, here it's simple: use the same one the (only) parent uses.

    2. Only base classes are included in the 2nd argument to the metaclass; registry is not a base class, but a keyword argument intended to be passed to the metaclass. (If you see self.mapper_registry in the tuple, you should see a relevant error once you call the correct metaclass.)

    The following should work:

    mc = type(SQLModel)
    
    SQLModel_custom = mc('SQLModel_custom', 
                         (SQLModel,),
                         {'metadata': MetaData(schema=self.schema)},
                         registry=self.mapper_registry)
    

    Point 1 is less of an issue than I previously thought; the error message comes from trying to use the types of SQLModel and self.mapper_registry to determine the correct metaclass. type itself apparently can return an instance of the proper metaclass without you needing to call it explicitly. A simple example:

    class MyMeta(type):
        pass
    
    
    class Foo(metaclass=MyMeta):
        pass
    
    
    Bar = type('Bar', (Foo,), {})
    
    
    # The call to type figures out that the correct metaclass is
    # type(Foo) == MyMeta, not type itself.
    assert isinstance(Bar, MyMeta)