/py-yaml-decoder

A yaml to dataclass decoder

Primary LanguagePython

YamlDecoder šŸ„½

This is a simple python package to decode yaml files into dataclasses with type check.

This works similarly to the yaml decoder package in Go.

Using This Module

Run this commmand in any python project you are working on (as long as it uses pipenv):

# for a specific commit version
pipenv install -e git+https://github.com/sportsball-ai/py-yaml-decoder.git@<commit-id>#egg=yamldecoder
# for the latest version
pipenv install -e git+https://github.com/sportsball-ai/py-yaml-decoder#egg=yamldecoder

Using The Decoder

A fully working example is available here example.py.

Getting Started

The yamldecoder is very easy to use. Here is a example snippet of how it works:

from pprint import pprint
from dataclasses import dataclass, field

from yamldecoder import YamlDecoder


@dataclass
class Config:
    a: int = field(metadata={"yaml": "A"})
    b: float = field(metadata={"yaml": "B"})
    c: str = field(metadata={"yaml": "C"})


yaml_stream = """
A: 1
B: 1.0
C: "test"
"""

cfg = YamlDecoder(yaml_stream).decode(Config)
pprint(cfg)

There are two main parts to this code:

The first part consists in creating a dataclass:

@dataclass
class Config:
    a: int = field(metadata={"yaml": "A"})
    b: float = field(metadata={"yaml": "B"})
    c: str = field(metadata={"yaml": "C"})

In the code snippet above we are telling the YamlDecoder that we want to decode 3 fields a, b and c, each with their own type. We are also telling the decoder what the field name key will be in the yaml stream (this is done by setting a field metadata: = field(metadata={"yaml": "A"})). For example, the fieldA: 1 will map to a: int in the dataclass.

The second part consists in calling the decoder:

cfg = YamlDecoder(yaml_stream).decode(Config)

This snippet should be pretty straight forward. We are telling the decoder to read the yaml string in yaml_stream and decode it using the "schema" define in the dataclass Config. This returns a dataclass of type Config.

If any value is missing in the yaml, the corresponding class field will be set to None.

Recursively Using Dataclasses

You can use a dataclass inside a dataclass to recursively decode values in a yaml stream.

For example:

from pprint import pprint
from dataclasses import dataclass, field

from yamldecoder import YamlDecoder


@dataclass
class ConfigB:
    a: int = field(metadata={"yaml": "A"})
    b: float = field(metadata={"yaml": "B"})
    c: str = field(metadata={"yaml": "C"})
    d: tuple = field(metadata={"yaml": "D"})
    e: list = field(metadata={"yaml": "E"})


@dataclass
class Config:
    config_b: ConfigB = field(metadata={"yaml": "ConfigB"})


yaml_stream = """
ConfigB:
  A: 1
  B: 1.0
  C: "test"
  D: [1,2,3,4,5]
  E: [1,2,3,4,5]
"""

cfg = YamlDecoder(yaml_stream).decode(Config)
pprint(cfg)

Reading From A File

You can decode a yaml file directly like this:

from pprint import pprint
from dataclasses import dataclass, field

from yamldecoder import YamlDecoder


@dataclass
class ConfigB:
    a: int = field(metadata={"yaml": "A"})
    b: float = field(metadata={"yaml": "B"})
    c: str = field(metadata={"yaml": "C"})
    d: tuple = field(metadata={"yaml": "D"})
    e: list = field(metadata={"yaml": "E"})


@dataclass
class Config:
    config_b: ConfigB = field(metadata={"yaml": "ConfigB"})


def load_config() -> Config:
    """This opens up the `config.yaml` file and decodes it into `Config`

    Returns:
        Config: the decoded yaml file
    """
    with open("config.yaml", "r", encoding="utf8") as stream:
        return YamlDecoder(stream).decode(Config)


if __name__ == "__main__":
    cfg = load_config()
    pprint(cfg)