PythonizeYAML
Tutorial
API
GitHub
  • English
  • 简体中文
Tutorial
API
GitHub
  • English
  • 简体中文
  • Tutorial

    • Tutorial
    • Loading and dumping
    • Editing values
    • Command line
    • Comments, styles, tags, and anchors
    • Loading untrusted YAML
  • Worked examples

    • Edit a service configuration
    • Build a release metadata command

Editing values

This chapter is about changing data: reading nested values, assigning to them, creating new nodes, and reshaping sequences — all while the rest of the file stays byte-identical.

1. Assignment works like Python

A loaded document is a dict or list subclass, so ordinary assignment is already a round-trip edit:

import pythonizeyaml as yaml

document = yaml.load("service:\n  host: 127.0.0.1\n  port: 8080\n")
document["service"]["port"] = 9090

assert yaml.dump(document) == "service:\n  host: 127.0.0.1\n  port: 9090\n"

Only the 8080 scalar was rewritten; the key order, indentation, and any comments elsewhere are untouched.

2. Reading nested values

Nested reads are chained subscripts — a document returns plain Python values for scalars and round-trip containers for nested collections, so the same syntax keeps going deeper:

document["service"]["port"]             # 9090
document["artifacts"][0]                # first list item

Document.get() is the dict-style read with a fallback. A tuple or list is treated as a nested path:

document.get("service", {}).get("port")  # 9090
document.get(("service", "port"))        # 9090
document.get("missing", "fallback")      # "fallback"

Direct subscripts keep typos loud — a missing path raises PathError:

from pythonizeyaml import PathError

try:
    document["service"]["poort"]
except PathError as error:
    print(error)  # no YAML node at path ('service', 'poort')

3. Creating and replacing nodes

Assignment has plain-dict semantics: the parent must exist, and intermediate levels are created by assigning containers first:

document = yaml.load("service:\n  host: 127.0.0.1\n")
document["service"]["healthcheck"] = "GET /health"

Styles, comments, tags, and anchors live on the values themselves — see the next chapter. set() applies several fields in one validated step:

document["service"]["timeout"] = 30
document["service"]["timeout"].set(
    inline="# seconds before we give up",
)

set() validates everything before applying anything. An invalid combination raises StyleError and leaves the document exactly as it was.

4. Working with sequences

Sequence edits are ordinary list calls on the wrapped container, and the document keeps item styles and source metadata aligned with the data:

document = yaml.load("artifacts:\n  - pythonizeyaml\n")

document["artifacts"].append("pythonizeyaml-docs")
document["artifacts"].insert(0, "mirror")
document["artifacts"].pop(0)

Removal also works through del — on the document itself for mapping keys (del document["key"]) and on containers for indexes (del document["items"][0]). Removing a node discards its comments, styles, tags, and anchors together with it, and refuses to remove any node that still has aliases pointing at it.

5. Building documents from scratch

Document.new() creates a document from plain Python data — useful for generating config files that should still look hand-written:

document = yaml.Document.new(
    {
        "service": {"host": "127.0.0.1", "port": 8080},
        "artifacts": ["pythonizeyaml"],
    }
)
document["service"]["debug"] = False
document["service"]["debug"].inline = "# set true locally"

text = document.dump()

New data has no source layout to preserve, so the IndentConfig settings decide the indentation — two spaces by default, with the sequence dash flush against its parent key.

6. How emission stays lossless

dump() does not re-serialize the data. For an unchanged document it replays the original source text byte for byte. For edited documents it patches the changed spans in place:

  • Unchanged subtrees keep their exact source bytes, comments included.
  • Modified scalars are rewritten only within their own span.
  • Replaced or newly created subtrees are emitted canonically with the active IndentConfig.

The practical consequence: a tool that changes one value produces a diff with one changed line, which is exactly what human reviewers and git blame expect.

Last Updated: 10/3/26, 3:52 PM
Contributors: originalFactor
Prev
Loading and dumping
Next
Command line