Skip to content

Instantly share code, notes, and snippets.

@ergo
Last active April 12, 2018 00:21
Show Gist options
  • Save ergo/40f2dab8b0e52eeb9e7d400fd5b16407 to your computer and use it in GitHub Desktop.
Save ergo/40f2dab8b0e52eeb9e7d400fd5b16407 to your computer and use it in GitHub Desktop.
Comparison between Marshmallow and Colander
import timeit
import uuid
import colander
import marshmallow
class BarList(colander.SequenceSchema):
item = colander.SchemaNode(
colander.Integer(), validator=colander.Range(min=1))
class SignupSchema(colander.MappingSchema):
@staticmethod
def schema_type():
return colander.Mapping(unknown='preserve')
username = colander.SchemaNode(colander.String())
foo = colander.SchemaNode(
colander.Integer(), validator=colander.Range(min=1))
bar = BarList()
@colander.deferred
def def_uuid(node, kw):
return str(uuid.uuid4())
@colander.deferred
def def_validator(node, kw):
return colander.Function(lambda v: kw['bound'] == v)
class SignupSchemaWithDeferred(SignupSchema):
uuid = colander.SchemaNode(colander.String(), missing=def_uuid)
bound = colander.SchemaNode(colander.String(), validator=def_validator)
class SignupSchemaM(marshmallow.Schema):
class Meta:
strict = True
ordered = True
preserve = True
username = marshmallow.fields.String(required=True)
foo = marshmallow.fields.Integer(
required=True,
validate=[marshmallow.validate.Range(min=1)])
bar = marshmallow.fields.List(marshmallow.fields.Integer(
validate=[marshmallow.validate.Range(min=1)]), many=True)
@marshmallow.post_load(pass_original=True)
def _add_unknown(self, data, original):
"""Preserve unknown keys during deserialization."""
for key, val in original.items():
if key not in self.fields:
data[key] = val
return data
class SignupSchemaContextM(SignupSchemaM):
uuid = marshmallow.fields.String(missing=lambda: str(uuid.uuid4()))
bound = marshmallow.fields.String()
@marshmallow.validates('bound')
def validate_bound(self, value):
if self.context['bound'] != value:
raise marshmallow.ValidationError('WRONG!')
data = {'foo': 1, 'bar': [1, 2, 3],
'bound': 'YYYY',
'baz': {'a': 'test',
'b': ['a', 'b', 'c']},
'username': 'ergo'}
def test_colander():
schema = SignupSchema()
return schema.deserialize(data)
to_bind_schema = SignupSchemaWithDeferred()
def test_colander_bind():
schema = to_bind_schema.bind(bound='YYYY')
return schema.deserialize(data)
def test_marshmallow():
schema = SignupSchemaM()
return schema.load(data).data
def test_marshmallow_context():
schema = SignupSchemaContextM()
schema.context.setdefault('bound', 'YYYY')
return schema.load(data).data
print(test_colander_bind())
print(test_marshmallow_context())
c_result = timeit.timeit(test_colander, number=10000)
m_result = timeit.timeit(test_marshmallow, number=10000)
print(f'Colander took {c_result:.4f}')
print(f'Marshmallow took {m_result:.4f}')
print('-----------------------')
print(f'Marshmallow took {m_result/c_result:.2f}x of Colander execution time')
print('\nNow testing with binds/context\n')
c_result = timeit.timeit(test_colander_bind, number=10000)
m_result = timeit.timeit(test_marshmallow_context, number=10000)
print(f'Colander w/bind took {c_result:.4f}')
print(f'Marshmallow w/context took {m_result:.4f}')
print('-----------------------')
print(f'Marshmallow w/context took {m_result/c_result:.2f}x of Colander w/bind execution time')
@sloria
Copy link

sloria commented Apr 12, 2018

@ergo I updated the script to separate instantiation from the benchmark functions: https://gist.github.com/sloria/b84cd0118337bb3b6d001d959d95a922 .

I also posted the results for both marshmallow 2 and marshmallow 3 on my fork.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment