jsonapitransformer 1.0.0
jsonapi-transformer
jsonapi-transformer is a Python library for producing, consuming, and manipulating JSON:API data. It makes developing with JSON:API more manageable by converting from and to JSON:API-formatted data.
This library follows the JSON:API v1.1 release candidate specification.
Quick Start
The following example is based on Example 7.2.2.4 of the JSON:API v1.1 specification.
import json
from transformers import JSONAPITransformer, from_jsonapi_generic
Data can be manually set on a JSONAPITransformer instance...
transformer = JSONAPITransformer(
type_name="articles",
id="1",
attributes={
"title": "Rails is Omakase",
},
relationships={
"author": JSONAPITransformer(
type_name="people",
id="9",
)
}
)
... and then converted to jsonapi.
>>> jsonapi = transformer.to_jsonapi()
>>> print(json.dumps(jsonapi, indent=4))
{
"data": {
"type": "articles",
"id": "1",
"attributes": {
"title": "Rails is Omakase"
},
"relationships": {
"author": {
"data": {
"type": "people",
"id": "9"
}
}
}
}
}
If you already have jsonapi data, you can load that into a JSONAPITransformer instance as well.
>>> transformer = from_jsonapi_generic(jsonapi)
>>> print(transformer.id)
1
>>> print(transformer.type_name)
articles
Installation
jsonapi-transformer is available on PyPI.
pip install --upgrade pip
pip install jsonapi-transformer
jsonapi-transformer supports Python 3.7+.
Notable Features
Code snippets in this section use the Quick Start example above as a starting point.
Support for both id and lid.
The included list is generated automatically from items in relationships when to_jsonapi() is called -- there's no need to manipulate an item in both the included list and an object's relationships.
Convenience method to get an attribute or relationship or a default if the key is not found.
# First, try `transformer.attributes["greeting"]` -- greeting is not found!
# Next, try `transformer.relationships["greeting"]` -- greeting is not found!
# Finally, the default is returned.
>>> default = "hello"
>>> greeting = transformer.get("greeting", default)
>>> print(greeting)
hello
Convenience accessor for getting attributes and relationships using [] notation.
# First, try `transformer.attributes["title"]` -- title is found!
>>> print(transformer["title"])
Rails is Omakase
# First, try `transformer.attributes["author"]` -- author is not found!
# Next, try `transformer.relationships["author"]` -- author is found!
>>> author = transformer["author"]
>>> print(author.to_jsonapi())
{"data": {"type": "people", "id": "9"}}
Convenience accessor for setting attributes using [] notation.
>>> transformer["my_new_attribute"] = "hello"
Convenience accessor for deleting attributes using [] notation.
>>> del transformer["my_new_attribute"]
Convenience accessor for contains in attributes and relationships using the in keyword.
>>> "title" in transformer
True
>>> "author" in transformer
True
>>> "book" in transformer
False
Deep equality test between transformers, comparing all of:
type_name
id
lid
attributes
relationships
included
>>> book = JSONAPITransformer(type_name="book", id="1")
>>> article = JSONAPITransformer(type_name="article", id="1")
>>> book == article
False
Derived transformer classes can contain business logic.
from transformers import JSONAPITransformer, JSONAPITransformerFactory
class People(JSONAPITransformer):
type_name = "people"
@property
def full_name(self):
"""Custom business logic for this class, keyed on `type_name`."""
return f"{self['last_name'], self['first_name']}"
jsonapi = {
"data": {
"type": "articles",
"id": "1",
"attributes": {
"title": "Rails is Omakase"
},
"relationships": {
"author": {
"data": {
"type": "people",
"id": "9"
}
}
}
},
"included": [
{
"type": "people",
"id": "9",
"attributes": {
"first_name": "Jon",
"last_name": "George"
}
}
]
}
# By using a factory instead of `from_jsonapi_generic(...)`, we can provide a list
# of classes that are instantiated based on the `type` field in the jsonapi data.
# Since we didn't provide a class for the "articles" type, setting `allow_generic`
# to True allows the "articles" object to load as a generic JSONAPITransformer
factory = JSONAPITransformerFactory([People], allow_generic=True)
>>> transformer = factory.from_jsonapi(jsonapi)
>>> print(type(transformer))
<class 'JSONAPITransformer'>
>>> author = transformer["author"]
>>> print(type(author))
<class 'People'>
# Access business logic unique to the `People` class.
>>> print(author.full_name)
George, Jon
Lists of JSON:API objects can reside in the same document, and any shared relationships are de-duplicated in the included list.
from transformers import JSONAPIListTransformer, JSONAPITransformer, from_jsonapi_generic
article1 = JSONAPITransformer(
type_name="articles",
id="1",
attributes={
"title": "Rails is Omakase",
},
relationships={
"author": JSONAPITransformer(
type_name="people",
id="9",
attributes={
"first_name": "Jon",
"last_name": "George"
}
)
}
)
article2 = JSONAPITransformer(
type_name="articles",
id="2",
attributes={
"title": "Now is better than never.",
},
relationships={
"author": JSONAPITransformer(
type_name="people",
id="9",
attributes={
"first_name": "Jon",
"last_name": "George"
}
)
}
)
>>> transformer = JSONAPIListTransformer([article1, article2])
>>> print(type(transformer))
<class 'JSONAPIListTransformer'>
# The `included` section is deduplicated.
>>> jsonapi = transformer.to_jsonapi()
>>> print(json.dumps(jsonapi, indent=4))
{
"data": [
{
"type": "articles",
"id": "1",
"attributes": {
"title": "Rails is Omakase"
},
"relationships": {
"author": {
"data": {
"type": "people",
"id": "9"
}
}
}
},
{
"type": "articles",
"id": "2",
"attributes": {
"title": "Now is better than never."
},
"relationships": {
"author": {
"data": {
"type": "people",
"id": "9"
}
}
}
}
],
"included": [
{
"type": "people",
"id": "9",
"attributes": {
"first_name": "Jon",
"last_name": "George"
}
}
]
}
# Convert back to transformers.
>>> transformers = from_jsonapi_generic(jsonapi)
>>> print(type(transformers))
<class 'list'>
>>> for x in transformers:
... print(type(x))
<class 'JSONAPITransformer'>
<class 'JSONAPITransformer'>
Unsupported
These top-level members of the JSON:API specification are unsupported:
errors
jsonapi
links
meta
Development Locally
Contributors to jsonapi-transformer can install an editable version of this library after cloning the repository:
pip install --upgrade pip
pip install -e .[tests,dev]
Development with Docker
Contributors to jsonapi-transformer can do all development tasks within a Docker container:
Build
Don't forget the trailing .!!!
docker build -f Dockerfile.testing \
--build-arg PYTHON_VERSION=3.10 \
-t jsonapi-transformer:latest \
.
Run Tests with Coverage
docker run --rm -it \
--entrypoint pytest \
jsonapi-transformer:latest \
--cov --cov-report=term-missing
Run Type Checking
docker run --rm -it \
--entrypoint mypy \
jsonapi-transformer:latest \
--show-error-context \
--show-error-codes \
--strict \
src
For personal and professional use. You cannot resell or redistribute these repositories in their original state.
There are no reviews.