Partial loading of nested fields with Marshmallow

Marshmallow is one of the best (de)serialization&validation libraries available for Python, so it came at a surprise to me it does not support nested partial loading out of the box.

Following snippet shows how to circumvent this limitation by creating a copy of your schema with partial enabled for all nested fields (no matter how deep they are).

In [1]:
import collections
import copy
from typing import Any, Dict

import marshmallow


def partial_nested_schema(schema: marshmallow.Schema, partial: bool = True) -> marshmallow.Schema:
    """Recursively apply `partial` to schema"""
    modified_schema = copy.deepcopy(schema)
    schemas = collections.deque([modified_schema])
    while schemas:
        c_schema = schemas.pop()
        c_schema.partial = partial
        schemas.extend(
            field.schema
            for field in c_schema.fields.values()
            if isinstance(field, marshmallow.fields.Nested)
        )
    return modified_schema

Usage example

In [2]:
class TestSchemaA(marshmallow.Schema):
    value1 = marshmallow.fields.String(required=True)
    value2 = marshmallow.fields.String(required=True)
    

class TestSchemaB(marshmallow.Schema):
    value = marshmallow.fields.String(required=True)
    nested = marshmallow.fields.Nested(TestSchemaA, required=True)
    
data = {
    'value': 'test',
    'nested': {'value2': 'test'},
}
In [3]:
test_schema = TestSchemaB(partial=True)
test_schema.load(data)
Out[3]:
UnmarshalResult(data={'value': 'test', 'nested': {'value2': 'test'}}, errors={'nested': {'value1': ['Missing data for required field.']}})
In [4]:
partial_test_schema = partial_nested_schema(test_schema)
partial_test_schema.load(data)
Out[4]:
UnmarshalResult(data={'value': 'test', 'nested': {'value2': 'test'}}, errors={})

Caveats

This function does not support Nested fields embedded in Dict fields (feature just recently introduced), but adding support for it should be simple enough.

Thumbnail source: https://www.flickr.com/photos/katerha/4835856136