Skip to content

Node Class

Node Class

The Node class provides a borrowed view into a YamlDocument's AST, enabling tree traversal, query, and mutation operations. A node is created via doc.node(), doc.find("$.path"), or doc.walk().

Overview

class Node:
    """A node in the YAML AST, backed by a YamlDocument and a path."""

Each Node stores a reference to its parent YamlDocument and a path tuple that navigates to the target node within the document's AST. Nodes become stale when the document is modified or released.

Constructor

Node.__init__()

Node.__init__(document: YamlDocument, path: tuple = ()) -> None

Parameters:

  • document — The parent YamlDocument
  • path — A tuple of path segments (keys/indexes) navigating to the target node

Properties

value

Get the scalar value of this node.

value -> Any | None

Returns None for non-scalar nodes (mappings, sequences).

root_type

Get the type of this node.

root_type -> str

Returns one of "scalar", "mapping", "sequence", "null".

_path

The path tuple that navigates to this node within the document's AST.

_path -> tuple

path

Get this node's path segments (tuple of keys and indices). Unlike _path, this is the documented public accessor — useful for logging, error messages, and re-finding a node after it becomes stale.

path -> tuple

children

Get the child nodes of this node.

children -> list[Node]

Returns an empty list for scalar/null nodes.

parent

Get the parent Node, or None if this is the root.

parent -> Node | None

comment

Get this node's comment text, or None if it has no comment.

comment -> str | None

anchor

Get this node's anchor name, or None if it has no anchor.

anchor -> str | None

tag

Get this node's YAML tag string (e.g. !!str), or None if it has no tag.

tag -> str | None

scalar_style

Get the scalar style ("plain", "single_quoted", "double_quoted", "literal", "folded"), or None for non-scalar nodes.

scalar_style -> str | None

flow_style

Get the flow style (True = flow {}/[], False = block), or None for non-container nodes.

flow_style -> bool | None

chomping

Get the chomping indicator ("strip", "clip", "keep"), or None for non-scalar nodes.

chomping -> str | None

Methods

find()

Find a node by JSONPath-like path.

find(path: str) -> Node | list[Node]

Supported path syntax:

Pattern Description
$.key Root key
$.key.subkey Nested key
$.arr[0] Index into sequence
$.arr[*] All items in sequence
$..key Deep search for key at any depth
$..* All descendant nodes

Returns: A single Node for exact paths, or a list[Node] for wildcard/deep-scan queries.

find_first()

Find the first matching node by JSONPath-like path. Always returns a single Node or None (never a list) — for wildcard paths ([*]), returns the first element.

find_first(path: str) -> Node | None

walk()

Walk all descendant nodes (depth-first pre-order).

walk() -> Iterator[Node]

Yields: The node itself, then all descendants recursively.

filter()

Filter descendant nodes by a predicate function.

filter(predicate: Callable[[Node], bool]) -> list[Node]

Parameters:

  • predicate — A function taking a Node and returning bool

Example:

scalars = root.filter(lambda n: n.root_type == "scalar")

set_value()

Replace this node's value, preserving its metadata (comment, anchor, tag, style).

set_value(value: Any, create_missing: bool = False) -> None

With create_missing=True, missing intermediate mapping keys along the path are created as nested mappings. Index segments that miss are still an error.

append()

Append a value to a sequence node.

append(value: Any) -> None

insert()

Insert into a sequence node at an index.

insert(index: int, value: Any) -> None

delete()

Remove this node and its comments. The node becomes stale afterwards.

delete() -> None

rename()

Rename this node's mapping key. The node must be a mapping value.

rename(new_key: str) -> None

set_comment()

Set (or replace) this node's comment. With standalone=True (default) the comment is emitted on its own line above the node; with standalone=False it is emitted inline after the node.

set_comment(text: str, standalone: bool = True) -> None

remove_comment()

Remove this node's comment.

remove_comment() -> None

set_anchor()

Set (or replace) this node's anchor.

set_anchor(name: str) -> None

