rypipe documentation¶
rypipe is a format-agnostic columnar engine that turns byte streams into
Apache Arrow record batches. It separates format-specific parsing from
format-agnostic execution, so the same engine can parse XML, JSON, CSV,
HTML, or any other row-oriented format once you provide a small adapter.
rypipe itself does not ship parsers for any format. Adapters live in
separate packages. Install the engine plus the adapters you need.
Origin:
rypipewas born as the engine insidecrxml: a high-throughput Crystal Reports XML parser. Thecrxmladapter was the original idea and the first production user; the engine was then separated and abstracted intorypipeso any row-oriented format (CSV, JSON, XML, HTML, …) could reuse the sameSplitter+RecordParser+ColumnarSink+TableBuilder+ExecutionPlanmachinery.crxmlnow lives as a thin adapter crate (crxml-core) on top ofrypipe-core.
What rypipe is¶
- A Rust workspace with two crates:
rypipe-core: the generic engine (see Architecture overview for crate map).rypipe-python: PyO3 bindings and helper functions for adapter packages.- Zero-copy friendly: decoders emit borrowed strings; the engine copies only when necessary.
- GIL-free parsing: all heavy work runs outside Python's GIL.
- Memory-bounded and parallel by design.
What rypipe is not¶
- Not a full query engine. It handles projection, renaming, dropping, casting, filtering, and dictionary encoding, not joins, aggregations, or SQL.
- Not a one-size-fits-all parser. Each format needs a
RecordParser+Splitteradapter from a separate package.
Quick start¶
From Python¶
import rypipe
import my_adapter
# Format is inferred from the extension; mode defaults to parallel.
table = rypipe.read(
"data.myfmt",
fields={"amount": "float64"},
filter={"field": "status", "op": "==", "value": "active"},
)
print(table.num_rows, table.num_columns)
Pipeline API¶
Adapters that expose a rypipe.Adapter subclass give you a chainable pipeline
with automatic fusion of rename, drop, cast, and filter stages into the Rust
parse loop. Subclasses only implement read(path, **kwargs)::
from rypipe import RenameFields, DropFields, CastTypes, FilterRows
import my_adapter
source = my_adapter.MySource("data.myfmt")
df = (
source
| RenameFields({"old_name": "new_name"})
| DropFields(["internal_id"])
| CastTypes({"amount": float, "qty": int})
| FilterRows(field="status", op="==", value="active")
).to_dataframe()
From Rust¶
use rypipe_core::{ExecutionPlan, FieldType, Pipeline};
use my_adapter::{MySplitter, MyDecoder}; // separate adapter crate
let batch = Pipeline::new(MySplitter::new(), MyDecoder::new())
.with_plan(
ExecutionPlan::new()
.type_as("amount", FieldType::Float64)
.filter_eq("status", "active"),
)
.read_path("data.myfmt", false, false)?;
Guides¶
- Architecture: how the pieces fit together (start with Overview, then Engine, Columnar, Plan, Execution, Data flow, Storage, Optimizations).
- Why Python?: why rypipe is Rust core plus Python surface, not pure Rust : the data driven case for the hybrid.
- Python API: the
rypipepackage and_rypipehelpers. - Rust API: using
rypipe-coreand writing custom adapters. - Writing a format adapter: adding CSV, JSON, etc.
- Performance: benchmarks and tuning knobs.