Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature request: dynamic field type #2174

Open
matejsp opened this issue Aug 18, 2023 · 1 comment
Open

Feature request: dynamic field type #2174

matejsp opened this issue Aug 18, 2023 · 1 comment

Comments

@matejsp
Copy link

matejsp commented Aug 18, 2023

I would like to have dynamic field that resolves its type based on data. One way is to create schema from dict.

Example (data based on section value):

        'section': 'basic',
        'data': {
            'occupation': 'bla',
            'profession': 'bla',
        }

or

        'section': 'extended',
        'data': {
            'place_of_birth': 'bla',
            'country': 'bla',
        }

I have prepared one simple version just to get a feedback if you like it and can be part of marshmallow.
Essentially DynamicField accepts a dynamic lambda that returns the field based on data and/or context. And then it delegates _deserialize and _serialize to that field.

import typing

import marshmallow
from marshmallow.fields import Field, String


class DynamicField(marshmallow.fields.Field):

    def __init__(
         self,
         dynamic: typing.Callable[[typing.Any, typing.Any, typing.Any], Field],
         **kwargs,
     ):
        super().__init__(
            **kwargs
        )
        self.dynamic = dynamic

    def get_field(self, data):
        context = getattr(self.parent, "context", {})
        field = self.dynamic(self.parent, data, context)
        return field

    def _serialize(self, value, attr, obj, **kwargs) -> typing.Any | None:
        if value is None:
            return None

        field = self.get_field(obj)
        return field._serialize(value, attr, obj, **kwargs)

    def _deserialize(self, value, attr, data, **kwargs) -> typing.Any:
        field = self.get_field(data)
        return field._deserialize(value, attr, data, **kwargs)


# Example
class BasicSectionSchema(marshmallow.Schema):
    occupation = String(required=True)
    profession = String(required=True)


class ExtendedSectionSchema(marshmallow.Schema):
    place_of_birth = String(required=True)
    country = String(required=True)


class SubmitSectionSchema(marshmallow.Schema):

    def resolve_field(self, data, context):
        if data.get("section", None) == "basic":
            return marshmallow.fields.Nested(BasicSectionSchema)
        elif data.get("section", None) == "extended":
            return marshmallow.fields.Nested(ExtendedSectionSchema)
        else:
            return marshmallow.fields.String()

    section = String(required=True)
    data = DynamicField(dynamic=resolve_field, required=True)


if __name__ == '__main__':
    schema = SubmitSectionSchema()
    schema.load({
        'section': 'basic',
        'data': {
            'occupation': 'bla',
            'profession': 'bla',
        },
    })

    schema.load({
        'section': 'extended',
        'data': {
            'place_of_birth': 'bla',
            'country': 'bla',
        },
    })

    schema.load({
        'section': 'junk',
        'data': 'some junk',
    })

    try:
        schema.load({
            'section': 'basic',
            'data': {
                'profession': 'bla',
            },
        })
    except marshmallow.ValidationError as e:
        print(e.messages)

    try:
        schema.load({
            'section': 'extended',
            'data': {
                # 'profession': 'bla',
            },
        })
    except marshmallow.ValidationError as e:
        print(e.messages)

    try:
        schema.load({
            'section': 'junk',
            'data': {
                # 'profession': 'bla',
            },
        })
    except marshmallow.ValidationError as e:
        print(e.messages)
@lafrech
Copy link
Member

lafrech commented Aug 18, 2023

Search for marshmallow-oneofschema (and perhaps marshmallow-polyfield). And polymorphism tag in here.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

2 participants