Related is a Python library for creating nested object models
that can be serialized to and de-serialized from
nested python dictionaries.
When paired with other libraries (e.g. PyYAML),
Related object models can be used to convert to and from
nested data formats (e.g. JSON, YAML).
Example use cases for
related object models include:
pip install related
import related @related.immutable class Person(object): first_name = related.StringField() last_name = related.StringField() @related.immutable class RoleModels(object): scientists = related.SetField(Person) people = [Person(first_name="Grace", last_name="Hopper"), Person(first_name="Katherine", last_name="Johnson"), Person(first_name="Katherine", last_name="Johnson")] print(related.to_yaml(RoleModels(scientists=people)))
scientists: - first_name: Grace last_name: Hopper - first_name: Katherine last_name: Johnson
The below example is based off of this Docker Compose example. It shows how a YAML file can be loaded into an object model, tested, and then generated back into a string that matches the original YAML.
version: '2' services: web: build: . ports: - 5000:5000 volumes: - .:/code redis: image: redis
Below is the
related object model that represents the above configuration.
Notice how the name-based mapping of services (i.e. web, redis) are
represented by the model.
import related @related.immutable class Service(object): name = related.StringField() image = related.StringField(required=False) build = related.StringField(required=False) ports = related.SequenceField(str, required=False) volumes = related.SequenceField(str, required=False) command = related.StringField(required=False) @related.immutable class Compose(object): version = related.StringField(required=False, default=None) services = related.MappingField(Service, "name", required=False)
The above yaml can then be loaded by using one of the convenience
method and then round-tripped back to yaml to check that the format
has been maintained. The
related module uses
in order to maintain sort order by default.
from os.path import join, dirname from model import Compose from related import to_yaml, from_yaml, to_model YML_FILE = join(dirname(__file__), "docker-compose.yml") def test_compose_from_yml(): original_yaml = open(YML_FILE).read().strip() yml_dict = from_yaml(original_yaml) compose = to_model(Compose, yml_dict) assert compose.version == '2' assert compose.services['web'].ports == ["5000:5000"] assert compose.services['redis'].image == "redis" generated_yaml = to_yaml(compose, suppress_empty_values=True, suppress_map_key_values=True).strip() assert original_yaml == generated_yaml
More examples can be found by reviewing the tests/ folder of this project. Below are links and descriptions of the tests provided so far.
|Example 00||First example above that shows how SetFields work.|
|Example 01||Second example above that demonstrates YAML (de)serialization.|
|Example 02||Compose v3 with long-form ports and singledispatch to_dict|
|Example 03||A single class (Company) with a bunch of value fields.|
|Example 04||A multi-class object model with Enum class value field.|
|Example 05||Handling of renaming of attributes including Python keywords.|
|Example 06||Basic JSON (de)serialization with TimeField, DateTimeField and DecimalField.|
|Example 07||Function decorator that converts inputs to obj and outputs to dict|
|Example 08||Handle self-referencing and out-of-order references using strings.|
Below is a quick version of documentation until more time can be dedicated.
The attrs library is the underlying engine for
As explained in this article by Glyph,
attrs cleanly and cleverly
eliminates a lot of the boilerplate
required when creating Python classes
without using inheritance.
Some core functionality provided by attrs:
related project is an opinionated layer
built on top of the
that provides the following:
to_dictfunction that converts nested object graphs to python dictionaries. Made customizable (without resorting to monkey-patching) by the singledispatch library.
to_modelfunction that instantiated classes used by the de-serialization process going from python dictionaries to the related model.
from_json) for easily going between related models and data formats.
@immutablefor decorating classes as related models without the need for inheritance increasing maintainability and flexibility.
|@mutable||Activate a related class that instantiates changeable objects.|
|@immutable||Activate a related class that instantiates unchangeable objects.|
See the decorators.py file to view the source code until proper documentation is generated.
|ChildField||Child object of a specified type
|MappingField(cls,key)||Dictionary of objects of type
|SequenceField(cls)||List of objects all of specified type
|SetField||Set of objects all of a specified type
|UUIDField||UUID object, will create uuid4 by default if not specified.|
Adding your own field types is fairly straightforward
due to the power of the underlying
See the fields.py file to see how the above are constructed.
|from_json(s,cls)||Convert a JSON string or stream into specified class.|
|from_yaml(s,cls)||Convert a YAML string or stream into specified class.|
|is_related(obj)||Returns True if object is @mutable or @immutable.|
|to_dict(obj)||Singledispatch function for converting to a dict.|
|to_json(obj)||Convert object to a (pretty) JSON string via to_dict.|
|to_model(cls,value)||Convert a value to a
|to_yaml(obj)||Convert object to a YAML string via to_dict.|
See the functions.py file to view the source code until proper documentation is generated.
related project has been heavily influenced by the following
projects that might be worth looking at if
related doesn't meet your needs.