anton

anton is a Python library for auto instantiating YAML or JSON definitions to user defined dataclasses.

Avoid boilerplate code of YAML or JSON loading and specific runtime type checking before the objects are created.

Note

Currently anton only supports Python3.8.

Support for Python3.8+ and missing python types will be coming in further versions of the project.

Usage

Consider a hypothetical YAML configuration file being used:

# saved in the file: index.yaml
"""
point1:
  x: 10
  y: 10
point2:
  x: 20
  y: 20
line_segment1:
  first_point:
    x: 0
    y: 0
  second_point:
    x: 10
    y: 10
line_segment2:
  first_point:
    x: 0
    y: 10
  second_point:
    x: 10
    y: 0

Assuming the dataclass definitions as:

from dataclasses import dataclass

@dataclass
class Point:
    x: int
    y: int

@dataclass
class LineSegment:
    first_point: Point
    second_point: Point

Before anton, to load the YAML configuration one had to ensure types in the YAML files are right and also ensure the exact keys are present in the YAML configuration.

# Define the dataclass
from dataclasses import dataclass

@dataclass
class CustomInput:
    point1: Point
    point2: Point
    line_segment1: LineSegment
    line_segment2: LineSegment


# Loading the YAML configuration to the dataclass:
import typing
import yaml

with open("index.yaml") as stream:
    try:
        conf_dict = yaml.safe_load(stream)
    except yaml.YAMLError as exc:
        raise exc

if not all(key in conf_dict.keys() for key in ["point1", "point2", "line_segment1", "line_segment2"]):
    raise ValueError("Input all the required keys.")

if not (isinstance(conf_dict["point1"], typing.Dict) and all(arg in conf_dict["point1"].keys() for arg in ["x", "y"])):
    raise ValueError("Input `point1` as a mapping with keys `x` and `y`.")

if not (isinstance(conf_dict["point2"], typing.Dict) and all(arg in conf_dict["point2"].keys() for arg in ["x", "y"])):
    raise ValueError("Input `point2` as a mapping with keys `x` and `y`.")

if not (
    isinstance(conf_dict["line_segment1"], typing.Dict)
    and all(arg in conf_dict["line_segment1"].keys() for arg in ["first_point", "second_point"])
):
    raise ValueError("Input `line_segment1` as a mapping with keys `first_point` and `second_point`.")

if not (
    isinstance(conf_dict["line_segment2"], typing.Dict)
    and all(arg in conf_dict["line_segment2"].keys() for arg in ["first_point", "second_point"])
):
    raise ValueError("Input `line_segment2` as a mapping with keys `first_point` and `second_point`.")

custom_input = CustomInput(
    point1=Point(**conf_dict["point1"]),
    point2=Point(**conf_dict["point2"]),
    line_segment1=LineSegment(
        first_point=conf_dict["line_segment1"]["first_point"],
        second_point=conf_dict["line_segment1"]["second_point"],
    ),
    line_segment2=LineSegment(
        first_point=conf_dict["line_segment2"]["first_point"],
        second_point=conf_dict["line_segment2"]["second_point"],
    ),
)

With anton, all the boilerplate can be avoided by using the decorators yaml_conf and json_conf that wraps the dataclasses.dataclass decorator to provide auto instantiation from YAML and JSON definitions respectively with runtime type checking of values. This helps avoid writing the biolerplate code for loading these definitions.

import anton

@anton.yaml_conf(conf_path="index.yaml")
class CustomInput:
    point1: Point
    point2: Point
    line_segment1: LineSegment
    line_segment2: LineSegment