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 Dead Code Free

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 (2,000 runs)

Engine Load avg (ms) Dump avg (ms) Relative to naay (native)
naay (native) 0.09 0.04 baseline
naay (pure-python) 0.64 0.29 ~7× slower loads, ~7× slower dumps (still >10× faster than PyYAML loads)
PyYAML safe_* 7.53 5.01 ~84× slower loads, ~125× slower dumps
ruamel.yaml (safe) 1.59 2.34 ~18× slower loads, ~58× slower dumps

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

Engine Load avg (ms) Dump avg (ms) Notes
naay (native) 2.62 3.85 baseline
naay (pure-python) 5.35 4.69 ~2× slower loads, ~1.2× slower dumps; still stack-safe
PyYAML safe_* fail fail hit Python recursion depth on the first iteration
ruamel.yaml (safe) 18.82 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.63 0.45
naay (pure-python) 4.16 2.06
PyYAML safe_* 51.90 32.49
ruamel.yaml (safe) 15.06 28.31

Even on this uniform synthetic workload, naay (native) loads about 82× faster than PyYAML and 24× faster than ruamel, while dumping is 72× faster than PyYAML and 62× faster than ruamel. The pure-Python fallback still loads ~12× faster than PyYAML and dumps ~16× 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 v1.0

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.8.post2.tar.gz (36.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.8.post2-cp313-cp313-win_amd64.whl (163.1 kB view details)

Uploaded CPython 3.13Windows x86-64

naay-2025.12.8.post2-cp313-cp313-manylinux_2_34_x86_64.whl (261.0 kB view details)

Uploaded CPython 3.13manylinux: glibc 2.34+ x86-64

naay-2025.12.8.post2-cp313-cp313-macosx_11_0_arm64.whl (232.7 kB view details)

Uploaded CPython 3.13macOS 11.0+ ARM64

File details

Details for the file naay-2025.12.8.post2.tar.gz.

File metadata

  • Download URL: naay-2025.12.8.post2.tar.gz
  • Upload date:
  • Size: 36.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.8.post2.tar.gz
Algorithm Hash digest
SHA256 08d2e5404cc92c9dd826316981e7fe1c669b69598a3092be19092c29ac7de4e6
MD5 8d8ef5c556d2d36cf925eed664db2087
BLAKE2b-256 2612bbfd7184141ca023d2a9042610b09ddb7756f00877230fc498ffbe1945d5

See more details on using hashes here.

Provenance

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

File metadata

File hashes

Hashes for naay-2025.12.8.post2-cp313-cp313-win_amd64.whl
Algorithm Hash digest
SHA256 d5312c7bae488cf7a4240115936478e2bffbf3c2de2bff2cd47938c47f5e29e7
MD5 b22c9fd37e853cc2e85c53faa6a0e20a
BLAKE2b-256 e071983edbe71660b785aa387c202866908f5339aa9ed102ae6d47a3c5bd3754

See more details on using hashes here.

Provenance

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

File metadata

File hashes

Hashes for naay-2025.12.8.post2-cp313-cp313-manylinux_2_34_x86_64.whl
Algorithm Hash digest
SHA256 663f1dbf3b7774748177e2873e3c2519bdefc6345a81b0d5531e9e601587e59e
MD5 bd8863bfe1b39634c25634862c3c5a0c
BLAKE2b-256 c8f0328d9b909a381375a40e181e321cafacd4824c40c38960880609de9d1571

See more details on using hashes here.

Provenance

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

File metadata

File hashes

Hashes for naay-2025.12.8.post2-cp313-cp313-macosx_11_0_arm64.whl
Algorithm Hash digest
SHA256 e3db5b647a1340898fc033e8cbe64e5677e966d989be2dc5ea8ca54fb1452fe7
MD5 f2e01924e08c7b76b9b3afdfecfabbab
BLAKE2b-256 b73ff9f04ea15b9e326bdf6cf094d374e9bbaf665fce8621f42c982329a126d9

See more details on using hashes here.

Provenance

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