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]

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: frame | 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: 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)
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: frame | 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: frame | 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: frame | 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'>