API Reference

Loading / Saving

yatiml.load_function() Callable[[str | Path | IO], Any]
yatiml.load_function(result: Type[T], *args: Type) Callable[[str | Path | IO], T]

Create a load function for the given type.

This function returns a callable object which takes an input (str with YAML input, pathlib.Path, or an open stream) and tries to load an object of the type given as the first argument. Any user-defined classes needed by the result must be passed as the remaining arguments.

Note that mypy will give an error if you try to pass some of the special type-like objects from typing. typing.Dict and typing.List seem to be okay, but typing.Union, typing.Optional, and abstract containers typing.Sequence, typing.Mapping, typing.MutableSequence and typing.MutableMapping will give an error. They are supported however, and work fine, there is just no way presently to explain to mypy that they are okay.

So, if you want to tell YAtiML that your YAML file may contain either a string or an int, you can use Union[str, int] for the first argument, but you’ll have to add a # type: ignore or two to tell mypy to ignore the issue. The resulting Callable will have return type Any in this case.

Examples

load_int_dict = yatiml.load_function(Dict[str, int])
my_dict = load_int_dict('x: 1')
load_config = yatiml.load_function(Config, Setting)
my_config = load_config(Path('config.yaml'))

# or

with open('config.yaml', 'r') as f:
    my_config = load_config(f)

Here, Config is the top-level class, and Setting is another class that is used by Config somewhere.

# Needs an ignore, on each line if split over two lines
load_int_or_str = yatiml.load_function(     # type: ignore
        Union[int, str])                    # type: ignore
Parameters:
  • result – The top level type, return type of the function.

  • *args – Any other (custom) types needed.

Returns:

A function that can load YAML input from a string, Path or stream and convert it to an object of the first type given.

yatiml.dumps_function(*args: Type) Callable[[Any], str]

Create a dumps function for the given types.

This function returns a callable object which takes an object and returns a string containing the YAML serialisation of that object. The type of the object, and any other custom types needed, must have been passed to dumps_function().

Note that only custom classes need to be passed, the built-in types are, well, built in.

Examples

dumps = yatiml.dumps_function()
yaml_text = dumps({'x': 1})
assert yaml_text == 'x: 1\n'
dumps_config = yatiml.dumps_function(Config, Setting)
yaml_text = dumps_config(my_config)

Here, Config is the top-level class, and Setting is another class that is used by Config somewhere. Note that any object can be passed to the resulting dumps function, as long as it is of a built-in type or its type and any types needed to represent it have been passed to dumps_function().

Parameters:

*args – Any custom types needed.

Returns:

A function that takes an object and produces a YAML string representing it.

yatiml.dumps_json_function(*args: Type) DumpsJsonFunctionType

Create a dumps function for the given types that writes JSON.

This function returns a callable object which takes an object and returns a string containing the JSON serialisation of that object. The type of the object, and any other custom types needed, must have been passed to dumps_json_function().

By default, the produced JSON is in ASCII and in a compact format without newlines or spaces. To make it more readable, you can pass an indent keyword argument to the returned function, which causes the output to be formatted with the given indent.

To output unicode characters as-is, pass ensure_ascii=False to the returned dumps function.

Note that only custom classes need to be passed, the built-in types are, well, built in.

Examples

dumps_json = yatiml.dumps_json_function()
json_text = dumps_json({'x': 1})
assert json_text == '{"x": 1}'

json_text = dumps_json({'x': 1}, indent=2)
assert json_text == (
    '{\n'
    '  "x": 1\n'
    '}\n')
dumps_config_to_json = yatiml.dumps_json_function(Config, Setting)
json_text = dumps_config_to_json(my_config)

Here, Config is the top-level class, and Setting is another class that is used by Config somewhere. Note that any object can be passed to the resulting dumps function, as long as it is of a built-in type or its type and any types needed to represent it have been passed to dumps_json_function().

Parameters:

*args – Any custom types needed.

Returns:

A function that takes an object and produces a YAML string representing it.

yatiml.dump_function(*args: Type) Callable[[Any, str | Path | IO], None]

Create a dump function for the given types.

This function returns a callable object which takes an object and a stream, Path or file name, and writes the YAML serialisation of that object to the target. The type of the object, and any other custom types needed, must have been passed to dump_function().

Note that only custom classes should be passed, the built-in types are, well, built in.

Examples

dump = yatiml.dump_function()
with open('test.yaml', 'w') as f:
    dump({'x': 1}, f)
