0

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!

ibi0tux
  • 2,481
  • 4
  • 28
  • 49

1 Answers1

1

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)
chepner
  • 497,756
  • 71
  • 530
  • 681
  • It seems to work well. Thank you for the explanation, but I don't really understand why I need to create the `mc` metaclass first. It seems to work equally with `type('SQLModel_custom', (SQLModel,), {'metadata': MetaData(schema=self.schema)}, registry=self.mapper_registry)`. – ibi0tux May 22 '23 at 14:16
  • 1
    Hm, I guess the error could result from trying to use `type(self.mapper_registry)` to determine the metaclass. `type` itself may be able to look at the base class(es) and return an instance of the correct metaclass if you don't call the correct metaclass directly; I'm not sure. – chepner May 22 '23 at 14:20
  • I had misunderstood the way `type()` works. It makes much more sense now. Thanks. – ibi0tux May 22 '23 at 14:24
  • `type` is weird: with one argument, it simply returns the type of its argument. With three arguments (plus any optional keyword arguments), it *creates* a new instance of `type`. – chepner May 22 '23 at 14:26