Jsonable API

Module for the jsonable interface

class pheres._jsonable.jsonable(cls: type = None, /, after: Union[str, Iterable[str]] = (), internal: str = None, only_marked: bool = False)

Class decorator to make a class jsonable

This decorator adds members to the decorated class to serialize or deserialize instance from JSON format. Class decorated this way are called jsonable classess. The members added by this decorator are described in JsonableDummy. Some may not be added by certain usage of this decorator, see below.

Jsonable classes are typed. This means the values found in JSON must have the correct types when deserializing. Types are specified in python code by using PEP 526 type annotations. No typecheck occurs on serialization, as python’s type annotations are not binding. Pheres trusts that the code follows the annotations it exposes, but does not enforce it. Ill-implemented code may well crash when deserializing instances that did not abide to their annotations.

@jsonable can be parametrized by type-hints, much like types from the typing module (e.g. @jsonable[int]). This will produce various types of jsonable classes. Refer to the Usage section below for details.

There are four types of jsonable classes: jsonable value, jsonable array, jsonable dict and jsonable object. These jsonable class have different JSON representation and are documented below.

This class decorator is fully compatible with the dataclasses module and the attr.s decorator, though it must be used as the inner-most decorator (that is, it must be executed first on the class). It will raise an error if this is not the case.

Parameters
  • after (Union[str, Iterable[str]]) –

    Modify the decorated class only after the listed member become available in the class’ module. This allows for circular definitions. When the class is modified, the class object is retrieved again from the module it was defined in. This makes after compatibles with decorators that replace the class object with another one, such as attr.s in certain circumstances.

    Checks for dependencies are performed after each use of the @jsonable decorator. If your class is the last decorated one in the file, call jsonable.decorate_delayed to modify it before usage.

  • internal (str) – For jsonable value, array of dict. Name of the attribute where the JSON value is stored, allowing Pheres to make add the to_json method to the decorated class

  • only_marked (bool) – For jsonable object only. If True, all annotated attributes are used. If False, only marked attributes are used. See jsonable objects below. Attributes lacking a PEP 526 annotation are always ignored.

Raises
Jsonable values:

Jsonable values are represented by a single JSON value (null, a number, a string). To make a jsonable value by decorating a class with @jsonable, the class must:

  • have an __init__ method accepting the call Class(value), where value is the JSON value for an instance of the class (this is used for deserialization)

  • implement the to_json(self) method, that returns the value to represents the instance with in JSON (e.g. None, an int or a str). This is because Pheres cannot know what the class does with the value, like it does with jsonable objects.

See the Usage section below for how to make jsonable values

Jsonable arrays:

Jsonable arrays are represented by a JSON array. There are two kind of arrays, fixed-length arrays and arbitrary length arrays. To make a jsonable array by decorating a class with @jsonable, the class must:

  • have an __init__ method that accept the call Class(*array), where array is the python list for the JSON array. This means:

    • For fixed-length array, a signature __init__(self, v1, v2, ...) with a fixed number of arguments is accepted

    • For arbitrary length array, the number of arguments is not known in advance, so the signature must be similar to __init__(self, *array) (this signature is also valid for fixed-length arrays as it does accept the call above).

  • implement the to_json(self) method, that returns the python list that represents the instance in JSON. This is because Pheres cannot know what the class does with the values like it does with jsonable objects.

See the Usage section below for how to make jsonable arrays

Jsonable dicts:

Jsonable dicts are represented by a JSON Object with arbitrary key-value pairs. For JSON object with known key-value pairs, see jsonable objects below. To make a jsonable dict by decorating a class with @jsonable, the class must:

  • have an __init__ method that accept the call Class(**object), where object is the python dict representing the instance in JSON. The only signature that supports that in python is of the sort __init__(self, **object) (the actual name of the object parameter can vary). Optional arguments may be added, but all key-value pairs found in the deserialized JSON will be passed as keyword arguments. This is used for deserialization.

  • implement the to_json(self) method, that returns a python dict that represents the instance in JSON. This is because Pheres cannot know what the class does with the key-value pairs like it does with jsonable objects.

See the Usage section below for how to make jsonable dicts

Jsonable objects:

Jsonable objects are represented by a JSON Object with known key-value pairs. The key-value paires are obtained from the class by inspecting PEP 526 annotations. Attributes must be annotated to be considered by Pheres, then:

  • If only_marked is False (the default), all annotated attributes are used

  • If only_marked is False, attributes must be marked for use (see Marked and marked)

