marshmallow
marshmallow copied to clipboard
Meta Decorator
Motivation
I often have lots of small schemas containing unique Meta
classes with only a few attributes. The Meta
class sometimes requires more lines than the schema definition and makes a file containing multiple schemas harder to read. This is especially true when using marshmallow-jsonapi
due to type_
.
Proposal
Provide a meta
decorator that will inject its kwargs into a Meta
class for the class it is wrapping.
Example
Before:
class TestSchema(Schema):
foo = fields.String()
bar = fields.String()
class Meta:
type_ = 'tests'
ordered = True
After:
@meta(type_='tests', ordered=True)
class TestSchema(Schema):
foo = fields.String()
bar = fields.String()
Would it replace or update an existing (inherited) Meta
class?
Updating would allow stacking, although I don't really see the use case for stacking.
Inheritance is another good use case where Meta
is likely to only need a few attributes. I was initially imagining spreading the parent meta manually like @meta(..., **vars(ParentSchema.Meta))
, but that doesn't actually work. Implicitly inheriting into a new class would be convenient.
A use case for stacking might be if you have enough attributes to justify a meta class, but still preferred the decorator syntax. It would avoid opening and indenting the meta arguments into a block (which a linter might do automatically).
@meta(
type_='tests',
include=['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'I', 'j', 'k', ...],
)
class TestSchema(Schema):
foo = fields.String()
bar = fields.String()
@meta(type_='tests')
@meta(include=['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'I', 'j', 'k', ...])
class TestSchema(Schema):
foo = fields.String()
bar = fields.String()
I ended up having to subclass the schema to get SchemaMeta.__new__
to rebuild opts
from Meta
. I am going to test drive this utility for a little while.
def meta(**kwargs):
def wrapper(schema):
class _Meta(schema.Meta):
pass
for key in kwargs:
setattr(_Meta, key, kwargs[key])
class _Schema(schema):
Meta = _Meta
_Meta.__name__ = schema.Meta.__name__
_Schema.__name__ = schema.__name__
return _Schema
return wrapper
I quite like the ergonomics of this proposal. I've no strong objections to adding this
How will this interact with Meta.ordered = True
?
When the specific class has set ordered=True
, the _declared_fields
is set to an OrderedDict()
instance with inherited fields listed first, followed by the fields declared on the class directly, in declaration order, followed by any Meta.include
pairs.
The issue I can see is that if the current class is inheriting from another Schema
with Meta.ordered=False
, then by the time the class decorator comes to the scene the _declared_fields
dictionary will be a regular dictionary and the directly-declared fields are no longer easily distinguished from the inherited fields or Meta.include
fields, especially if the latter overrides some of the class or inherited fields.
I ended up having to subclass the schema to get
SchemaMeta.__new__
to rebuildopts
fromMeta
.
This also rebuilds _declared_fields
with the new meta options.
I published a module for this functionality. I think this should just be a community library for now. Once I cover it with tests I will add it to the wiki and close this issue.
https://github.com/deckar01/marshmallow-meta