ypres: ridiculously fast object serialization
This project started as a fork of the amazing Serpy serializer, which has been
marked as feature-complete by the original author. This fork
adds some newer features, such as asyncio support so that asynchronous
methods may be called from within a serializer.
It was renamed to "ypres" ("serpy" backwards, pronounced like the Belgian town name) to avoid confusion with the original.
Since forking it has undergone numerous changes and rewrites. The core of it is still somewhat recognizable, but there have also been many changes.
ypres is a simple object serialization framework built for speed. ypres serializes complex datatypes (Django Models, custom classes, ...) to simple native types (dicts, lists, strings, ...). The native types can easily be converted to JSON or any other format needed.
The goal of ypres is to be able to do this simply, reliably, and quickly. Since serializers are class based, they can be combined, extended and customized with very little code duplication.
Changes from Serpy
There are some notable changes from the original Serpy serializer in this fork.
New Serializer classes: AsyncSerializer and AsyncDictSerializer
Serpy did not allow for MethodField implementations to use async / await methods.
For those instances where you wish to embed an async / await coroutine in your serializer,
two new serializer classes, AsyncSerializer and AsyncDictSerializer, will automatically
detect whether the method being called is a coroutine and handle it appropriately.
New StaticField class
When combining many fields and manipulating output, it is sometimes desirable to have
a fixed value for certain fields in the output. The new StaticField class allows
you to specify a fixed value for the field, and this will always appear in the output.
Serializers allow a context object
Additional context can be passed in to a serializer. This is helpful if you have some context that you wish to use when serializing the object. For example, you might pass in a user object that could customize the responses in the serializer with their name, or only perform certain serialization tasks if they are of a specific class (e.g., admin).
Date and DateTime Serializer Fields
Date and DateTime fields can be serialized, based on the implementation from another fork, https://github.com/PKharlamov/drf-serpy/blob/master/drf_serpy/fields.py.
Deprecated the .data property.
Since .data can return either a list (with many=True) or a dict, type checkers
complained when you serialized a single object because the calling code does not handle
the case of data being a list.
Instead, two new properties, serialized and serialized_many are introduced that
return a dict and a list directly. In the course of doing this work the class structure
for the serializers was reworked to better implement common checks and data in the superclass.
import ypres
class MySerializer(ypres.Serializer):
foo = ypres.MethodField()
blah = ypres.MethodField()
def get_foo(self, obj):
foo_data = obj.foo
ctx_data = self.context.get("additional", "")
return f"{foo_data}_{ctx_data}"
def get_blah(self, obj):
blah_data = obj.blah
ctx_data = self.context.get("additional", "")
return f"{blah_data}_{ctx_data}"
class Foo:
foo = "foo"
blah = "blah"
my_data = MySerializer(Foo(), context={"additional": "bar"}).serialized
# {"foo": "foo_bar", "blah": "blah_bar"}
Changed behaviour of None
By default, data that evaluates to a value of None will not be included
in the output. To explicitly mark that a field should emit a None value,
it should be instantiated with an emit_none=True argument.
Note that the combination of emit_none and required deserve special attention.
- If
emit_noneisFalseandrequiredisTrue(default), then the object being serialized must have the matching attribute available, otherwise it will raise an error. The only exception is if the field is aMethodField, in which case the attribute does not need to be present on the object. This behaviour is not changed. - If
emit_noneisFalseandrequiredisFalsethen the object being serialized will not appear in the output if its value isNone - If
emit_noneisTrueandrequiredisTrue, then the object being serialized will attempt to return the value. However, it may fail if theto_valuemethod being used does not acceptNone. An example of this is theIntFieldserializer, where theto_valuemethod would effectively be callingint(None). In this case, aTypeErrorwill be raised. (This is the same as trying to serialize a string with anIntField, for example) - If
emit_noneisTrueandrequiredisFalse, then the object being serialized will actually skip theto_valuestep and simply returnNone.
Further to this, the behaviour of the StrField and BoolField were changed,
where calling StrField on a value of None would actually return the string
"None". Similarly, calling bool(None) evaluates to False. In both of these
cases the to_value handler has been modified to return None if the incoming
value is None.
This prevents unexpected type values from appearing in the
output. For values that cannot be cast to None for IntField and FloatField,
a None input will raise an exception.
Modern Python standards
The project uses update Python packaging setups with pyproject.toml. It also
adds configurations for ruff, works with uv, and fully supports type annotations.
Source
Source at: https://github.com/rism-digital/ypres
If you want a feature, send a pull request!
Installation
$ pip install git+https://github.com/rism-digital/ypres
Examples
Simple Example
import ypres
class Foo(object):
"""The object to be serialized."""
y = 'hello'
z = 9.5
def __init__(self, x):
self.x = x
class FooSerializer(ypres.Serializer):
"""The serializer schema definition."""
# Use a Field subclass like IntField if you need more validation.
x = ypres.IntField()
y = ypres.Field()
z = ypres.Field()
f = Foo(1)
FooSerializer(f).serialized
# {'x': 1, 'y': 'hello', 'z': 9.5}
fs = [Foo(i) for i in range(100)]
FooSerializer(fs, many=True).serialized_many
# [{'x': 0, 'y': 'hello', 'z': 9.5}, {'x': 1, 'y': 'hello', 'z': 9.5}, ...]
Nested Example
import ypres
class Nestee(object):
"""An object nested inside another object."""
n = 'hi'
class Foo(object):
x = 1
nested = Nestee()
class NesteeSerializer(ypres.Serializer):
n = ypres.Field()
class FooSerializer(ypres.Serializer):
x = ypres.Field()
# Use another serializer as a field.
nested = NesteeSerializer()
f = Foo()
FooSerializer(f).serialized
# {'x': 1, 'nested': {'n': 'hi'}}
Complex Example
import ypres
class Foo(object):
y = 1
z = 2
super_long_thing = 10
def x(self):
return 5
class FooSerializer(ypres.Serializer):
w = ypres.Field(attr='super_long_thing')
x = ypres.Field(call=True)
plus = ypres.MethodField()
def get_plus(self, obj):
return obj.y + obj.z
f = Foo()
FooSerializer(f).serialized
# {'w': 10, 'x': 5, 'plus': 3}
Inheritance Example
import ypres
class Foo(object):
a = 1
b = 2
class ASerializer(ypres.Serializer):
a = ypres.Field()
class ABSerializer(ASerializer):
"""ABSerializer inherits the 'a' field from ASerializer.
This also works with multiple inheritance and mixins.
"""
b = ypres.Field()
f = Foo()
ASerializer(f).serialized
# {'a': 1}
ABSerializer(f).serialized
# {'a': 1, 'b': 2}
License
ypres is free software distributed under the terms of the MIT license. See the LICENSE file.
Metadata
Release files for ypres 1.1.4
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| ypres-1.1.4.tar.gz | 119.9 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| ypres-1.1.4-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 131.7 kB
Release files / ypres-1.1.4.tar.gz
| Download URL | ypres-1.1.4.tar.gz |
|---|---|
| Size | 119.9 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
42cd6e02daf096f5e0795b3fd8fe78fde2a42624968494577323d5f9008e0cf1
|
|
BLAKE2b-256 checksum How to use checksums |
094dade1c13d7c748c3124e9e6ea2a6384b3ba114b11bfe72a0b8d1fa7eb08ed
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Sep 3, 2026.
Transparency logRelease files / ypres-1.1.4-py3-none-any.whl
| Download URL | ypres-1.1.4-py3-none-any.whl |
|---|---|
| Size | 11.8 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
53abf52574d6ce68ae8d667aabb6c0a23d7aa05e01bc3490131e3b76711718fd
|
|
BLAKE2b-256 checksum How to use checksums |
c54160080b3bad0f66a42afe20c636a56c5afc700d157b170398dba10faf1099
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Sep 3, 2026.
Transparency log