Convention based, effortless serialization and deserialization
Project description
Convention Based, Effortless Serialization & Deserialization
uniserde can convert Python classes to/from JSON and BSON without effort from
your side. Simply define the classes, and the library does the rest.
Define your types as classes with type annotations, and use one of uniserde's
serializers / deserializers:
import uniserde
from datetime import datetime, timezone
from dataclasses import dataclass
from bson import ObjectId
@dataclass
class Person:
id: ObjectId
name: str
birth_date: datetime
betty = Person(
id=ObjectId(),
name="Betty",
birth_date=datetime(year=1988, month=12, day=1, tzinfo=timezone.utc),
)
serde = uniserde.JsonSerde()
print(serde.as_json(betty))
This will print a dictionary similar to this one
{
'id': '62bc6c77792fc617c52499d0',
'name': 'Betty',
'birth_date': '1988-12-01T00:00:00+00:00'
}
You can easily convert this to a string using Python's built-in json module if
that's what you're after.
API
The API is extremely simple. Functions/Classes you might be interested in are:
-
JsonSerdeis used to serialize/deserialize Python values to/from JSON. It takes some configuration options like custom handlers for specific types.Use
JsonSerde.as_jsonto serialize a Python object to JSON, andJsonSerde.from_jsonto deserialize a JSON object to a Python object. -
BsonSerdeis likeJsonSerde, but for BSON. In addition to serialization & deserialization, it also supports MongoDB schema generation.Use
BsonSerde.as_bsonto serialize a Python object to BSON,BsonSerde.from_bsonto deserialize a BSON object to a Python object, andBsonSerde.as_mongodb_schemato generate a MongoDB schema from a Python class. -
SerdeErrro: The error raised when something goes wrong during serialization or deserialization, e.g. when a required field is missing. -
Sometimes a class simply acts as a type-safe base, but you really just want to serialize the children of that class. In that case you can decorate the class with
@as_child. This will store an additionaltypefield in the result, so the correct child class can be instantiated when deserializing. -
Custom serialization / deserialization can be achieved by giving custom handlers to the Serde instances or by defining the appropriate methods on your classes:
_uniserde_as_json__uniserde_as_bson__uniserde_from_json__uniserde_from_bson__uniserde_as_mongodb_schema_
When called, these are passed the same parameters:
- The
JsonSerde/BsonSerdeinstance doing the serialization/deserialization - The value to be serialized/deserialized
- As which type to serialize/deserialize the value (This is needed e.g. for generics, that may not know the type of all of their child attributes).
(
_uniserde_as_monogodb_schema_only receives the Serde instance and the type, since there is no value to process.) -
The library also exposes a couple handy type definitions:
Jsonable,Bsonable-- Any type which can occur in a JSON / BSON file respectively, i.e. (bool, int, float, ...)JsonDoc,BsonDoc-- A dictionary mapping strings toJsonables /Bsonable
JSON Conventions
| Python | JSON | Notes |
|---|---|---|
bool |
bool |
|
int |
float |
|
float |
float |
|
str |
str |
|
tuple |
list |
|
list |
list |
|
set |
list |
|
Optional |
value or None |
|
Any |
as-is | The value is kept unchanged, without any checks. |
Literal[str] |
str |
|
enum.Enum |
str |
Enum values are mapped to their name (NOT value!) |
enum.Flag |
list[str] |
Each flag is encoded the same way a regular enum.Enum value would. |
| custom class | dict |
Each attribute is stored as key, in lowerCamelCase. If marked with as_child, an additional type field is added. |
bytes |
str |
base64 encoded |
datetime.datetime |
str |
as ISO 8601 - with timezone. Naïve datetimes are intentionally not supported. Do yourself a favor and don't use them. |
datetime.timedelta |
float |
duration, in seconds |
dict[K, V] |
dict[str, ...] |
Dictionary keys can be str or int |
bson.ObjectId |
str |
|
pathlib.Path |
str |
Paths are made absolute before serialization. |
uuid.UUID |
str |
BSON Conventions
BSON mostly uses the same conventions as JSON, with just a few changes:
| Python | BSON | Notes |
|---|---|---|
bytes |
bytes |
|
datetime.datetime |
datetime.datetime |
Serialization requires a timezone be set. Deserialization imputes UTC, to match MongoDB. |
bson.ObjectId |
bson.ObjectId |
|
uuid.UUID |
uuid.UUID |
MongoDB Schema Generation
If you are working with MongoDB you will come to appreciate the automatic schema
generation. Calling uniserde.as_mongodb_schema on any supported class will
return a MongoDB compatible JSON schema without hassle.
For example, here's the result of uniserde.as_mongodb_schema(Person) with the
Person class above:
{
'type': 'object',
'properties': {
'id': {
'bsonType': 'objectId'
},
'name': {
'type': 'string'
},
'birth_date': {
'bsonType': 'date'
}
},
'additionalProperties': False,
'required': [
'id',
'name',
'birth_date'
]
}
Lazy Deserialization
Normally, serialization happens all at once: You tell uniserde to create a
class instance from a JSON, uniserde processes all of the fields and returns
the finished class.
This works great, but can be wasteful if you are working with large documents
and only need to access few fields. To help with this, you can pass lazy=True
when deserializing any object. uniserde will then hold off deserializing
fields until they are accessed for the first time, saving precious processing
time.
A word of caution: Data is validated as it is deserialized. Since lazy deserialization defers work until the data is accessed, this means any data you don't access also won't be validated. Thus, lazy serialization can be a very powerful tool for speeding up interactions with large objects, but you should only use when you are absolutely certain the data is correct. (For example because you have just fetched the object from your own, trusted, database.)
Maximizing performance
Whenever uniserde needs to serialize a type, it builds a handler specifically
for that type. That handler is then cached in the active Serde instance, so that
any future serialization / deserialization of that type is much faster. Thus, to
get maximum performance, you can create one global Serde instance configure it
to your liking then reuse it every time you need one.
Limitations
- Recursive types are not supported - Types that reference themselves (directly
or indirectly) will result in a
SerdeError. - Support for
Unionis currently very limited. Really onlyOptionalis supported (which Python internally maps toUnion) Literalcurrently only supports strings- Examples for custom serialization / deserialization
- Extend
as_child, to allow marking some classes as abstract. i.e. their parents/children can be serialized, but not those classes themselves - Being able to specify additional limitations to fields would be nice:
- must match regex
- minimum / maximum
- custom validation functions
- more Unit tests (custom de-serializers!?)
- Add more examples to the README
- show custom serializers/deserializers
- recommended usage
- calling
uniserde.serializeon non-classes causes problems, because the serializationas_typeis guessed incorrectly. e.g.[1, 2, 3]will be incorrectly serialized aslistrather thanlist[int].
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file uniserde-0.4.1.tar.gz.
File metadata
- Download URL: uniserde-0.4.1.tar.gz
- Upload date:
- Size: 44.0 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: uv/0.9.13 {"installer":{"name":"uv","version":"0.9.13"},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Arch Linux","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
1fa540e2fbfbed7e4d71e9e1e471f003281142f8ea36bdcac3554d2200734a93
|
|
| MD5 |
f575b003e7ba3526e28b623e890c2099
|
|
| BLAKE2b-256 |
d5b74917e500c29e064a9f514b856cf0a4a27f2c906a295d7218ce30c1ad2165
|
File details
Details for the file uniserde-0.4.1-py3-none-any.whl.
File metadata
- Download URL: uniserde-0.4.1-py3-none-any.whl
- Upload date:
- Size: 39.6 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: uv/0.9.13 {"installer":{"name":"uv","version":"0.9.13"},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Arch Linux","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
5d0db1e1b5e2a4d50b89badf72a804229902fe8b754a2af3f32956e1e32279e8
|
|
| MD5 |
caf63d041a418062a2290807b611f104
|
|
| BLAKE2b-256 |
9ef1e29dc64eec88681d0d910e96c926163e49371595a39667bea6fcf185125e
|