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.post6.tar.gz (32.8 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.post6-cp313-cp313-win_amd64.whl (158.5 kB view details)

Uploaded CPython 3.13Windows x86-64

naay-2025.12.5.post6-cp313-cp313-manylinux_2_34_x86_64.whl (302.9 kB view details)

Uploaded CPython 3.13manylinux: glibc 2.34+ x86-64

naay-2025.12.5.post6-cp313-cp313-macosx_11_0_arm64.whl (258.9 kB view details)

Uploaded CPython 3.13macOS 11.0+ ARM64

File details

Details for the file naay-2025.12.5.post6.tar.gz.

File metadata

  • Download URL: naay-2025.12.5.post6.tar.gz
  • Upload date:
  • Size: 32.8 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.post6.tar.gz
Algorithm Hash digest
SHA256 b75ef32da7acd0dd737b356bb9f69c3f09042bc9b5fc2c340aa31abf157c53df
MD5 cf0bfac9f59e92265cf7b8283fdec6b7
BLAKE2b-256 141b272d624e3bfea74cba5f7d9e58cb06d4d57188ab8579173df6eed281ccba

See more details on using hashes here.

Provenance

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

File metadata

File hashes

Hashes for naay-2025.12.5.post6-cp313-cp313-win_amd64.whl
Algorithm Hash digest
SHA256 71a99c7ca1a8caf07d1217461d9b9e7c337467a4c1e90a71f5c844e820f79b7c
MD5 7b9eb6fc2ab294dd1ae8e45baf521298
BLAKE2b-256 be7856f6bac2df5c3bda001592dd37440549a89b6a4f487fba2217412cc2fe15

See more details on using hashes here.

Provenance

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

File metadata

File hashes

Hashes for naay-2025.12.5.post6-cp313-cp313-manylinux_2_34_x86_64.whl
Algorithm Hash digest
SHA256 b59bee63ae1557410a5db4c3796332c277db381fe80ad902eaad113a8d8954a7
MD5 c1bd2ae4aed04df49c0da918fc311fdd
BLAKE2b-256 847e9ab61b0a5e2eaae871ff383c5cdb13c40453d5b66097df8ec2c589162521

See more details on using hashes here.

Provenance

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

File metadata

File hashes

Hashes for naay-2025.12.5.post6-cp313-cp313-macosx_11_0_arm64.whl
Algorithm Hash digest
SHA256 121a37c1279d162ceddf3efe7f0a45d2e6db7ea4e9c1ea45406dfd4e6572eebf
MD5 361b13ad789eff7bc44d1f42c3b5a202
BLAKE2b-256 bce37b7a98918237dca90e5a3db97b9fe0e6c9ae2f805ccf12f4b31df323171d

See more details on using hashes here.

Provenance

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