Changelog
Changelog¶
All notable changes to this project will be documented in this file.
The format is based on Keep a Changelog, and this project adheres to Semantic Versioning.
[Unreleased]¶
Added¶
- Command-line interface — a new
pyrs-yamlcommand (opt-in viapip install "pyrs-yaml[cli]", requires Python 3.10+) exposing the library from the terminal:fmt(round-trip reformat preserving comments/anchors/ order),get(JSONPath queries with--format yaml|json|text),set/delete/rename(path-based edits with--inplace,--string,--create-missing),validate(CI-friendly exit codes), andto-json/from-jsonconversions. All commands read stdin via-and default to stdout output. Implemented in pure Python (python/pyrs_yaml/cli/) on top of Cyclopts as an optional extra, so the base install keeps zero extra dependencies and Python 3.8 support. - CLI expansion —
sort-keys(sort mapping keys at a path),move(relocate a subtree to an existing destination),frontmatter(extract Markdown front matter as YAML, optional body split), andcompliance(YAML Test Suite report with--json) commands;-A/--all-docsmulti-document mode onfmt/get/set/delete/rename/sort-keys/validate/to-json; and mutually exclusivevalidate --schema <name>vs--schema-file <path>. The undocumentedpython -m pyrs_yaml.complianceentry point was removed in favor of the subcommand.- Optional third-party type plugins —!duration(pendulum.Duration),!arrow(arrow.Arrow), and!ulid(ulid.ULID) auto-register when the corresponding library is installed (_register_third_partyinpython/pyrs_yaml/plugins/_builtin.py). Each uses a distinct tag so existing!timestamp/!date/!uuidhandlers are unaffected; a plain stdlibtimedeltais never matched by!duration. - pydantic-settings YAML source —
PyrsYamlConfigSettingsSource(python/pyrs_yaml/settings.py) is a drop-in replacement forpydantic_settings.YamlConfigSettingsSourcethat parses with pyrs-yaml (YAML 1.2 core schema) instead of PyYAML. It is exported lazily soimport pyrs_yamlnever requires pydantic-settings; install withpip install "pyrs-yaml[settings]"(Python 3.10+).dump_pydanticandparse_asnow use the same lazy module-level__getattr__export pattern.
[v0.15.0] — 2026-08-19¶
Added¶
- Node metadata setters/getters —
Node.comment/Node.anchor/Node.tagread properties andset_comment/set_anchor/set_tag(plusremove_*variants), backed by new path-based edit operations. Editing an alias or a missing path raises; standalone comments on inline scalar values and sequence items are now serialized on their own indented lines (fixes pre-existing broken round-trip forchild:\n # c\n valand- a\n# c\n- b). - Verbatim tags —
set_tag("!<tag:yaml.org,2002:str>")now produces a verbatim tag (empty handle), and verbatim tags parsed from source survive round-trip:Tag'sDisplayemits!<...>wrapping for empty-handle tags,parse_tagrecognizes the!<...>form, and stream events serialize tags throughDisplay. - Schema file IO and listing —
load_schema(name, path)reads a schema definition from a file and registers it;list_schemas()returns all registered schema names (built-infailsafe/json/core/yaml1.1plus custom). - Node style/format setters/getters —
Node.scalar_style/Node.flow_style/Node.chompingread properties andset_scalar_style/set_flow_style/set_chompingmethods. ScalarStyle/Chomping now deriveCopy. Non-scalar nodes returnNone/ are no-op; aliases and missing paths raise. - Schema structural validation — a
validatesection in a schema definition adds structural checks (path-qualified scalar types,sequence_of/mapping_ofcontainers,required);validate_against_schema(data, schema_yaml)raisesYamlValidateErrorlisting every failure. Node.copy()— deep-copies a subtree as a standalone Python value (dict/list/scalar), detached from the document, for pasting viaset_value().- Deep editing API —
doc.set_many({path: value})sets multiple paths (with wildcard[*]and deep-scan..support) in a single splice burst;doc.sort_keys()orders mapping keys in place;Node.move(new_path)relocates a subtree;Node.path/Node.find_first()/Node.value_eq()add path access, first-wildcard lookup, and value comparison. - Property-based testing for 0.14+ features — Rust proptests for
validate_node, schema parsing, style-settings round-trip; Python hypothesis tests forset_manywildcards, metadata-edit preservation,sort_keysidempotency.hypothesismoved to thetestgroup so CI runs property tests. - Serializer fix — standalone comments on empty flow containers (
key: {}/key: []) no longer produce invalid YAML; demoted to inline.
Changed¶
- NumPy re-enabled on free-threaded (cp314t) wheels — the
--no-default-featuresflag is removed from the cp314t build lines; rust-numpy 0.29 supports free-threaded Python, sonumpy.ndarrayserialization is now available on free-threaded wheels when NumPy is installed (auto-detected at runtime).
Docs¶
- Corrected stale references across all locale docs (en/zh/ja/ko) —
saphyr-parser→granit-parser, YAML compliance 98.1% → 99.75% (405/406 suite cases), ABI3 support 3.9–3.13 → 3.8–3.15 (py3.9+ → py3.8+), and benchmark tables updated to current CodSpeed CI numbers (parse 21–43×, serialize 55–177× faster than PyYAML). Rust-side benchmark sections migrated from Criterion to divan (benches/yaml_bench.rs→crates/pyrs-yaml/benches/yaml_bench.rs).
[v0.14.1] — 2026-08-15¶
Fixed¶
- Single-quoted scalars with backslash + control/noncharacter — such values now use double-quoting; single quotes cannot escape control chars/noncharacters.
- Noncharacters and BOM quoted —
needs_quotes/needs_double_quotednow require quoting for U+FFFE/U+FFFF/plane-end noncharacters and U+FEFF (BOM). - Double-quoted escape width — code points above U+FFFF now emit the 8-digit
\Uxxxxxxxxform (the 4-digit\uform is BMP-only). - Folded plain-scalar continuation indent — continuation indent is derived from the value's start column so nested sequence/mapping items stay indented past the parent block indent.
- Multi-byte wrap boundary —
wrap_plain_scalarfloors the wrap slice to a char boundary instead of panicking on 4-byte UTF-8 straddling. hypothesisin publish test requirements —.ci/requirements-test.txtpinshypothesis>=6.113.0so the publish workflow can run the property suite.
Added¶
scripts/fuzz_panics.py— high-volume local Hypothesis fuzz harness with a hostile strategy across dump/parse/edit/idempotency.
[v0.14.0] — 2026-08-14¶
Added¶
- YAML Schema Language — define custom schemas with
rulesmapping regex patterns to YAML types; register viaregister_schema(). - Inline dict schema —
schemaparameter acceptsdictinYAML(),parse(),safe_load(), etc. - Community Plugins —
CustomTypebase class withfrom_yaml/to_yaml/can_parse/validate; register viaregister_type(). - Built-in plugins —
!timestamp(datetime) and!setregistered by default.
Changed¶
- Schema resolution is pluggable —
SchemaResolvertrait +Schemaenum with globalSchemaRegistry. Built-in schemas retain zero-cost dispatch. node_to_pyobjectanddirect_dumpcheck registeredCustomTypes — tagged scalars convert viafrom_yaml(); objects serialize viato_yaml().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().
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.
[v0.13.0] — 2026-08-10¶
Changed¶
- Rust MSRV raised to 1.96 and edition bumped to 2024 - both crates now
declare
rust-version = "1.96"andedition = "2024"; CI pins thebuild/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 tostd::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 native
Event::Commentemission, eliminating the full-textscan_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 intodocument.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 sharedtests/data/yaml_samples.pyfixtures and streaming coverage.
[v0.12.1] — 2026-08-06¶
Added¶
set(create_missing=True)- missing intermediate mapping keys along the edit path are created as nested mappings (e.g. settinga.b.cona: 1createsbandc); index segments that miss are still an error, and a scalar intermediate along the path still raises.doc.walk()/doc.scalars()- Rust-backed depth-first AST traversal yieldingNodeobjects, avoiding per-nodeto_dict()resolution.walk()returns all nodes;scalars()returns only scalar/null nodes.- Rust core module tests - 39 new tests covering
editing::navigate(key_eq, navigate, navigate_mut, normalize_index, mapping_key_index),editing::region(line helpers, node_is_flow, extend_delete_over_comments, nav_err),editing::dirty(DirtyKind/DirtyUnit constructors), andediting::metadata(with_metadata_from, needs_quoting). - Python doc.walk() edge case tests - 9 new tests for empty doc, null values, deeply nested, flow collections, mixed types.
Changed¶
- Monorepo workspace - source code split into
crates/pyrs-yaml-core/(pure Rust, no PyO3) andcrates/pyrs-yaml/(PyO3 bindings). RootCargo.tomlis now a workspace. Oldsrc/directory andbuild.rsremoved. - pyproject.toml - added
tool.maturin.manifest-pathpointing tocrates/pyrs-yaml/Cargo.toml. - Parse hot paths - single-pass comment/anchor extraction, lazy
duplicate-key detection,
shift_insertmerge prepending, and skippedDocumentEnddeep-clone for single-document parses cut large-document parse cost ~19% (CodSpeed: parse[large] +13.9%, parse[medium] +16.6%, roundtrip[large] +12.2%). Arc<str>scalar storage -CustomNode::Scalarand comment/event text share allocations viaArc<str>; AST nodes shrink 8 bytes and clones become refcount bumps instead of deep copies.
Fixed¶
set(create_missing=True)nested chain build - the created mapping chain no longer duplicates the first segment as a nested key level.set(create_missing=True)eligibility - freshly created keys are now eligible for the value write (the eligibility check no longer runs after the synthetic pair is inserted).- Standalone comments before simple mapping keys - round-trip previously dropped standalone comments attached to simple-key nodes; now preserved (two regression tests).
[0.11.7] - 2026-08-04¶
Changed¶
- stub-build-check replaced with release-guard - the always-red container
build (
validate.yml) that deliberately failed to reproduce the v0.10.0--generate-stubsfailure mode is replaced with three static assertions that pass when the repo is correct:grepguardspublish.ymlagainst--generate-stubs,git ls-filesasserts the committed.pyiis tracked, andtest -fcheckspy.typedexists. The job now gives green CI on correct state, red only on regression.
Added¶
- Numpy free-threaded tracking - ROADMAP.md now tracks
rust-numpyfree- threaded support status (PyO3/rust-numpy#476) as a dependency for re-enabling ndarray serialization on cp314t wheels when the Rust binding matures.
[0.11.6] - 2026-08-04¶
Changed¶
- Free-threaded (cp314t) wheels are now numpy-free - built with
--no-default-features, so rust-numpy is excluded entirely (smaller binary, no runtime probe).safe_dumpon anumpy.ndarrayraisesYamlTypeErroron free-threaded builds; GIL builds (Python 3.8-3.15) keep full ndarray serialization.
Added¶
- Free-threaded CI validation -
test-freethreadedjob now builds and tests with--no-default-features, matching the shipped free-threaded wheel configuration. - Install docs -
docs/{en,zh,ja,ko}note that free-threaded wheels are numpy-free (ndarray serialization unavailable on cp314t).
[0.11.5] - 2026-08-04¶
Changed¶
- Parser robustness items 3/4/5 closed via Phase 0 strictness audit — the 70-probe corpus (indentation, block-mapping keys, flow context) compared against a PyYAML oracle showed no fixable accepted-but-invalid case (64/70 match; the 6 divergences are deliberate YAML 1.2 / yaml-test-suite requirements where PyYAML is the outlier, and one deliberate duplicate-key strictness). Compliance stays at 99.75% (405/406). Full write-up in
ROADMAP.md§v0.11.5 andtests/test_strictness_audit.py.
Added¶
tests/test_strictness_audit.py— 70-probe strictness regression corpus pinning current rejection/acceptance behavior (both directions), so future parser changes cannot silently regress strictness or over-reject.
[0.11.4] - 2026-08-04¶
Fixed¶
- Duplicate null/empty mapping keys no longer error (
: a\n: b,~: a\n~: b) — matches yaml-test-suite 2JQS; real duplicate keys still raiseYamlDuplicateKeyError - Compliance harness: correctly-rejected invalid YAML now counts as pass (was lowering the rate despite compliant behavior)
- Compliance harness:
convert_special_charstab decoding via regex — any run of—/‖+»is one tab, fixing tab-encoded suite cases
Changed¶
- YAML Test Suite pass rate gate raised from >75% to ≥95%; current rate 99.75% (405/406)
- Known deviation documented:
ZYU8(%YAML 1.1 1.2) is rejected by design (invalid per YAML 1.2 grammar, matches PyYAML/libyaml)
[0.11.3] - 2026-08-03¶
Added¶
- Streaming write:
YAML.dump_stream(file_obj, iterable)/YAML.dump_file(path, iterable)with document-level constant memory, auto---separators, andexplicit_start/explicit_endflags YamlDocumentwithcontext manager: snapshot/rollback transaction scopingcompliance_report(): public YAML Test Suite pass-rate reporting (version-consistent)
Changed¶
- Edit-burst line-offset cache: internal O(N+edit) carry-through in the splice layer (public API unchanged)
compute_compliancemoved from tests topyrs_yaml.compliance; version no longer hardcoded
Fixed¶
- Changelog mirror drift guard: prek hook + CI job assert root/mirror
[Unreleased]sync - Publish stub pre-validation: CI reproduces v0.10.0-class
--generate-stubscontainer failures before Release
[0.11.2] - 2026-08-03¶
Added¶
YAML.load_stream(file_obj)/YAML.load_stream_file(path): lazy event iterators with O(anchors + chunk) memory
Performance¶
- Parse no longer computes splice eligibility — the O(document) layout check now runs lazily on the first edit via
YamlDocument.splice_checked, restoring the v0.11.0 regression: parse_comments -59%, parse_anchors -42%, parse/roundtrip/edit -10~35% all back to v0.10.0 levels - Linear-cursor layout check — replaces per-node binary search over precomputed line offsets (monotonic source-order traversal)
Changed¶
parse_with_optionsreturnsCustomNode(was(CustomNode, bool)); splice eligibility is now internal toYamlDocumentand computed on demand
[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¶
flush_sourcenow 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)
[0.10.0] - 2026-08-01¶
Added¶
- In-place editing — edit parsed documents without losing formatting metadata:
- Path API:
doc.set(path, value),doc.insert(path, index, value),doc.append(path, value),doc.delete(path),doc.rename(path, new_key)with JSONPath-style paths ($.a.b[0]); root sugar viadoc["key"] = valueanddel doc["key"] - Node API:
doc.node()/doc.find(path)returnNodeobjects withset_value/append/insert/delete/rename, plus tree traversal (parent,children,walk,filter) - Full metadata preservation — replaced scalars keep comment/anchor/tag/quoting; renamed keys keep position and comments; mapping order preserved on delete
- Atomic edits — failed operations leave the document (and its revision) untouched
- Lazy source re-sync —
source()/to_yaml()/reparse()re-serialize only after a successful edit - Stale-node detection —
Nodeaccess after a document edit raisesYamlDocumentError(withRuntimeWarning) - New exceptions:
YamlEditError,YamlPathError(i18n across en/zh-CN/ja-JP/ko-KR) - Alias-aware editing — setting an alias's own path replaces it in place; editing through an alias raises
YamlEditError
- Path API:
- Edit benchmarks — 6 new divan benchmarks in
benches/yaml_bench.rs(set/insert/delete on small–large documents)
Changed¶
YamlDocument.source()now returnsstrand lazily re-serializes after in-place edits
[0.9.0] - 2026-08-01¶
Added¶
- Python 3.13, 3.14 and 3.15 support — PyO3
abi3-py38wheel covers Python 3.8-3.15 (GIL build);abi3t+abi3t-py315provide free-threaded stable ABI - Free-threaded CPython (no-GIL) support —
#[pymodule(gil_used = false)]declares module as thread-safe for free-threaded Python;Py_GIL_DISABLEDcfg flag gates numpy (rust-numpy has no free-threaded support yet — numpy feature must be disabled for free-threaded builds via--no-default-features) - CI free-threaded job — new
test-freethreadedworkflow job validates compilation and tests against Python 3.14t pyo3-build-configbuild dependency — enables#[cfg(Py_GIL_DISABLED)],#[cfg(Py_3_15)]etc. compiler flags viabuild.rsnumpymade optional — feature-gated behindnumpyfeature (default enabled); excluded automatically underPy_GIL_DISABLEDallow_duplicate_keys—YAML(allow_duplicate_keys=True),parse(..., allow_duplicate_keys=True),parse_file,safe_load,safe_loads,parse_all_docsall accept the flag; duplicate mapping keys raiseYamlDuplicateKeyErrorby default,last value winswhen allowedSerializeOptionsexpansion —doc.to_yaml_with_options()gainswidth(line wrapping, 0 = off),indent_mapping,indent_sequence,indent_offsetalongside existingindent_size/explicit_start/explicit_end/sort_keys/max_depth(src/py/mod.rs:432)- Tag handler registry —
register_tag("!custom")decorator and imperative forms +clear_tag_handlers(); scalar nodes carrying a registered tag are transformed through the handler (src/py/tag_registry.rs) - Tag handler chaining with priority — multiple handlers per tag run in ascending
priorityorder;YamlTagSkiplets a handler pass through to the next, fallback keeps the original value - Pydantic integration —
parse_as(Model, yaml, **yaml_kwargs)parses YAML and validates against a Pydantic v2 model; raisesImportErrorwith guidance when pydantic is absent (python/pyrs_yaml/pydantic.py) .pyitype stubs — auto-generated by maturin and committed soregister_tag,parse_as,to_yaml_with_optionsand the new exceptions are visible to type checkers
Changed¶
- CI Python matrix expanded: 3.8-3.14 across ubuntu, windows, macos
- Stable ABI:
abi3-py39→abi3-py38(wider Python 3.8+ support), addedabi3t+abi3t-py315(free-threaded stable ABI) pyproject.tomlclassifiers updated with 3.13, 3.14, 3.15 entries- CI optimization: redundant Rust compilation eliminated — a single
rust-lintjob runscargo clippy+cargo testonce; the build job produces one abi3 wheel per OS which test jobs install instead of runningmaturin develop, removing Rust compilation from 21 matrix jobs (~86% fewer compiles);Swatinem/rust-cacheadded to all jobs - pydantic test dependency —
pydantic>=2.10.6added to[dependency-groups] testand.ci/requirements-test.txt(SSOT viauv syncin ci.yml)
Fixed¶
- Windows DLL loading — removed
#[cfg(test)]block fromsrc/py/tag_registry.rswhich brokeimport pyrs_yamlon Windows (250b8d0) - Python 3.8 compatibility —
from __future__ import annotationsinpydantic.py(63d2495) - CI pydantic skip —
pytest.importorskip("pydantic")so tests pass when pydantic is not installed (7be011d) - CI glob expansion on Windows —
shell: bashforpip install dist/*.whl(PowerShell does not expand*) (2f7778d) - Non-string tag handler returns now raise
YamlTagError— a handler returning a non-strvalue (previously silently ignored, keeping the original scalar) now errors withTag handler '!x' must return a string(src/py/mod.rs:resolve_tags) to_yaml_with_optionsindent wiring —indent_mapping/indent_sequence/indent_offsetare now honored by the serializer (previously dead fields); each defaults toindent_size/0 when omitted (src/serializer.rs)widthno longer hangs for tiny values —width < continuation indentfalls back to emitting the remainder unwrapped instead of looping forever (src/serializer.rs:write_plain_scalar)remove_tag(name)— new function to unregister a tag handler; complementsregister_tag/clear_tag_handlers(src/py/tag_registry.rs)duplicate-keyerrors are i18n'd —YamlDuplicateKeyErrormessages now flow throughformat_i18n_erroracross all 4 locales (src/i18n/locales/*.yml)
[0.8.0] - 2026-07-30¶
Added¶
YAML()instance API —YAML(typ="rt"|"safe"|"full", schema="core"|"yaml1.1", max_depth=1000)with reusable configuration;.parse(),.safe_load(),.safe_loads(),.parse_file(),.parse_all_docs()methods- Python
NodeAPI —Nodeclass withfind(),filter(),walk(),to_yaml(),parent,children,root_type,valuefor AST navigation; JSONPath-like query language ($.key.sub,$.arr[0],$..deep) doc.versionmetadata —YamlDocument.version()returns the YAML spec version (default "1.2")MergedView—doc.merged()returns a read-only dict-like view with merge keys resolved- Lifecycle warnings —
Node.release()to explicitly invalidate a node; stale access emitsRuntimeWarning+YamlDocumentError
Changed¶
parse()/safe_load()now delegate toYAML().parse()/.safe_load()as syntactic sugarYamlDocumentnow storesversionfield for document metadata
[0.7.1] - 2026-07-30¶
Added¶
- ryaml benchmark comparison —
tests/test_benchmark.pynow benchmarks againstryaml(Rust YAML library) alongside PyYAML and ruamel.yaml;benchmark_compare.pyrewritten as a feature comparison report (tests/test_benchmark.py:25-28,.github/workflows/ci.yml:219) - CI compliance threshold raised — YAML Test Suite compliance gate increased from 70% to 75% in
test_compliance_report(); valid parse rate gate at 95% (tests/test_yaml_suite.py:251) - CI dependency consolidation — added
.ci/requirements-test.txtand.ci/requirements-test-lite.txtfor unified test dependency management across publish workflow and local dev - Benchmark modernization — migrated from
pytest-benchmarktopytest-codspeedfor faster C-extension-based statistical benchmarking; all CI jobs now use-r .ci/requirements-test.txt - Rust benchmarks migrated to Divan — replaced
codspeed-criterion-compatwithcodspeed-divan-compatv5.0.1; 16 benchmarks rewritten from Criterion groups to#[divan::bench]attributes (Cargo.toml,benches/yaml_bench.rs)
Changed¶
- CI benchmark job installs
ryamlfor cross-library comparison benchmark_compare.pynow delegates timing topytest-benchmarkand serves as a feature comparison/reporting tool
[0.7.0] - 2026-07-29¶
Added¶
- Serializer
max_depthguard —serialize_node_internalnow tracks recursion depth and raisesYamlMaxDepthErrorwhen exceeding the limit (default 1000), matching the parser's protection (src/serializer.rs:135-145) - Serializer hot-path optimization — 5 optimizations targeting block-style serialization for ~4.9% roundtrip speedup:
- Inlined
write_anchor_tagandwrite_inline_commentNone checks (eliminates method calls for ~99% of nodes) write_indenthot/cold path split (direct index for cached levels ≤64)write_plain_scalarfast path for short ASCII alphanumeric strings (≤8 chars)write_scalar_for_keydirect dispatch for Plain scalars (avoids dispatch chain)
- Inlined
- pytest-benchmark migration — Python benchmarks migrated from raw
time.perf_counter()topytest-benchmarkfor statistical rigor, structured JSON output, and CI integration (tests/test_benchmark.py+ updatedtests/test_performance.py)
Changed¶
pytest-benchmarkreplaces rawtimeitin Python benchmarks- CI benchmark job now runs
pytest --benchmark-jsoninstead of standalone script
Removed¶
write_inline_commentmethod — inlined at all call sitesCommentimport from serializer — no longer needed
[0.6.0] - 2026-07-27¶
Added¶
- Async serialization —
safe_dumps_async,safe_dump_async,safe_loads_async,safe_load_asyncviaasyncio.run_in_executor(python/pyrs_yaml/async_dump.py) - JSON Schema validation —
YamlValidateErrorexception +YamlDocument.validate(schema)method (acceptsstrordict); delegates to Pythonjsonschemamodule YamlDocument.to_json()— serialize document to JSON string (uses Pythonjson.dumps)- Incremental re-parse —
YamlDocumentnow stores source text (doc.source());doc.reparse(resolve_merges=True, schema="core")re-parses in-place - 29 new tests across
test_async.py(8),test_validate.py(14),test_reparse.py(7)
Changed¶
YamlValidateErrorregistered as new custom exception (inheritsValueError)rust_i18n::i18n!macro path updated to"src/i18n/locales"validate_translations()test paths updated to match new locale directory
Removed¶
- Deleted redundant
src/i18n/en.ftl,src/i18n/zh-CN.ftl(never referenced by rust-i18n) - Moved
locales/*.yml→src/i18n/locales/(co-located with i18n module)
Dependency Changes¶
- Runtime dependency:
jsonschema>=4.25.1 - Dev dependency:
pytest-asyncio>=0.23(moved from runtime, no longer pinned)
[0.5.0] - 2026-07-27¶
Fixed¶
Serializer::write_node—.unwrap()onvalues.iter().next().unwrap()inblock_mapping/block_sequencereplaced with safe indexed access to eliminate potential panic on edge-case ASTsYAML_SCHEMAconstant — typoyamorg2002corrected toyamlorg2002(matches YAML 1.2 spec URL)- Development documentation —
AGENTS.mdupdated with mandatoryuv runprefix for Python commands and directcargofor Rust commands
[0.4.0] - 2026-07-27¶
Added¶
- 132 new gap-filling tests — comprehensive coverage for previously untested APIs
- i18n function tests —
set_language,get_language,list_languages,detect_language,negotiate_language parse_all_docsdedicated test suite — single doc, multiple docs, empty, commentsparse_filesuccess case tests — basic parsing, comments preservation, file-not-found errorto_yaml_with_optionstests —explicit_start,explicit_end,indent_size,sort_keysorder preservationto_dict()method tests — scalar root, nested, list, bool, null, anchor resolution, empty mapping/sequence- YamlDocument dunder method tests —
__repr__,__str__,__contains__,__len__,__iter__,__getitem__,root_type() - Bytes input tests —
parse(b"key: value"), UTF-8 bytes, invalid UTF-8 error - Unicode & special character tests — CJK, emoji, roundtrip, CRLF line endings, duplicate keys
safe_load/safe_loadsfeature coverage — anchors, merge keys, block scalars, flow collections, special floats, type resolutionfrom_dictedge cases — special characters in keys, nested lists, None values, empty dict/listfrom_jsonround-trip — nested structures, arrays, invalid JSON errordump_filetests — success path, invalid path error- YAML Test Suite individual case tests — octal, hex, scientific notation, NaN, infinity, merge keys, explicit/implicit keys, bool/null variants, block scalar strip (
|-), flow collections resolve_mergesparameter tests — preserving<<when disabled, resolving by default- Flow collections roundtrip — root-level and nested flow mapping/sequence
- Anchor on non-scalar nodes — mapping anchors (
&defaults) and sequence anchors (&items) - Sequence indexing tests — positive index, out-of-range error
- Merge key integration — roundtrip with resolved and unresolved merge keys
- Tag preservation —
!!seqand!!maptag test coverage - Comment preservation — inline and standalone comment tests on complex structures
Changed¶
- Fixed version sync:
python/pyrs_yaml/__init__.py__version__updated from 0.2.0 to 0.4.0 to match Cargo.toml/pyproject.toml - Removed stale 0.2.0 wheel artifacts from
dist/
[0.3.0] - 2026-07-27¶
Added¶
- NumPy ndarray serialization —
safe_dump()/safe_dumps()/from_dict()/dump_file()now supportnumpy.ndarrayof all dimensions (0-D through N-D)- Supported dtypes:
int8/16/32/64,uint8/16/32/64,float32/64,complex64/128,bool - Multi-dimensional arrays serialize as nested YAML lists with correct indentation
- Complex numbers serialize as
(re+imj)string format 0-Dscalar arrays reshape to 1-D and serialize as a single-item listPyUntypedArray+PyArrayDynvianumpyRust crate for zero-copy dtype dispatch- GIL released during slice iteration for maximum performance
- Supported dtypes:
quoted_scalar()— newCustomNode::quoted_scalar()constructor for values requiring single-quoted YAML style- Type resolution for quoted scalars —
resolve_yaml_typenow applied toSingleQuoted/DoubleQuotedscalars for correct round-trip of quoted negative numbers - Comprehensive NumPy test suite — 42 tests covering all dtypes, dimensions (0-D through 4-D), negative numbers, infinity, NaN, empty arrays, and edge cases
- Flow collections (
{}/[]) round-trip support withflow_stylefield on Mapping/Sequence AST nodes parse()accepts bothstrandbytesinputparse()supportsresolve_mergesparameter to opt out of merge key expansionparse_all_docs()for multi-document parsing via saphyr eventsto_yaml_with_options()withindent_size,explicit_start,explicit_end,sort_keysparametersget()supports default value parameterdump_file()for writing YAML to files- Criterion benchmarks in
benches/yaml_bench.rs(parse/serialize/roundtrip) - GitHub Actions CI with matrix testing (3 OS x 4 Python versions)
- Anchor name parsing expanded to full YAML 1.2 spec (dots, colons, hashes, quoted anchors)
__version__attribute,py.typedPEP 561 marker
Fixed¶
- Negative number round-trip — YAML 1.2 block sequences cannot contain plain scalars starting with
-; negative numbers are now quoted during serialization and correctly parsed back as integers/floats - N-D array support — replaced
PyArray1<T>withPyArrayDyn<T>to support arrays of any dimension, not just 1-D - Correct nesting depth — multi-dimensional arrays now produce exactly N levels of nesting (shape[1..] handles inner dimensions, root dimension wrapped by
plain_sequence) - Alias resolution in
to_dict()andsafe_load()— aliases now resolve to referenced values instead ofNone safe_loads()no longer uses naivesplit("---")— uses saphyr's document events- Mapping/Sequence tags no longer discarded during parsing
format_scalar_for_key()now handles Literal/Folded block scalar styles
Changed¶
- Added
numpycrate (v0.29) as a dependency for ndarray type dispatch - Upgraded PyO3 from 0.21 to 0.29
- Replaced 15+ boilerplate
CustomNodeconstructions withplain_scalar()/plain_mapping()/plain_sequence()/plain_null()constructors - Serializer extracted
write_anchor_tag()andwrite_inline_comment()helpers - Parser extracted
detect_flow_style()helper - Removed dead code:
ParseOptions,find_inline_comment,find_standalone_comment_before,format_yaml_type(test-only) - Consolidated 6 duplicate test files, moved 9 diagnostic scripts to
scripts/ - Improved error messages with key/index/type context
[0.1.0] - 2026-07-25¶
Added¶
- Initial release with YAML 1.2 compliance via saphyr-parser
- Custom AST with full metadata (comments, anchors, tags, chomping, scalar styles)
- Round-trip preservation of comments, anchors, tags, and formatting
- PyYAML-compatible API (
safe_load/safe_dump) from_dict/from_jsonconversion functionsread_markdown/read_markdown_strfor YAML frontmatter extraction- Block scalars (
|/>) with chomping indicators (|-/|+/>-/>+) - Escape sequences (
\n,\t,\uXXXX,\xXX) - YAML 1.2 type resolution (null, bool, int, float, infinity, NaN)
- Merge key resolution (
<<: *alias) - Complex keys (sequence/mapping as key)