Skip to main content

annotation guided serialization

Project description

AGS: Annotation Guided Serialization

The ags module facilitates the serialization and deserialization of arbitrary Python objects to a supported backend format, currently JSON or YAML. It does so by recursively lowering the objects into the domain of the backend -- primitive types such as integers, strings and lists -- before passing them on for serialization. Conversely, after deserialization the primitive types are converted back from primitive types to the actual objects.

The approach that AGS takes is different from a project such as jsonpickle, which employs a bijective map from the space of arbitrary Python objects to the JSON domain. AGS in contrast uses a simpler, non-bijective map, aimed at cleaner files that are suitable for manual editing, at the expense of reversibility.

To restore reversibility, additional information is provided to the serialize and deserialize operations in the form of type annotations. The thinking here is that many modern codes already have type annotations in place, so this information is readily available. For codes that at present lack type annotations, it is a motivation to go with the times and migrate to static typing.

Here is a very simple example of AGS in use:

>>> import dataclasses, enum, ags
>>>
>>> class Shape(enum.Enum):
...     square = enum.auto()
...     circle = enum.auto()
>>>
>>> @dataclasses.dataclass
... class MyData:
...     value: complex
...     shape: Shape
>>>
>>> d = MyData(1+2j, Shape.circle)
>>>
>>> ags.dump("data.json", d, MyData)
>>> ags.load("data.json", MyData) == d
True

The dump and load function are passed MyData as an additional argument as there is no annotation to work with for the initial object. From here on, though, the annotations of the type object are used to guide the recursive map, resulting in a cast of the complex number to a value dictionary and the enum object to a string. The generated data.json file is:

{
  "value": {
    "real": 1.0,
    "imag": 2.0
  },
  "shape": "circle"
}

The load and dump functions determine the desired backend based on the file extension: .json for json and .yml for yaml. These are shorthands for the functions by the same name in the ags.json and ags.yaml modules which take a file-like argument instead. Additionally, these modules provide loads and dumps function for serialization to/from a string.

Supported annotations

The json and yaml formats both support the primitive types int, float, bool and str as well as list and dict with string valued keys. Additionally, yaml has support for date, time and datetime.

The following overview lists the conversion rules that are applied to lower Python objects into this domain based on their type annotation.

  • int, float, bool, str

    These types are passed through as-is.

  • typing.Literal

    Literal instances of the above primitive types are also passed through unchanged.

  • complex

    Complex numbers are not natively supported by either JSON or YAML. Numbers without an imaginary part are converted to a float, the rest to a dictionary with keys 'real' and 'imag' and floating point values.

  • bytes

    Bytes are not natively supported by either JSON or YAML, and are converted to a base85 encoded string. Alternatively, if the bytes sequence matches an encoded unicode string, then this string prefixed with the encoding and a colon (like utf8:) is also valid (the colon is excluded from the base85 character set).

  • typing.Optional

    Optional data is returned as None or the converted non-optional value.

  • typing.Union

    Unions of multiple types are converted to a single item dictionary, where the key is the name of the type and the value the converted object.

    Here we run into an issue if any of the options has no __name__ attribute, or its value is not descriptive enough ("int", "str") for the purposes of a data format. This can be solved by annotating the type, e.g, typing.Annotated[int, "amount"]. Note that while this convention is AGS specific, annotations of this type can be added freely and do not interfere with static type checkers.

    Note that unions are the main reason that type annotations must be provided to the dump function, as without it the mapping would be strictly injective from object space.

  • list, tuple

    Lists and tuples are converted to lists of their converted items. Item type annotations (e.g. list[int]) are required. Both uniform tuples (tuple[int, ...]) and fixed length tuples (tuple[int, str]) are supported.

  • dict

    Dictionaries are converted to dictionaries with their values converted; only string valued keys are supported for now. Annotations for both key and value are mandatory.

  • dataclasses.dataclass

    Data classes are converted to dictionaries based on their attribute annotations.

  • datetime.date, datetime.time, datetime.datetime

    Date objects are natively supported by YAML and returned as-is. For JSON they are converted to a string in ISO 8601 format.

  • enum.Enum

    Enum objects are identified by their name.

  • inspect.Signature

    A function signature will not generally be used as a type annotation, but it is supported for dumping and loading of an inspect.BoundArguments instance. This is a convenient way of converting all arguments of a function to and from a dictionary.

  • objects that reduce to a single constructor argument

    Lastly, objects that reduce to their own class and a single argument are identified by that argument.

    >>> from typing import Self
    >>>
    >>> class MyClass:
    ...     def __init__(self, s: str):
    ...         self.s = s
    ...     def __reduce__(self) -> tuple[type[Self], tuple[str]]:
    ...         return MyClass, (self.s,)
    >>>
    >>> ags.json.dumps(MyClass("foo"), MyClass)
    '"foo"\n'
    

    The particular return annotation for the __reduce__ method is required for AGS to recognize this structure.

Project details


Download files

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

Source Distribution

ags-0.1.tar.gz (6.9 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

ags-0.1-py3-none-any.whl (8.0 kB view details)

Uploaded Python 3

File details

Details for the file ags-0.1.tar.gz.

File metadata

  • Download URL: ags-0.1.tar.gz
  • Upload date:
  • Size: 6.9 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: uv/0.7.20

File hashes

Hashes for ags-0.1.tar.gz
Algorithm Hash digest
SHA256 1fb69a954ec3dfd02c3f5432f70fd55950e5ca794c372570c767f99cd7034e21
MD5 a3307eb2e0a0b09b4155a995d4447b12
BLAKE2b-256 9a1d2ea90d39fe01785edd949937f129c4341db3645282fa3f1970818fe3cf15

See more details on using hashes here.

File details

Details for the file ags-0.1-py3-none-any.whl.

File metadata

  • Download URL: ags-0.1-py3-none-any.whl
  • Upload date:
  • Size: 8.0 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: uv/0.7.20

File hashes

Hashes for ags-0.1-py3-none-any.whl
Algorithm Hash digest
SHA256 7c0cf104097ee96da36b88f41eb9626fe4ffca5702d34707f90979a91f279c35
MD5 19748e53012e6287fe9983e64abb2373
BLAKE2b-256 8fdc35721db5b59ed14a2f39d9f99e78e2185abe6ac29f187ada266276d78de5

See more details on using hashes here.

Supported by

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