# will write 'x: 1\n' to test.yaml
my_config = Config(...)
dump_config = yatiml.dump_function(Config, Setting)
dump_config(my_config, 'config.yaml')

Here, Config is the top-level class, and Setting is another class that is used by Config somewhere. Note that any object can be passed to the resulting dump function, as long as it is of a built-in type or its type and any types needed to represent it have been passed to dump_function().

Parameters:

*args – Any custom types needed.

Returns:

A function that takes an object and a stream, file name or Path, and writes a YAML string representing the object to the file.

yatiml.dump_json_function(*args: Type) DumpJsonFunctionType

Create a dump function for the given types that writes JSON.

This function returns a callable object which takes an object and a stream, Path or file name, and writes the JSON serialisation of that object to the target. The type of the object, and any other custom types needed, must have been passed to dump_json_function().

By default, the produced JSON is in ASCII and in a compact format without newlines or spaces. To make it more readable, you can pass an indent keyword argument to the returned function, which causes the output to be formatted with the given indent.

To output unicode characters as-is, pass ensure_ascii=False to the returned dumps function.

Note that only custom classes need to be passed, the built-in types are, well, built in.

Examples

dump_json = yatiml.dump_json_function()
with open('test.json', 'w') as f:
    dump_json({'x': 1}, f)
# will write '{"x": 1}' to test.json

dump_json({'x': 1}, 'test.json', indent=2)
# will write the same, but nicely formatted
my_config = Config(...)
dump_config_as_json = yatiml.dump_function(Config, Setting)
dump_config_as_json(my_config, 'config.json', indent=2)

Here, Config is the top-level class, and Setting is another class that is used by Config somewhere. Note that any object can be passed to the resulting dump function, as long as it is of a built-in type or its type and any types needed to represent it have been passed to dump_json_function().

Parameters:

*args – Any custom types needed.

Returns:

A function that takes an object and a stream, file name or Path, and writes a JSON string representing the object to the file.

Seasoning

class yatiml.UnknownNode(recognizer: IRecognizer, node: Node)

Utility functions for recognizing nodes.

This class defines a number of helper function for you to use when writing _yatiml_recognize() functions.

yaml_node

The yaml.Node wrapped by this object.

require_scalar(*args: Type) None

Require the node to be a scalar.

If additional arguments are passed, these are taken as a list of valid types; if the node matches one of these, then it is accepted.

Example

# Match either an int or a string
node.require_scalar(int, str)
Parameters:

args – One or more types to match one of.

require_mapping() None

Require the node to be a mapping.

require_sequence() None

Require the node to be a sequence.

require_attribute(attribute: str, typ: None | Type = yatiml.helpers._Any) None

Require an attribute on the node to exist.

This implies that the node must be a mapping.

If typ is given, the attribute must have this type.

Parameters:
  • attribute – The name of the attribute / mapping key.

  • typ – The type the attribute must have.

require_attribute_value(attribute: str, value: int | str | float | bool | None) None

Require an attribute on the node to have a particular value.

This requires the attribute to exist, and to have the given value and corresponding type.

Parameters:
  • attribute – The name of the attribute / mapping key.

  • value – The value the attribute must have to recognize an object of this type.

Raises:

yatiml.RecognitionError – If the attribute does not exist, or does not have the required value.

require_attribute_value_not(attribute: str, value: int | str | float | bool | None) None

Require an attribute on the node to not have a given value.

This requires the attribute to exist, and to not have the given value.

Parameters:
  • attribute – The name of the attribute / mapping key.

  • value – The value the attribute must not have to recognize an object of this type.

Raises:

yatiml.RecognitionError – If the attribute does not exist, or has the required value.

class yatiml.Node(node: Node)

A wrapper class for yaml Nodes that provides utility functions.

This class defines a number of helper function for you to use when writing _yatiml_sweeten() and _yatiml_savorize() functions. It also gives access to the underlying yaml.Node, so you can do anything PyYAML can do if you’re willing to dive into its internals.

yaml_node

The wrapped yaml Node. You can read and modify this, yourself if you want, or replace it completely with a new yaml.Node.

is_scalar(typ: Type | None = yatiml.helpers._Any) bool

Returns True iff this represents a scalar node.

If a type is given, checks that the node represents this type. Type may be str, int, float, bool, or None.

If no type is given, then any of the above types will return True.

is_mapping() bool

Returns True iff this represents a mapping node.

