├── .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 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 |
ProjectSerializationEncodingDeserializationValidation
Django REST FrameworkYesYesYesYes
serpyYesNoNoNo
MarshmallowYesYesYesYes
LollipopYesNoYesYes
StrainerYesNoYesYes
KimYesNoYesYes
serpycoYesYesYesYes
Toasted MarshmallowYesYesYesYes
ColanderYesNoYes<Yes
LimaYesNoNoNo
AvroYesYesYesNo
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 | --------------------------------------------------------------------------------