lovasoa / marshmallow_dataclass

Automatic generation of marshmallow schemas from dataclasses.

Home Page:https://lovasoa.github.io/marshmallow_dataclass/html/marshmallow_dataclass.html

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Use case help: Schema to expect field a, but to generate field b

seedship opened this issue · comments

I want to use marshmallow_dataclass to do the following. The input dict will have a field a, and will use it to compute field b (let's say b = 2 * a for simplicity). However, I don't want the serialized object to contain a, nor allow the user to define b directly in the input dict.

So input dict: input_dict = {a: 2}. After running schema.load(input_dict), I expect loaded_class(b=4). And an input of input_dict = {b:4} shouldn't be allowed.

Does marshmallow_dataclass have the tools to do this? I know I can make fields not serialized using the init flag, but is there a way to add new fields to the class based on existing fields, as well as make some fields not expected in serialization?

Since it's just a dataclass you can always define a __post_init__ method to add whatever other attributes you want.

The renaming can be handled by passing data_key to the field constructor.

The scaling of the value can be handled by creating a custom marshmallow.Field class to do the scaling.

E.g. (untested code follows)

from dataclasses import dataclass, field
from marshmallow import fields
from marshmallow_dataclass import class_schema, NewType

class DoubledNumberField(fields.Number):
    def _serialize(self, value, attr, obj, **kwargs):
        if value is None:
            return None
        return super()._serialize(value / 2, attr, obj, **kwargs)

    def _deserialize(self, value, attr, data, **kwargs):
        a = super()._deserialize(value, attr, data, **kwargs)
        return 2 * a if a is not None else None

DoubledFloat = NewType("DoubledFloat", float, field=DoubledNumberField)

@dataclass
class Data:
    b: DoubledFloat = field(metadata={"data_key": "a"})

DataSchema = class_schema(Data)

Closing, since this help request rather than a bug.

@dairiki It looks like the data_key and marshmallow_field cannot be used at the same time, is that right? Does that mean that the NewType approach is the "better" way to do it?

from dataclasses import field
from enum import Enum

from marshmallow import fields
from marshmallow_dataclass import dataclass

class CustomEnumSerializer(fields.Field):
  def _serialize(self, value, attr, obj, **kwargs):
    pass

  def _deserialize(self, value, attr, data, **kwargs):
    return CustomEnum(value)

class CustomEnum(str, Enum):
  A = "a"
  B = "b"

@dataclass
class CustomSchemaA:
  renamed_field: CustomEnum = field(metadata={"data_key": "enum_field", "marshmallow_field": CustomEnumSerializer()})

@dataclass
class CustomSchemaB:
  renamed_field: CustomEnum = field(metadata={"data_key": "enum_field"})


if __name__ == '__main__':
  CustomSchemaA.Schema().load({"enum_field": "a"}) # raises marshmallow.exceptions.ValidationError: {'enum_field': ['Unknown field.']}
  CustomSchemaB.Schema().load({"enum_field": "a"}) # raises marshmallow.exceptions.ValidationError: {'enum_field': ['Must be one of: A, B.']}

Sorry, I actually found the answer to that. For anyone else, with a custom field you can pass a data_key into the serializer object:

@dataclass
class CustomSchemaA:
  renamed_field: CustomEnum = field(metadata={"marshmallow_field": CustomEnumSerializer(data_key="enum_field")})