is_sequence() bool

Returns True iff this represents a sequence node.

get_value() str | int | float | bool | None

Returns the value of a scalar node.

Use is_scalar() to check which type the node has.

set_value(value: str | int | float | bool | None) None

Sets the value of the node to a scalar value.

After this, is_scalar(type(value)) will return True.

Parameters:

value – The value to set this node to, a str, int, float, bool, or None.

make_mapping() None

Replaces the node with a new, empty mapping.

Note that this will work on the Node object that is passed to a _yatiml_savorize() or _yatiml_sweeten() function, but not on any of its attributes or items. If you need to set an attribute to a complex value, build a yaml.Node representing it and use set_attribute() with that.

has_attribute(attribute: str) bool

Whether the node has an attribute with the given name.

Use only if is_mapping() returns True.

Parameters:

attribute – The name of the attribute to check for.

Returns:

True iff the attribute is present.

has_attribute_type(attribute: str, typ: Type | None) bool

Whether the given attribute exists and has a compatible type.

Returns true iff the attribute exists and is an instance of the given type. Matching between types passed as typ and yaml node types is as follows:

typ

yaml

str

ScalarNode containing string

int

ScalarNode containing int

float

ScalarNode containing float

bool

ScalarNode containing bool

None

ScalarNode containing null

list

SequenceNode

dict

MappingNode

Parameters:
  • attribute – The name of the attribute to check.

  • typ – The type to check against.

Returns:

True iff the attribute exists and matches the type.

get_attribute(attribute: str) Node

Returns the node representing the given attribute’s value.

Use only if is_mapping() returns true.

Parameters:

attribute – The name of the attribute to retrieve.

Raises:

KeyError – If the attribute does not exist.

Returns:

A node representing the value.

set_attribute(attribute: str, value: str | int | float | bool | None | Node) None

Sets the attribute to the given value.

Use only if is_mapping() returns True.

If the attribute does not exist, this adds a new attribute, if it does, it will be overwritten.

If value is a str, int, float, bool or None, the attribute will be set to this value. If you want to set the value to a list or dict containing other values, build a yaml.Node and pass it here.

Parameters:
  • attribute – Name of the attribute whose value to change.

  • value – The value to set.

remove_attribute(attribute: str) None

Remove an attribute from the node.

Use only if is_mapping() returns True.

Parameters:

attribute – The name of the attribute to remove.

remove_attributes_with_default_values(cls: Type) None

Remove attributes with default values.

If you have a class with many optional attributes, then saving it to YAML may yield a very large dictionary with many values set to e.g. None. If there’s no risk of creating an ambiguity, then you may want to remove any attributes whose value matches the default.

This function can be used in _yatiml_sweeten() to do that. For cls, pass the cls first argument of _yatiml_sweeten().

If the default for the parameter is not the same as the default of the attribute, for example in this common situation:

def __init__(
        self, my_list: Optional[List[int]] = None) -> None:
    if my_list is None:
        my_list = list()
    self.my_list = my_list

then this function will not remove the attribute if it is an empty list, because it compares with None in the type annotation. To fix that, you can define _yatiml_defaults like this:

def __init__(
        self, my_list: Optional[List[int]] = None) -> None:
    if my_list is None:
        my_list = list()
    self.my_list = my_list

_yatiml_defaults = {'my_list': []}  # type: Dict[str, Any]

@classmethod
def _yatiml_sweeten(cls, node: yatiml.Node) -> None:
    node.remove_attributes_with_default_values(cls)

Note that this function currently only works for the built-in types bool, float, int, str and for None values, not for classes or enums.

Use only if is_mapping() returns True.

Parameters:

cls – The class we’re sweetening.

rename_attribute(attribute: str, new_name: str) None

Renames an attribute.

Use only if is_mapping() returns True.

If the attribute does not exist, this will do nothing.

Parameters:
  • attribute – The (old) name of the attribute to rename.

  • new_name – The new name to rename it to.

unders_to_dashes_in_keys() None

Replaces underscores with dashes in key names.

For each attribute in a mapping, this replaces any underscores in its keys with dashes. Handy because Python does not accept dashes in identifiers, while some YAML-based formats use dashes in their keys.

dashes_to_unders_in_keys() None

Replaces dashes with underscores in key names.

For each attribute in a mapping, this replaces any dashes in its keys with underscores. Handy because Python does not accept dashes in identifiers, while some YAML-based file formats use dashes in their keys.

