kwutil.util_yaml module¶
Wrappers around pyyaml or ruamel.yaml.
The important functions to know are:
Loads and Dumps are strightforward. Loads takes a block of text and passes it through the ruamel.yaml or pyyaml to parse the string. Dumps takes a data structure and turns it into a YAML string. Roundtripping is supported with the ruamel.yaml backend.
Coerce will accept input as a non-string data structure, and simply return it, a path to a file, or a string which it assumes is YAML text (note: there is a small ambiguity introduced here). If coerce encounters a string that looks like an existing path it reads it. This does not happen by default in longer YAML text inputs, but the parser does respect a !include constructor, which does let you make nested configs by pointing to other configs.
- kwutil.util_yaml._custom_ruaml_loader()[source]¶
old method
References
https://stackoverflow.com/questions/59635900/ruamel-yaml-custom-commentedmapping-for-custom-tags https://stackoverflow.com/questions/528281/how-can-i-include-a-yaml-file-inside-another https://stackoverflow.com/questions/76870413/using-a-custom-loader-with-ruamel-yaml-0-15-0
- kwutil.util_yaml._custom_ruaml_dumper()[source]¶
References
https://stackoverflow.com/questions/59635900/ruamel-yaml-custom-commentedmapping-for-custom-tags
- kwutil.util_yaml._custom_new_ruaml_yaml_obj(version=None)[source]¶
new method
- Parameters:
version (None | Tuple[int, int]) – if specified, force a specific YAML version (e.g. (1, 1))
References
https://stackoverflow.com/questions/59635900/ruamel-yaml-custom-commentedmapping-for-custom-tags https://stackoverflow.com/questions/528281/how-can-i-include-a-yaml-file-inside-another https://stackoverflow.com/questions/76870413/using-a-custom-loader-with-ruamel-yaml-0-15-0
CommandLine
xdoctest -m kwutil.util_yaml _custom_new_ruaml_yaml_obj
Example
>>> # xdoctest: +REQUIRES(module:ruamel.yaml) >>> from kwutil.util_yaml import * # NOQA >>> from kwutil.util_yaml import _custom_ruaml_loader, _custom_new_ruaml_yaml_obj, _custom_ruaml_dumper, _custom_pyaml_dumper >>> from kwutil.util_yaml import _YamlRepresenter >>> # Test new load >>> import io >>> file = io.StringIO('[a, b, c]') >>> yaml_obj = _custom_new_ruaml_yaml_obj() >>> data = yaml_obj.load(file) >>> print(data) >>> # Test round trip tump >>> file = io.StringIO() >>> yaml_obj.dump(data, file) >>> print(file.getvalue()) >>> # >>> # Test new dump >>> data2 = ub.udict(a=1, b=2) >>> file = io.StringIO() >>> yaml_obj.dump(data2, file) >>> print(file.getvalue())
Example
>>> # xdoctest: +REQUIRES(module:ruamel.yaml) >>> from kwutil.util_yaml import * # NOQA >>> from kwutil.util_yaml import _custom_ruaml_loader, _custom_new_ruaml_yaml_obj, _custom_ruaml_dumper, _custom_pyaml_dumper >>> from kwutil.util_yaml import _YamlRepresenter >>> # Test handling of different YAML versions >>> yaml_obj = _custom_new_ruaml_yaml_obj(version='1.1') >>> print(yaml_obj.load(io.StringIO('on'))) True >>> file = io.StringIO() >>> yaml_obj.dump('on', file) >>> print(file.getvalue()) %YAML 1.1 --- 'on' >>> yaml_obj = _custom_new_ruaml_yaml_obj(version='1.2') >>> print(yaml_obj.load(io.StringIO('on'))) False >>> file = io.StringIO() >>> yaml_obj.dump('on', file) >>> print(file.getvalue()) %YAML 1.2 --- on
- class kwutil.util_yaml.Yaml[source]¶
Bases:
objectNamespace for yaml functions
Example
>>> # xdoctest: +REQUIRES(module:ruamel.yaml) >>> from kwutil.util_yaml import Yaml >>> import ubelt as ub >>> data = { >>> 'a': 'hello world', >>> 'b': ub.udict({'a': 3}) >>> } >>> text1 = Yaml.dumps(data, backend='ruamel') >>> # Coerce is idempotent and resolves the input to nested Python >>> # structures. >>> resolved1 = Yaml.coerce(data) >>> resolved2 = Yaml.coerce(text1) >>> resolved3 = Yaml.coerce(resolved2) >>> assert resolved1 == resolved2 == resolved3 == data >>> # with ruamel >>> data2 = Yaml.loads(text1) >>> assert data2 == data >>> # with pyyaml >>> data2 = Yaml.loads(text1, backend='pyyaml') >>> assert data2 == data
- static dumps(data, backend='ruamel', version=None)[source]¶
Dump yaml to a string representation (and account for some of our use-cases)
- Parameters:
data (Any) – yaml representable data
backend (str) – either ruamel or pyyaml
version (str) – version of YAML spec to use. (e.g. ‘1.1’)
- Returns:
yaml text
- Return type:
Example
>>> # xdoctest: +REQUIRES(module:pyyaml) >>> # xdoctest: +REQUIRES(module:ruamel.yaml) >>> import ubelt as ub >>> data = { >>> 'a': 'hello world', >>> 'b': ub.udict({'a': 3}) >>> } >>> text2 = Yaml.dumps(data, backend='pyyaml') >>> print(text2) >>> text1 = Yaml.dumps(data, backend='ruamel') >>> print(text1) >>> assert text1 == text2 >>> print(Yaml.dumps({'key': 'on'}, backend='ruamel', version='1.1')) %YAML 1.1 --- key: 'on'
- static load(file, backend='ruamel', version=None)[source]¶
Load yaml from a file
- Parameters:
file (io.TextIOBase | PathLike | str) – yaml file path or file object
backend (str) – either ruamel or pyyaml
- Returns:
object
Example
>>> # xdoctest: +REQUIRES(module:pyyaml) >>> # xdoctest: +REQUIRES(module:ruamel.yaml) >>> from kwutil.util_yaml import Yaml >>> import ubelt as ub >>> data = { >>> 'a': 'hello world', >>> 'b': ub.udict({'a': 3}) >>> } >>> text1 = Yaml.dumps(data, backend='ruamel') >>> import io >>> # with ruamel >>> file = io.StringIO(text1) >>> data2 = Yaml.load(file) >>> assert data2 == data >>> # with pyyaml >>> file = io.StringIO(text1) >>> data2 = Yaml.load(file, backend='pyyaml') >>> assert data2 == data
- static loads(text, backend='ruamel', version=None)[source]¶
Load yaml from a text
- Parameters:
text (str) – yaml text
backend (str) – either ruamel or pyyaml
- Returns:
object
Example
>>> # xdoctest: +REQUIRES(module:pyyaml) >>> # xdoctest: +REQUIRES(module:ruamel.yaml) >>> import ubelt as ub >>> data = { >>> 'a': 'hello world', >>> 'b': ub.udict({'a': 3}) >>> } >>> print('data = {}'.format(ub.urepr(data, nl=1))) >>> print('---') >>> text = Yaml.dumps(data) >>> print(ub.highlight_code(text, 'yaml')) >>> print('---') >>> data2 = Yaml.loads(text) >>> assert data == data2 >>> data3 = Yaml.loads(text, backend='pyyaml') >>> print('data2 = {}'.format(ub.urepr(data2, nl=1))) >>> print('data3 = {}'.format(ub.urepr(data3, nl=1))) >>> assert data == data3
- static coerce(data, backend='ruamel', path_policy='existing_file_with_extension')[source]¶
Attempt to convert input into a parsed yaml / json data structure. If the data looks like a path, it tries to load and parse file contents. If the data looks like a yaml/json string it tries to parse it. If the data looks like parsed data, then it returns it as-is.
- Parameters:
data (str | PathLike | dict | list)
backend (str) – either ruamel or pyyaml
path_policy (str) – Determines how we determine if something looks like a path. Pre 0.3.2 behavior is from path_policy=’existing_file’. Default is ‘existing_file_with_extension’. Can also be ‘never’ to disable the path feature and decrease ambiguity.
- Returns:
parsed yaml data
- Return type:
Note
The input to the function cannot distinguish a string that should be loaded and a string that should be parsed. If it looks like a file that exists it will read it. To avoid this coerner case use this only for data where you expect the output is a List or Dict.
References
https://stackoverflow.com/questions/528281/how-can-i-include-a-yaml-file-inside-another
Example
>>> # xdoctest: +REQUIRES(module:pyyaml) >>> # xdoctest: +REQUIRES(module:ruamel.yaml) >>> from kwutil.util_yaml import Yaml >>> text = ub.codeblock( ''' - !!float nan - !!float inf - nan - inf # Seems to break older ruamel.yaml 0.17.21 # - .nan # - .inf - null ''') >>> Yaml.coerce(text, backend='pyyaml') >>> Yaml.coerce(text, backend='ruamel')
Example
>>> # xdoctest: +REQUIRES(module:pyyaml) >>> # xdoctest: +REQUIRES(module:ruamel.yaml) >>> Yaml.coerce('"[1, 2, 3]"') [1, 2, 3] >>> fpath = ub.Path.appdir('cmd_queue/tests/util_yaml').ensuredir() / 'file.yaml' >>> fpath.write_text(Yaml.dumps([4, 5, 6])) >>> Yaml.coerce(fpath) [4, 5, 6] >>> Yaml.coerce(str(fpath)) [4, 5, 6] >>> dict(Yaml.coerce('{a: b, c: d}')) {'a': 'b', 'c': 'd'} >>> Yaml.coerce(None) None
Example
>>> # xdoctest: +REQUIRES(module:pyyaml) >>> # xdoctest: +REQUIRES(module:ruamel.yaml) >>> assert Yaml.coerce('') is None
Example
>>> # xdoctest: +REQUIRES(module:pyyaml) >>> # xdoctest: +REQUIRES(module:ruamel.yaml) >>> dpath = ub.Path.appdir('cmd_queue/tests/util_yaml').ensuredir() >>> fpath = dpath / 'external.yaml' >>> fpath.write_text(Yaml.dumps({'foo': 'bar'})) >>> text = ub.codeblock( >>> f''' >>> items: >>> - !include {dpath}/external.yaml >>> ''') >>> data = Yaml.coerce(text, backend='ruamel') >>> print(Yaml.dumps(data, backend='ruamel')) items: - foo: bar
>>> text = ub.codeblock( >>> f''' >>> items: >>> !include [{dpath}/external.yaml, blah, 1, 2, 3] >>> ''') >>> data = Yaml.coerce(text, backend='ruamel') >>> print('data = {}'.format(ub.urepr(data, nl=1))) >>> print(Yaml.dumps(data, backend='ruamel'))
- static Dict(data)[source]¶
Get a ruamel-enhanced dictionary
Example
>>> # xdoctest: +REQUIRES(module:pyyaml) >>> # xdoctest: +REQUIRES(module:ruamel.yaml) >>> data = {'a': 'avalue', 'b': 'bvalue'} >>> data = Yaml.Dict(data) >>> data.yaml_set_start_comment('hello') >>> # Note: not working https://sourceforge.net/p/ruamel-yaml/tickets/400/ >>> data.yaml_set_comment_before_after_key('a', before='a comment', indent=2) >>> data.yaml_set_comment_before_after_key('b', 'b comment') >>> print(Yaml.dumps(data))