Skip to main content

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.

  1. Support for both id and lid.
  2. 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.
  3. 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
    
  4. 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"}}
    
  5. Convenience accessor for setting attributes using [] notation.
    >>> transformer["my_new_attribute"] = "hello"
    
  6. Convenience accessor for deleting attributes using [] notation.
    >>> del transformer["my_new_attribute"]
    
  7. Convenience accessor for contains in attributes and relationships using the in keyword.
    >>> "title" in transformer
    True
    
    >>> "author" in transformer
    True
    
    >>> "book" in transformer
    False
    
  8. 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
    
  9. 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
    
  10. 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


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distributions

No source distribution files available for this release.See tutorial on generating distribution archives.

Built Distribution

jsonapi_transformer-1.0.0-py3-none-any.whl (19.1 kB view hashes)

Uploaded Python 3

Supported by

AWS AWS Cloud computing and Security Sponsor Datadog Datadog Monitoring Fastly Fastly CDN Google Google Download Analytics Microsoft Microsoft PSF Sponsor Pingdom Pingdom Monitoring Sentry Sentry Error logging StatusPage StatusPage Status page