New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Partial loading on nested fields #438

Closed
pablomolnar opened this Issue Apr 22, 2016 · 4 comments

Comments

Projects
None yet
4 participants
@pablomolnar

pablomolnar commented Apr 22, 2016

Given an object with a nested field, is it possible to partially load the object and all nested fields?
e.g.

#!/usr/bin/env python
# -*- coding: utf-8 -*-
from marshmallow import Schema, fields


class Address(Schema):
    street = fields.Str(required=True)
    city = fields.Str(required=True)
    state = fields.Str(required=True)
    zipcode = fields.Str(required=True)
    country = fields.Str(required=True)


class User(Schema):
    first_name = fields.Str(required=True)
    last_name = fields.Str(required=True)
    address = fields.Nested(Address)


payload = {
    'first_name': 'John',
    'address': {
        'zipcode': '90210'
    }
}

user_schema = User()
data, errors = user_schema.load(payload, partial=True)
if errors:
    raise Exception(errors)

Output:

Traceback (most recent call last):
  File "test_partial_nested.py", line 30, in <module>
    raise Exception(errors)
Exception: {'address': {'city': ['Missing data for required field.'], 'state': ['Missing data for required field.'], 'street': ['Missing data for required field.'], 'country': ['Missing data for required field.']}}

Input is coming from a REST API and ideally I would like to bind all incoming payload at once.

Thanks!

@sloria

This comment has been minimized.

Member

sloria commented Apr 23, 2016

There isn't built-in support for nested partial fields, but you could achieve this by using a schema factory. Something like

def PartialSchemaFactory(schema_cls):
    schema = schema_cls(partial=True)
    for field_name, field in schema.fields.items():
        if isinstance(field, fields.Nested):
            new_field = deepcopy(field)
            new_field.schema.partial = True
            schema.fields[field_name] = new_field
    return schema

A full gist is here: https://gist.github.com/sloria/9f6214b37aab3a6aa37e9a361a50cfd8

@sloria sloria added the question label Apr 24, 2016

@xingweicmu

This comment has been minimized.

xingweicmu commented Apr 27, 2016

👍

@lafrech

This comment has been minimized.

Member

lafrech commented Oct 20, 2016

The schema factory workaround does not provide a Schema with the ability to do both partial and not partial depending on what parameters are provided to load.

In other words, when the factory creates a PartialSchema, calling schema.load(partial=False) will only affect first level fields.

See this example. I added a partial parameter to the factory.

from copy import deepcopy

from marshmallow import Schema, fields

def PartialSchemaFactory(schema_cls, partial=True):
    schema = schema_cls(partial=partial)
    for field_name, field in schema.fields.items():
        if isinstance(field, fields.Nested):
            new_field = deepcopy(field)
            new_field.schema.partial = partial
            schema.fields[field_name] = new_field
    return schema

class Address(Schema):
    street = fields.Str(required=True)
    city = fields.Str(required=True)
    state = fields.Str(required=True)
    zipcode = fields.Str(required=True)
    country = fields.Str(required=True)

class User(Schema):
    first_name = fields.Str(required=True)
    last_name = fields.Str(required=True)
    address = fields.Nested(Address)

payload = {
    'first_name': 'John',
    'last_name': 'Doe',
    'address': {
        'zipcode': '90210'
    }
}

user_schema = PartialSchemaFactory(User)
data, errors = user_schema.load(payload, partial=False)
assert errors == {}

This passes because first_name and last_name are provided and partial=False is not propagated down.

Maybe a more complete workaround would create two schemas: PartialSchemaFactory(User, True) and PartialSchemaFactory(User, False), and pick the right one when calling load depending on the value of partial. This sounds pretty hackish.

It would be really nice if the partial option to load was propagated down to nested fields.

Can this be considered a feature request? What do you think of the idea?

@sloria

This comment has been minimized.

Member

sloria commented Jan 8, 2017

Sorry for the delay in responding.

I am considering this issue closed for now.

@lafrech I am not opposed to the idea of propagating partial to nested fields, though I haven't given much thought to it. Feel free to open an issue to start discussion.

@sloria sloria closed this Jan 8, 2017

arbor-dwatson added a commit to arbor/marshmallow that referenced this issue Jun 21, 2018

Allow partial loading of nested fields
This change passes the `partial` keyword argument from a `schema.load`
call down into nested fields (see marshmallow-code#438).  In the context of a REST
api, this allows a POST to require a subset of the fields to be
specified, while a PATCH can use `partial=True` to suppress any
required fields.

This change also supports using a tuple as the `partial` argument
using dot notation to configure partial loading of nested schemas.
For example: `partial=('x', 'y.z')` would allow `x` in the top level
schema to be missing or for `z` in the sub-schema for field `y` to be
missing.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment