- Rust 78.3%
- Python 21.7%
|
All checks were successful
CI / Rust: build (release, no features) (push) Successful in 14s
CI / Rust: unit tests (push) Successful in 27s
CI / Rust: integration tests (debug) (push) Successful in 26s
CI / Rust: integration tests (release) (push) Successful in 32s
CI / Rust: performance (push) Successful in 41s
CI / Package: wheel (windows-x86_64) (push) Successful in 51s
CI / Package: wheel (linux-x86_64) (push) Successful in 44s
CI / Package: wheel (macos-aarch64) (push) Successful in 1m0s
CI / Package: wheel (macos-x86_64) (push) Successful in 55s
CI / Package: sdist (push) Successful in 17s
CI / Smoke: wheel (py3.13) (push) Successful in 14s
CI / Smoke: wheel (py3.8) (push) Successful in 14s
CI / Conformance: Python performance (shipped wheel) (push) Successful in 1m45s
CI / Conformance: differential fuzz (PyYAML + ruamel) (push) Successful in 37s
CI / Conformance: YAML test suite (ecosystem + JSON oracles) (push) Successful in 21s
CI / Smoke: sdist (build from source) (push) Successful in 29s
CI / Publish to PyPI (push) Has been skipped
|
||
|---|---|---|
| .forgejo/workflows | ||
| benches | ||
| docs | ||
| examples | ||
| python/turboyaml | ||
| scripts | ||
| src | ||
| tests | ||
| .gitignore | ||
| Cargo.toml | ||
| LICENSE | ||
| perf_test.py | ||
| pyproject.toml | ||
| README.md | ||
| spec.md | ||
TurboYAML
Subset of YAML 1.2, built for parsing speed. A Rust parser with Rust and Python bindings.
Valid TurboYAML is always valid YAML 1.2. The reverse need not hold — so parse with TurboYAML and fall back to a full YAML parser when you hit something it deliberately doesn't support.
Why
TurboYAML trades YAML's full feature richness for speed, while keeping a useful, real-world feature set alive. On typical config and data it parses roughly 25–45× faster than PyYAML's libyaml C extension, producing native Python objects (dict / list / str / int / float / bool / None) directly — no intermediate tree.
| Profile | turboyaml (Python) | PyYAML (libyaml) | speedup |
|---|---|---|---|
| Flat records | 9.4M lines/s | 372k lines/s | 25× |
| Deep tree | 4.1M lines/s | 111k lines/s | 37× |
| Inline flow collections | 1.5M lines/s | 32k lines/s | 47× |
| Block scalars | 7.2M lines/s | 238k lines/s | 30× |
| Mixed (nested + flow + blocks) | 3.0M lines/s | 90k lines/s | 33× |
Measured on one machine over a randomized corpus that includes quoted/escaped scalars and block scalars; reproduce with cargo run --release --features gen --bin perf_test && python perf_test.py. Speedup is the relative metric; absolute throughput varies with document complexity, but both parsers see the same corpus.
Install
pip install rs-turboyaml
The PyPI distribution is rs-turboyaml; the import name is turboyaml.
Usage
Python
import turboyaml
obj = turboyaml.load(text) # → dict / list / str / int / float / bool / None
# Multi-document streams (--- separated) — mirrors yaml.safe_load_all,
# returning an eager list with one element per document:
docs = turboyaml.load_all(text) # → [doc0, doc1, ...]
# Options (both default False), accepted by load and load_all:
# strict=True → raise ParseError on duplicate mapping keys
# yaml_1_1_bools=True → yes/no/on/off/y/n become bool (YAML 1.1 parity)
obj = turboyaml.load(text, yaml_1_1_bools=True)
load reads a single document — a lone leading --- / trailing ... is fine, but
a stream with more than one document raises ParseError; use load_all for that.
Fall back to a full parser when a document uses something TurboYAML doesn't cover.
Catch the base TurboYamlError so the fallback also triggers on the handful of
valid-YAML constructs (e.g. multi-line scalars) that surface as ParseError:
try:
obj = turboyaml.load(text) # fast path
except turboyaml.TurboYamlError: # UnsupportedFeatureError or ParseError
import yaml
obj = yaml.safe_load(text) # full parser is the authority
This is safe because TurboYAML never silently returns data that differs from a full parser — for anything it can't handle it raises (checked in CI by a differential against PyYAML on unsupported constructs).
Rust
let value = turboyaml::parse(input)?; // one document
let docs = turboyaml::parse_all(input)?; // Vec<Value>, one per document
Supported
- Block mappings and sequences, arbitrarily nested — including a sequence value indented at the same column as its mapping key (
key:then- item, the common real-world form) - Compact notation —
- key: value,- - nested - Scalar type inference:
null/~, booleans, integers (decimal /0x/0o), floats (incl..inf/.nan), strings - Quoted strings (single line) — double-quoted with the YAML 1.2 escape set (
\n,\t,\",\uXXXX, …) and single-quoted (literal,''escape) - Flow collections —
[1, 2],{a: 1}— single-line, nestable - Block scalars —
|literal and>folded, with+/-chomping and explicit indentation indicators (|2,>3) #comments- Any scalar type as a mapping key (
42:,True:,null:) - Document markers — a single leading
---/ trailing..., and multi-document streams (---separated) viaload_all/parse_all; a document body may be inline on the marker line, including a block scalar (--- |,--- >) %YAML 1.2directive — accepted and ignored (TurboYAML follows the 1.2 core schema)- Optional duplicate-key rejection —
load(text, strict=True)/parse_strict
Notable differences from full YAML 1.2
- Indentation is spaces in multiples of two — no tabs, no odd widths.
- Core-schema booleans by default: the three YAML 1.2 spellings of each —
true/True/TRUEandfalse/False/FALSE— are bool; any other casing (tRuE) and the YAML 1.1 wordsyes/no/on/off/y/nare strings. Passyaml_1_1_bools=Trueto opt into full YAML 1.1 boolean parity (those tokens become bool, matching ruamel.yaml's 1.1 resolver). - Quoted strings are single line — for multi-line text, use a block scalar.
- UTF-8 only, no BOM.
Not supported
These raise a typed UnsupportedFeatureError — distinct from a malformed-input ParseError — so the fallback pattern above can recover them cleanly:
- Anchors and aliases —
&anchor,*alias - Tags —
!!str,!custom - Merge keys (
<<:) and explicit/complex keys (? key) - Directives other than
%YAML 1.2—%YAML 1.1(full YAML 1.1 is not implemented),%TAG, and reserved directives (%FOO)
Multi-line quoted/plain scalars are also out of scope (for multi-line text, use a block scalar), as is a block collection begun on a --- marker line (--- key: value). These (and the items above) raise rather than silently mis-parse, so the fallback recovers them.
Status
Alpha, but extensively validated. Correctness rests on layered, independent checks, all run in CI:
- Unit + integration tests — hand-written fixtures plus generator round-trips
- A randomized differential fuzz against PyYAML — 70k+ documents, 0 mismatches
- A boundary differential proving TurboYAML raises (never silently diverges) on unsupported constructs — the guarantee that makes the fallback safe
- A multi-document differential —
load_allmatchesyaml.safe_load_allacross ~5k randomized streams plus the document-counting edge cases, 0 divergences - Exact YAML 1.1 boolean parity with ruamel.yaml (for
yaml_1_1_bools=True) - The official YAML test suite — checked against the libyaml + ruamel ecosystem (TurboYAML is never more permissive than both) and the suite's expected JSON; see docs/yaml-test-suite.md for the conformance breakdown
See spec.md for the normative specification, and docs/architecture.md for how the parser is built and why it is fast (including the optimizations that didn't work).