Data input and output
Use an explicit factory for files, named columns, and NumPy arrays.
flow(source) recognizes supported tabular objects and protocols, but treats
ordinary iterables as supplied. It does not infer a file format from a path or
sample nested lists to decide whether they are tables.
Input matrix
| Entry point | Input | Output | Evaluation | Replayability | Extra |
|---|---|---|---|---|---|
flow(source) |
Iterable, Flow/Rows, recognized tabular object or protocol provider | Flow |
Lazy for ordinary iterables | Follows source | None for core |
rows(source) |
Records, Flow, recognized tabular input | Rows |
Lazy view | Follows source | None for core |
rows.from_csv(path) |
CSV path, text handle, or opener | string-valued dictionaries | Opens or reads on execution | Path/opener replayable; handle one-shot | None |
rows.scan_csv(path) |
CSV file | typed dictionaries from Arrow batches | File opens on execution | Reopens by path | arrow |
rows.from_jsonl(path) |
JSONL path, text/binary handle, or opener | dictionaries | Opens or reads on execution | Path/opener replayable; handle one-shot | None |
flow.from_columns / rows.from_columns |
Explicit mapping of equal-length columns | named record pipeline | Arrow table built at construction | Replays the retained table | arrow |
flow.from_numpy |
Explicit one- or two-dimensional array | Python scalars for 1D; named dictionaries for 2D | numpy.asarray at construction; values converted on execution |
Replays the retained array | data |
rows.from_numpy |
Explicit two-dimensional array | dictionaries with named columns | numpy.asarray at construction; rows converted on execution |
Replays the retained array | data |
flow.from_arrow / rows.from_arrow |
Arrow table, batch, reader or C stream provider | record pipeline | Adapter-dependent | Table/batch reusable; reader/stream one-shot | arrow |
flow.from_dataframe / rows.from_dataframe |
__dataframe__ provider |
record pipeline | Conversion deferred | Provider-dependent | data |
flow.from_polars / rows.from_polars |
Polars DataFrame/LazyFrame | record pipeline | LazyFrame collection deferred | Provider-dependent | polars |
flow.from_parquet / rows.from_parquet |
Parquet path/dataset | typed records | Scan opens on execution | Reopens by source | arrow |
rows.from_db(connect, query, ...) |
DB-API connection factory | dictionaries | Connects on execution | Reconnects through factory | Driver supplied by app |
rows.from_sqlite(database, query, ...) |
SQLite path or URI | dictionaries | Connects on execution | Reconnects by path | Standard library |
flow(source) recognizes concrete PyArrow, pandas, and Polars types only when
their package is already loaded. It does not import all optional packages to
probe every arbitrary object. Standard __arrow_c_stream__ and __dataframe__
providers are recognized directly; Arrow wins when an object exposes both.
CSV: compatibility and typed scan
from_csv() reads string cells with Python's CSV parser. scan_csv() uses
Arrow's typed batch reader.
rows.from_csv
Use this reader for string cells and Python csv dialect options.
from fpstreams import rows
paid = rows.from_csv("orders.csv", encoding="utf-8").where(status="paid").to_list()
- the header becomes dictionary keys;
- cells remain strings unless a later
castorparseconverts them; - a path opens only when the pipeline executes and reopens on later executions;
- an already-open text handle is caller-owned, starts at its current position, is not closed by fpstreams, and makes the pipeline one-shot;
- a zero-argument opener is called for each execution, and fpstreams closes every handle it returns;
- duplicate header names raise
DuplicateKeyErrorbefore the first row is emitted.
Pass an open handle to read it once, or an opener to read it again on each execution:
from io import StringIO
uploaded = StringIO("id,name\n1,Ada\n")
records = rows.from_csv(uploaded).to_list()
assert not uploaded.closed
# An opener makes the source replayable. Each returned handle is library-owned.
records = rows.from_csv(lambda: bucket.open_text("orders.csv"))
rows.scan_csv
Use the Arrow-backed scanner for typed, batched work and query projection.
from fpstreams import col, rows
paid = rows.scan_csv("orders.csv").where(col("status") == "paid").select("region", "amount")
Arrow owns CSV type inference and parsing rules. Projection can avoid materializing
unused columns. Install with pip install "fpstreams[arrow]".
JSON Lines
rows.from_jsonl reads one JSON value per physical line and requires each value
to be an object. The default maximum record size protects against a single
unbounded line; tune it explicitly for trusted larger records.
Duplicate object keys are rejected rather than silently choosing one value.
max_record_bytes=None disables the byte limit for trusted input.
Paths, handles, and openers follow the same ownership contract as from_csv. JSONL handles may
yield bytes or str. encoding decodes paths and binary handles; it also defines encoded-byte
accounting for a text handle when max_record_bytes is active.
Column mappings
Use from_columns when data already consists of independent named columns. The
mapping itself is not reinterpreted by flow(columns) or rows(columns); the
explicit factory constructs and retains a PyArrow table immediately.
from fpstreams import flow
records = flow.from_columns(
{"id": [1, 2], "status": ["open", "closed"]},
batch_size=1_024,
)
Column names must be unique, non-empty strings and all columns must have the
same length. The retained table is replayable. Install with
pip install "fpstreams[arrow]".
NumPy arrays
NumPy input is explicit. Ordinary flow(array) keeps an ndarray's normal
iterable behavior, and plain two-dimensional lists are never inspected to guess
that they are tables. flow.from_numpy accepts a one-dimensional array as a
scalar source or a two-dimensional array as named records. rows.from_numpy
accepts only the record form:
import numpy as np
from fpstreams import flow, rows
values = flow.from_numpy(np.asarray([1, 2, 3]))
assert values.to_list() == [1, 2, 3]
measurements = rows.from_numpy(
np.asarray([[1, 20.5], [2, 21.0]]),
columns=["sensor_id", "temperature"],
)
assert measurements.select("sensor_id").to_list() == [
{"sensor_id": 1},
{"sensor_id": 2},
]
from_numpy calls numpy.asarray once when the adapter is constructed. The
exact ndarray returned by asarray is retained and the conversion is not
repeated on later executions. Whether that array owns or shares its storage
follows NumPy's rules: mutations to the original input are visible only when
the retained result shares that storage. The source is replayable; Python
scalars or dictionary rows are produced only as each execution pulls them.
columns is invalid for a one-dimensional array. For a two-dimensional array,
omit it to use the string names "0", "1", and so on. Explicit names must be
unique, non-empty strings matching the array width.
Rows.to_numpy(*selectors, dtype=None, copy=None) always returns a
two-dimensional ndarray. Selectors use the normal Rows field, path, index,
expression, callable, and SelectionError rules. Without selectors, record
fields follow first-seen order and missing fields become None. Empty output
has shape (0, number_of_selected_columns); an empty source with no known
columns has shape (0, 0).
The copy parameter follows the installed NumPy version: None copies only when
needed, and True requests a distinct result. False is a strict no-copy request
on NumPy 2.x, which raises when the shape or dtype requires allocation; NumPy
1.x treats it as a best-effort preference. fpstreams does not promise zero-copy
conversion in general. Selectors, record alignment, dtype conversion, and
non-NumPy sources can all require allocation.
Arrow and the C stream protocol
Accepted Arrow sources include tables, record batches, record-batch readers,
and providers of __arrow_c_stream__. Use from_parquet for an Arrow Dataset.
- Tables and record batches are retained and can normally be evaluated again.
- A
RecordBatchReaderis one-shot. - A custom C stream is imported once at construction and is one-shot.
- Column-compatible filters, projections, casts, and aggregations may remain in
Arrow until a row-only operation requires Python-visible records;
Rows.explain()reports the retained prefix and its boundary. - Crossing into arbitrary Python callbacks materializes Python-visible values as required by the callback contract.
The C stream protocol lets libraries exchange Arrow data. Schema conversion, unsupported data types, and conversion to Python rows can still allocate or copy data.
Dataframe interchange and pandas
from_dataframe accepts the standard __dataframe__ protocol. Conversion is
deferred until execution where the provider permits it. Pandas indices are not
emitted as data columns; reset or copy the index into a column first when it is
part of the dataset.
from_pandas is an alias of from_dataframe. Install the data integration with
pip install "fpstreams[data]".
Polars
from_polars accepts a Polars DataFrame or LazyFrame. A LazyFrame is retained
until execution rather than collected during construction. Conversion uses Arrow
interoperability and therefore requires the polars extra.
Use to_polars_batches or polars_batches when downstream code can consume
batches and a complete DataFrame is unnecessary.
Parquet
Parquet adapters accept explicit projection and filtering supported by the Arrow dataset layer. Select only required columns before a Python callback so the scanner can avoid unnecessary I/O and decoding.
Directory and dataset replayability follows the underlying path or dataset object. Files are opened during execution, not when the plan is created.
Databases and SQLite
rows.from_db accepts a connection factory rather than an already-open global
connection. Each execution calls the factory, executes the query, derives field
names from the cursor description, and closes resources it owns.
import sqlite3
from fpstreams import rows
orders = rows.from_db(
lambda: sqlite3.connect("shop.db"),
"select id, amount from orders where status = ?",
parameters=("paid",),
batch_size=1_000,
)
rows.from_sqlite is the convenience adapter for a database path or URI. Query
parameters must use the database driver's binding mechanism; never interpolate
untrusted values into SQL text.
Output matrix
| Method | Result or effect | Materialization | Extra |
|---|---|---|---|
Flow.to_list, Flow.to_tuple, Flow.to_set |
Python container | Full result | None |
Rows.to_list |
List of records | Full result | None |
Rows.to_columns |
dictionary of column lists | Full result | None |
Rows.to_numpy |
two-dimensional NumPy array, optionally selected | Full result | data |
Flow.to_json |
JSON array file | Streams values to the destination | None |
Flow.to_csv |
scalar/sequence/mapping CSV file | Streams rows to file | None |
Rows.to_csv |
record CSV file | Streams rows; schema/header policy is explicit | None |
Rows.to_jsonl |
JSON object lines | Streams rows | None |
to_arrow |
Arrow table | Full result | arrow |
to_arrow_batches |
Arrow record batches | Batch materialization | arrow |
to_pandas |
pandas DataFrame | Full result | data |
to_polars |
Polars DataFrame | Full result | polars |
to_parquet |
Local Parquet file | Streams bounded row groups, then atomically publishes the file | arrow |
to_db / to_sqlite |
Inserted database rows | Batched side effect | Driver / standard library |
Rows-specific output signatures are available only after entering the Rows view.
For example, flow(records).rows().to_csv(...) exposes fieldnames, header, and
extra-field policies; Flow.to_csv(...) accepts arbitrary value shapes.
Spreadsheet safety
CSV intended for Excel, Sheets, or similar applications can treat leading
characters such as =, +, -, and @ as formulas. Set
spreadsheet_safe=True for untrusted text. fpstreams prefixes suspect cells with
a single quote. Leave it disabled for machine interchange where byte-level value
preservation is required.
Files, errors, and partial effects
Read adapters close the files and connections they open on completion, early termination, and failure. Writers may stream directly, replace a completed file, or use a database transaction; check the method's contract. A failed streaming write may leave partial output.
Common adapter failures include:
ImportErrorwith the required optional extra when an integration is absent;SelectionErrorfor a missing field or invalid selector;DuplicateKeyErrorfor duplicate JSON keys or a duplicate key under an error-on-duplicate dictionary policy;BufferLimitErrorwhen a configured record, batch, spill, fan-out, or output budget is exceeded;- the underlying parser, filesystem, Arrow, dataframe, or database exception when that boundary owns the failure.
Browser playground
The browser playground installs a pure-Python wheel into a Pyodide worker. It is meant for in-memory core examples. Browser security does not expose arbitrary local paths, normal process pools, or the CPython/Rust extension. Use the installed package for production I/O and native execution.