seq_attribute_to_map(attribute: str, key_attribute: str, value_attribute: str | None = None, strict: bool | None = True) None

Converts a sequence attribute to a map.

This function takes an attribute of this Node that is a sequence of mappings and turns it into a mapping of mappings. It assumes that each of the mappings in the original sequence has an attribute containing a unique value, which it will use as a key for the new outer mapping.

An example probably helps. If you have a Node representing this piece of YAML:

items:
- item_id: item1
  description: Basic widget
  price: 100.0
- item_id: item2
  description: Premium quality widget
  price: 200.0

and call seq_attribute_to_map('items', 'item_id'), then the Node will be modified to represent this:

items:
  item1:
    description: Basic widget
    price: 100.0
  item2:
    description: Premium quality widget
    price: 200.0

which is often more intuitive for people to read and write.

If the attribute does not exist, or is not a sequence of mappings, this function will silently do nothing. If the keys are not unique and strict is False, it will also do nothing. If the keys are not unique and strict is True, it will raise an error.

With thanks to the makers of the Common Workflow Language for the idea.

Parameters:
  • attribute – Name of the attribute whose value to modify.

  • key_attribute – Name of the attribute in each item to use as a key for the new mapping.

  • value_attribute – Name of the attribute in each item to use for the value in the new mapping, if only a key and value have been given.

  • strict – Whether to give an error if the intended keys are not unique.

Raises:

yatiml.SeasoningError – If the keys are not unique and strict is True.

map_attribute_to_seq(attribute: str, key_attribute: str, value_attribute: str | None = None) None

Converts a mapping attribute to a sequence.

This function takes an attribute of this Node whose value is a mapping or a mapping of mappings and turns it into a sequence of mappings. Each entry in the original mapping is converted to an entry in the list. If only a key attribute is given, then each entry in the original mapping must map to a (sub)mapping. This submapping becomes the corresponding list entry, with the key added to it as an additional attribute. If a value attribute is also given, then an entry in the original mapping may map to any object. If the mapped-to object is a mapping, the conversion is as before, otherwise a new submapping is created, and key and value are added using the given key and value attribute names.

An example probably helps. If you have a Node representing this piece of YAML:

items:
  item1:
    description: Basic widget
    price: 100.0
  item2:
    description: Premium quality widget
    price: 200.0

and call map_attribute_to_seq('items', 'item_id'), then the Node will be modified to represent this:

items:
- item_id: item1
  description: Basic widget
  price: 100.0
- item_id: item2
  description: Premium quality widget
  price: 200.0

which once converted to an object is often easier to deal with in code.

Slightly more complicated, this YAML:

items:
  item1: Basic widget
  item2:
    description: Premium quality widget
    price: 200.0

when passed through

map_attribute_to_seq('items', 'item_id', 'description')

will result in the equivalent of:

items:
- item_id: item1
  description: Basic widget
- item_id: item2
  description: Premium quality widget
  price: 200.0

If the attribute does not exist, or is not a mapping, this function will silently do nothing.

With thanks to the makers of the Common Workflow Language for the idea.

Parameters:
  • attribute – Name of the attribute whose value to modify.

  • key_attribute – Name of the new attribute in each item to add with the value of the key.

  • value_attribute – Name of the new attribute in each item to add with the value of the key.

index_attribute_to_map(attribute: str, key_attribute: str, value_attribute: str | None = None) None

Converts an index attribute to a map.

It is often convenient to represent a collection of objects by a dict mapping a name of each object to that object (let’s call that an index). If each object knows its own name, then the name is stored twice, which is not nice to have to type and keep synchronised in YAML.

In YAML, such an index is a mapping of mappings, where the key of the outer mapping matches the value of one of the items in the corresponding inner mapping. This function removes the redundant key/value from the inner mapping. If that leaves only a single key in the inner mapping, and it matches value_attribute, then the value corresponding to that key becomes the value for the item in the outer mapping.

An example probably helps. Let’s say we have a class Employee and a Company which has employees:

class Employee:
    def __init__(self, name: str, role: str) -> None:
        ...

class Company:
    def __init__(
            self, employees: Dict[str, Employee]
            ) -> None:
        ...

my_company = Company({
    'Mary': Employee('Mary', 'Director'),
    'Vishnu': Employee('Vishnu', 'Sales'),
    'Susan': Employee('Susan', 'Engineering')})

By default, this will turn into the following YAML when saved:

