Force A Class With Multiple Inheritance To Have A Specific Metaclass In Python
I have a class (a Marshmallow schema) that subclasses two parents:
class OptionalLinkSchema(JsonApiSchema, ModelSchema): pass
JsonApiSchemahas the metaclass
ModelSchemahas the metaclass
ModelSchemaMeta(SchemaMeta)(it is a subclass of
Now, I don't want the
ModelSchemaMeta metaclass for my class, I just want the simpler
SchemaMeta. However, according to the Python docs, "The most derived metaclass is selected", meaning that no matter what I do,
ModelSchemaMeta will be selected as the metaclass.
Even if I try to manually choose a metaclass, the same thing happens:
class OptionalLinkSchema(JsonApiSchema, ModelSchema, metaclass=SchemaMeta): pass print(type(OptionalLinkSchema))
Is there no way to override Python's behaviour of always choosing the most derived metaclass?
First things first: I should warn you that you likely have an "xy" problem there: If a class is designed to work counting with the mechanisms in its metaclass, if those are not run when it is created, chances are the class won't work.
Second thing: the language won't do that "on its own". And it won't because it would break things for the reason above. So, there are some approaches that can be taken, all intrusive, and expecting that you know what you are doing. If your
OptionaLinkSchema depends on any features on
ModelSchemaMeta, it will simply break.
I can think of 3 ways of achieving the equivaent of "stripping an inherited metaclass"
You don't say anything on your question, but I see no
ModelSchemaMeta in Marshmallow's source tree - is it a metaclass that you have created? If so, since Marshmallow uses the "djangoish" idiom of having a nested
Meta class to declare things about the class itself, you could use an attribute in this
Meta namespace to skip your own metaclass.
If so, add code like this to your undesired metaclass'
__new__ method (I am picking the "meta" parsing code from Marshmallow's source code itself):
class ModelSchemaMeta(SchemaMeta): def __new__(mcs, name, bases, attrs): meta = attrs.get("Meta") if getattr(meta, "skip_model_schema_meta", False): # force ShemaMeta metaclass cls = super().__new__(SchemaMeta, name, bases, attrs) # manually call `__init__` because Python does not do that # if the value returned from `__new__` is not an instance # of `mcs` cls.__init__(name, bases, attrs) return cls # Your original ModelSchemaMeta source code goes here class OptionalLinkSchema(...): ... class Meta: skip_model_schema_meta = True ...
A severely more invasive way of doing this, but that would work for any class inheritance tree is having code that will take the superclass with the unwanted metaclass and create a clone of it. However, since there is a custom metaclass other than
type involved, chances of this working are slim in this case - as the "cloning" process won't feed the "grandparent" metaclass the expected raw fields it wants to convert to the attributes in the final class. Also, Marsmallow uses a class registry - creating such an intermediate clone would have the clone registered there as well.
In short: this approach does not apply to your case, but I describe it here since it might be useful for others that hit this question:
def strip_meta(cls,): "builds a clone of cls, stripping the most derived metaclass it has. There are no warranties the resulting cls will work at all - specially if one or more of the metaclasses that are being kept make transformations on the attributes as they are declared in the class body. " new_meta = type(cls).__mro__[1:] return (new_meta(cls.__name__, cls.__mro__, dict(cls.__dict__))) class OptionalLinkSchema(FirstClass, strip_meta(SecondClass)): ...
(Anyone trying to use this not that if the SecondClass is itself derived from others that use the unwanted metaclass, the strategy have to be modified to recursively strip the metaclass of the superclasses as well)
Another, simpler approach would be to create manually the derived metaclass and just hardcode the calls to the desired metaclass, skipping the superclass. This thing might yield "sober" enough code to be actually usable in production.
Therefore, you do not "set an arbitrary metaclass" - instead, you create a valid metaclass that does what you want.
class NewMeta(ModelSchemaMeta, SchemaMeta): """Order of inheritance matters: we ant to skip methods on the first baseclass, by hardwiring calls to SchemaMeta. If we do it the other way around, `super` calls on SchemaMeta will go to ModelSchemaMeta). Also, if ModeSchemaMeta inherits from SchemaMeta, you can inherit from it alone """ def __new__(*args, **kw): return SchemaMeta.__new__(*args, **kw) def __init__(*args, **kw): return SchemaMeta.__init__(*args, **kw) # repeat for other methos you want to use from SchemaMeta # also, undesired attributes can be set to "None": undesired_method_in_model_schema_meta = None
The difference from this to the first approach is that
ModelSchemaMeta will be the inheritance tree for the metaclass, and the final metaclass will be this new, derived metaclass. But this won't depend on you having control of
ModelSchemaMeta code - and, as I said before, this is more "within the expected rules" of the language than the second approach.
- → What are the pluses/minuses of different ways to configure GPIOs on the Beaglebone Black?
- → Django, code inside <script> tag doesn't work in a template
- → React - Django webpack config with dynamic 'output'
- → GAE Python app - Does URL matter for SEO?
- → Put a Rendered Django Template in Json along with some other items
- → session disappears when request is sent from fetch
- → Python Shopify API output formatted datetime string in django template
- → Shopify app: adding a new shipping address via webhook
- → Shopify + Python library: how to create new shipping address
- → shopify python api: how do add new assets to published theme?
- → Access 'HTTP_X_SHOPIFY_SHOP_API_CALL_LIMIT' with Python Shopify Module