A Python library for producing, consuming, and manipulating JSON:API data.
Project description
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
andlid
. - The
included
list is generated automatically from items inrelationships
whento_jsonapi()
is called -- there's no need to manipulate an item in both theincluded
list and an object'srelationships
. - Convenience method to get an
attribute
orrelationship
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
andrelationships
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
andrelationships
using thein
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
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 Distributions
Built Distribution
Hashes for jsonapi_transformer-1.0.0-py3-none-any.whl
Algorithm | Hash digest | |
---|---|---|
SHA256 | e47a66761f507f2f947ff4d76c8cea205cf4d01310b66fd35bd4e7c21686bef5 |
|
MD5 | d4573f73035623e1c6d7898aeb108617 |
|
BLAKE2b-256 | 09b248e3d30cf056a33b4be59b894a3c1d6bd8af6a95d1befc405c634c3c124e |