All Projects â†’ rossmacarthur â†’ serde

rossmacarthur / serde

Licence: Apache-2.0, MIT licenses found Licenses found Apache-2.0 LICENSE-APACHE MIT LICENSE-MIT
đźšť (unmaintained) A framework for defining, serializing, deserializing, and validating data structures

Programming Languages

python
139335 projects - #7 most used programming language

Projects that are alternatives of or similar to serde

Marshmallow
A lightweight library for converting complex objects to and from simple Python datatypes.
Stars: âś­ 5,857 (+11853.06%)
Mutual labels:  serialization, schema, serde, deserialization
Schematics
Project documentation: https://schematics.readthedocs.io/en/latest/
Stars: âś­ 2,461 (+4922.45%)
Mutual labels:  serialization, schema, deserialization
har-rs
A HTTP Archive format (HAR) serialization & deserialization library, written in Rust.
Stars: âś­ 25 (-48.98%)
Mutual labels:  serialization, serde, deserialization
avrow
Avrow is a pure Rust implementation of the avro specification https://avro.apache.org/docs/current/spec.html with Serde support.
Stars: âś­ 27 (-44.9%)
Mutual labels:  serialization, schema, deserialization
Beeschema
Binary Schema Library for C#
Stars: âś­ 46 (-6.12%)
Mutual labels:  serialization, schema, deserialization
avro-serde-php
Avro Serialisation/Deserialisation (SerDe) library for PHP 7.3+ & 8.0 with a Symfony Serializer integration
Stars: âś­ 43 (-12.24%)
Mutual labels:  serialization, serde, deserialization
kafka-protobuf-serde
Serializer/Deserializer for Kafka to serialize/deserialize Protocol Buffers messages
Stars: âś­ 52 (+6.12%)
Mutual labels:  serialization, serde, deserialization
Typical
Typical: Fast, simple, & correct data-validation using Python 3 typing.
Stars: âś­ 111 (+126.53%)
Mutual labels:  serialization, serde, deserialization
Awesome Python Models
A curated list of awesome Python libraries, which implement models, schemas, serializers/deserializers, ODM's/ORM's, Active Records or similar patterns.
Stars: âś­ 124 (+153.06%)
Mutual labels:  serialization, schema, deserialization
sexp-grammar
Invertible parsing for S-expressions
Stars: âś­ 28 (-42.86%)
Mutual labels:  serialization, deserialization
Marshmallow Jsonapi
JSON API 1.0 (https://jsonapi.org/) formatting with marshmallow
Stars: âś­ 203 (+314.29%)
Mutual labels:  serialization, deserialization
json struct
json_struct is a single header only C++ library for parsing JSON directly to C++ structs and vice versa
Stars: âś­ 279 (+469.39%)
Mutual labels:  serialization, deserialization
Django Rest Marshmallow
Marshmallow schemas for Django REST framework
Stars: âś­ 198 (+304.08%)
Mutual labels:  serialization, schema
Aspjson
A fast classic ASP JSON parser and encoder for easy JSON manipulation to work with the new JavaScript MV* libraries and frameworks.
Stars: âś­ 165 (+236.73%)
Mutual labels:  serialization, deserialization
Dart Json Mapper
Serialize / Deserialize Dart Objects to / from JSON
Stars: âś­ 206 (+320.41%)
Mutual labels:  serialization, deserialization
Orjson
Fast, correct Python JSON library supporting dataclasses, datetimes, and numpy
Stars: âś­ 2,595 (+5195.92%)
Mutual labels:  serialization, deserialization
Jsonapi Rb
Efficiently produce and consume JSON API documents.
Stars: âś­ 219 (+346.94%)
Mutual labels:  serialization, deserialization
Noproto
Flexible, Fast & Compact Serialization with RPC
Stars: âś­ 138 (+181.63%)
Mutual labels:  serialization, deserialization
Mashumaro
Fast and well tested serialization framework on top of dataclasses
Stars: âś­ 208 (+324.49%)
Mutual labels:  serialization, deserialization
kafka-serde-scala
Implicitly converts typeclass encoders to kafka Serializer, Deserializer, Serde.
Stars: âś­ 52 (+6.12%)
Mutual labels:  serialization, serde

Unmaintained

I will personally not be contributing any features or bug fixes to this project, but I am happy to accept pull requests. If you would like to maintain this project please let me know. If you are using this project please consider alternatives like:

Serde

PyPI Version Documentation Status Build Status Code Coverage Code Style

Serde is a lightweight, general-purpose framework for defining, serializing, deserializing, and validating data structures in Python.

Getting started

Installation

Serde is available on PyPI, you can install it using

pip install serde

Extended features can be installed with the ext feature.

pip install serde[ext]

Introduction

In Serde models are containers for fields. Data structures are defined by subclassing Model and assigning Field instances as class annotations. These fields handle serialization, deserialization, normalization, and validation for the corresponding model attributes.

from datetime import date
from serde import Model, fields

class Artist(Model):
    name: fields.Str()

class Album(Model):
    title: fields.Str()
    release_date: fields.Optional(fields.Date)
    artist: fields.Nested(Artist)

album = Album(
    title='Dangerously in Love',
    release_date=date(2003, 6, 23),
    artist=Artist(name='Beyoncé')
)
assert album.to_dict() == {
    'title': 'Dangerously in Love',
    'release_date': '2003-06-23',
    'artist': {
        'name': 'Beyoncé'
    }
}

album = Album.from_json("""{
    "title": "Lemonade",
    "artist": {"name": "Beyoncé"}}"
""")
assert album == Album(title='Lemonade', artist=Artist(name='Beyoncé'))

Basic usage

Below we create a User model by subclassing Model and adding the name and email fields.

>>> from datetime import datetime
>>> from serde import Model, fields
>>>
>>> class User(Model):
...     name: fields.Str(rename='username')
...     email: fields.Email()

The corresponding attribute names are used to instantiate the model object and access the values on the model instance.

>>> user = User(name='Linus Torvalds', email='[email protected]')
>>> user.name
'Linus Torvalds'
>>> user.email
'[email protected]'

Models are validated when they are instantiated and a ValidationError is raised if you provide invalid values.

>>> User(name='Linus Torvalds', email='not an email')
Traceback (most recent call last):
...
serde.exceptions.ValidationError: {'email': 'invalid email'}

Models are serialized into primitive Python types using the to_dict() method on the model instance.

>>> user.to_dict()
OrderedDict([('username', 'Linus Torvalds'), ('email', '[email protected]')])

Or to JSON using the to_json() method.

>>> user.to_json()
'{"username": "Linus Torvalds", "email": "[email protected]"}'

Models are also validated when they are deserialized. Models are deserialized from primitive Python types using the reciprocal from_dict() class method.

>>> user = User.from_dict({
...     'username': 'Donald Knuth',
...     'email': '[email protected]'
... })

Or from JSON using the from_json() method.

>>> user = User.from_json('''{
...     "username": "Donald Knuth",
...     "email": "[email protected]"
... }''')

Attempting to deserialize invalid data will result in a ValidationError.

>>> User.from_dict({'username': 'Donald Knuth'})
Traceback (most recent call last):
...
serde.exceptions.ValidationError: {'email': "missing data, expected field 'email'"}

Models

Models can be nested and used in container-like fields. Below we create a Blog with an author and a list of subscribers which must all be User instances.

>>> class Blog(Model):
...     title: fields.Str()
...     author: fields.Nested(User)
...     subscribers: fields.List(User)

When instantiating you have to supply instances of the nested models.

>>> blog = Blog(
...     title="sobolevn's personal blog",
...     author=User(name='Nikita Sobolev', email='[email protected]'),
...     subscribers=[
...         User(name='Ned Batchelder', email='[email protected]')
...     ]
... )

Serializing a Blog would serialize the entire nested structure.

>>> print(blog.to_json(indent=2))
{
  "title": "sobolevn's personal blog",
  "author": {
    "username": "Nikita Sobolev",
    "email": "[email protected]"
  },
  "subscribers": [
    {
      "username": "Ned Batchelder",
      "email": "[email protected]"
    }
  ]
}

Similiarly deserializing a Blog would deserialize the entire nested structure, and create instances of all the submodels.

Subclassed models

Models can be subclassed. The subclass will have all the fields of the parent and any additional ones. Consider the case where we define a SuperUser model which is a subclass of a User. Simply a User that has an extra level field.

>>> class SuperUser(User):
...     # inherits name and email fields from User
...     level: fields.Choice(['admin', 'read-only'])

We instantiate a subclassed model as normal by passing in each field value.

>>> superuser = SuperUser(
...     name='Linus Torvalds',
...     email='[email protected]',
...     level='admin'
... )

This is great for many cases, however, a commonly desired paradigm is to be able to have the User.from_dict() class method be able to deserialize a SuperUser as well. This can be made possible through model tagging.

Model tagging

Model tagging is a way to mark serialized data in order to show that it is a particular variant of a model. Serde provides three types of model tagging, but you can also define you own custom Tag. A Tag can be thought of in the same way as a Field but instead of deserializing data into an attribute on a model instance, it deserializes data into a model class.

Internally tagged

Internally tagged data stores a tag value inside the serialized data.

Let us consider an example where we define a Pet model with a tag. We can then subclass this model and deserialize arbitrary subclasses using the tagged model.

>>> from serde import Model, fields, tags
>>>
>>> class Pet(Model):
...     name: fields.Str()
...
...     class Meta:
...         tag = tags.Internal(tag='species')
...
>>> class Dog(Pet):
...     hates_cats: fields.Bool()
...
>>> class Cat(Pet):
...     hates_dogs: fields.Bool()

We refer to the Dog and Cat subclasses as variants of Pet. When serializing all parent model tag serialization is done after field serialization.

>>> Cat(name='Fluffy', hates_dogs=True).to_dict()
OrderedDict([('name', 'Fluffy'), ('hates_dogs', True), ('species', '__main__.Cat')])

When deserializing, tag deserialization is done first to determine which model to use for the deserialization.

>>> milo = Pet.from_dict({
...     'name': 'Milo',
...     'hates_cats': False,
...     'species': '__main__.Dog'
... })
>>> milo.__class__
<class '__main__.Dog'>
>>> milo.name
'Milo'
>>> milo.hates_cats
False

An invalid or missing tag will raise a ValidationError.

>>> Pet.from_dict({'name': 'Milo', 'hates_cats': False})
Traceback (most recent call last):
...
serde.exceptions.ValidationError: missing data, expected tag 'species'
>>>
>>> Pet.from_dict({'name': 'Duke', 'species': '__main__.Horse'})
Traceback (most recent call last):
...
serde.exceptions.ValidationError: no variant found

Externally tagged

Externally tagged data uses the tag value as a key and nests the content underneath that key. All other processes behave similarly to the internally tagged example above.

>>> class Pet(Model):
...     name: fields.Str()
...
...     class Meta:
...         tag = tags.External()
...
>>> class Dog(Pet):
...     hates_cats: fields.Bool()
...
>>> Dog(name='Max', hates_cats=True).to_dict()
OrderedDict([('__main__.Dog', OrderedDict([('name', 'Max'), ('hates_cats', True)]))])

Adjacently tagged

Adjacently tagged data data stores the tag value and the content underneath two separate keys. All other processes behave similarly to the internally tagged example.

>>> class Pet(Model):
...     name: fields.Str()
...
...     class Meta:
...         tag = tags.Adjacent(tag='species', content='data')
...
>>> class Dog(Pet):
...     hates_cats: fields.Bool()
...
>>> Dog(name='Max', hates_cats=True).to_dict()
OrderedDict([('species', '__main__.Dog'), ('data', OrderedDict([('name', 'Max'), ('hates_cats', True)]))])

Abstract models

By default model tagging still allows deserialization of the base model. It is common to have this model be abstract. You can do this by setting the abstract Meta field to True. This will make it uninstantiatable and it won't be included in the variant list during deserialization.

>>> class Fruit(Model):
...     class Meta:
...         abstract = True
...
>>> Fruit()
Traceback (most recent call last):
...
TypeError: unable to instantiate abstract model 'Fruit'

Custom tags

It is possible to create your own custom tag class by subclassing any of tags.External, tags.Internal, tags.Adjacent or even the base tags.Tag. This will allow customization of how the variants are looked up, how the tag values are generated for variants, and how the data is serialized.

Consider an example where we use a class attribute code as the tag value.

>>> class Custom(tags.Internal):
...     def lookup_tag(self, variant):
...         return variant.code
...
>>> class Pet(Model):
...     name: fields.Str()
...
...     class Meta:
...         abstract = True
...         tag = Custom(tag='code')
...
>>> class Dog(Pet):
...     code = 1
...     hates_cats: fields.Bool()
...
>>> Dog(name='Max', hates_cats=True).to_dict()
OrderedDict([('name', 'Max'), ('hates_cats', True), ('code', 1)])
>>> max = Pet.from_dict({'name': 'Max', 'hates_cats': True, 'code': 1})
>>> max.__class__
<class '__main__.Dog'>
>>> max.name
'Max'
>>> max.hates_cats
True

Fields

Fields do the work of serializing, deserializing, normalizing, and validating the input values. Fields are always assigned to a model as instances , and they support extra serialization, deserialization, normalization, and validation of values without having to subclass Field. For example

from serde import Model, fields, validators

class Album(Model):
    title: fields.Str(normalizers=[str.strip])
    released: fields.Date(
        rename='release_date',
        validators=[validators.Min(datetime.date(1912, 4, 15))]
    )

In the above example we define an Album class. The title field is of type str , and we apply the str.strip normalizer to automatically strip the input value when instantiating or deserializing the Album. The released field is of type datetime.date and we apply an extra validator to only accept dates after 15th April 1912. Note: the rename argument only applies to the serializing and deserializing of the data, the Album class would still be instantiated using Album(released=...).

If these methods of creating custom Field classes are not satisfactory, you can always subclass a Field and override the relevant methods.

>>> class Percent(fields.Float):
...     def validate(self, value):
...         super().validate(value)
...         validators.Between(0.0, 100.0)(value)

Model states and processes

In Serde, there are two states that the data can be in:

  • Serialized data
  • Model instance

There are five different processes that the data structure can go through when moving between these two states.

  • Deserialization happens when you create a model instance from a serialized version using from_dict() or similar.
  • Instantiation happens when you construct a model instance in Python using the __init__() constructor.
  • Normalization happens after instantiation and after deserialization. This is usually a way to transform things before they are validated. For example: this is where an Optional field sets default values.
  • Validation is where the model and fields values are validated. This happens after normalization.
  • Serialization is when you serialize a model instance to a supported serialization format using to_dict() or similar.

The diagram below shows how the stages (uppercase) and processes (lowercase) fit in with each other.

                    +---------------+
                    | Instantiation |
                    +---------------+
                            |
                            v
+---------------+   +---------------+
|Deserialization|-->| Normalization |
+---------------+   +---------------+
        ^                   |
        |                   v
        |           +---------------+
        |           |   Validation  |
        |           +---------------+
        |                   |
        |                   v
+-------+-------+   +---------------+
|SERIALIZED DATA|   | MODEL INSTANCE|
+---------------+   +---------------+
        ^                   |
        |                   |
+-------+-------+           |
| Serialization |<----------+
+---------------+

License

Serde is licensed under either of

at your option.

Note that the project description data, including the texts, logos, images, and/or trademarks, for each open source project belongs to its rightful owner. If you wish to add or remove any projects, please contact us at [email protected].