Marshmallow: Is it possible to dynamically nest other schemas?

Created on 3 Jun 2020  路  5Comments  路  Source: marshmallow-code/marshmallow

I have a MySQL table that has MANY relationships (about 50) and I would like to avoid:

  • Include all relationships in nested items
  • Write schemas for each case, depending on which relationship to nest

I tried setting the nested items in the WebsiteSchema constructor, setting it in the include_items() method, but nothing works, the items property only exists if it has been defined completely in the class definition. I tried searching in the docs and Google but I cannot find how to do this, I have a hunch I should be using the OPTIONS_CLASS but I don't quite understand how it works.

Code showing what I tried:

from components.ma import ma
from schemas.item import ItemSchema

class WebsiteSchema(ma.SQLAlchemyAutoSchema):
    # This line works obviously
    # items = ma.Nested(ItemSchema, many=True)

    items = None

    # Not working
    def __init__(self, **kwargs):
        self.items = ma.Nested(ItemSchema, many=True)
        super().__init__(**kwargs)

    # Not working
    def include_items(self):
        self.items = ma.Nested(ItemSchema, many=True)
question

All 5 comments

I'm afraid you have to do it at class creation, not instantiation.

Related: https://stackoverflow.com/q/42231334/4653485

Perhaps you could override __new__ if you want it more dynamic.

You could make use of Schema.from_dict to dynamically create new schemas at runtime. Something like:

class WebsiteSchema(ma.SQLAlchemyAutoSchema):
    ...common fields...

RELATIONSHIP_FIELDS = {
    "items": ma.List(ma.Nested(ItemSchema()))
}

def make_schema(relationships: Sequence[str]):
    return WebsiteSchema.from_dict({
        name: RELATIONSHIP_FIELDS[name]
        for name in relationships
    })


# ------

schema = make_schema(["items"])()
schema.load({"items": [...]})

EDIT: fix accessing fields

I'm afraid you have to do it at class creation, not instantiation.

Related: https://stackoverflow.com/q/42231334/4653485

Perhaps you could override __new__ if you want it more dynamic.

Thanks for the ideas, __new__ isn't working either, type('WebsiteSchema', (WebsiteSchema,), dict(items=ma.Nested(ItemSchema, many=True))) returns "TypeError: dump() missing 1 required positional argument: 'obj'" and I have no idea why. type() would have been nice however :)

You could make use of Schema.from_dict to dynamically create new schemas at runtime. Something like:

class WebsiteSchema(ma.SQLAlchemyAutoSchema):
    ...common fields...

RELATIONSHIP_FIELDS = {
    "items": fields.List(ItemSchema())
}

def make_schema(relationships: Sequence[str]):
    return WebsiteSchema.from_dict({
        name: RELATIONSHIP_FIELDS[name]
        for name in relationships
    })


# ------

schema = make_schema(["items"])()
schema.load({"items": [...]})

Thanks friend, my Python skills are not advanced enough to know this was possible! What import are you using for fields however? Though it would be from marshmallow import fields, however with that import or any other I could think of, I have the ValueError: The list elements must be a subclass or instance of marshmallow.base.FieldABC. exception.

Oh, that was haste on my part. Since it looks like you're using flask-marshmallow, then you can access the fields from ma. I also fixed the List(Nested(...)) usage. I updated my code snippet.

Thanks a million! from_dict with the extra Python theory I didn't know about will be of tremendous help!

Was this page helpful?
0 / 5 - 0 ratings

Related issues

DenisKuplyakov picture DenisKuplyakov  路  4Comments

jayennis22 picture jayennis22  路  4Comments

manoadamro picture manoadamro  路  3Comments

lupodellasleppa picture lupodellasleppa  路  3Comments

ambye85 picture ambye85  路  4Comments