marshmallow_dataclass.class_schema

marshmallow_dataclass.class_schema(clazz: type, base_schema: Type[Schema] | None = None, *, globalns: Dict[str, Any] | None = None, localns: Dict[str, Any] | None = None) Type[Schema][source]
marshmallow_dataclass.class_schema(clazz: type, base_schema: Type[Schema] | None = None, clazz_frame: frame | None = None, *, globalns: Dict[str, Any] | None = None) Type[Schema]

Convert a class to a marshmallow schema

Parameters:
  • clazz – A python class (may be a dataclass)

  • base_schema – marshmallow schema used as a base class when deriving dataclass schema

  • clazz_frame – frame of cls definition

Returns:

A marshmallow Schema corresponding to the dataclass

Note

All the arguments supported by marshmallow field classes can be passed in the metadata dictionary of a field.

If you want to use a custom marshmallow field (one that has no equivalent python type), you can pass it as the marshmallow_field key in the metadata dictionary.

>>> import typing
>>> Meters = typing.NewType('Meters', float)
>>> @dataclasses.dataclass()
... class Building:
...   height: Optional[Meters]
...   name: str = dataclasses.field(default="anonymous")
...   class Meta:
...     ordered = True
...
>>> class_schema(Building) # Returns a marshmallow schema class (not an instance)
<class 'marshmallow.schema.Building'>
>>> @dataclasses.dataclass()
... class City:
...   name: str = dataclasses.field(metadata={'required':True})
...   best_building: Building # Reference to another dataclass. A schema will be created for it too.
...   other_buildings: List[Building] = dataclasses.field(default_factory=lambda: [])
...
>>> citySchema = class_schema(City)()
>>> city = citySchema.load({"name":"Paris", "best_building": {"name": "Eiffel Tower"}})
>>> city
City(name='Paris', best_building=Building(height=None, name='Eiffel Tower'), other_buildings=[])
>>> citySchema.load({"name":"Paris"})
Traceback (most recent call last):
    ...
marshmallow.exceptions.ValidationError: {'best_building': ['Missing data for required field.']}
>>> city_json = citySchema.dump(city)
>>> city_json['best_building'] # We get an OrderedDict because we specified order = True in the Meta class
OrderedDict([('height', None), ('name', 'Eiffel Tower')])
>>> @dataclasses.dataclass()
... class Person:
...   name: str = dataclasses.field(default="Anonymous")
...   friends: List['Person'] = dataclasses.field(default_factory=lambda:[]) # Recursive field
...
>>> person = class_schema(Person)().load({
...     "friends": [{"name": "Roger Boucher"}]
... })
>>> person
Person(name='Anonymous', friends=[Person(name='Roger Boucher', friends=[])])

Marking dataclass fields as non-initialized (init=False), by default, will result in those fields from being exluded in the schema. To override this behaviour, set the Meta option include_non_init=True.

>>> @dataclasses.dataclass()
... class C:
...   important: int = dataclasses.field(init=True, default=0)
...    # Only fields that are in the __init__ method will be added:
...   unimportant: int = dataclasses.field(init=False, default=0)
...
>>> c = class_schema(C)().load({
...     "important": 9, # This field will be imported
...     "unimportant": 9 # This field will NOT be imported
... }, unknown=marshmallow.EXCLUDE)
>>> c
C(important=9, unimportant=0)
>>> @dataclasses.dataclass()
... class C:
...   class Meta:
...     include_non_init = True
...   important: int = dataclasses.field(init=True, default=0)
...   unimportant: int = dataclasses.field(init=False, default=0)
...
>>> c = class_schema(C)().load({
...     "important": 9, # This field will be imported
...     "unimportant": 9 # This field will be imported
... }, unknown=marshmallow.EXCLUDE)
>>> c
C(important=9, unimportant=9)
>>> @dataclasses.dataclass
... class Website:
...  url:str = dataclasses.field(metadata = {
...    "marshmallow_field": marshmallow.fields.Url() # Custom marshmallow field
...  })
...
>>> class_schema(Website)().load({"url": "I am not a good URL !"})
Traceback (most recent call last):
    ...
marshmallow.exceptions.ValidationError: {'url': ['Not a valid URL.']}
>>> @dataclasses.dataclass
... class NeverValid:
...     @marshmallow.validates_schema
...     def validate(self, data, **_):
...         raise marshmallow.ValidationError('never valid')
...
>>> class_schema(NeverValid)().load({})
Traceback (most recent call last):
    ...
marshmallow.exceptions.ValidationError: {'_schema': ['never valid']}
>>> @dataclasses.dataclass
... class Anything:
...     name: str
...     @marshmallow.validates('name')
...     def validates(self, value):
...         if len(value) > 5: raise marshmallow.ValidationError("Name too long")
>>> class_schema(Anything)().load({"name": "aaaaaargh"})
Traceback (most recent call last):
...
marshmallow.exceptions.ValidationError: {'name': ['Name too long']}

You can use the metadata argument to override default field behaviour, e.g. the fact that Optional fields allow None values:

>>> @dataclasses.dataclass
... class Custom:
...     name: Optional[str] = dataclasses.field(metadata={"allow_none": False})
>>> class_schema(Custom)().load({"name": None})
Traceback (most recent call last):
    ...
marshmallow.exceptions.ValidationError: {'name': ['Field may not be null.']}
>>> class_schema(Custom)().load({})
Custom(name=None)