├── .gitignore
├── .travis.yml
├── Dockerfile
├── README.md
├── benchmark.py
├── create_report.sh
├── data.py
├── deploy-key.enc
├── deploy.sh
├── disscussion.md
├── docker-compose.yml
├── requirements.txt
├── subjects
├── __init__.py
├── avro.py
├── col.py
├── hand.py
├── k.py
├── lim.py
├── loli.py
├── marsh.py
├── pickle.py
├── rf.py
├── serp.py
├── serpy.py
├── strain.py
└── tmarsh.py
└── tests
├── __init__.py
└── test_serializers.py
/.gitignore:
--------------------------------------------------------------------------------
1 | .cache/
2 | .venv/
3 | __pycache__
4 | *.pyc
5 | index.html
6 | REPORT.md
7 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: python
2 | python:
3 | - '3.6'
4 | install: pip install -r requirements.txt
5 | script: py.test
6 | deploy:
7 | provider: script
8 | script: bash ./deploy.sh
9 | on:
10 | branch: master
11 | env:
12 | global:
13 | - ENCRYPTION_LABEL: 1dbde6ec09a5
14 | - COMMIT_AUTHOR_EMAIL: voidfiles@gmail.com
15 |
--------------------------------------------------------------------------------
/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM python:3
2 |
3 |
4 | ADD . /opt/code
5 | WORKDIR /opt/code/
6 |
7 | RUN pip install -r requirements.txt
8 | CMD ["python", "benchmark.py"]
9 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Python Serialization Benchmark
2 |
3 | This [repository](http://github.com/voidfiles/python-serialization-benchmark) maintains a set of benchmarks for python serialization frameworks.
4 |
5 | You can find the latest benchmarks on [this page](https://voidfiles.github.io/python-serialization-benchmark/).
6 |
7 | Currently the following projects are benchmarked.
8 |
9 | * [Django REST Framework](http://www.django-rest-framework.org/)
10 | * [serpy](http://serpy.readthedocs.io/)
11 | * [Marshmallow](https://marshmallow.readthedocs.io/en/latest/)
12 | * [Strainer](https://github.com/voidfiles/strainer)
13 | * [Lollipop](http://lollipop.readthedocs.io/en/latest/)
14 | * [Kim](http://kim.readthedocs.io/en/latest/)
15 | * [Toasted Marshmallow](https://github.com/lyft/toasted-marshmallow)
16 | * [Colander](https://docs.pylonsproject.org/projects/colander/en/latest/)
17 | * [Lima](https://github.com/b6d/lima/)
18 | - [Serpyco](https://gitlab.com/sgrignard/serpyco)
19 | * [Avro](https://avro.apache.org/)
20 |
21 | Along with a baseline custom function that doesn't use a framework.
22 |
23 |
24 | ## Running the test suite
25 |
26 | A Docker container is bundled with the repository which you can use to run the benchmarks. Firstly make sure you have Docker installed.
27 |
28 | 1. Install Docker
29 |
30 | 2. Build the container `$ docker-compose build`
31 |
32 | 3. Run the tests. `$ docker-compose run --rm tests`
33 |
--------------------------------------------------------------------------------
/benchmark.py:
--------------------------------------------------------------------------------
1 | import time
2 | from tabulate import tabulate
3 | from contextlib import contextmanager
4 |
5 | from subjects import (marsh, rf, serp, strain, col, hand, loli, k, lim, tmarsh, avro, pickle, serpy)
6 | from data import ParentTestObject
7 |
8 | SUBJECTS = (marsh, rf, serp, strain, col, hand, loli, k, lim, tmarsh, avro, pickle, serpy)
9 |
10 | test_object = ParentTestObject()
11 |
12 |
13 | @contextmanager
14 | def timer(tracker):
15 | start = time.time()
16 | yield
17 | end = time.time()
18 | tracker += [end - start]
19 |
20 |
21 | def test_many(func, limit=1000):
22 | for i in range(0, limit):
23 | subject.serialization_func([test_object, test_object], True)
24 |
25 |
26 | def test_one(func, limit=1000):
27 | for i in range(0, limit):
28 | subject.serialization_func(test_object, False)
29 |
30 | table = []
31 | for subject in SUBJECTS:
32 | row = [subject.name]
33 |
34 | test_many(subject.serialization_func, 2) # Warmup
35 | with timer(row):
36 | test_many(subject.serialization_func)
37 |
38 | test_one(subject.serialization_func, 2) # Warmup
39 | with timer(row):
40 | test_one(subject.serialization_func)
41 |
42 | table += [row]
43 |
44 | table = sorted(table, key=lambda x: x[1] + x[2])
45 | relative_base = min([x[1] + x[2] for x in table])
46 | for row in table:
47 | result = (row[1] + row[2]) / relative_base
48 | row.append(result)
49 | print(tabulate(table, headers=['Library', 'Many Objects (seconds)', 'One Object (seconds)', 'Relative']))
50 |
--------------------------------------------------------------------------------
/create_report.sh:
--------------------------------------------------------------------------------
1 | #! /bin/bash
2 |
3 | cat README.md > REPORT.md
4 | echo "\`\`\`" >> REPORT.md
5 | python benchmark.py >> REPORT.md
6 | echo "\`\`\`" >> REPORT.md
7 | cat disscussion.md >> REPORT.md
8 | mkdir -p out
9 | echo '' > out/index.html
10 | echo '
' >> out/index.html
11 | echo '' >> out/index.html
12 | echo '' >> out/index.html
13 | echo '' >> out/index.html
14 | echo '' >> out/index.html
15 | python -m markdown -x markdown.extensions.fenced_code REPORT.md >> out/index.html
16 | echo '
' >> out/index.html
17 | echo '' >> out/index.html
18 | echo '' >> out/index.html
19 |
--------------------------------------------------------------------------------
/data.py:
--------------------------------------------------------------------------------
1 |
2 | class ChildTestObject(object):
3 | def __init__(self, multiplier=None):
4 | self.w = 1000 * multiplier if multiplier else 100
5 | self.x = 20 * multiplier if multiplier else 20
6 | self.y = 'hello' * multiplier if multiplier else 'hello'
7 | self.z = 10 * multiplier if multiplier else 10
8 |
9 |
10 | class ParentTestObject(object):
11 | def __init__(self):
12 | self.foo = 'bar'
13 | self.sub = ChildTestObject()
14 | self.subs = [ChildTestObject(i) for i in range(10)]
15 |
16 | def bar(self):
17 | return 5
18 |
--------------------------------------------------------------------------------
/deploy-key.enc:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/voidfiles/python-serialization-benchmark/b45cb024a553e78a81e1a8c5a3056b7b9100baab/deploy-key.enc
--------------------------------------------------------------------------------
/deploy.sh:
--------------------------------------------------------------------------------
1 | #! /bin/bash
2 | set -ex # Exit with nonzero exit code if anything fails
3 |
4 | SOURCE_BRANCH="master"
5 | TARGET_BRANCH="gh-pages"
6 |
7 | function doCompile {
8 | ./create_report.sh
9 | }
10 |
11 | # Pull requests and commits to other branches shouldn't try to deploy, just build to verify
12 | if [ "$TRAVIS_PULL_REQUEST" != "false" -o "$TRAVIS_BRANCH" != "$SOURCE_BRANCH" ]; then
13 | echo "Skipping deploy; just doing a build."
14 | doCompile
15 | exit 0
16 | fi
17 |
18 | # Save some useful information
19 | REPO=`git config remote.origin.url`
20 | SSH_REPO=${REPO/https:\/\/github.com\//git@github.com:}
21 | SHA=`git rev-parse --verify HEAD`
22 |
23 | # Clone the existing gh-pages for this repo into out/
24 | # Create a new empty branch if gh-pages doesn't exist yet (should only happen on first deply)
25 | git clone $REPO out
26 | cd out
27 | git checkout $TARGET_BRANCH || git checkout --orphan $TARGET_BRANCH
28 | cd ..
29 |
30 | # Clean out existing contents
31 | rm -rf out/**/* || exit 0
32 |
33 | # Run our compile script
34 | doCompile
35 |
36 | # Now let's go have some fun with the cloned repo
37 | cd out
38 | git config user.name "Travis CI"
39 | git config user.email "$COMMIT_AUTHOR_EMAIL"
40 |
41 | # Commit the "changes", i.e. the new version.
42 | # The delta will show diffs between new and old versions.
43 | git add .
44 | git commit -m "Deploy to GitHub Pages: ${SHA}"
45 |
46 | # Get the deploy key by using Travis's stored variables to decrypt deploy_key.enc
47 | ENCRYPTED_KEY_VAR="encrypted_${ENCRYPTION_LABEL}_key"
48 | ENCRYPTED_IV_VAR="encrypted_${ENCRYPTION_LABEL}_iv"
49 | ENCRYPTED_KEY=${!ENCRYPTED_KEY_VAR}
50 | ENCRYPTED_IV=${!ENCRYPTED_IV_VAR}
51 | openssl aes-256-cbc -K $ENCRYPTED_KEY -iv $ENCRYPTED_IV -in ../deploy-key.enc -out deploy-key -d
52 | chmod 600 deploy-key
53 | eval `ssh-agent -s`
54 | ssh-add deploy-key
55 |
56 | rm -fR __pycache__
57 | rm -fR subjects/__pycache__
58 |
59 | # Now that we're all set up, we can push.
60 | git push $SSH_REPO $TARGET_BRANCH
61 |
--------------------------------------------------------------------------------
/disscussion.md:
--------------------------------------------------------------------------------
1 | ## The Benchmark
2 |
3 | Each framework is asked to serialize a list of 2 objects a 1000 times, and then 1 object a 1000 times.
4 |
5 | This is the current object that is being serialized.
6 |
7 | ```python
8 | class ChildTestObject(object):
9 | def __init__(self, multiplier=None):
10 | self.w = 1000 * multiplier if multiplier else 100
11 | self.x = 20 * multiplier if multiplier else 20
12 | self.y = 'hello' * multiplier if multiplier else 'hello'
13 | self.z = 10 * multiplier if multiplier else 10
14 |
15 |
16 | class ParentTestObject(object):
17 | def __init__(self):
18 | self.foo = 'bar'
19 | self.sub = ChildTestObject()
20 | self.subs = [ChildTestObject(i) for i in xrange(10)]
21 |
22 | def bar(self):
23 | return 5
24 |
25 | benchmark_object = ParentTestObject()
26 | ```
27 |
28 | ## Discussion
29 |
30 | Serialization from python objects to JSON, XML, or other transmission formats is a common task for many web related projects. In order to fill that need a number of frameworks have arised. While their aims are similar, they don't all share the same attributes. Here are how some of the features comapre.
31 |
32 |
122 |
123 | * **Serialization**: Does the framework provide a way of serializing python objects to simple datastructures
124 | * **Encoding**: Does the framework provide a way of encoding data into a wire format
125 | * **Deserialization**: Does the framework provide a way of deserializing simple data structures into complex data structures
126 | * **Validation**: Does the framework provide a way of validating datastructures, and reprorting error conditions
127 | * **Part of Framework**: Is serialization apart of a larger framework
128 |
--------------------------------------------------------------------------------
/docker-compose.yml:
--------------------------------------------------------------------------------
1 | tests:
2 | build: .
3 | command: python benchmark.py
4 | dockerfile: Dockerfile
5 | volumes:
6 | - .:/opt/code
7 |
--------------------------------------------------------------------------------
/requirements.txt:
--------------------------------------------------------------------------------
1 | toastedmarshmallow==2.15.2.post1
2 | serpy==0.3.1
3 | colander==1.7.0
4 | djangorestframework==3.9.4
5 | django==2.2.13
6 | pytest
7 | pystrainer==1.3.0
8 | tabulate==0.7.7
9 | lollipop==1.1.7
10 | py-kim==1.2.2
11 | lima==0.5
12 | markdown
13 | avro-python3==1.8.2
14 | serpyco==1.1.0
--------------------------------------------------------------------------------
/subjects/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/voidfiles/python-serialization-benchmark/b45cb024a553e78a81e1a8c5a3056b7b9100baab/subjects/__init__.py
--------------------------------------------------------------------------------
/subjects/avro.py:
--------------------------------------------------------------------------------
1 | import avro.schema
2 | from avro.io import DatumWriter
3 |
4 | name = 'Avro'
5 |
6 | class NullWriter(object):
7 | def write(self, *args, **kwargs):
8 | pass
9 |
10 | def write_utf8(self, *args, **kwargs):
11 | self.write(args, kwargs)
12 |
13 | def write_int(self, *args, **kwargs):
14 | self.write(args, kwargs)
15 |
16 | def write_long(self, *args, **kwargs):
17 | self.write(args, kwargs)
18 |
19 | schema_name_tracker = avro.schema.Names()
20 |
21 | child_test_object_schema = avro.schema.SchemaFromJSONData({
22 | "namespace": "benchmark.avro",
23 | "type": "record",
24 | "name": "childTestObject",
25 | "fields": [
26 | {"name": "w", "type": "int"},
27 | {"name": "x", "type": "int"},
28 | {"name": "y", "type": "string"},
29 | {"name": "z", "type": "int"}
30 | ]
31 | }, names=schema_name_tracker)
32 |
33 | parent_test_object_schema = avro.schema.SchemaFromJSONData({
34 | "namespace": "benchmark.avro",
35 | "type": "record",
36 | "name": "parentTestObject",
37 | "fields": [
38 | {"name": "foo", "type": "string"},
39 | {"name": "sub", "type": "childTestObject"},
40 | {
41 | "name": "subs",
42 | "type": {
43 | "type": "array",
44 | "items": "childTestObject"
45 | }
46 | }
47 | ]
48 | }, names=schema_name_tracker)
49 |
50 | datum_writer = DatumWriter(writer_schema=parent_test_object_schema)
51 | null_binary_writer = NullWriter()
52 |
53 | # Avro for Python can't directly serialize a class, it must be a dictionary.
54 | def nested_class_to_dict(obj):
55 | if not hasattr(obj,"__dict__"):
56 | return obj
57 | result = {}
58 | for key, val in obj.__dict__.items():
59 | if key.startswith("_"):
60 | continue
61 | element = []
62 | if isinstance(val, list):
63 | for item in val:
64 | element.append(nested_class_to_dict(item))
65 | else:
66 | element = nested_class_to_dict(val)
67 | result[key] = element
68 | return result
69 |
70 | def serialization_func(to_serialize, many):
71 | if many:
72 | return [datum_writer.write(nested_class_to_dict(obj), null_binary_writer) for obj in to_serialize]
73 | else:
74 | return datum_writer.write(nested_class_to_dict(to_serialize), null_binary_writer)
75 |
--------------------------------------------------------------------------------
/subjects/col.py:
--------------------------------------------------------------------------------
1 | import colander
2 | from colander import null
3 |
4 | name = 'Colander'
5 |
6 |
7 | class ObjectType(colander.Mapping):
8 | """
9 | A colander type representing a generic python object
10 | (uses colander mapping-based serialization).
11 | """
12 |
13 | def serialize(self, node, appstruct):
14 | appstruct = appstruct.__dict__
15 | return super(ObjectType, self).serialize(node, appstruct)
16 |
17 | def deserialize(self, node, cstruct):
18 | data = super(ObjectType, self).deserialize(node, cstruct)
19 | appstruct = node.instance.__class__()
20 | appstruct.__dict__.update(data)
21 | return appstruct
22 |
23 |
24 | class ObjectSchema(colander.SchemaNode):
25 | schema_type = ObjectType
26 | instance = None
27 |
28 | def serialize(self, appstruct):
29 | if not self.instance:
30 | # set the instance on all child schema nodes as they
31 | # may need to access the instance environment
32 | self.instance = appstruct
33 | for subnode in self.children:
34 | if isinstance(subnode, MethodSchema):
35 | setattr(subnode, 'instance', appstruct)
36 | return super(ObjectSchema, self).serialize(appstruct)
37 |
38 | def deserialize(self, cstruct):
39 | appstruct = super(ObjectSchema, self).deserialize(cstruct)
40 | if not self.instance:
41 | self.instance = appstruct
42 | return appstruct
43 |
44 |
45 | class CallableSchema(colander.SchemaNode):
46 | def serialize(self, appstruct):
47 | if appstruct is null:
48 | return null
49 | appstruct = appstruct()
50 | return super(CallableSchema, self).serialize(appstruct)
51 |
52 | def deserialize(self, cstruct):
53 | if cstruct is null:
54 | return null
55 | appstruct = super(CallableSchema, self).deserialize(cstruct)
56 | return lambda: appstruct
57 |
58 |
59 | class MethodSchema(CallableSchema):
60 | def serialize(self, appstruct):
61 | if appstruct is null:
62 | appstruct = getattr(self.instance, self.name)
63 | return super(MethodSchema, self).serialize(appstruct)
64 |
65 |
66 | class Differential(colander.SchemaNode):
67 | def __init__(self, typ, differential=0):
68 | self.differential = differential
69 | super(Differential, self).__init__(typ)
70 |
71 | def serialize(self, appstruct):
72 | # operator could be overloaded by the appstruct class if necessary
73 | appstruct += self.differential
74 | return super(Differential, self).serialize(appstruct)
75 |
76 | def deserialize(self, cstruct):
77 | # operator could be overloaded by the appstruct class if necessary
78 | appstruct = super(Differential, self).deserialize(cstruct)
79 | appstruct -= self.differential
80 | return appstruct
81 |
82 |
83 | class ChildSchema(ObjectSchema):
84 | w = colander.SchemaNode(colander.Int())
85 | y = colander.SchemaNode(colander.String())
86 | x = Differential(colander.Int(), 10)
87 | z = colander.SchemaNode(colander.Int())
88 |
89 |
90 | class ChildListSchema(colander.SequenceSchema):
91 | sub = ChildSchema()
92 |
93 |
94 | class ParentSchema(ObjectSchema):
95 | foo = colander.SchemaNode(colander.String())
96 | bar = MethodSchema(colander.Int())
97 | sub = ChildSchema()
98 | subs = ChildListSchema()
99 |
100 |
101 | class ParentListSchema(colander.SequenceSchema):
102 | parents = ParentSchema()
103 |
104 |
105 | unit_schema = ParentSchema()
106 | seq_schema = ParentListSchema()
107 |
108 |
109 | def serialization_func(obj, many):
110 | schema = seq_schema if many else unit_schema
111 | return schema.serialize(obj)
112 |
--------------------------------------------------------------------------------
/subjects/hand.py:
--------------------------------------------------------------------------------
1 | name = 'Custom'
2 |
3 |
4 | def sub_to_cstruct(obj):
5 | return {
6 | 'w': obj.w,
7 | 'y': obj.y,
8 | 'x': obj.x + 10,
9 | 'z': obj.z
10 | }
11 |
12 |
13 | def obj_to_cstruct(obj):
14 | return {
15 | 'foo': obj.foo,
16 | 'bar': obj.bar(),
17 | 'sub': sub_to_cstruct(obj.sub),
18 | 'subs': [sub_to_cstruct(x) for x in obj.subs],
19 | }
20 |
21 |
22 | def serialization_func(obj, many):
23 | if many:
24 | return [obj_to_cstruct(x) for x in obj]
25 | else:
26 | return obj_to_cstruct(obj)
27 |
--------------------------------------------------------------------------------
/subjects/k.py:
--------------------------------------------------------------------------------
1 | from kim import Mapper, field
2 |
3 | name = 'kim'
4 |
5 |
6 | class Complex(object):
7 | pass
8 |
9 |
10 | class SubResource(object):
11 | pass
12 |
13 |
14 | def bar_pipe(session):
15 | session.output['bar'] = session.data()
16 |
17 |
18 | def x_pipe(session):
19 | session.output['x'] = session.data + 10
20 |
21 |
22 | class SubMapper(Mapper):
23 | __type__ = SubResource
24 | w = field.String()
25 | x = field.String(extra_serialize_pipes={'output': [x_pipe]})
26 | y = field.String()
27 | z = field.String()
28 |
29 |
30 | class ComplexMapper(Mapper):
31 | __type__ = Complex
32 | foo = field.String()
33 | bar = field.String(extra_serialize_pipes={'output': [bar_pipe]})
34 | sub = field.Nested(SubMapper)
35 | subs = field.Collection(field.Nested(SubMapper))
36 |
37 |
38 | def serialization_func(obj, many):
39 | if many:
40 | return ComplexMapper.many().serialize(obj)
41 | else:
42 | return ComplexMapper(obj=obj).serialize()
43 |
--------------------------------------------------------------------------------
/subjects/lim.py:
--------------------------------------------------------------------------------
1 | import lima
2 |
3 | name = 'lima'
4 |
5 |
6 | class SubM(lima.Schema):
7 | w = lima.fields.Integer()
8 | x = lima.fields.Integer(get=lambda obj: obj.x + 10)
9 | y = lima.fields.Integer()
10 | z = lima.fields.Integer()
11 |
12 |
13 | class ComplexM(lima.Schema):
14 | foo = lima.fields.String()
15 | bar = lima.fields.Integer(get=lambda obj: obj.bar())
16 | sub = lima.fields.Embed(schema=SubM)
17 | subs = lima.fields.Embed(schema=SubM, many=True)
18 |
19 |
20 | schema = ComplexM()
21 | many_scheam = ComplexM(many=True)
22 |
23 |
24 | def serialization_func(obj, many):
25 | if many:
26 | return many_scheam.dump(obj)
27 | else:
28 | return schema.dump(obj)
29 |
--------------------------------------------------------------------------------
/subjects/loli.py:
--------------------------------------------------------------------------------
1 | from collections import namedtuple
2 | from lollipop.types import Object, String, Integer, List, FunctionField, MethodField
3 |
4 | name = 'Lollipop'
5 |
6 | SubS = namedtuple('SubS', ['w', 'x', 'y', 'z'])
7 | ComplexS = namedtuple('ComplexS', ['foo', 'bar', 'sub', 'subs'])
8 |
9 |
10 | def get_x(obj):
11 | return obj.x + 10
12 |
13 |
14 | SubSType = Object({
15 | 'w': Integer(),
16 | 'x': FunctionField(Integer(), get_x),
17 | 'y': String(),
18 | 'z': Integer(),
19 | }, constructor=SubS)
20 |
21 | ComplexSType = Object({
22 | 'foo': String(),
23 | 'bar': MethodField(Integer(), 'bar'),
24 | 'sub': SubSType,
25 | 'subs': List(SubSType),
26 | }, constructor=ComplexS)
27 |
28 |
29 | def serialization_func(obj, many):
30 | if many:
31 | return List(ComplexSType).dump(obj)
32 | else:
33 | return ComplexSType.dump(obj)
34 |
--------------------------------------------------------------------------------
/subjects/marsh.py:
--------------------------------------------------------------------------------
1 | import marshmallow
2 |
3 | name = 'Marshmallow'
4 |
5 |
6 | class SubM(marshmallow.Schema):
7 | w = marshmallow.fields.Int()
8 | x = marshmallow.fields.Method('get_x')
9 | y = marshmallow.fields.Str()
10 | z = marshmallow.fields.Int()
11 |
12 | def get_x(self, obj):
13 | return obj.x + 10
14 |
15 |
16 | class ComplexM(marshmallow.Schema):
17 | bar = marshmallow.fields.Int()
18 | foo = marshmallow.fields.Str()
19 | sub = marshmallow.fields.Nested(SubM)
20 | subs = marshmallow.fields.Nested(SubM, many=True)
21 |
22 |
23 | schema = ComplexM()
24 |
25 |
26 | def serialization_func(obj, many):
27 | return schema.dump(obj, many=many).data
28 |
--------------------------------------------------------------------------------
/subjects/pickle.py:
--------------------------------------------------------------------------------
1 | import pickle
2 |
3 | name = 'Pickle'
4 |
5 |
6 | def serialization_func(obj, many):
7 | return pickle.dumps(obj)
8 |
--------------------------------------------------------------------------------
/subjects/rf.py:
--------------------------------------------------------------------------------
1 | from django.conf import settings
2 | settings.configure()
3 |
4 | import django
5 | django.setup()
6 |
7 | from rest_framework import serializers as rf_serializers
8 |
9 | name = 'Django REST Framework'
10 |
11 |
12 | class SubRF(rf_serializers.Serializer):
13 | w = rf_serializers.IntegerField()
14 | x = rf_serializers.SerializerMethodField()
15 | y = rf_serializers.CharField()
16 | z = rf_serializers.IntegerField()
17 |
18 | def get_x(self, obj):
19 | return obj.x + 10
20 |
21 |
22 | class ComplexRF(rf_serializers.Serializer):
23 | foo = rf_serializers.CharField()
24 | bar = rf_serializers.IntegerField()
25 | sub = SubRF()
26 | subs = SubRF(many=True)
27 |
28 |
29 | def serialization_func(obj, many):
30 | return ComplexRF(obj, many=many).data
31 |
--------------------------------------------------------------------------------
/subjects/serp.py:
--------------------------------------------------------------------------------
1 | import serpy
2 |
3 | name = 'serpy'
4 |
5 |
6 | class SubS(serpy.Serializer):
7 | w = serpy.IntField()
8 | x = serpy.MethodField()
9 | y = serpy.StrField()
10 | z = serpy.IntField()
11 |
12 | def get_x(self, obj):
13 | return obj.x + 10
14 |
15 |
16 | class ComplexS(serpy.Serializer):
17 | foo = serpy.StrField()
18 | bar = serpy.IntField(call=True)
19 | sub = SubS()
20 | subs = SubS(many=True)
21 |
22 |
23 | def serialization_func(obj, many):
24 | return ComplexS(obj, many=many).data
25 |
--------------------------------------------------------------------------------
/subjects/serpy.py:
--------------------------------------------------------------------------------
1 | import dataclasses
2 | import typing
3 |
4 | import serpyco
5 |
6 | from data import ParentTestObject
7 |
8 | name = "serpyco"
9 |
10 |
11 | def get_x(obj):
12 | return obj.x + 10
13 |
14 |
15 | @dataclasses.dataclass
16 | class SubM:
17 | w: int
18 | y: str
19 | z: int
20 | x: int = serpyco.field(getter=get_x)
21 |
22 |
23 | @dataclasses.dataclass
24 | class ComplexM:
25 | foo: str
26 | sub: SubM
27 | subs: typing.List[SubM]
28 | bar: int = serpyco.field(getter=ParentTestObject.bar)
29 |
30 |
31 | serializer = serpyco.Serializer(ComplexM)
32 |
33 |
34 | def serialization_func(obj, many):
35 | return serializer.dump(obj, many=many)
36 |
--------------------------------------------------------------------------------
/subjects/strain.py:
--------------------------------------------------------------------------------
1 | import strainer
2 |
3 | name = 'Strainer'
4 |
5 | sub_strainer_serializer = strainer.serializer(
6 | strainer.field('w'),
7 | strainer.field('x', attr_getter=lambda obj: obj.x + 10),
8 | strainer.field('y'),
9 | strainer.field('z'),
10 | )
11 |
12 | complex_strainer_serializer = strainer.serializer(
13 | strainer.field('foo'),
14 | strainer.field('bar', attr_getter=lambda obj: obj.bar()),
15 | strainer.child('sub', serializer=sub_strainer_serializer),
16 | strainer.many('subs', serializer=sub_strainer_serializer),
17 | )
18 |
19 |
20 | def serialization_func(obj, many):
21 | if many:
22 | return [complex_strainer_serializer.serialize(x) for x in obj]
23 | else:
24 | return complex_strainer_serializer.serialize(obj)
25 |
--------------------------------------------------------------------------------
/subjects/tmarsh.py:
--------------------------------------------------------------------------------
1 | import toastedmarshmallow
2 | import marshmallow
3 |
4 | name = 'Toasted Marshmallow'
5 |
6 |
7 | class SubM(marshmallow.Schema):
8 | w = marshmallow.fields.Int()
9 | x = marshmallow.fields.Method('get_x')
10 | y = marshmallow.fields.Str()
11 | z = marshmallow.fields.Int()
12 |
13 | def get_x(self, obj):
14 | return obj.x + 10
15 |
16 |
17 | class ComplexM(marshmallow.Schema):
18 | foo = marshmallow.fields.Str()
19 | bar = marshmallow.fields.Int()
20 | sub = marshmallow.fields.Nested(SubM)
21 | subs = marshmallow.fields.Nested(SubM, many=True)
22 |
23 |
24 | schema = ComplexM()
25 | schema.jit = toastedmarshmallow.Jit
26 |
27 |
28 | def serialization_func(obj, many):
29 | return schema.dump(obj, many=many).data
30 |
--------------------------------------------------------------------------------
/tests/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/voidfiles/python-serialization-benchmark/b45cb024a553e78a81e1a8c5a3056b7b9100baab/tests/__init__.py
--------------------------------------------------------------------------------
/tests/test_serializers.py:
--------------------------------------------------------------------------------
1 | from subjects import rf, serp, strain, col, hand, loli, k, lim, tmarsh
2 | from data import ParentTestObject
3 | import pprint
4 | TARGET = {
5 | 'foo': 'bar',
6 | 'bar': 5,
7 | 'sub': {'w': 100, 'y': 'hello', 'z': 10, 'x': 30},
8 | 'subs': [
9 | {'w': 100, 'y': 'hello', 'z': 10, 'x': 30},
10 | {'w': 1000, 'y': 'hello', 'z': 10, 'x': 30},
11 | {'w': 2000, 'y': 'hellohello', 'z': 20, 'x': 50},
12 | {'w': 3000, 'y': 'hellohellohello', 'z': 30, 'x': 70},
13 | {'w': 4000, 'y': 'hellohellohellohello', 'z': 40, 'x': 90},
14 | {'w': 5000, 'y': 'hellohellohellohellohello', 'z': 50, 'x': 110},
15 | {'w': 6000, 'y': 'hellohellohellohellohellohello', 'z': 60, 'x': 130},
16 | {'w': 7000, 'y': 'hellohellohellohellohellohellohello', 'z': 70, 'x': 150},
17 | {
18 | 'w': 8000,
19 | 'y': 'hellohellohellohellohellohellohellohello',
20 | 'z': 80,
21 | 'x': 170,
22 | }, {
23 | 'w': 9000,
24 | 'y': 'hellohellohellohellohellohellohellohellohello',
25 | 'z': 90,
26 | 'x': 190,
27 | }
28 | ]
29 | }
30 |
31 |
32 | def test_serializers():
33 | test_object = ParentTestObject()
34 |
35 | for subject in (rf, tmarsh, serp, strain, col, hand, loli, k, lim):
36 | print(subject.__name__)
37 | data = subject.serialization_func(test_object, False)
38 | pprint.pprint(data)
39 | assert str(data['foo']) == str(TARGET['foo'])
40 | assert str(data['bar']) == str(TARGET['bar'])
41 | assert str(data['sub']['w']) == str(TARGET['sub']['w'])
42 | assert str(data['subs'][3]['y']) == str(TARGET['subs'][3]['y'])
43 | assert str(data['subs'][3]['x']) == str(TARGET['subs'][3]['x'])
44 |
45 | datas = subject.serialization_func([test_object, test_object], True)
46 | for data in datas:
47 | assert str(data['foo']) == str(TARGET['foo'])
48 | assert str(data['sub']['w']) == str(TARGET['sub']['w'])
49 | assert str(data['subs'][3]['y']) == str(TARGET['subs'][3]['y'])
50 | assert str(data['subs'][3]['x']) == str(TARGET['subs'][3]['x'])
51 |
52 | if __name__ == '__main__':
53 | test_serializers()
54 |
--------------------------------------------------------------------------------