Irrespective of the value of only_marked, Marked and marked can always be used for refined control (see their documentation).

If an attribute has a value in the class body, it is taken to be the default value of this attribute. The attribute becomes optional in the JSON representation. Defaults are always deep-copied when instantiating the class from a JSON representation, so list and dict are safe to use.

To make a jsonable object by decorating a class with @jsonable, the class must:

  • have an __init__ method that accept the call Class(attr1=val1, attr2=val2, ...) where attributes values are passed as keyword arguments. This is automatically the case if you used dataclasses.dataclass or attr.s and is the intended usage for this functuonality.

In particular, jsonable object do not need to implement a to_json(self) method, as Pheres knows exactly what attributes to use. This is in contrast with other jsonable classes.

See the Usage section below for how to make jsonable objects

Usages:

There are several usage of this decorator.

Type parametrization:

To produce jsonable values, arrays or dict, the decorator must be parametrized by a type-hint. Any valid type-hint in JSON context can be used. The following syntax may be used:

@jsonable[T]
@jsonable.Value[T]
@jsonable.Array[T]
@jsonable.Dict[T]

The first forms encompasses the following three: If the passed type T is a JSON value, it will produce a jsonable value after decorating the class. If T is typing.List or typing.Tuple (or list or tuple since python 3.9), it will produce a jsonable array. Finally, if T is typing.Dict (or dict), it will produce a jsonable dict. If you wish to produce a jsonable object, do not parametrize the decorator.

The type must be fully parametrized, to provide the type of the JSON representation down to the last value. typing.Union can be used. This means that:

@jsonable[List] # not valid
@jsonable[List[int]] # valid
@jsonable[List[Union[int, str]]] # valid

For jsonable arrays, typing.List will produce an arbitrary-length array, while typing.Tuple will produce a fixed-length array, unless an ellipsis is used (e.g. jsonable[Tuple[int, Ellipsis]]). The literal ellipsis ... may be used, but this is avoided in this documentation to prevent confusion with the meaning that more arguments can be provided.

@jsonable.Value[T, ...] is equivalent to jsonable[Union[T, ...]] (The ... here is not python ellipsis but indicates that more than one types may be passed).

@jsonable.Array[T, ...] is equivalent to jsonable[Tuple[T, ...]] (where the ... indicates that more than one argument may be passed). This produces a fixed-length array – use Ellispis in second position to procude an arbitrary-length array.

@jsonable.Dict[T, ...] is equivalent to @jsonable[dict[str, Union[T, ...]]] and produces a jsonable dict. To produce a jsonable object, do not parametrize the decorator.

Arguments:

If specified, arguments must be provided after the type parametrization. after can be used for all jsonable classes, but only_marked only has an effect on jsonable objects.

Notes

@jsonable only add members that are not explicitely defined by the decorated class (inherited implementation are ignored). This means you can overwrite the default behavior if necessary.

When parametrized by a type or passed arguments, this decorator returns an object that may be re-used. This means that the following code is valid:

from pheres import jsonable, Marked

my_decorator = jsonable(only_marked=True)

@my_decorator
class MyClass:
    python: int
    json: Marked[int]
Value[T, ...]

Shortcut for jsonable[Union[T, ...]]

Array[T, ...]

Shortcut for jsonable[Tuple[T, ...]]

Dict[T, ...]

Shortcut for jsonable[Dict[str, Union[T, ...]]]

static decorate_delayed()

Modifies all delayed jsonables whose dependencies are now met

You only need to call this if you do not use @jsonable after the dependencies are made available

class pheres._jsonable.JsonableDummy

Bases: object

Dummy class providing dummy members for all members added by @jsonable. Allows type checkers and linters to detect said attributes

Decoder

alias of pheres._datatypes.UsableDecoder

classmethod from_json(obj: Any) → AnyClass

Converts a JSON file, string or object to an instance of that class

to_json() → JSONType

Converts an instance of that class to a JSON object

class pheres._jsonable.JsonMark(key: Optional[str] = None, json_only: bool = MISSING)

Bases: object

Annotation for jsonized arguments that provides more control on the jsonized attribute behavior. All arguments are optional.

Parameters
  • key – Set the name of the key in JSON for that attribute. Defaults to the name of the attribute in python

  • json_only – Make the attribute only present in JSON. The attribute must have a default value or be a typing.Literal of a single value. The attribute is removed from the class’ annotations at runtime Defaults to True for Literals of a single value; False for all other types

