However, an example I've encountered where using inheritance was a better fit was when I modelled the arguments for certain graphs I was generating. Here, I wanted general arguments that would be passed into the underlying plotting library, but I wanted the child schemas to refine the schema and use more specific validations:
class PlotSchema(Schema): "" " Data that can be used to generate a plot "" " id = f.String(dump_only = True) type = f.String() x = f.List(f.Raw()) y = f.List(f.Raw()) text = f.List(f.Raw()) hoverinfo = f.Str() class TrendSchema(PlotSchema): "" " Data that can be used to generate a trend plot "" " x = f.List(f.DateTime()) y = f.List(f.Number())
By default, pre- and post-processing methods receive one object/datum at a time, transparently handling the many parameter passed to the Schema’s dump()/load() method at runtime.,In cases where your pre- and post-processing methods needs to handle the input collection when processing multiple objects, add pass_many=True to the method decorators.,Pre- and post-processing methods may raise a ValidationError. By default, errors will be stored on the "_schema" key in the errors dictionary.,Data pre-processing and post-processing methods can be registered using the pre_load, post_load, pre_dump, and post_dump decorators.
from marshmallow import Schema, fields, post_load class UserSchema(Schema): name = fields.Str() slug = fields.Str() @post_load def slugify_name(self, in_data, ** kwargs): in_data["slug"] = in_data["slug"].lower().strip().replace(" ", "-") return in_data schema = UserSchema() result = schema.load({ "name": "Steve", "slug": "Steve Loria " }) result["slug"] # => 'steve-loria'
from marshmallow import Schema, fields, pre_load, post_load, post_dump class BaseSchema(Schema): # Custom options __envelope__ = {"single": None, "many": None} __model__ = User def get_envelope_key(self, many): """Helper to get the envelope key.""" key = self.__envelope__["many"] if many else self.__envelope__["single"] assert key is not None, "Envelope key undefined" return key @pre_load(pass_many=True) def unwrap_envelope(self, data, many, **kwargs): key = self.get_envelope_key(many) return data[key] @post_dump(pass_many=True) def wrap_with_envelope(self, data, many, **kwargs): key = self.get_envelope_key(many) return {key: data} @post_load def make_object(self, data, **kwargs): return self.__model__(**data) class UserSchema(BaseSchema): __envelope__ = {"single": "user", "many": "users"} __model__ = User name = fields.Str() email = fields.Email() user_schema = UserSchema() user = User("Mick", email="mick@stones.org") user_data = user_schema.dump(user) # {'user': {'email': 'mick@stones.org', 'name': 'Mick'}} users = [ User("Keith", email="keith@stones.org"), User("Charlie", email="charlie@stones.org"), ] users_data = user_schema.dump(users, many=True) # {'users': [{'email': 'keith@stones.org', 'name': 'Keith'}, # {'email': 'charlie@stones.org', 'name': 'Charlie'}]} user_objs = user_schema.load(users_data, many=True) # [<User(name='Keith Richards')>, <User(name='Charlie Watts')>]
from marshmallow import Schema, fields, ValidationError, pre_load class BandSchema(Schema): name = fields.Str() @pre_load def unwrap_envelope(self, data, ** kwargs): if "data" not in data: raise ValidationError('Input data must have a "data" key.') return data["data"] sch = BandSchema() try: sch.load({ "name": "The Band" }) except ValidationError as err: err.messages # { '_schema': ['Input data must have a "data" key.'] }
from marshmallow import Schema, fields, ValidationError, pre_load class BandSchema(Schema): name = fields.Str() @pre_load def unwrap_envelope(self, data, ** kwargs): if "data" not in data: raise ValidationError( 'Input data must have a "data" key.', "_preprocessing" ) return data["data"] sch = BandSchema() try: sch.load({ "name": "The Band" }) except ValidationError as err: err.messages # { '_preprocessing': ['Input data must have a "data" key.'] }
from marshmallow
import Schema, fields, pre_load
# YES
class MySchema(Schema):
field_a = fields.Field()
@pre_load
def preprocess(self, data, ** kwargs):
step1_data = self.step1(data)
step2_data = self.step2(step1_data)
return step2_data
def step1(self, data):
do_step1(data)
# Depends on step1
def step2(self, data):
do_step2(data)
# NO
class MySchema(Schema):
field_a = fields.Field()
@pre_load
def step1(self, data, ** kwargs):
do_step1(data)
# Depends on step1
@pre_load
def step2(self, data, ** kwargs):
do_step2(data)
from marshmallow
import Schema, fields, validates_schema, ValidationError
class NumberSchema(Schema):
field_a = fields.Integer()
field_b = fields.Integer()
@validates_schema
def validate_numbers(self, data, ** kwargs):
if data["field_b"] >= data["field_a"]:
raise ValidationError("field_a must be greater than field_b")
schema = NumberSchema()
try:
schema.load({
"field_a": 1,
"field_b": 2
})
except ValidationError as err:
err.messages["_schema"]
# => ["field_a must be greater than field_b"]
Schema subclasses can add extra settings to the schema constructor. For example marshmallow-jsonapi addds include_data, which lets you control the amount of data you return for each related resource,Since asking this question, I've done a ton of work using Marshmallow, so hopefully I can explain somewhat.,You end up writing the least code
However, an example I've encountered where using inheritance was a better fit was when I modelled the arguments for certain graphs I was generating. Here, I wanted general arguments that would be passed into the underlying plotting library, but I wanted the child schemas to refine the schema and use more specific validations:
class PlotSchema(Schema): ""
" Data that can be used to generate a plot "
""
id = f.String(dump_only = True) type = f.String() x = f.List(f.Raw()) y = f.List(f.Raw()) text = f.List(f.Raw()) hoverinfo = f.Str() class TrendSchema(PlotSchema): ""
" Data that can be used to generate a trend plot "
""
x = f.List(f.DateTime()) y = f.List(f.Number())