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
idandlid. - The
includedlist is generated automatically from items inrelationshipswhento_jsonapi()is called -- there's no need to manipulate an item in both theincludedlist and an object'srelationships. - Convenience method to get an
attributeorrelationshipor 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
attributesandrelationshipsusing[]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
attributesusing[]notation.>>> transformer["my_new_attribute"] = "hello"
- Convenience accessor for deleting
attributesusing[]notation.>>> del transformer["my_new_attribute"]
- Convenience accessor for contains in
attributesandrelationshipsusing theinkeyword.>>> "title" in transformer True >>> "author" in transformer True >>> "book" in transformer False
- Deep equality test between transformers, comparing all of:
type_nameidlidattributesrelationshipsincluded
>>> 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
includedlist.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:
errorsjsonapilinksmeta
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
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file jsonapi_transformer-1.0.0-py3-none-any.whl.
File metadata
- Download URL: jsonapi_transformer-1.0.0-py3-none-any.whl
- Upload date:
- Size: 19.1 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/4.0.1 CPython/3.10.5
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
e47a66761f507f2f947ff4d76c8cea205cf4d01310b66fd35bd4e7c21686bef5
|
|
| MD5 |
d4573f73035623e1c6d7898aeb108617
|
|
| BLAKE2b-256 |
09b248e3d30cf056a33b4be59b894a3c1d6bd8af6a95d1befc405c634c3c124e
|