- Rust 64.9%
- Python 35.1%
| Filename | Latest commit message | Latest commit date |
|---|---|---|
|
All checks were successful
CI / Gate: fmt + clippy (push) Successful in 29s
CI / Perf: comparative benchmark (non-gating) (push) Has been skipped
CI / Gate: build (release, as published) (push) Successful in 13s
CI / Gate: unit tests (push) Successful in 16s
CI / Gate: integration tests (debug) (push) Successful in 16s
CI / Gate: speed floors (push) Successful in 16s
CI / Gate: integration tests (release) (push) Successful in 21s
CI / Package: wheel (windows-x86_64) (push) Successful in 52s
CI / Package: wheel (linux-x86_64) (push) Successful in 47s
CI / Package: wheel (macos-aarch64) (push) Successful in 59s
CI / Package: wheel (macos-x86_64) (push) Successful in 58s
CI / Package: sdist (push) Successful in 18s
CI / Smoke: wheel (py3.11) (push) Successful in 17s
CI / Smoke: wheel (py3.13) (push) Successful in 15s
CI / Smoke: wheel (py3.14) (push) Successful in 15s
CI / Smoke: wheel (py3.8) (push) Successful in 15s
CI / Conformance: differential fuzz (PyYAML + ruamel) (push) Successful in 1m54s
CI / Conformance: character-mutation differential (push) Successful in 39s
CI / Conformance: YAML test suite (ecosystem + JSON oracles) (push) Successful in 21s
CI / Conformance: real-world corpora (PyYAML + libyaml + SchemaStore) (push) Successful in 34s
CI / Perf: anchor scaling invariant (gating) (push) Successful in 18s
CI / Smoke: sdist (build from source) (push) Successful in 38s
CI / Publish to PyPI (push) Has been skipped
|
||
| .forgejo/workflows | ||
| crates | ||
| docs | ||
| python/turboyaml | ||
| scripts | ||
| src | ||
| tests | ||
| .gitignore | ||
| Cargo.toml | ||
| LICENSE | ||
| pyproject.toml | ||
| README.md | ||
| spec.md | ||
TurboYAML
Standard YAML Parser, built for parsing speed. Turboyaml is, to my knowledge, the fastest YAML parser available. Implemented in Rust, with Rust and Python bindings. Supports YAML 1.1 and 1.2. The python release is extensively verified against the currently existing featureset. The Rust bindings are currently less well tested and not released. Currently supports most of the YAML featureset, with some open items. Most notably unsupported today are Tags and Complex / explicit keys and sets. Turboyaml strives to parse exactly in agreement with the Python parser Ecosystem (PyYAML, libyaml, ruamel) and not necessarily with the YAML specification.
Why
Because my everyday work made a faster YAML parser for python necessary. Producing native Python objects (dict / list / str / int / float / bool / date / datetime / None), Turbo is 18–27× faster than PyYAML's libyaml C extension (the fastest available Python parser today) and 130–170× faster than pure-Python PyYAML — no intermediate Rust tree.
Against the other parsers, grouped by what each one actually produces — ratios are how many times longer the rival takes:
| corpus shape → | flat | deep | flow | block | combo | real | |
|---|---|---|---|---|---|---|---|
| Python objects | PyYAML CSafeLoader |
19.5× | 21.4× | 26.8× | 19.4× | 18.3× | 18.6× |
PyYAML SafeLoader (pure) |
171× | 163× | 167× | 166× | 133× | 156× | |
| Owned Rust tree | saphyr YamlOwned |
2.57× | 2.40× | 2.41× | 2.66× | 2.34× | 2.69× |
| yaml-rust2 | 2.97× | 2.62× | 2.56× | 2.97× | 2.47× | 3.04× | |
| Scan, nothing kept | libyaml (events) | 3.12× | 3.59× | 2.95× | 3.50× | 2.52× | 4.47× |
real is ~960 real third-party configs from SchemaStore; the rest are generated shapes. Every contender parses identical bytes in one process, interleaved so machine drift lands on all of them equally, and each group is timed only over documents every member of that group accepts — TurboYAML never gets to skip what it refuses while a full parser pays for it.
Ratios are compared only within a group. "Parse" means four different amounts of work here (discard the document; borrow from the caller's buffer; build an owned native tree; build CPython objects), and comparing across those is where most published YAML speedups come from. Absolute MB/s is not quoted because it varies ~2.6× with document shape for a single parser — the ratio is the portable number. Reproduce with cargo run -p turboyaml-bench --release --bin gen_corpus && cargo run -p turboyaml-bench --release --bin bench_native && python scripts/bench_python.py && python scripts/bench_report.py.
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).
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 /
# datetime.date / datetime.datetime / 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, accepted by load and load_all:
# strict=True → raise ParseError on duplicate mapping keys
# version=(1, 2) → resolve scalars with the YAML 1.2 core schema
# (default is 1.1, matching yaml.safe_load)
obj = turboyaml.load(text, version=(1, 2))
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
Choosing a schema
turboyaml.load(text) # YAML 1.1 (default) → 0123 is 83, yes is True
turboyaml.load(text, version=(1, 2)) # YAML 1.2 core → 0123 is 123, yes is 'yes'
A %YAML 1.1 / %YAML 1.2 directive at the top of the first document overrides
version, matching ruamel. The schema is then fixed for the whole stream; a later
directive that disagrees raises rather than switching mid-stream.
The versions differ in grammar as well as resolution — 1.2 relaxed plain scalars in
flow context, so [:ab], [a?b] and {: v} parse under version=(1, 2) and are
rejected under 1.1, exactly as every 1.1 parser rejects them.
Rust
let value = turboyaml::parse(input)?; // one document, YAML 1.1
let docs = turboyaml::parse_all(input)?; // Vec<Value>, one per document
use turboyaml::YamlVersion;
let v = turboyaml::parse_with(input, /* strict */ false, YamlVersion::V1_2)?;
Not supported
Block and flow collections, every scalar style (plain, quoted, block, and
multi-line), and multi-document streams are all supported — The full supported grammar is
in spec.md. What TurboYAML deliberately leaves out raises a typed
UnsupportedFeatureError, distinct from a malformed-input ParseError, so a fallback layer can be implemented:
- Tags —
!!str,!custom - explicit/complex keys (
? key) %TAGand reserved directives (%FOO) —%YAML 1.1and%YAML 1.2are supported
Correctness
The parser was extensively validated for correctness. Among internal fixed test cases and fuzz testing, it strives for 100% coverage of https://github.com/yaml/yaml-test-suite. It is also extensively verified against lots of real YAML from https://github.com/schemaStore/schemastore.
The basic test strategy is: run all YAML input through turboyaml and the big YAML parsers that define the ecosystem (PyYAML, libyaml, and ruamel at both 1.1 and 1.2) and compare if turboyaml's produced object tree matches the existing parsers.
Where YAML spec and ecosystem differ, turboyaml strives for parity with the ecosystem.
Status
Alpha, but extensively validated. Correctness rests on layered, independent checks, all run in CI:
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).
YAML's three authorities — the spec, the ecosystem, and the test suite — do not always agree. Where they don't, TurboYAML's position and the measurement behind it are in docs/edge_case_decisions.md. The short version: follow the ecosystem over the spec, generally follow ruamel where the ecosystem splits, and raise where any answer would contradict half of it.