Pyjo.YAML - Minimal YAML loader and dumper

from Pyjo.YAML import dump, dump_all, load, load_all

config = load("""\
foo: bar
list:
  - one
  - two
""")

documents = load_all('--- first\n--- second\n')

string = dump({'foo': 'bar', 'list': ['one', 'two']})

Pyjo.YAML loads and dumps the YAML that configuration files and workflows use, a subset of YAML 1.2, which Pyjoyment.Plugin.YAMLConfig reads, as Python has no YAML in its standard library:

  • mappings and sequences in the block style, nested by their indentation, and a mapping or a sequence that starts in an item of a sequence (- name: value, - - item);

  • flow sequences and mappings, [a, b] and {a: 1}, nested, and over several lines;

  • scalars, plain, in single quotes (with '' for a quote) and in double quotes (with the escapes \n, \t, \xHH, \uHHHH and the others of YAML), over several lines too, and the literal | and folded > block scalars, with their empty lines, and the indicators - and + that strip and keep the final line breaks;

  • comments, documents that --- starts and ... ends, a scalar after --- and the directive %YAML.

A plain scalar has the type of the core schema of YAML 1.2: null and ~ are None, true and false are booleans, and numbers, with 0o and 0x too, .inf and .nan, are int and float, as 404: is a key of int. The words and the forms of YAML 1.1, as yes, off, 0755 for an octal number and an implicit date, are not of the core schema, and stay strings or decimal numbers. A quoted scalar and a block scalar are strings.

# {'foo': [1, True, 'yes', '2']}
load("foo:\n  - 1\n  - true\n  - yes\n  - '2'\n")

A standard tag gives a scalar its type: !!str, !!int, !!float, !!bool and !!null of YAML 1.2, and !!binary, Base64 for bytes, and !!timestamp, ISO 8601 for datetime.date and datetime.datetime, of YAML 1.1.

# {'port': '8080', 'key': b'hello', 'day': datetime.date(2001, 12, 14)}
load('port: !!str 8080\nkey: !!binary aGVsbG8=\nday: !!timestamp 2001-12-14\n')

An anchor &name names a node, a value or a key, in a block or a flow collection, and an alias *name after it in the same document is the same object; an anchor defined again names the node of the aliases after it. The merge key << of YAML 1.1 puts the keys of the mapping that it names, or of a sequence of mappings, the earlier ones first, into the mapping, under the keys of the mapping itself.

# {'base': {'a': 1, 'b': 1}, 'copy': {'a': 1, 'b': 2}}
load('base: &base\n  a: 1\n  b: 1\ncopy:\n  <<: *base\n  b: 2\n')

Other tags, complex keys and the indicator of the indentation of a block scalar are not supported, and raise an error, as do lines that the subset does not know.

dump() writes what the loader reads, in the block style of configuration files: a dict is a mapping, a list and a tuple are a sequence, {} and [] when they are empty, and a string is plain where it reads back as the same string, quoted otherwise, and a literal block scalar when it has several lines. bytes and dates have the tags !!binary and !!timestamp, and another object is what Pyjo.JSON encodes, the value of its method __json__ or its string. A dict or a list that is there more than once has an anchor where it is first and an alias after it, which the loader reads back as the same object, and one inside itself raises an error, as the loader cannot read it back; a tuple has no identity of its own, as the compiler can share one that is a constant, and it is written again.

# 'name: Pyjoyment\nlist:\n  - one\n  - 2\nrun: |\n  make\n'
dump({'name': 'Pyjoyment', 'list': ['one', 2], 'run': 'make\n'})

See also

Pyjoyment.Plugin.YAMLConfig, https://mojolicious.org.

Inspired by CPAN::Meta::YAML of Adam Kennedy and David Golden, which Mojolicious uses, whose regular expressions of scalars and keys it borrows, with thanks.

Pyjo.YAML.dump(value: Any, *, indent: int = 2, indent_sequences: bool = True, quote: Literal["'", '"'] | None = None, quote_keys: Literal["'", '"'] | None = None, multiline: bool = True, explicit_start: bool = False, version: Literal['1.1', '1.2'] = '1.1', anchors: bool = True) → str[source]
string = dump(value)
string = dump(value, indent=4, indent_sequences=False, quote='"')

Dump a value to a YAML string of one document, with the options:

  • indent, the number of spaces of a level of the indentation, 2 by default;

  • indent_sequences, whether a sequence below a key is indented, True by default, or has its items at the column of the key;

  • quote, the quotes of every string value, "'" or '"', or None by default, for quotes only where a string needs them, single ones unless it needs the escapes of the double ones, which a string gets with any of them;

  • quote_keys, the same for the keys, which are quoted only where they need it by default;

  • multiline, whether a string of several lines is a literal block scalar, True by default, or a quoted scalar; a string whose lines a block scalar cannot keep, as one with a first line that starts with a space, is quoted anyway;

  • explicit_start, whether the document starts with ---, False by default; a scalar document always does;

  • version, the YAML whose reader gets the strings back: '1.1' by default quotes also the strings that YAML 1.1 reads as other types, as yes, off, 1_000, 12:30 and 2001-12-14, which PyYAML reads so, and '1.2' only those of the core schema of YAML 1.2, as true, null and 1.5;

  • anchors, whether a dict or a list that is there more than once has an anchor where it is first and an alias after it, True by default, or is written again; an anchor has the name of the key of the collection, or of the sequence of the item, with the characters that are not A-Z, a-z, 0-9, _ and - as _, id without a key, and a number from 2 for another anchor of the same name.

    # 'foo:\n- "bar"\n'
    dump({'foo': ['bar']}, indent_sequences=False, quote='"')
    
Pyjo.YAML.dump_all(values: Iterable[Any], *, indent: int = 2, indent_sequences: bool = True, quote: Literal["'", '"'] | None = None, quote_keys: Literal["'", '"'] | None = None, multiline: bool = True, explicit_start: bool = False, version: Literal['1.1', '1.2'] = '1.1', anchors: bool = True) → str[source]
string = dump_all(values)

Dump values to a YAML string of documents, one for each value, with the options of dump(), where the documents after the first one start with ---.

# '--- first\n--- second\n'
dump_all(['first', 'second'])
Pyjo.YAML.load(string: str) → Any[source]
value = load(string)

Load a YAML string and return its document, or its last one when there are several, as the Load of YAML of Perl does, or None for an empty string.

# {'foo': 'bar'}
load('foo: bar')
Pyjo.YAML.load_all(string: str) → list[Any][source]
values = load_all(string)

Load a YAML string and return a list of all its documents.

# ['first', 'second']
load_all('--- first\n--- second\n')