employees:
  Mary:
    name: Mary
    role: Director
  Vishnu:
    name: Vishnu
    role: Sales
  Susan:
    name: Susan
    role: Engineering

If you call node.index_attribute_to_map('employees', 'name') in Company._yatiml_sweeten(), then the output will be

employees:
  Mary:
    role: Director
  Vishnu:
    role: Sales
  Susan:
    role: Engineering

If you call node.index_attribute_to_map('employees', 'name', 'role') then it will turn into

employees:
  Mary: Director
  Vishnu: Sales
  Susan: Engineering

If the attribute does not exist, or is not a mapping of mappings, this function will silently do nothing.

See map_attribute_to_index() for the reverse.

With thanks to the makers of the Common Workflow Language for the idea.

Parameters:
  • attribute – Name of the attribute whose value to modify.

  • key_attribute – Name of the attribute in each item to remove.

  • value_attribute – Name of the attribute in each item to use for the value in the new mapping, if only a key and value have been given.

map_attribute_to_index(attribute: str, key_attribute: str, value_attribute: str | None = None) None

Converts a mapping attribute to an index .

It is often convenient to represent a collection of objects by a dict mapping a name of each object to that object (let’s call that an index). If each object knows its own name, then the name is stored twice, which is not nice to have to type and keep synchronised in YAML.

In YAML, such an index is a mapping of mappings, where the key of the outer mapping matches the value of one of the items in the corresponding inner mapping.

This function enables a short-hand notation for the above, where the name of the object is mentioned only in the key of the mapping and not again in the values, and, if value_attribute is specified, converting any entries with a scalar value (rather than a mapping) to a mapping with ‘’value_attribute’’ as the key and the original value as the value.

An example probably helps. Let’s say we have a class Employee and a Company which has employees:

class Employee:
    def __init__(
            self, name: str, role: str, hours: int = 40
            ) -> None:
        ...

class Company:
    def __init__(
            self, employees: Dict[str, Employee]
            ) -> None:
        ...

my_company = Company({
    'Mary': Employee('Mary', 'Director'),
    'Vishnu': Employee('Vishnu', 'Sales'),
    'Susan': Employee('Susan', 'Engineering')})

By default, to load this from YAML, you have to write:

employees:
  Mary:
    name: Mary
    role: Director
  Vishnu:
    name: Vishnu
    role: Sales
  Susan:
    name: Susan
    role: Engineering

If you call node.map_attribute_to_index('employees', 'name') in Company._yatiml_savorize(), then the following will also work:

employees:
  Mary:
    role: Director
  Vishnu:
    role: Sales
  Susan:
    role: Engineering

And if you call node.map_attribute_to_index('employees', 'name', 'role') then you can also write:

employees:
  Mary: Director
  Vishnu: Sales
  Susan: Engineering

If the attribute does not exist, or is not a mapping of mappings, this function will silently do nothing.

See index_attribute_to_map() for the reverse.

With thanks to the makers of the Common Workflow Language for the idea.

Parameters:
  • attribute – Name of the attribute whose value to modify.

  • key_attribute – Name of the attribute in each item to add, with the value of the key.

  • value_attribute – Name of the attribute in each item to use for the value in the new mapping, if only a key and value have been given.

is_empty() bool

Returns whether a sequence or mapping is empty.

Use only if is_sequence() or is_mapping() returns True.

seq_items() List[Node]

Returns the items in the sequence.

Use only if is_sequence() returns True.

Errors

class yatiml.RecognitionError
class yatiml.SeasoningError

Miscellaneous

class yatiml.bool_union_fix
yatiml.logger

The YAtiML root logger. Use this to set YAtiML’s log level.

In particular, if something goes wrong with loading or dumping from or to YAML, and you want more debug output from YAtiML, use:

import logging

yatiml.logger.setLevel(logging.INFO)

or for even more:

yatiml.logger.setLevel(logging.DEBUG)
class yatiml.String

Tag for classes that should be serialised to YAML as a string.

If your class has this class as a base class, then YAtiML will:

  • expect a string on the YAML side when recognising,

  • call __init__ with that string as the sole argument when loading,

  • use str(obj) to obtain the string representation on dumping.

You can still override what’s written to the YAML file by defining _yatiml_sweeten() in the usual way.

Like classes derived from str and UserString, classes tagged like this can be used as keys for dictionaries. Be sure to implement __hash__() and __eq__() to make that work on the Python side.

Deprecated