marshmallow_dataclass package

Table of contents

marshmallow_dataclass.dataclass()

This decorator does the same as dataclasses.dataclass, but also applies add_schema().

marshmallow_dataclass.class_schema()

Convert a class to a marshmallow schema

Module

This library allows the conversion of python 3.7’s dataclasses to marshmallow schemas.

It takes a python class, and generates a marshmallow schema for it.

Simple example:

from marshmallow import Schema
from marshmallow_dataclass import dataclass

@dataclass
class Point:
  x:float
  y:float

point = Point(x=0, y=0)
point_json = Point.Schema().dumps(point)

Full example:

from marshmallow import Schema
from dataclasses import field
from marshmallow_dataclass import dataclass
import datetime

@dataclass
class User:
  birth: datetime.date = field(metadata= {
    "required": True # A parameter to pass to marshmallow's field
  })
  website:str = field(metadata = {
    "marshmallow_field": marshmallow.fields.Url() # Custom marshmallow field
  })
  Schema: ClassVar[Type[Schema]] = Schema # For the type checker
marshmallow_dataclass.NewType(name: str, typ: Type[_U], field: Type[Field] | None = None, **kwargs) Callable[[_U], _U][source]

DEPRECATED: Use typing.Annotated instead. NewType creates simple unique types to which you can attach custom marshmallow attributes. All the keyword arguments passed to this function will be transmitted to the marshmallow field constructor.

>>> import marshmallow.validate
>>> IPv4 = NewType('IPv4', str, validate=marshmallow.validate.Regexp(r'^([0-9]{1,3}\.){3}[0-9]{1,3}$'))
>>> @dataclass
... class MyIps:
...   ips: List[IPv4]
>>> MyIps.Schema().load({"ips": ["0.0.0.0", "grumble grumble"]})
Traceback (most recent call last):
...
marshmallow.exceptions.ValidationError: {'ips': {1: ['String does not match expected pattern.']}}
>>> MyIps.Schema().load({"ips": ["127.0.0.1"]})
MyIps(ips=['127.0.0.1'])
>>> Email = NewType('Email', str, field=marshmallow.fields.Email)
>>> @dataclass
... class ContactInfo:
...   mail: Email = dataclasses.field(default="anonymous@example.org")
>>> ContactInfo.Schema().load({})
ContactInfo(mail='anonymous@example.org')
>>> ContactInfo.Schema().load({"mail": "grumble grumble"})
Traceback (most recent call last):
...
marshmallow.exceptions.ValidationError: {'mail': ['Not a valid email address.']}
marshmallow_dataclass.add_schema(_cls: Type[_U]) Type[_U][source]
marshmallow_dataclass.add_schema(base_schema: Type[Schema] | None = None) Callable[[Type[_U]], Type[_U]]
marshmallow_dataclass.add_schema(_cls: Type[_U], base_schema: Type[Schema] | None = None, cls_frame: FrameType | None = None, stacklevel: int = 1) Type[_U]

This decorator adds a marshmallow schema as the ‘Schema’ attribute in a dataclass. It uses class_schema() internally.

Parameters:
  • _cls (type) – The dataclass to which a Schema should be added

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

  • cls_frame – frame of cls definition

>>> class BaseSchema(marshmallow.Schema):
...   def on_bind_field(self, field_name, field_obj):
...     field_obj.data_key = (field_obj.data_key or field_name).upper()
>>> @add_schema(base_schema=BaseSchema)
... @dataclasses.dataclass
... class Artist:
...    names: Tuple[str, str]
>>> artist = Artist.Schema().loads('{"NAMES": ["Martin", "Ramirez"]}')
>>> artist
Artist(names=('Martin', 'Ramirez'))
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: FrameType | 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)
marshmallow_dataclass.dataclass(_cls: Type[_U], *, repr: bool = True, eq: bool = True, order: bool = False, unsafe_hash: bool = False, frozen: bool = False, base_schema: Type[Schema] | None = None, cls_frame: FrameType | None = None) Type[_U][source]
marshmallow_dataclass.dataclass(*, repr: bool = True, eq: bool = True, order: bool = False, unsafe_hash: bool = False, frozen: bool = False, base_schema: Type[Schema] | None = None, cls_frame: FrameType | None = None) Callable[[Type[_U]], Type[_U]]

This decorator does the same as dataclasses.dataclass, but also applies add_schema(). It adds a .Schema attribute to the class object

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

  • cls_frame – frame of cls definition, used to obtain locals with other classes definitions. If None is passed the caller frame will be treated as cls_frame

>>> @dataclass
... class Artist:
...    name: str
>>> Artist.Schema
<class 'marshmallow.schema.Artist'>
>>> from typing import ClassVar
>>> from marshmallow import Schema
>>> @dataclass(order=True) # preserve field order
... class Point:
...   x:float
...   y:float
...   Schema: ClassVar[Type[Schema]] = Schema # For the type checker
...
>>> Point.Schema().load({'x':0, 'y':0}) # This line can be statically type checked
Point(x=0.0, y=0.0)
marshmallow_dataclass.field_for_schema(typ: type, default: ~typing.Any = <marshmallow.missing>, metadata: ~typing.Mapping[str, ~typing.Any] | None = None, base_schema: ~typing.Type[~marshmallow.schema.Schema] | None = None, typ_frame: ~types.FrameType | None = None) Field[source]

Get a marshmallow Field corresponding to the given python type. The metadata of the dataclass field is used as arguments to the marshmallow Field.

Parameters:
  • typ – The type for which a field should be generated

  • default – value to use for (de)serialization when the field is missing

  • metadata – Additional parameters to pass to the marshmallow field constructor

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

  • typ_frame – frame of type definition

>>> int_field = field_for_schema(int, default=9, metadata=dict(required=True))
>>> int_field.__class__
<class 'marshmallow.fields.Integer'>
>>> int_field.dump_default
9
>>> field_for_schema(str, metadata={"marshmallow_field": marshmallow.fields.Url()}).__class__
<class 'marshmallow.fields.Url'>