Ecosyste.ms: Awesome
An open API service indexing awesome lists of open source software.
https://github.com/monomonedula/nvelope
Define your JSON schema as Python dataclasses
https://github.com/monomonedula/nvelope
json json-deserialization json-schema json-serialization marshalling python3 unmarshalling
Last synced: 5 days ago
JSON representation
Define your JSON schema as Python dataclasses
- Host: GitHub
- URL: https://github.com/monomonedula/nvelope
- Owner: monomonedula
- License: mit
- Created: 2021-10-06T10:06:49.000Z (about 3 years ago)
- Default Branch: master
- Last Pushed: 2023-09-23T09:54:12.000Z (about 1 year ago)
- Last Synced: 2024-09-17T20:09:08.887Z (about 2 months ago)
- Topics: json, json-deserialization, json-schema, json-serialization, marshalling, python3, unmarshalling
- Language: Python
- Homepage:
- Size: 90.8 KB
- Stars: 65
- Watchers: 3
- Forks: 1
- Open Issues: 1
-
Metadata Files:
- Readme: README.md
- License: LICENSE
Awesome Lists containing this project
README
[![codecov](https://codecov.io/gh/monomonedula/nvelope/branch/master/graph/badge.svg?token=yunFiDdUEK)](https://codecov.io/gh/monomonedula/nvelope)
[![Build Status](https://app.travis-ci.com/monomonedula/nvelope.svg?branch=master)](https://app.travis-ci.com/monomonedula/nvelope)
[![Code style: black](https://img.shields.io/badge/code%20style-black-000000.svg)](https://github.com/psf/black)
[![Downloads](https://pepy.tech/badge/nvelope)](https://pepy.tech/project/nvelope)
# nvelopeDefine your JSON schema as Python dataclasses
It's kinda like Pydantic but better.
Now with JSON-schema generation!
## Installation
`pip install nvelope`## The problem it solves
With `nvelope` you can define dataclasses which know how to convert themselves from/to JSON.
All with custom checks and custom defined conversions from/to JSON for any type you want to put into your dataclass.This library was designed with extensibility in mind,
so it relies on interfaces (for the most part) rather than
some weird inheritance stuff and other magic.You can (and probably should) take a look at the code!
The code base is microscopic compared to Pydantic.## Usage
Say you have a JSON representing a user in your app looking something like this
```json
{
"id": 530716139,
"username": "johndoe",
"language_code": "en"
}
```You define an envelope for it
```python
from dataclasses import dataclassfrom nvelope import (Obj, int_conv, string_conv)
@dataclass # note the @dataclass decorator, it is important
class User(Obj):
_conversion = {
"id": int_conv,
"language_code": string_conv,
"username": string_conv,
}id: int
language_code: str
username: str```
Now you have a model that knows how to read data from the JSON
(not the raw string, actually, but to the types that are allowed by the
standard `json.dumps` function e.g. `dict`, `list`, `str`, `int`, `float`, `bool`, `None` ) ...```python
user = User.from_json(
{
"id": 530716139,
"username": "johndoe",
"language_code": "en"
}
)
```
... and knows how to convert itself into JSON```python
User(
id=530716139,
username="johndoe",
language_code="en",
).as_json()# returns a dictionary {
# "id": 530716139,
# "username": "johndoe",
# "language_code": "en"
# }
```### Compound envelopes
You can also define compound envelopes.Say we want to define a message and include info about the sender.
Having defined the `User` envelope, we can do it like this:```python
from nvelope import CompoundConv@dataclass
class Message(Obj):
_conversion = {
"message_id": int_conv,
"from_": CompoundConv(User),
"text": string_conv,
}from_: User
text: str
message_id: int
```
and use it the same way:```python
# reading an obj from parsed json like thisMessage.from_json(
{
"message_id": 44,
"text": "hello there",
"from_": {
"id": 530716139,
"username": "johndoe",
"language_code": "en"
}
}
)# and dumping an object to json like this
import json
json.dumps(
Message(
message_id=44,
text="whatever",
from_=User(
id=530716139,
username="johndoe",
language_code="en",
)
).as_json()
)
```### Arrays
This is how you define arrays:
```python
from nvelope import Arr, CompoundConvclass Users(Arr):
conversion = CompoundConv(User)# Same API inherited from nvelope.Compound interface
Users.from_json(
[
{
"id": 530716139,
"username": "johndoe",
"language_code": "en",
},
{
"id": 452341341,
"username": "ivandrago",
"language_code": "ru",
}
]
)Users(
[
User(
id=530716139,
username="johndoe",
language_code="en",
),
User(
id=452341341,
username="ivandrago",
language_code="ru",
),
]
).as_json()
```### Field aliases
At some point you may need to define an envelope for an API containing certain field names which cannot be
used in python since they are reserved keywords (such as `def`, `from`, etc.).There's a solution for this:
```python
from dataclasses import dataclass
from nvelope import Obj, string_conv, CompoundConv, AliasTable@dataclass
class Comment(Obj):
_conversion = {
"text": string_conv,
"from_": CompoundConv(User),
}
_alias_table = AliasTable({"from_": "from"})
text: str
from_: User```
In this case `from` key gets replaced by `from_` in the python model.
The `from_` field gets translated back to `from` when calling `.as_json()`### Missing and optional fields
There's a difference between fields that can be set to `None` and fields which may be missing in the JSON at all.
This is how you specify that a some field may be missing from the JSON and that's OK:
```python
from dataclasses import dataclass
from typing import Optionalfrom nvelope import MaybeMissing, Obj, OptionalConv, AliasTable
@dataclass
class Comment(Obj):
_alias_table = AliasTable(
{"from_": "from"}
)
text: str
img: Optional[str] # this field can be set to None (null), but is must always be present in the JSON
from_: MaybeMissing[User] # this field can be missing from JSON body_conversion = {
"text": string_conv,
"img": OptionalConv(string_conv), # note the wrapping with OptionalConv
"from_": CompoundConv(User),
}```
This is how you check if the `MaybeMissing` field is actually missing
```python
comment.from_.has() # returns False if the field is missing
```and this is how you get the value:
```python
comment.value() # raises an error if there's no value,
# so it is recommended to check the output of .has()
# before calling .value()
```### Json-schema support
The `Comment` model from we have defined generates schema like this:
```python
Comment.schema()
```with the returned schema looking like this:
```python
{
"type": "object",
"properties": {
"from": {
"properties": {
"id": {"type": "integer"},
"language_code": {"type": "string"},
"username": {"type": "string"},
},
"required": ["id", "language_code", "username"],
"type": "object",
},
"img": {"type": ["string", "null"]},
"text": {"type": "string"},
},
"required": ["text", "img"],
}
```
**NOTE**: `nvelope` does not perform json schema checks.### Custom conversions
You may define a custom conversions inheriting from `nvelope.nvelope.Conversion` abstract base class
or using `nvelope.nvelope.ConversionOf` class.For example, this is how `datetime_iso_format_conv` is defined:
```python
from nvelope import WithTypeCheckOnDump, ConversionOfdatetime_iso_format_conv = WithTypeCheckOnDump(
datetime.datetime,
ConversionOf(
to_json=lambda v: v.isoformat(),
from_json=lambda s: datetime.datetime.fromisoformat(s),
),
)```
Say we want to jsonify a `datetime` field as POSIX timestamp, instead of storing it in ISO string format.
```python
datetime_timestamp_conv = ConversionOf(
to_json=lambda v: v.timestamp(),
from_json=lambda s: datetime.datetime.fromtimestamp(s),
schema={"type": "number"},
)
```We could also add `WithTypeCheckOnDump` wrapper in order to add explicit check that
the value passed into `.from_json()`
is indeed `float`.```python
from nvelope import ConversionOfdatetime_timestamp_conv = WithTypeCheckOnDump(
float,
ConversionOf(
to_json=lambda v: v.timestamp(),
from_json=lambda s: datetime.datetime.fromtimestamp(s),
schema={"type": "number"},
)
)
```You may also go further and implement custom conversion.
Inherit from `nvelope.Conversion` interface, implement its abstract methods, and you are good to go.### Custom compounds
You can also define custom alternatives to `nvelope.Obj` and `nvelope.Arr`.
It will work fine as long as they inherit `nvelope.Compound` interface.It currently required 3 methods:
- `from_json`
- `as_json`
- `schema`