Feature request: dynamic field type
matejsp opened this issue · 1 comments
matejsp commented
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 commented
Search for marshmallow-oneofschema (and perhaps marshmallow-polyfield). And polymorphism tag in here.