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 Distribution

naay-2025.12.5.post5.tar.gz (32.5 kB view details)

Uploaded Source

Built Distributions

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

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

Uploaded CPython 3.13Windows x86-64

naay-2025.12.5.post5-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.post5-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.post5.tar.gz.

File metadata

  • Download URL: naay-2025.12.5.post5.tar.gz
  • Upload date:
  • Size: 32.5 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.7

File hashes

Hashes for naay-2025.12.5.post5.tar.gz
Algorithm Hash digest
SHA256 f2e61ff587c984a4d8e98d3a1dd40cca32d148ed8d99cdd7d06ccf0c46afc062
MD5 038be748f1f3c3eba88e56c200ab8d34
BLAKE2b-256 9668e6bce19528c5ab674f56d661d0676a725249ff746bb567ff11c1c22ccbd6

See more details on using hashes here.

Provenance

The following attestation bundles were made for naay-2025.12.5.post5.tar.gz:

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.post5-cp313-cp313-win_amd64.whl.

File metadata

File hashes

Hashes for naay-2025.12.5.post5-cp313-cp313-win_amd64.whl
Algorithm Hash digest
SHA256 636cb242bea1b27bda854c8eecacb4add130594ca4ab2999f6235410da3b8f7c
MD5 937a364bb74ba427d59119950b064d4b
BLAKE2b-256 dd6af6f7def595a8ca3efdde345be74c88f144531c94ef67a20a26b3fbc84dd9

See more details on using hashes here.

Provenance

The following attestation bundles were made for naay-2025.12.5.post5-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.post5-cp313-cp313-manylinux_2_34_x86_64.whl.

File metadata

File hashes

Hashes for naay-2025.12.5.post5-cp313-cp313-manylinux_2_34_x86_64.whl
Algorithm Hash digest
SHA256 9462ce07f89546e96c9eb564a9b6827a8b7219e05b84a84050d1b2ffb687c36e
MD5 80829cfa7d17adf94720f114f14a7316
BLAKE2b-256 6e2df9c1e324825e2b3ca3bb68dec35a2cb7d3e976354787fdca8dc179212250

See more details on using hashes here.

Provenance

The following attestation bundles were made for naay-2025.12.5.post5-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.post5-cp313-cp313-macosx_11_0_arm64.whl.

File metadata

File hashes

Hashes for naay-2025.12.5.post5-cp313-cp313-macosx_11_0_arm64.whl
Algorithm Hash digest
SHA256 f4275b15940ad8dc26fb984cd89df2a6fa237ec8697265bdffb8d6bbbbbe3ec6
MD5 62b879fa48f0fd2984cca3fc73977da6
BLAKE2b-256 8f7dcc6af826e4fc298e002fb72267dd41b205e7503ae4c5cfbd5db893f22c3b

See more details on using hashes here.

Provenance

The following attestation bundles were made for naay-2025.12.5.post5-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