Skip to main content

A fast YAML subset parser/dumper for Python powered by Rust

Project description

naay

PyPI - Python Version License Platforms GitHub release

Vote naay to complicated yaml syntax and parsers.

The intent of this project is to define a tiny strict YAML subset where all values are strings, plus support for | block literals, anchors, merges, and YAML-compatible single-line comments — implemented with a Rust core and a Python binding. Standalone # ... lines and inline comments attached to mappings/sequences are retained in their original positions when you round-trip through the Rust core. Speed of parsing and dumping is a chief concern. As well as a very small syntax. The syntax used retains full compatablility with yaml while only supporting a very limited subset of yaml. Good for configs or basic human editable data transfer.

This version enforces a root _naay_version field that must match the current release string:

_naay_version: "1.0"

Note: The Python binding exposes plain dict / list / str objects, so comment metadata is dropped as soon as you convert the parsed tree into native Python types. Use the Rust API directly if you need to preserve comments while mutating the tree.

Layout

  • naay-core/ – Rust library that parses/dumps the restricted YAML subset.
  • naay-py/ – Python extension module using pyo3 that exposes loads / dumps.
  • examples/ – Example YAML and Python usage.

Building (with maturin)

  1. Install Rust and maturin:

    pip install maturin
    
  2. Build and install the Python extension (from naay-py directory):

    cd naay-py
    maturin develop
    

    This will build the naay Python module in your current environment.

Python usage

import naay
from pathlib import Path

text = Path("examples/campaign.yaml").read_text(encoding="utf-8")
data = naay.loads(text)
print("Loaded data:", data)

out = naay.dumps(data)
print("Round-tripped YAML:")
print(out)

Benchmarks

Numbers below were collected on Windows 11 (Ryzen 9 7950X / Python 3.13.2) using uv run python examples/demo.py (which exercises the real YAML fixtures) and a small synthetic benchmark (snippet shown later). Each value is the average wall clock time per operation.

examples/stress_test0.yaml (500 runs)

Engine Load avg (ms) Dump avg (ms) Relative to naay (native)
naay (native) 0.13 0.06 baseline
naay (pure-python) 0.96 0.45 ~7× slower loads, ~8× slower dumps (still >10× faster than PyYAML loads)
PyYAML safe_* 10.64 7.91 ~82× slower loads, ~132× slower dumps
ruamel.yaml (safe) 3.02 4.18 ~23× slower loads, ~70× slower dumps

examples/stress_test1.yaml (20 runs, deeply nested)

Engine Load avg (ms) Dump avg (ms) Notes
naay (native) 4.91 5.89 baseline
naay (pure-python) 9.82 7.78 ~2× slower than native; still stack-safe
PyYAML safe_* fail fail hit Python recursion depth on the first iteration
ruamel.yaml (safe) 33.80 fail ~7× slower on load; dump also exceeded recursion depth

The pure-Python rows above use _naay_pure.parser, the fallback shipped alongside the wheel for platforms where compiling the Rust extension is not possible.

Synthetic dense map (1,500 flat scalars, 200 runs)

Engine Load avg (ms) Dump avg (ms)
naay (native) 0.88 0.68
naay (pure-python) 6.01 3.23
PyYAML safe_* 82.41 45.77
ruamel.yaml (safe) 19.96 40.09

Even on this uniform synthetic workload, naay (native) loads about 93× faster than PyYAML and 23× faster than ruamel, while dumping is 68× faster than PyYAML and 59× faster than ruamel. The pure-Python fallback still loads ~13× faster than PyYAML and dumps ~14× faster, so the all-Python wheel remains viable when the Rust extension is unavailable.

The synthetic numbers come from examples/synthetic_dense_bench.py. Reproduce them with:

uv run python examples/synthetic_dense_bench.py --runs 200 --keys 1500

The helper accepts --runs and --keys flags if you want to probe different shapes or shorter smoke tests.

Spec

Required Preamble

  • The document root must be a mapping containing _naay_version: "1.0" as its first key.
  • No other document-level metadata or directives are permitted.

Scalars

  • Every non-block scalar is interpreted as a UTF-8 string; numbers/booleans are not auto-coerced.
  • Quoted scalars may use single or double quotes; escaping follows standard YAML rules.
  • Multiline content is emitted and parsed via the | block literal style only; folded scalars (>) are not allowed.
  • Trailing whitespace is preserved inside quoted and block scalars but trimmed for bare scalars.