pheres._jsonable.Marked

Simple type alias to quickly mark an attribute as jsonized

Marked[T] is equivalent to Annotated[T, JsonMark()]

alias of TypeT

pheres._jsonable.marked(tp: TypeHint, /, **kwargs) → TypeHint

Shortcut for Annotated[T, JsonMark(**kwargs)]

See JsonMark for a list of supported keyword arguments. marked may not be compatible with type checkers due to being a runtime definition

Parameters
  • tp – Type hint to mark

  • **kwargs – additional info to pass to JsonMark

See also

JsonMark

class pheres._jsonable.JSONableEncoder(*, skipkeys=False, ensure_ascii=True, check_circular=True, allow_nan=True, sort_keys=False, indent=None, separators=None, default=None)

Bases: json.encoder.JSONEncoder

JSONEncoder subclass that supports jsonable classes

default(obj: object)

Overrides json.JSONEncoder.default to support jsonable classes

pheres._jsonable.dump(obj, fp, *, skipkeys=False, ensure_ascii=True, check_circular=True, allow_nan=True, cls=None, indent=None, separators=None, default=None, sort_keys=False, **kw)

Simple wrapper around json.dump that uses JSONableEncoder as the default encoder.

Wrapped function docstring:

Serialize obj as a JSON formatted stream to fp (a .write()-supporting file-like object).

If skipkeys is true then dict keys that are not basic types (str, int, float, bool, None) will be skipped instead of raising a TypeError.

If ensure_ascii is false, then the strings written to fp can contain non-ASCII characters if they appear in strings contained in obj. Otherwise, all such characters are escaped in JSON strings.

If check_circular is false, then the circular reference check for container types will be skipped and a circular reference will result in an OverflowError (or worse).

If allow_nan is false, then it will be a ValueError to serialize out of range float values (nan, inf, -inf) in strict compliance of the JSON specification, instead of using the JavaScript equivalents (NaN, Infinity, -Infinity).

If indent is a non-negative integer, then JSON array elements and object members will be pretty-printed with that indent level. An indent level of 0 will only insert newlines. None is the most compact representation.

If specified, separators should be an (item_separator, key_separator) tuple. The default is (', ', ': ') if indent is None and (',', ': ') otherwise. To get the most compact JSON representation, you should specify (',', ':') to eliminate whitespace.

default(obj) is a function that should return a serializable version of obj or raise TypeError. The default simply raises TypeError.

If sort_keys is true (default: False), then the output of dictionaries will be sorted by key.

To use a custom JSONEncoder subclass (e.g. one that overrides the .default() method to serialize additional types), specify it with the cls kwarg; otherwise JSONEncoder is used.

pheres._jsonable.dumps(obj, *, skipkeys=False, ensure_ascii=True, check_circular=True, allow_nan=True, cls=None, indent=None, separators=None, default=None, sort_keys=False, **kw)

Simple wrapper around json.dumps that uses JSONableEncoder as the default encoder.

Wrapped function docstring:

Serialize obj to a JSON formatted str.

If skipkeys is true then dict keys that are not basic types (str, int, float, bool, None) will be skipped instead of raising a TypeError.

If ensure_ascii is false, then the return value can contain non-ASCII characters if they appear in strings contained in obj. Otherwise, all such characters are escaped in JSON strings.

If check_circular is false, then the circular reference check for container types will be skipped and a circular reference will result in an OverflowError (or worse).

If allow_nan is false, then it will be a ValueError to serialize out of range float values (nan, inf, -inf) in strict compliance of the JSON specification, instead of using the JavaScript equivalents (NaN, Infinity, -Infinity).

If indent is a non-negative integer, then JSON array elements and object members will be pretty-printed with that indent level. An indent level of 0 will only insert newlines. None is the most compact representation.

If specified, separators should be an (item_separator, key_separator) tuple. The default is (', ', ': ') if indent is None and (',', ': ') otherwise. To get the most compact JSON representation, you should specify (',', ':') to eliminate whitespace.

default(obj) is a function that should return a serializable version of obj or raise TypeError. The default simply raises TypeError.

If sort_keys is true (default: False), then the output of dictionaries will be sorted by key.

To use a custom JSONEncoder subclass (e.g. one that overrides the .default() method to serialize additional types), specify it with the cls kwarg; otherwise JSONEncoder is used.