Round-trip YAML 1.2 library with comment preservation — a modern ruamel.yaml replacement
Project description
yamlsmith
Round-trip YAML 1.2 library with comment preservation — a modern, drop-in replacement for ruamel.yaml.
Pure Python, zero dependencies, fully typed (PEP 561), Python 3.10+.
Why yamlsmith?
ruamel.yaml has 155M+ monthly downloads and is the only production-ready Python library for round-trip YAML with comment preservation. But it carries significant risk:
- Bus factor = 1. Single maintainer with no governance structure.
- PEP 625 crisis. The namespace package naming may prevent continued PyPI uploads.
- Hostile contribution model. SourceForge/Mercurial hosting makes community contribution nearly impossible.
- Failed fork. ruyaml (pycontribs) was created to address these risks and itself stalled.
yamlsmith solves this: same round-trip semantics, modern codebase, zero dependencies, YAML 1.2 strict mode (no legacy boolean quirks from 1.1).
Installation
pip install yamlsmith
Quick Start
Load and modify without losing comments
from yamlsmith import load, dump
text = """\
# Database configuration
host: localhost
port: 5432 # default PostgreSQL port
enabled: true
"""
data = load(text)
data["port"] = 5433 # modify a value
data["timeout"] = 30 # add a new key
print(dump(data))
Output — comments preserved, structure intact:
# Database configuration
host: localhost
port: 5433 # default PostgreSQL port
enabled: true
timeout: 30
Multi-document streams
from yamlsmith import load_all, dump_all
text = """\
# Document 1
name: alice
---
# Document 2
name: bob
"""
docs = load_all(text)
docs[0]["name"] = "ALICE"
print(dump_all(docs))
File I/O
from yamlsmith import YAML
yaml = YAML()
with open("config.yaml") as f:
data = yaml.load(f)
data["version"] = "2.0"
with open("config.yaml", "w") as f:
yaml.dump(data, f)
API Reference
Convenience functions
from yamlsmith import load, dump, load_all, dump_all
| Function | Signature | Description |
|---|---|---|
load |
(text: str | bytes | IO) → Any |
Load a single YAML document |
dump |
(data: Any, stream: IO | None = None) → str |
Dump to YAML string (optionally also write to stream) |
load_all |
(text: str | bytes | IO) → list[Any] |
Load all documents from a multi-document stream |
dump_all |
(data: list[Any], stream: IO | None = None) → str |
Dump a list of documents separated by --- |
All functions use round-trip mode: mappings and sequences are loaded as RoundTripDict / RoundTripList with comment metadata attached.
YAML class
from yamlsmith import YAML
yaml = YAML(indent=2, default_flow_style=False)
Constructor parameters:
| Parameter | Type | Default | Description |
|---|---|---|---|
indent |
int |
2 |
Indentation width for block mappings and sequences |
default_flow_style |
bool |
False |
Emit flow style ({a: 1}) instead of block style by default |
Methods:
| Method | Signature | Description |
|---|---|---|
load |
(stream: str | bytes | IO) → Any |
Load a single YAML document |
dump |
(data: Any, stream: IO | None = None) → str |
Dump to YAML string, optionally also write to stream |
load_all |
(stream: str | bytes | IO) → list[Any] |
Load all documents from a stream |
dump_all |
(data: list[Any], stream: IO | None = None) → str |
Dump multiple documents with --- separators |
Streams may be str, bytes, or any file-like object with a .read() / .write() method.
Round-trip types
When you load YAML, mappings and sequences are returned as round-trip types that carry comment metadata:
RoundTripDict
A dict subclass preserving insertion order and YAML comments.
from yamlsmith import RoundTripDict
d = RoundTripDict({"a": 1, "b": 2})
# Attach comments to a key
d.set_comment("a", pre="# section header", inline="# inline note")
# Read comments back
pre, inline = d.get_comment("a")
| Method | Signature | Description |
|---|---|---|
set_comment |
(key, *, pre=None, inline=None) |
Attach a pre-comment and/or inline comment to key |
get_comment |
(key) → tuple[str | None, str | None] |
Return (pre_comment, inline_comment) for key |
RoundTripList
A list subclass preserving YAML comments per item.
from yamlsmith import RoundTripList
lst = RoundTripList([1, 2, 3])
# Attach a comment to item at index 0
lst.set_item_comment(0, pre="# first item", inline="# note")
pre, inline = lst.get_item_comment(0)
| Method | Signature | Description |
|---|---|---|
set_item_comment |
(index, *, pre=None, inline=None) |
Attach a pre-comment and/or inline comment to item index |
get_item_comment |
(index) → tuple[str | None, str | None] |
Return (pre_comment, inline_comment) for item index |
RoundTripScalar
A wrapper for a scalar value that carries comment metadata and style information. Returned by the loader when a scalar has an inline comment or non-default quoting style.
from yamlsmith import RoundTripScalar
s = RoundTripScalar(42, inline_comment="# answer", style="plain")
print(s.value) # 42
print(s == 42) # True — compares by .value
| Attribute | Type | Description |
|---|---|---|
value |
Any |
The underlying Python value |
pre_comment |
str | None |
Comment line(s) before the scalar |
inline_comment |
str | None |
Comment after the value on the same line |
style |
str | None |
Scalar style: plain, single, double, literal, folded |
tag |
str | None |
Explicit YAML tag, or None for implicit resolution |
Error types
All errors inherit from YAMLError.
from yamlsmith import YAMLError, ScannerError, ParserError, ComposerError, ConstructorError, EmitterError
try:
data = load("key: :")
except YAMLError as e:
print(f"YAML error: {e}")
| Exception | Raised when |
|---|---|
YAMLError |
Base class for all yamlsmith errors |
ScannerError |
Invalid character or token in the input stream |
ParserError |
Structurally invalid YAML (bad nesting, missing values) |
ComposerError |
Undefined alias reference (*anchor without &anchor) |
ConstructorError |
Unknown YAML tag or type conversion failure |
EmitterError |
Serialization failure during emit |
Comment Preservation
yamlsmith attaches comments to the nearest YAML node:
| Comment type | Example | Stored on |
|---|---|---|
| Pre-comment | # header on its own line before a key |
the key's node |
| Inline comment | value # note after a value |
the value's node |
| Post-comment | trailing comment after a block | the block node |
| Document comment | comment before --- or after ... |
the document node |
Comments survive load → modify → dump cycles as long as the node they are attached to is not replaced with a plain Python object. If you replace a RoundTripDict with a plain dict, its comments are discarded.
YAML 1.2 Strict Mode
yamlsmith implements YAML 1.2 only. Legacy YAML 1.1 boolean strings are treated as plain strings:
| Expression | ruamel.yaml (1.1) | yamlsmith (1.2) |
|---|---|---|
yes |
True |
"yes" |
no |
False |
"no" |
on |
True |
"on" |
off |
False |
"off" |
true |
True |
True |
false |
False |
False |
Only true/True/TRUE and false/False/FALSE are recognised as booleans.
Migration from ruamel.yaml
Import changes
| ruamel.yaml | yamlsmith |
|---|---|
from ruamel.yaml import YAML |
from yamlsmith import YAML |
from ruamel.yaml.comments import CommentedMap |
from yamlsmith import RoundTripDict |
from ruamel.yaml.comments import CommentedSeq |
from yamlsmith import RoundTripList |
API compatibility
The core YAML class API is identical:
# ruamel.yaml
from ruamel.yaml import YAML
yaml = YAML()
data = yaml.load(stream)
yaml.dump(data, stream)
# yamlsmith — same calls
from yamlsmith import YAML
yaml = YAML()
data = yaml.load(stream)
yaml.dump(data, stream)
Type name changes
| ruamel.yaml | yamlsmith | Notes |
|---|---|---|
CommentedMap |
RoundTripDict |
Same dict semantics |
CommentedSeq |
RoundTripList |
Same list semantics |
CommentedSeq |
RoundTripList |
Same list semantics |
scalarstring.* |
RoundTripScalar(style=...) |
Unified scalar wrapper |
Behaviour differences
| Behaviour | ruamel.yaml | yamlsmith |
|---|---|---|
| YAML spec | 1.1 + 1.2 hybrid | 1.2 strict |
yes/no/on/off |
booleans | plain strings |
dump() return value |
None (writes to stream) |
always returns str |
| Initialisation | YAML(typ="rt") for round-trip |
round-trip is the only mode |
| Python object tags | !!python/object supported |
not supported (safe by default) |
dump() return value
ruamel.yaml's dump() writes to a stream and returns None. yamlsmith's dump() always returns the YAML string and optionally also writes to the stream if one is provided:
# ruamel.yaml
import io
buf = io.StringIO()
yaml.dump(data, buf)
text = buf.getvalue()
# yamlsmith — simpler
text = yaml.dump(data) # or:
text = yaml.dump(data, stream=f) # also writes to f
Features
- YAML 1.2 strict mode (no legacy 1.1 boolean quirks)
- Round-trip comment preservation (pre, inline, post, document-level)
- Block and flow style preservation
- Anchor and alias support
- Multi-document streams (
load_all/dump_all) - Literal (
|) and folded (>) block scalars - All standard YAML types:
str,int,float,bool,null,datetime,binary - Octal (
0o777) and hexadecimal (0xFF) integer literals inf,-inf,.nanfloat values- Full type annotations, mypy
--strictclean - PEP 561 typed package
- Zero dependencies
License
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file yamlsmith-0.1.1.tar.gz.
File metadata
- Download URL: yamlsmith-0.1.1.tar.gz
- Upload date:
- Size: 50.1 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: uv/0.10.10 {"installer":{"name":"uv","version":"0.10.10","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
991003d440db5dc2aec97babfe21eb4bff0ace3acef7bcd72bc0e89aeca3085e
|
|
| MD5 |
2cd63c3453a911ec739adb304a3d6a9a
|
|
| BLAKE2b-256 |
84a08978c43c1922b1ca2eaefdd450b451061a2528729e1e6445598d2729747d
|
File details
Details for the file yamlsmith-0.1.1-py3-none-any.whl.
File metadata
- Download URL: yamlsmith-0.1.1-py3-none-any.whl
- Upload date:
- Size: 23.0 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: uv/0.10.10 {"installer":{"name":"uv","version":"0.10.10","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
c1d250b9374e40dfa2977601ddc8f1ce77adf2ab130d5eb51f38c7118fadf33e
|
|
| MD5 |
1974983a78fc3d6a9bfc3d041fb4a099
|
|
| BLAKE2b-256 |
c5e66a014f2a55702360a1c29b22e51869361c3ea8bc2a495f165507d2de2f28
|