Sequences

  • Denoted with - items at consistent indentation; nested collections are indented by two spaces.
  • Empty sequences are serialized as [] and parsed equivalently anywhere (top-level, nested, inline).
  • Inline sequences ([a, b]) are not part of the subset; use block form instead.

Mappings

  • Keys must be plain strings; quoting is required when keys contain whitespace or reserved characters :#?.
  • Empty mappings serialize as {} and parse equivalently at any depth.
  • Inline mappings ({a: b}) are allowed only for a single key/value emitted inline after - key:; multi-key inline maps are parsed but immediately expanded into block form.

Anchors and Aliases

  • Anchors are declared via &name preceding a nested block; aliases via *name anywhere a value is allowed.
  • The merge key << supports alias merging; merged values must themselves be mappings.
  • Anchors cannot reference scalars that lack a nested block (mirrors YAML behavior).

Comments

  • Full-line comments begin with # at any indentation; these are preserved when using the Rust dumper.
  • Inline comments (after content, starting with #) are preserved when associated with sequences/mappings.
  • Comments are dropped when parsing through the Python API (which returns dict/list/str).

Indentation and Formatting

  • Only spaces are allowed; tabs cause a parse error.
  • Indentation increments must be exactly two spaces for nested blocks.
  • Empty lines are discarded; trailing whitespace on content lines is trimmed before parsing.

Serialization Guarantees

  • Empty lists/maps always emit as []/{} so downstream tools can distinguish them from empty strings.
  • Scalars containing newlines are emitted as | blocks with consistent two-space indentation.
  • The dumper preserves comment placement, anchor structure, and ordering of keys/sequences as supplied.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distributions

No source distribution files available for this release.See tutorial on generating distribution archives.

Built Distributions

If you're not sure about the file name format, learn more about wheel file names.

naay-2025.12.5.post3-cp313-cp313-win_amd64.whl (158.4 kB view details)

Uploaded CPython 3.13Windows x86-64

naay-2025.12.5.post3-cp313-cp313-manylinux_2_34_x86_64.whl (302.8 kB view details)

Uploaded CPython 3.13manylinux: glibc 2.34+ x86-64

naay-2025.12.5.post3-cp313-cp313-macosx_11_0_arm64.whl (258.8 kB view details)

Uploaded CPython 3.13macOS 11.0+ ARM64

File details

Details for the file naay-2025.12.5.post3-cp313-cp313-win_amd64.whl.

File metadata

File hashes

Hashes for naay-2025.12.5.post3-cp313-cp313-win_amd64.whl
Algorithm Hash digest
SHA256 986715f112b696f3664426027f781f6b8d38eb75e5ac4f21441d29a8f27456f1
MD5 13f507e0bcc6cea183cfc3588ce26443
BLAKE2b-256 8fe50e7cba113cb04c7a75f4084a563ecc8090d94097fa07ad6dea050df70519

See more details on using hashes here.

Provenance

The following attestation bundles were made for naay-2025.12.5.post3-cp313-cp313-win_amd64.whl:

Publisher: release.yml on rbroderi/naay

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file naay-2025.12.5.post3-cp313-cp313-manylinux_2_34_x86_64.whl.

File metadata

File hashes

Hashes for naay-2025.12.5.post3-cp313-cp313-manylinux_2_34_x86_64.whl
Algorithm Hash digest
SHA256 b8ec01e014dbfd2b6339b902217740bedbea6568671b440b4d9b53da09193c1a
MD5 6736f079f3b3fb7993dbc327ee946dfb
BLAKE2b-256 a8aaeabfc37729bb5c1c3b397635ea001f51f9819abeca0667dedc2828c7199d

See more details on using hashes here.

Provenance

The following attestation bundles were made for naay-2025.12.5.post3-cp313-cp313-manylinux_2_34_x86_64.whl:

Publisher: release.yml on rbroderi/naay

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file naay-2025.12.5.post3-cp313-cp313-macosx_11_0_arm64.whl.

File metadata

File hashes

Hashes for naay-2025.12.5.post3-cp313-cp313-macosx_11_0_arm64.whl
Algorithm Hash digest
SHA256 48225579d25dae5e13fea84665044201a8ec9fe1cf89f9d00623ed7526dfca87
MD5 979b459ede746039f3838a7ba7588ad0
BLAKE2b-256 8968cdeee12038a6c108a2327c79f6d85a178eb14c076f8296f2a756bd5377f6

See more details on using hashes here.

Provenance

The following attestation bundles were made for naay-2025.12.5.post3-cp313-cp313-macosx_11_0_arm64.whl:

Publisher: release.yml on rbroderi/naay

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page