Marshmallow: Optional nesting (handling an embed parameter)

Created on 17 Feb 2015  路  7Comments  路  Source: marshmallow-code/marshmallow

I'm aware of how to nest items, as described in the documentation. Per those examples (with slight modifications) I am able to nest, say, a Author in a Book:

class AuthorSchema(Schema):
    class Meta:
        fields = ('id', 'name')

class BookSchema(Schema):
    author = fields.Nested(AuthorSchema, only='id')
    class Meta:
        fields = ('id', 'title', 'author')

This is all fine and dandy. However, say I was exposing these models over a REST API and I wanted to implement an embed paremeter, as described here. From what I can see, I would need to either (a) define a huge number of alternative schemas (i.e. BookSchema_AuthorID, BookSchema_AuthorName, BookSchema_AuthorIDName etc.) or (b) perform some processing on the serialized output and strip out fields I don't want. Neither of these are ideal. Is there any alternative?

Most helpful comment

Fixed by #468.

All 7 comments

I'd also find this very convenient. To word it differently: There should be a way to exclude specific fields on nested (at any level) schemas.

We have a similar requirement,
here is an optional solution, if approved I'll try and put up a PR:

  1. Support specifying the list of fields to be serialized in a nested schema using the dot notation in
    the "only" argument.
  2. Add "only" to dumps/dump methods that overrides the fields and self.only
  3. Raise validation error if the fields or nested fields passed as "only" aren't a subset
    of Meta class fields.

I've had a similar problem. The solution I've came up with is to use meta-classes and create schemas on the fly, like so:

# Function for creating schemas
def SchemaFactory(model, fields={}):
    dict = fields.copy()
    dict['Meta'] = type('Meta', (), {'model': model})

    return type(model.__name__ + 'Schema', (ma.ModelSchema,), dict)

# Sample usage
Schema = SchemaFactory(UnitTopic, {
  'topic': fields.Nested(TopicSchema),
  'unit': fields.Nested(UnitSchema)
})

schema = Schema(many=True)
schema.dump(unit_topics).data

So, you get to control what gets extended, by providing a fields.Nested entry to the factory. It might be a bit hacky, but seems to work.

Would this be another hacky solution?

def test_nested_exclude_issue151():
    class Inner(Schema):
        foo = fields.Field()
        bar = fields.Field()
        baz = fields.Field()

    class Outer(Schema):
        foo = fields.Field()
        inner = fields.Nested(Inner)

    data = dict(inner=dict(foo=42, bar=24, baz=242), foo=67)
    sch = Outer()
    sch.fields['inner'].exclude = ('bar', )
    result = sch.dump(data)
    assert 'bar' not in result.data['inner']

@artur-bekasov Could you please point me to a gist or similar with your snippet working?

@luismartingil Sure, see https://github.com/UoMCS/syllabus-visualisation. Look for server/schemas.py, server/models.py and unit_topics() in server/api.py

Version 2.8.0 added support for specifying nested field names to the onlyand exclude options on your schema constructors. This allows you to spin up deeply customized schema instances without declaring new schema classes or reaching into undocumented parts of the schema interface.

Check out the documentation for the dotted nesting syntax in the "Nesting Schema's" section of the docs.

https://marshmallow.readthedocs.io/en/latest/nesting.html#specifying-which-fields-to-nest

class SiteSchema(Schema):
    blog = fields.Nested(BlogSchema2)

schema = SiteSchema(only=['blog.author.email'])
result, errors = schema.dump(site)
pprint(result)
# {
#     'blog': {
#         'author': {'email': u'[email protected]'}
#     }
# }

Fixed by #468.

Was this page helpful?
0 / 5 - 0 ratings