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 (
strwith 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.Dictandtyping.Listseem to be okay, buttyping.Union,typing.Optional, and abstract containerstyping.Sequence,typing.Mapping,typing.MutableSequenceandtyping.MutableMappingwill 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: ignoreor two to tell mypy to ignore the issue. The resulting Callable will have return typeAnyin 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,
Configis the top-level class, andSettingis another class that is used byConfigsomewhere.# 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,
Configis the top-level class, andSettingis another class that is used byConfigsomewhere. 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 todumps_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
indentkeyword 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=Falseto 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,
Configis the top-level class, andSettingis another class that is used byConfigsomewhere. 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 todumps_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,
Configis the top-level class, andSettingis another class that is used byConfigsomewhere. 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 todump_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
indentkeyword 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=Falseto 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,
Configis the top-level class, andSettingis another class that is used byConfigsomewhere. 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 todump_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
typis 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
Trueiff this represents a scalar node.If a type is given, checks that the node represents this type. Type may be
str,int,float,bool, orNone.If no type is given, then any of the above types will return
True.
- is_mapping() bool
Returns
Trueiff this represents a mapping node.
- is_sequence() bool
Returns
Trueiff 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 returnTrue.- Parameters:
value – The value to set this node to, a
str,int,float,bool, orNone.
- 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 ayaml.Noderepresenting it and useset_attribute()with that.
- has_attribute(attribute: str) bool
Whether the node has an attribute with the given name.
Use only if
is_mapping()returnsTrue.- 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:
Trueiff 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()returnsTrue.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,boolorNone, 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()returnsTrue.- 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. Forcls, pass theclsfirst 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
Nonein the type annotation. To fix that, you can define_yatiml_defaultslike 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,strand forNonevalues, not for classes or enums.Use only if
is_mapping()returnsTrue.- Parameters:
cls – The class we’re sweetening.
- rename_attribute(attribute: str, new_name: str) None
Renames an attribute.
Use only if
is_mapping()returnsTrue.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 isTrue, 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
Employeeand aCompanywhich 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')inCompany._yatiml_sweeten(), then the output will beemployees: Mary: role: Director Vishnu: role: Sales Susan: role: Engineering
If you call
node.index_attribute_to_map('employees', 'name', 'role')then it will turn intoemployees: 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_attributeis 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
Employeeand aCompanywhich 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')inCompany._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()oris_mapping()returnsTrue.
- seq_items() List[Node]
Returns the items in the sequence.
Use only if
is_sequence()returnsTrue.
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
strandUserString, 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.