---------------------------------------------------------------------- This is the API documentation for the yaml12 library. ---------------------------------------------------------------------- ## Classes Wrapper types for tagged nodes and unhashable mapping keys Yaml(value, tag=None) Tagged node or hashable wrapper for unhashable mapping keys. ## Functions Public functions for parsing and emitting YAML parse_yaml(text, multi=False, handlers=None) Parse YAML text into Python values. Args: text (str | Iterable[str]): YAML text, or an iterable yielding text chunks. Chunks are concatenated exactly as provided (no implicit separators are inserted). multi (bool): Return a list of documents when true; otherwise a single document or None for empty input. handlers (dict[str, Callable] | None): Optional tag handlers for values and keys; matching handlers receive the parsed value. Returns: object: Parsed value(s): YAML mappings become `dict`, sequences become `list`, and scalars resolve using the YAML 1.2 core schema to `bool`/`int`/`float`/`None`/`str`. Unhashable mapping keys (for example `list`/`dict`) are wrapped in the lightweight `Yaml` wrapper to keep them hashable. Tagged nodes without a matching handler are also wrapped in `Yaml` so the tag can be preserved. Raises: ValueError: On YAML parse errors or invalid tag strings. TypeError: When inputs are the wrong type or handlers are not callables. Exception: Propagated directly from user-provided handlers. Examples: >>> parse_yaml('foo: 1\nbar: true') {'foo': 1, 'bar': True} read_yaml(path, multi=False, handlers=None) Read a YAML file from `path` and parse it. Args: path (str | os.PathLike | object with .read): Filesystem path or readable object whose `.read()` returns str/bytes. multi (bool): Return a list of documents when true; otherwise a single document or None for empty input. handlers (dict[str, Callable] | None): Optional tag handlers for values and keys; matching handlers receive the parsed value. Returns: object: Parsed value(s): YAML mappings become `dict`, sequences become `list`, and scalars resolve using the YAML 1.2 core schema to `bool`/`int`/`float`/`None`/`str`. Unhashable mapping keys (for example `list`/`dict`) are wrapped in the lightweight `Yaml` wrapper to keep them hashable. Tagged nodes without a matching handler are also wrapped in `Yaml` so the tag can be preserved. Raises: IOError: When the file cannot be read. ValueError: On YAML parse errors or invalid tag strings. TypeError: When handlers are not callables or inputs are the wrong type. Exception: Propagated directly from user-provided handlers. Examples: >>> read_yaml('config.yml') {'debug': True} format_yaml(value, multi=False) Serialize a Python value to a YAML string. Args: value (object): Python value or Yaml to serialize; for `multi` the value must be a sequence of documents. multi (bool): Emit a multi-document stream when true; otherwise a single document. Returns: str: YAML text; multi-document streams end with `...`. Raises: TypeError: When `multi` is true and value is not a sequence, or unsupported types are provided. Examples: >>> format_yaml({'foo': 1}) 'foo: 1' >>> format_yaml(['first', 'second'], multi=True).endswith('...\n') True write_yaml(value, path=None, multi=False) Write a Python value to YAML at `path` or stdout. Args: value (object): Python value or Yaml to serialize; for `multi` the value must be a sequence of documents. path (str | os.PathLike | text file-like | None): Destination path or object with `.write(str)`; when None the YAML is written to stdout. multi (bool): Emit a multi-document stream when true; otherwise a single document. Returns: None Raises: IOError: When writing to the file or stdout fails. TypeError: When `multi` is true and value is not a sequence, or unsupported types are provided. Examples: >>> write_yaml({'foo': 1}, path='out.yml') >>> Path('out.yml').exists() True >>> write_yaml(['first', 'second'], multi=True) # prints YAML ending with '...' ---------------------------------------------------------------------- This is the User Guide documentation for the package. ---------------------------------------------------------------------- ## Getting Started ### User Guide Welcome to the **yaml12** User Guide. This guide covers practical usage of the `yaml12` package, a YAML 1.2 parser and formatter for Python built with Rust for speed and correctness. - [YAML in 2 Minutes](yaml-in-2-minutes.qmd) — a quick introduction to YAML for Python users - [Tags, Anchors, and Advanced YAML](tags-anchors-and-advanced-yaml.qmd) — custom tags, document streams, anchors, and more - [Authors and Citation](authors-and-citation.qmd) — attribution and citation info - [Contributing](contributing.qmd) — how to build the docs and contribute ### YAML in 2 Minutes ```{python} import textwrap from yaml12 import parse_yaml first_example_text = textwrap.dedent( """\ title: A Modern YAML parser written in Rust properties: [correct, safe, fast, simple] score: 9.5 categories: - yaml - python - example settings: note: > This is a folded block that turns line breaks into spaces. note_literal: | This is a literal block that keeps line breaks. """ ) ``` Here’s a short introduction to YAML for Python users. YAML is a data serialization format designed to be easy for humans to read and write. Think of YAML as “JSON with comments and nicer multiline strings.” `yaml12` parses YAML 1.2 (the modern specification that removes some of YAML 1.1’s surprising eager conversions) into plain Python objects. YAML has three building blocks: **scalars** (single values), **sequences** (ordered collections), and **mappings** (key/value pairs). JSON is a subset of YAML 1.2, so all valid JSON is also valid YAML and parses the same way. ## A first example ```yaml title: A Modern YAML parser written in Rust properties: [correct, safe, fast, simple] score: 9.5 categories: - yaml - python - example settings: note: > This is a folded block that turns line breaks into spaces. note_literal: | This is a literal block that keeps line breaks. ``` Let’s parse that with `yaml12`: ```{python} doc = parse_yaml(first_example_text) assert doc == { "title": "A Modern YAML parser written in Rust", "properties": ["correct", "safe", "fast", "simple"], "score": 9.5, "categories": ["yaml", "python", "example"], "settings": { "note": "This is a folded block that turns line breaks into spaces.\n", "note_literal": "This is a literal block\nthat keeps\nline breaks.\n", }, } ``` ## Comments Comments start with `#` and run to the end of the line. They must be separated from values by whitespace and can sit on their own line or at line ends. `yaml12` ignores them. ```yaml # Whole-line comment title: example # inline comment items: [a, b] # trailing comment ``` → `{"title": "example", "items": ["a", "b"]}` ## Collections There are two collection types: **sequences** and **mappings**. ### Sequences: YAML's ordered collections A sequence is a list of items. Each item begins with `-` at the parent indent. ```yaml - cat - dog ``` → `["cat", "dog"]` Sequences become `list`s in Python. JSON-style arrays work too: ```yaml [cat, dog] ``` → same result Anything belonging to one of the sequence entries is indented at least one space past the dash: ```yaml - name: cat toys: [string, box] - name: dog toys: [ball, bone] ``` parses to: ```{python} [ {"name": "cat", "toys": ["string", "box"]}, {"name": "dog", "toys": ["ball", "bone"]}, ] ``` ### Mappings: key/value pairs A mapping is a set of `key: value` pairs at the same indent: ```yaml foo: 1 bar: true ``` → `{"foo": 1, "bar": True}` Mappings become `dict`s in Python. A key at its indent owns anything indented more: ```yaml settings: debug: true max_items: 3 ``` parses to `{"settings": {"debug": True, "max_items": 3}}`. JSON-style objects work too: ```yaml {a: true} ``` → `{"a": True}` ## Scalars All nodes that are not collections are scalars; these are the leaf values of a YAML document. Scalars can come in three forms: block, quoted, or plain. ### Block scalars `|` starts a **literal** block that keeps newlines; `>` starts a **folded** block that joins lines with spaces (except blank/indented lines keep breaks). Block scalars always become strings. ```yaml | hello world ``` → `"hello\nworld\n"` ```yaml > hello world ``` → `"hello world\n"` ### Quoted scalars Quoted scalars always become strings. Double quotes interpret escapes (`\n`, `\t`, `\\`, `\"`). Single quotes are literal and do not interpret escapes, except for `''` which is parsed as a single `'`. ```yaml ["line\nbreak", "quote: \"here\""] ``` → `["line\nbreak", 'quote: "here"']` ```yaml ['line\nbreak', 'quote: ''here'''] ``` → `["line\\nbreak", "quote: 'here'"]` ### Plain (unquoted) scalars Plain (unquoted) nodes can resolve to one of five types: string, int, float, bool, or null. - `true` / `false` -> `True` / `False` - `null`, `~`, or empty -> `None` - numbers: signed, decimal, scientific, hex (`0x`), octal (`0o`), `.inf`, `.nan` -> `int` or `float` - everything else stays a string (`yes`, `no`, `on`, `off` and other aliases remain strings in YAML 1.2) ```yaml [true, 123, 4.5e2, 0x10, .inf, yes] ``` → `[True, 123, 450.0, 16, float("inf"), "yes"]` ## End-to-end example ```yaml doc: pets: - cat - dog numbers: [1, 2.5, 0x10, .inf, null] integers: [1, 2, 3, 0x10, null] flags: {enabled: true, label: on} literal: | hello world folded: > hello world quoted: - "line\nbreak" - 'quote: ''here''' plain: [yes, no] mixed: [won't simplify, 123, true] ``` Python result (`parse_yaml()` with defaults): ```{python} end_to_end_text = textwrap.dedent( """\ doc: pets: - cat - dog numbers: [1, 2.5, 0x10, .inf, null] integers: [1, 2, 3, 0x10, null] flags: {enabled: true, label: on} literal: | hello world folded: > hello world quoted: - "line\\nbreak" - 'quote: ''here''' plain: [yes, no] mixed: [won't simplify, 123, true] """ ) parsed = parse_yaml(end_to_end_text) assert parsed == { "doc": { "pets": ["cat", "dog"], "numbers": [1, 2.5, 16, float("inf"), None], "integers": [1, 2, 3, 16, None], "flags": {"enabled": True, "label": "on"}, "literal": "hello\nworld\n", "folded": "hello world\n", "quoted": ["line\nbreak", "quote: 'here'"], "plain": ["yes", "no"], "mixed": ["won't simplify", 123, True], } } ``` ## Quick notes - Indentation defines structure for collections. Sibling elements share an indent; children are indented more. YAML 1.2 forbids tabs; use spaces. - All JSON is valid YAML. - Sequences stay Python lists; there is no vector "simplification." - Block scalars (`|`, `>`) always produce strings. - Booleans are only `true`/`false`; `null` maps to `None`. - Numbers can be signed, scientific, hex (`0x`), octal (`0o`), `.inf`, and `.nan`. These essentials cover most YAML you’ll run into in practice. If you encounter tags, anchors, or non-string mapping keys, the advanced guide walks through those in detail. ## Guides ### Tags, Anchors, and Advanced YAML This guide picks up where the “YAML in 2 Minutes” intro leaves off. It explains what YAML tags are and how to work with them in `yaml12` using tag handlers. Along the way we also cover complex mapping keys, document streams, and anchors, so you can handle real-world YAML 1.2. ## Tags in YAML and how yaml12 handles them Tags annotate any YAML node with extra meaning. In YAML syntax a tag always starts with `!`, and it appears before the node’s value; it is not part of the scalar text itself. `yaml12` preserves tags by wrapping nodes in a `Yaml` object. A `Yaml` is a small frozen dataclass that carries the parsed `value` (a regular Python type) and the `tag` string. Here is an example of parsing a tagged scalar: ```{python} from yaml12 import Yaml, parse_yaml color = parse_yaml("!color red") assert isinstance(color, Yaml) and (color.tag, color.value) == ("!color", "red") ``` The presence of a custom tag bypasses the usual scalar typing: the scalar is returned as a string even if it looks like another type. ```{python} assert parse_yaml("! true") == Yaml(value="true", tag="!") assert parse_yaml("true") is True ``` ### Using handlers to transform tagged nodes while parsing `parse_yaml()` and `read_yaml()` accept `handlers`: a `dict` mapping tag strings to callables. Handlers run on any matching tagged node. For tagged scalars the handler receives a plain Python scalar; for tagged sequences or mappings, it receives a plain list or dict. Here is an example of using a handler to evaluate `!expr` nodes: ```{python} from yaml12 import parse_yaml handlers = {"!expr": lambda value: eval(str(value))} assert parse_yaml("!expr 1 + 1", handlers=handlers) == 2 ``` Any errors from a handler stop parsing and propagate unchanged: ```{python} from yaml12 import parse_yaml def boom(value): raise ValueError("boom") try: parse_yaml("!boom 1", handlers={"!boom": boom}) raise AssertionError("expected ValueError from handler") except ValueError as err: assert "boom" in str(err) ``` Any tag without a matching handler is left preserved as a `Yaml` object, and handlers without matching tags are simply unused: ```{python} from yaml12 import Yaml, parse_yaml yaml_text = """ - !expr 1 + 1 - !upper yaml is awesome - !note this tag has no handler """ handlers = { "!expr": lambda value: eval(str(value)), "!upper": str.upper, "!lower": str.lower, # unused } out = parse_yaml(yaml_text, handlers=handlers) assert out == [2, "YAML IS AWESOME", Yaml("this tag has no handler", "!note")] ``` With a tagged sequence or mapping, the handler is called with a plain list or dict: ```{python} from yaml12 import parse_yaml def seq_handler(value): assert value == ["a", "b"] return "handled-seq" def map_handler(value): assert value == {"key1": 1, "key2": 2} return "handled-map" yaml_text = """ - !some_seq_tag [a, b] - !some_map_tag {key1: 1, key2: 2} """ out = parse_yaml( yaml_text, handlers={ "!some_seq_tag": seq_handler, "!some_map_tag": map_handler, }, ) assert out == ["handled-seq", "handled-map"] ``` Handlers apply to both values and keys, including the non-specific `!` tag if you register `"!"`. If a handler raises an exception, parsing stops and you see the exception unchanged, which makes debugging easy. Any tag without a matching handler stays as a `Yaml` object, and any unused handlers are simply ignored. Handlers make it easy to opt into powerful behaviors while keeping the default parser strict and safe. ### Post-process tags yourself If you want more control, you can parse without handlers and then walk the result yourself. For example, here is a tiny post-processor for `!expr` scalars: ```{python} from yaml12 import Yaml, parse_yaml def eval_yaml_expr_nodes(obj): if isinstance(obj, Yaml): if obj.tag == "!expr": return eval(str(obj.value)) return Yaml(eval_yaml_expr_nodes(obj.value), obj.tag) if isinstance(obj, list): return [eval_yaml_expr_nodes(item) for item in obj] if isinstance(obj, dict): return {eval_yaml_expr_nodes(k): eval_yaml_expr_nodes(v) for k, v in obj.items()} return obj raw = parse_yaml("!expr 1 + 1") assert isinstance(raw, Yaml) and eval_yaml_expr_nodes(raw) == 2 ``` Because tags can also appear on mapping keys, postprocessors should walk dict keys as well (as in the example above). If you transform all tagged keys into plain hashable scalars, you can rebuild a dict with those new keys. ## Mappings revisited: non-string keys and `Yaml` In YAML, mapping keys do not have to be plain strings; any node can be a key, including booleans, numbers, sequences, or other mappings. For example, this is valid YAML even though the key is a boolean: ```yaml true: true ``` When a key can’t be represented directly as a plain Python dict key, `yaml12` wraps it in `Yaml`. That preserves the original key and makes it hashable (with equality defined by structure), so it can safely live in a Python `dict`: ```{python} parsed = parse_yaml("true: true") key = next(iter(parsed)) assert key is True and parsed[key] is True and not isinstance(key, Yaml) ``` For complex key values, YAML uses the explicit mapping-key indicator `?`: ```yaml ? [a, b] : tuple ? {x: 1, y: 2} : map-key ``` Becomes: ```{python} from yaml12 import Yaml, parse_yaml yaml_text = """ ? [a, b] : tuple ? {x: 1, y: 2} : map-key """ parsed = parse_yaml(yaml_text) seq_key, map_key = list(parsed) assert isinstance(seq_key, Yaml) and seq_key.value == ["a", "b"] assert isinstance(map_key, Yaml) and map_key.value == {"x": 1, "y": 2} assert parsed[Yaml(["a", "b"])] == "tuple" assert parsed[Yaml({"x": 1, "y": 2})] == "map-key" ``` ### Tagged mapping keys Handlers run on keys too, so a handler can turn tagged keys into friendly Python keys before they are wrapped. ```{python} handlers = {"!upper": str.upper} result = parse_yaml("!upper key: value", handlers=handlers) assert result == {"KEY": "value"} ``` If a tagged key has no matching handler, it is preserved as a `Yaml` key: ```{python} from yaml12 import Yaml, parse_yaml parsed = parse_yaml("!custom foo: 1") key = next(iter(parsed)) assert isinstance(key, Yaml) and key.tag == "!custom" and key.value == "foo" ``` If you anticipate tagged mapping keys that you want to process yourself, walk the `Yaml` keys alongside the values and unwrap them as needed. ## Document streams and markers Most YAML files contain a single YAML *document*. YAML also supports *document streams*: multiple documents separated by `---` and optionally closed by `...`. ### Reading multiple documents `parse_yaml()` and `read_yaml()` default to `multi=False`. In that mode, they stop after the first document. When `multi=True`, all documents in the stream are returned as a list. ```{python} doc_stream = """ --- doc 1 --- doc 2 """ parsed_first = parse_yaml(doc_stream) parsed_all = parse_yaml(doc_stream, multi=True) assert (parsed_first, parsed_all) == ("doc 1", ["doc 1", "doc 2"]) ``` ### Writing multiple documents `write_yaml()` and `format_yaml()` also default to a single document. With `multi=True`, the value must be a sequence of documents and the output uses `---` between documents and `...` after the final one. For single documents, `write_yaml()` always wraps the body with `---` and a final `...`, while `format_yaml()` returns just the body. ```{python} import tempfile from pathlib import Path from yaml12 import format_yaml, write_yaml docs = ["first", "second"] text = format_yaml(docs, multi=True) assert text.startswith("---") and text.rstrip().endswith("...") with tempfile.TemporaryDirectory() as tmpdir: out_path = Path(tmpdir) / "out.yml" write_yaml(docs, path=out_path, multi=True) assert out_path.read_text(encoding="utf-8") == text ``` When `multi=False`, parsing stops after the first document—even if later content is not valid YAML. That makes it easy to extract front matter from files that mix YAML with other text (like Markdown). ```{python} rmd_lines = [ "---\n", "title: Front matter only\n", "params:\n", " answer: 42\n", "---\n", "# Body that is not YAML\n", ] frontmatter = parse_yaml(rmd_lines) assert frontmatter == {"title": "Front matter only", "params": {"answer": 42}} ``` ## Writing YAML with tags To emit a tag, wrap a value in `Yaml` before calling `format_yaml()` or `write_yaml()`. ```{python} import io from contextlib import redirect_stdout from yaml12 import Yaml, write_yaml tagged = Yaml("1 + x", "!expr") buf = io.StringIO() with redirect_stdout(buf): write_yaml(tagged) out = buf.getvalue() assert out.startswith("---\n!expr 1 + x\n") assert out.rstrip().endswith("...") ``` Tagged collections or mapping keys work the same way: ```{python} from yaml12 import Yaml, format_yaml, parse_yaml mapping = { "tagged_value": Yaml(["a", "b"], "!pair"), Yaml("tagged-key", "!k"): "v", } encoded = format_yaml(mapping) reparsed = parse_yaml(encoded) value = reparsed["tagged_value"] assert isinstance(value, Yaml) and value.tag == "!pair" and value.value == ["a", "b"] key = next(k for k in reparsed if isinstance(k, Yaml)) assert key.tag == "!k" and key.value == "tagged-key" and reparsed[key] == "v" ``` ## Serializing custom Python objects You can opt into richer domain types by tagging your own objects on emit and supplying a handler on parse. Here is a round-trip for a dataclass: ```{python} from dataclasses import dataclass, asdict from yaml12 import Yaml, format_yaml, parse_yaml @dataclass class Server: name: str host: str port: int def encode_server(server: Server) -> Yaml: return Yaml(asdict(server), "!server") def decode_server(value): return Server(**value) servers = [Server("api", "api.example.com", 8000), Server("db", "db.local", 5432)] yaml_text = format_yaml([encode_server(s) for s in servers]) round_tripped = parse_yaml(yaml_text, handlers={"!server": decode_server}) assert round_tripped == servers ``` By keeping the on-disk representation a plain mapping plus tag, you get a stable YAML format while still round-tripping your Python types losslessly. ## Anchors Anchors (`&id`) name a node; aliases (`*id`) copy it. `yaml12` resolves aliases before returning Python objects. ```{python} from yaml12 import parse_yaml parsed = parse_yaml(""" recycle-me: &anchor-name a: b c: d recycled: - *anchor-name - *anchor-name """) first, second = parsed["recycled"] assert first["a"] == "b" and second["c"] == "d" ``` ## Debugging If you want to inspect how YAML nodes are parsed before conversion to Python types, use the internal helper `yaml12._dbg_yaml()` to pretty-print the raw `saphyr::Yaml` structures: ```{python} import yaml12 yaml12._dbg_yaml("!custom [1, 2]") ``` `_dbg_yaml` is intended for debugging and may change without notice. ## (Very) advanced tags The following YAML features are uncommon, but `yaml12` supports them for full YAML 1.2 compliance. ### Tag directives (`%TAG`) YAML lets you declare tag handles at the top of a document. The syntax is `%TAG !! ` and it applies to the rest of the document. ```{python} text = """ %TAG !e! tag:example.com,2024:widgets/ --- item: !e!gizmo foo """ parsed = parse_yaml(text) assert parsed["item"].tag == "tag:example.com,2024:widgets/gizmo" ``` You can also declare a global tag prefix, which expands a bare `!`: ```{python} text = """ %TAG ! tag:example.com,2024:widgets/ --- item: !gizmo foo """ assert parse_yaml(text)["item"].tag == "tag:example.com,2024:widgets/gizmo" ``` ### Tag URIs To bypass handle resolution, use `!<...>` with a valid URI-like string: ```{python} parsed = parse_yaml(""" %TAG ! tag:example.com,2024:widgets/ --- item: ! foo """) parsed["item"].tag # verbatim tag, not expanded ``` ### Core schema tags Tags beginning with `!!` resolve against the YAML core schema handle (`tag:yaml.org,2002:`). Scalar core tags (`!!str`, `!!int`, `!!float`, `!!bool`, `!!null`, `!!seq`, `!!map`) add no information when emitting and are normalized to plain Python values. Informative core tags (`!!timestamp`, `!!binary`, `!!set`, `!!omap`, `!!pairs`) stay tagged so you can decide how to handle them. YAML 1.2 removed these informative tags from the core schema, but they are still valid tags and occasionally useful. `yaml12` preserves them as `Yaml` unless you supply a handler to convert them to richer Python types. ```{python} from yaml12 import Yaml, parse_yaml yaml_text = """ - !!timestamp 2025-01-01 - !!timestamp 2025-01-01 21:59:43.10-05:00 - !!binary UiBpcyBBd2Vzb21l """ parsed = parse_yaml(yaml_text) assert all(isinstance(item, Yaml) for item in parsed) and ( parsed[0].tag, parsed[2].tag, ) == ("tag:yaml.org,2002:timestamp", "tag:yaml.org,2002:binary") ``` Handlers can convert these to richer Python types: ```{python} import base64 from datetime import datetime, timezone from yaml12 import parse_yaml def ts_handler(value): return datetime.fromisoformat(str(value).replace("Z", "+00:00")).replace(tzinfo=timezone.utc) def binary_handler(value): return base64.b64decode(str(value)) converted = parse_yaml( yaml_text, handlers={ "tag:yaml.org,2002:timestamp": ts_handler, "tag:yaml.org,2002:binary": binary_handler, }, ) assert isinstance(converted[0], datetime) and isinstance(converted[2], (bytes, bytearray)) ``` ## About ## Authors - **Tomasz Kalinowski** - Author, maintainer. - **Posit Software, PBC** - Copyright holder, funder. ## Citation Kalinowski T (2025). *yaml12: Fast YAML 1.2 Parser and Formatter for Python*. Python package version 0.1.0. ``` @software{kalinowski_yaml12_python_2025, title = {yaml12: Fast YAML 1.2 Parser and Formatter for Python}, author = {Tomasz Kalinowski}, year = {2025}, version = {0.1.0}, url = {https://github.com/posit-dev/py-yaml12} } ``` ## Build or serve the docs locally This project uses [Great Docs](https://github.com/rich-iannone/great-docs) for documentation. First install Great Docs: ```bash pip install git+https://github.com/rich-iannone/great-docs.git ``` Then from the project root: ```bash # Build docs into great-docs/_site/ great-docs build # Preview with live reload great-docs preview ``` ## Running tests From the repo root: ```bash cargo fmt && cargo check && cargo test && cargo build && cargo clippy .venv/bin/pip install -e . --no-build-isolation .venv/bin/python -m pytest tests_py ``` ## Getting Started ### User Guide Welcome to the **yaml12** User Guide. This guide covers practical usage of the `yaml12` package, a YAML 1.2 parser and formatter for Python built with Rust for speed and correctness. - [YAML in 2 Minutes](yaml-in-2-minutes.qmd) — a quick introduction to YAML for Python users - [Tags, Anchors, and Advanced YAML](tags-anchors-and-advanced-yaml.qmd) — custom tags, document streams, anchors, and more - [Authors and Citation](authors-and-citation.qmd) — attribution and citation info - [Contributing](contributing.qmd) — how to build the docs and contribute