Introducing Pavlova, a new python deserialization library

Chris Trotman
Freelancer Engineering & Data Science
3 min readOct 11, 2018

Pavlova is a new python deserialization library that is built on top of dataclasses.

Within Escrow.com, we’ve been using Python 3.6 and type hinting for a while, and have absolutely loved it. Combined with mypy, it has helped us avoid multiple bugs within our system and increase developer productivity. However, there was something that always bugged us, and that was when we used webargs to parse the data we received in an HTTP request, the best we could do for typing was ‘Dict[str, Any]’.

This didn’t sit well with us. After falling in love with dataclasses, we started to look for a library that would give us better typing. While there are other libraries out there that do a good job at deserialization (Webargs, Marshmallow, Attrs to name a few), they either didn’t provide the additional typing we desired, or we felt they were too difficult to use.

So we built Pavlova. Pavlova is a simple library that parses a dictionary, and returns an instance of a dataclass. This is all done using plain dataclasses and python types.

from datetime import datetime
from dataclasses import dataclass

from pavlova import Pavlova

@dataclass
class Input:
id: int
name: str
date: datetime


Pavlova().from_mapping({
'id': 10,
'name': 100
'date': '2018-08-10',
}, Input)
# Input(id=10, name='100', date=datetime.datetime(2018, 8, 10, 0, 0))

Here, you can see that when we parse the dictionary, we get back an instance of the dataclass input. From there, mypy will then check the types. As you can see, because it is a dataclass, it is simple and easy to understand, however it doesn’t cover validation.

However, once again, validation is relatively straightforward to achieve with Pavlova. If you’re looking to achieve validation on a dataclass, dataclasses will call __post_init__ after creating a dataclass. If some of the fields are not valid input, then you can raise a ValueError exception.

If you would like to add validation on a special field, say for an email, you can also quite easily do this. The recommended way is subclassing your base python type, and adding validation into the constructor. Here is a simple “Email” type, that checks that there is an at symbol in the string:

class Email(str):
def __new__(cls, input_value: typing.Any) -> str:
if isinstance(input_value, str):
if '@' in input_value:
return str(input_value)
raise ValueError()
raise TypeError()

You simply then register the type with your pavlova instance,

from pavlova import Pavlova
from pavlova.parsers import GenericParser
pavlova = Pavlova()
pavlova.register_parser(Email, GenericParser(pavlova, Email))

Now, you can use the “Email” type in your dataclasses. This improves readability of your data structures, as it not only conveys the type of your variables, but what validation has been performed. You are also free to use this class in the rest of your code base, as it is not specific to Pavlova.

There is also a helper for flask, which will automatically parse your requests for you:

from dataclasses import dataclass, asdict

from flask import Flask, jsonify
from pavlova.flask import FlaskPavlova

pavlova = FlaskPavlova()
app = Flask(__name__)

@dataclass
class SampleInput:
id: int
name: str

@app.route('/post', methods=['POST'])
@pavlova.use(SampleInput)
def data(data: SampleInput):
data.id = data.id * len(data.name)
return jsonify(asdict(data))


app.run()

If you’re interested in adding support for other frameworks, pull requests are welcome. You can find pavlova on PyPi and Github.

--

--