remove_anchor()

Remove this node's anchor.

remove_anchor() -> None

set_tag()

Set (or replace) this node's YAML tag. "!custom" produces a local tag, "!!int" produces a primary (!!) tag, and "!<tag:yaml.org,2002:str>" produces a verbatim tag.

set_tag(tag: str) -> None

remove_tag()

Remove this node's YAML tag.

remove_tag() -> None

set_scalar_style()

Set (or replace) this node's scalar style. No-op on non-scalar nodes. Recognized values: "plain", "single_quoted", "double_quoted", "literal", "folded".

set_scalar_style(style: str) -> None

set_flow_style()

Set (or replace) this node's flow style. True emits flow ({}/[]), False emits block. No-op on non-container nodes.

set_flow_style(flow: bool) -> None

set_chomping()

Set (or replace) this node's chomping indicator. Recognized values: "strip" (-), "clip" (default), "keep" (+). No-op on non-scalar nodes.

set_chomping(chomp: str) -> None

sort_keys()

Sort the keys of this mapping node in place. No-op on non-mapping nodes.

sort_keys() -> None

move()

Move this subtree to a new path in the same document (copies the subtree to new_path, then removes the source). new_path is an absolute JSONPath.

move(new_path: str) -> None

to_yaml()

Serialize this subtree to a YAML string.

to_yaml() -> str

copy()

Deep-copy this subtree as a standalone Python value (dict/list/scalar), detached from the document. Useful for duplicating a subtree to paste elsewhere via set_value().

copy() -> Any

is_valid()

Check if the parent document is still alive and unmodified.

is_valid() -> bool

release()

Release the reference to the parent document, marking this node as stale.

release() -> None

After calling release(), any access to this node will emit a RuntimeWarning and raise YamlDocumentError.

Dunder Methods

__repr__()

__repr__() -> str

Returns Node(root_type=<type>, path=<path>) for valid nodes, Node(released) for released nodes, or Node(invalid) for stale nodes.

__eq__()

__eq__(other: object) -> bool

Two Node instances are equal if they share the same document, path, and alive state.

value_eq()

Compare this node's resolved value with another node or Python value. Unlike __eq__ (reference identity), this compares the actual YAML value.

value_eq(other: object) -> bool

Stale Node Behavior

Stale nodes

A Node is tied to the document's revision at creation time. Any document edit bumps the revision, so previously obtained nodes become stale. Always re-find a node after editing the document.

A node becomes stale when:

  • The parent YamlDocument is garbage collected
  • release() is called explicitly
  • The document is modified after the node was created

Accessing a stale node emits a RuntimeWarning and raises YamlDocumentError:

Stale node example
>>> node = doc.node()
>>> doc.set("$.key", "new_value")
>>> node.value  # (1)!
RuntimeWarning: Node is stale: the document was modified after this node was created
YamlDocumentError: document has been modified; re-find the node  # (2)!
  1. Accessing a stale node triggers a RuntimeWarning first.
  2. Then raises YamlDocumentError. Re-acquire the node via doc.node().find(path).

Example

import pyrs_yaml

doc = pyrs_yaml.parse("""
a:
  b: 1
  c: [2, 3, 4]
d: hello
""")

# Get root node
root = doc.node()
print(root.root_type)  # "mapping"

# Navigate
node = root.find("$.a.c[1]")
print(node.value)  # 3

# Walk
for n in root.walk():
    print(n._path, n.root_type)

# Filter
numbers = root.filter(lambda n: n.root_type == "scalar" and isinstance(n.value, int))
for n in numbers:
    print(n._path, n.value)  # ('a', 'b') 1, ('a', 'c', 0) 2, ...

# Mutate
root.find("$.a.b").set_value(42)
root.find("$.a.c").append(5)
root.find("$.d").rename("greeting")
root.find("$.a.c[0]").delete()

print(doc.to_yaml())
# a:
#   b: 42
#   c: [3, 4, 5]
# greeting: hello