Releases: 759401524/pyrs-yaml
Releases · 759401524/pyrs-yaml
Release list
v0.14.1
[v0.14.1] — 2026-08-15
Fixed
- Single-quoted scalars with backslash + control/noncharacter — a quoted
value containing a backslash was routed to single-quoting, but single quotes
cannot escape control characters or Unicode noncharacters, so the emitted
YAML was unparseable. Such values now use double-quoting (direct_dumpand
the sharedwrite_plain_scalarsingle-quote branch). - Noncharacters and BOM quoted —
needs_quotes/needs_double_quoted
now treat Unicode noncharacters (U+FFFE/U+FFFF and the plane-end twins) and
U+FEFF (BOM) as requiring quoting: granit drops a plain U+FEFF as a
document-start BOM and rejects raw noncharacters even inside quoted scalars. - Double-quoted escape width —
write_double_quoted_scalarescapes
noncharacters and control chars; for code points above U+FFFF it now emits
the 8-digit\Uxxxxxxxxform (the 4-digit\uform is only valid for the
BMP). - Folded plain-scalar continuation indent —
wrap_plain_scalar
continuation indent is no longer a fixed 2 spaces; it is derived from the
value's start column on the current line, so folded plain scalars inside
nested sequence/mapping items stay indented past the parent block indent
(granit otherwise reports "simple key expected ':'"). - Multi-byte wrap boundary —
wrap_plain_scalarnow floors the wrap slice
to a char boundary instead of panicking when a 4-byte UTF-8 character
straddles the wrap column. hypothesisin publish test requirements —.ci/requirements-test.txt
now pinshypothesis>=6.113.0so the publish workflow (which does not
install thedevdependency group) can run the property test suite.
Added
scripts/fuzz_panics.py— high-volume local Hypothesis fuzz harness that
bypasses pytest@settingscaps with a hostile strategy (control chars,
NBSP, backslashes, long multibyte runs) across dump/parse/edit/idempotency.
v0.14.0 — Core Engine Rebuild
[v0.14.0] — 2026-08-14
Added
- YAML Schema Language — define custom schemas as YAML files with a
ruleslist mapping regex patterns to YAML types (null/bool/int/
float/str), plus an optionalextendsbase schema. Registered via
register_schema(name, schema_yaml)and used asYAML(schema=name). - Inline dict schema — the
schemaparameter ofYAML(),parse(),
parse_file(),parse_all_docs(),safe_load(), andsafe_loads()
accepts an inline dict, serialized and registered automatically. - Community Plugins —
CustomTypebase class with
can_parse/from_yaml/to_yaml/validatemethods; register via
register_type()(imperative or decorator). Custom types handle tagged
scalars on load and Python objects on dump. - Built-in plugins —
!timestamp(maps todatetime) and!set
registered by default inpyrs_yaml/plugins/.
Changed
- Schema resolution is pluggable —
YamlSchemaenum refactored into a
SchemaResolvertrait +Schemaenum with a globalSchemaRegistry
pre-loaded with the four built-in schemas (failsafe,json,core,
yaml1.1). Custom schemas register via the registry; built-in Core keeps
its zero-costmatchdispatch. node_to_pyobjectanddirect_dumpcheck registeredCustomTypes —
tagged scalars convert viafrom_yaml()on load; matching Python objects
serialize viato_yaml()on dump.
Fixed
- Quoted scalars always load as strings — implicit type resolution now
applies only to plain scalars (YAML 1.2):safe_load('"true"')returns the
string"true", notTrue. The serializer keeps negative numbers
round-tripping through the document (to_yaml) path. - Lone-quote keys round-trip — mapping keys that are a single
'or"
are emitted as quoted scalars instead of unparseable YAML. - Empty collections emit
{}/[]— dumping empty mappings/sequences no
longer yields an empty document that re-parses asNone.
Changed
get()is literal-key only —YamlDocument.get()no longer guesses
JSONPath for keys containing.or[; every key is treated as a
top-level mapping key, consistent with__getitem__/__setitem__.
Path access stays available viafind()/node().
v0.13.0 — Core Engine Rebuild
[v0.13.0] — 2026-08-10
Changed
- Rust MSRV raised to 1.96 and edition bumped to 2024 - both crates now
declarerust-version = "1.96"andedition = "2024"; CI pins the
build/test-freethreadedjobs to Rust 1.96 for deterministic wheel builds
and adds anmsrv-checkjob runningcargo check/cargo testat the MSRV
to prevent silent MSRV drift (therust-lintjob stays onstable).
The floor is set above PyO3 0.29's own baseline (rustc 1.83) for std API
headroom (e.g.assert_matches!, stabilized 1.96) with no code migration
needed.TAG_REGISTRY(tag handler storage) refactored to
std::sync::LazyLock, dropping theMutex<Option<...>>indirection.
Performance
safe_dump/from_dict/dump_file/dump_iterable: direct writer
— Python→YAML serialization without intermediateCustomNodeAST.
Single-passdirect_dumpreplaces the old two-passpyobject_to_node+
to_yaml. 7x faster onsafe_dump(28ns→4ns), 6x faster onfrom_dict
(35ns→6ns). (#60)safe_load/safe_loads/to_dict: fast-path skip anchor tracking
— when input has no&characters, skipcollect_anchors+ anchor
resolution and use the simplernode_to_pyobject_simplepath. (#59)resolve_core_type: first-byte dispatch whitelist — non-numeric/
non-boolean first bytes returnStrimmediately, avoiding schema
resolution overhead for the common case. (#59)- granit-parser migration — saphyr-parser replaced with granit-parser
1.0.1 for nativeEvent::Commentemission, eliminating the full-text
scan_yaml()pre-scan. parse_small -18%, parse_large -21%,
roundtrip_large -18%.
Fixed
float_to_yaml_stringround-trip fix — appends.0when Rust
Display drops the decimal (42→42.0) so floats round-trip as
floats instead of becoming ints.- Reverted
count_nodespre-allocation — the full AST traversal cost
more than the reallocations it avoided (serialize_10mb was ~14% slower);
buffer growth is left to the Vec.
Added
max_depthon stream & frontmatter APIs —parse_stream(yaml, on_event, max_depth),
read_markdown(path, schema, max_depth),read_markdown_str(content, schema, max_depth)
acceptmax_depth(default 1000). Stream parsing now enforces the nesting-depth limit
via coreparse_stream_with_options(previously stream events had no depth limit).- Pydantic integration —
dump_pydantic()serializes a Pydantic model
to YAML string viamodel_dump(mode='json')+safe_dump;parse_as()
parses YAML string into a Pydantic model instance. Both use lazy imports,
no hard dependency on pydantic. (#61)
Internal
- Split
py/mod.rs— monolithic 1786-line module broken into
document.rs(YamlDocument),yaml_instance.rs(YAML class),
functions.rs(module-level functions),stream_iterator.rs,
walk_helpers.rs.mod.rsreduced to 128 lines. (#61) needs_quotes()guard +double_quoted_scalar()constructor —
strings like'true'/'42'/'null'now emit as double-quoted
scalars under the core schema instead of being misread on re-parse
(pyobject_to_node+json_value_to_node).- CodSpeed benchmarks unified on
codspeed-divan-compat—
exclude-allocationsremoves allocator noise; cross-library benchmarks
consolidated intotests/test_benchmark_crosslib.pywith shared
tests/data/yaml_samples.pyfixtures and streaming coverage.
v0.11.0 — Surgical Serialization
[0.11.0] - 2026-08-02
Added
- Surgical Serialization — byte-level source span tracking on every AST node; segment-based splice — edits regenerate only the touched region, untouched text is byte-copied
- proptest fidelity property tests (new dev-dependency)
- 10MB edit-flush benchmarks (divan)
Changed
- lush_source now splices segments; falls back to full serialization for flow-style regions, non-default layout documents, merged keys, CRLF/BOM documents, and after materialization (single-burst model)
- Splice edits preserve ---/.../directive marker lines as untouched bytes (full serialization previously dropped them — deliberate behavior difference)
v0.9.0 — Ecosystem Ready
feat: v0.9.0 Ecosystem Ready Python 3.13-3.15 + free-threaded CPython support, allow_duplicate_keys, SerializeOptions expansion (width / indent_mapping / indent_sequence / indent_offset), tag handler registry with chaining + remove_tag, Pydantic integration via parse_as(), committed .pyi stubs, and a CI optimization (single rust-lint job, wheel install instead of maturin develop). Review follow-up fixes: wired the previously dead serializer indent options, added a width=1 hang guard, i18n'd duplicate-key errors across 4 locales, and made non-string tag handler returns raise YamlTagError.
v0.8.0 — Unlock the AST
v0.8.0 — Unlock the AST
Breaking Changes
- YAML() instance API: New YAML() class replaces module-level parsing for configuration. pyrs_yaml.parse() still works as shorthand.
- Python Node API: New Node(doc) class exposes AST with ind(), walk(), ilter(), parent, children, o_yaml().
- MergedView: doc.merged() returns a read-only dict-like view with merge keys resolved.
- Lifecycle warnings: Node.release() marks stale nodes; accessing released nodes emits RuntimeWarning and raises YamlDocumentError.
- Document metadata: doc.version() returns the YAML spec version.
✨ Features
- Python Node API with JSONPath-like query language in Node.find()
- MergedView — read-only anchor merge view
- Node.release() + RuntimeWarning lifecycle warnings
- doc.version() metadata
- pytest parametrize refactoring across 5 test files (498→322 lines)
🔧 Bug Fixes
- Node.to_yaml(): rom_dict() returns str, not YamlDocument — fixed branching logic
- MergedView: handle sequence-root documents
- _parse_jsonpath: deep scan (..) and wildcard (\$..*) detection order
- Removed duplicate pytest_collection_modifyitems in conftest.py
🧪 Testing
- 3 new test files: test_yaml_instance.py, test_node_api.py, test_merged_view.py
- 52 new test cases across all v0.8.0 features
- 460 Python tests + 106 Rust tests all passing