Rows
Rows[T] adds record operations to a lazy Flow: field selection, joins,
grouping, reshaping, and record I/O.
Enter it with flow(source).rows(), construct it with rows(source), or use one
of the adapter factories. A Rows view shares its Flow's plan and source
ownership; it does not inspect the source or make a one-shot input reusable.
Inspect field inputs (2.5.0)
The source version supports static field dependencies through the existing explanation object. It inspects the logical plan without opening the source:
from fpstreams import col, rows
query = rows([{"price": 4, "tax": 2}]).with_columns(
total=col("price") + col("tax")
).select("total")
metadata = query.explain().to_dict(include_field_dependencies=True)["field_dependencies"]
assert metadata["fields"]["total"] == [
{"source": "source_0", "field": "price"},
{"source": "source_0", "field": "tax"},
]
Unknown dependencies are None; an empty list means no declared field input.
This is logical field metadata. It does not trace individual records or permit
operator reordering. See static field dependencies
and the plan dictionary reference.
Entering and leaving Rows
Nonconflicting record methods can be called directly on Flow. select(),
with_columns(), rename(), cast(), fill_nulls(), drop_nulls(),
explode(), unnest(), unpivot(), and pivot() return Rows, while
group_by() returns GroupedRows.
Four relation-building names already have Flow meanings. Use .rows() before
them when you intend these relational forms:
| Flow call | Flow meaning | Explicit Rows call | Rows meaning |
|---|---|---|---|
drop(count) |
Skip leading items | rows().drop(*columns) |
Remove record fields |
join(separator) |
Join string representations | rows().join(other, ...) |
Relational join |
aggregate(...) |
Execute and return a dictionary | rows().aggregate(...) |
Build a lazy one-row relation |
where(predicate) |
Alias of filter |
rows().where(predicate, **equalities) |
Filter records, including field equalities |
Flow also has same-named output methods. Enter .rows() before to_csv(),
to_pandas(), or to_df() when you need the record writer or Rows-specific
materialization options.
Rows.map() and Rows.flat_map() return Flow because their result may no
longer be record-shaped. If they do emit records, calling a nonconflicting record
method enters Rows again.
Rows.to_flow() returns the exact underlying Flow without adding an operation or
copying data. Rows.explain() delegates to that Flow and does not consume the
source, so relational plans can be inspected without using private attributes.
Rows.concat(*sources) drains each source in order and stays lazy. It preserves
records exactly as received; unlike a dataframe union, it does not align fields,
fill missing columns, or infer a common dtype.
Creating rows
| Call | Source |
|---|---|
rows(source) |
Records, a Flow, supported tabular objects, or standard tabular-protocol providers |
rows.from_csv(path) |
Compatible CSV rows from a path, text handle, or opener |
rows.scan_csv(path) |
Typed, streaming Arrow CSV batches |
rows.from_jsonl(path) |
JSON objects from a path, text/binary handle, or opener |
rows.from_arrow(source) |
An Arrow Table, RecordBatch, RecordBatchReader, or __arrow_c_stream__ provider |
rows.from_columns(columns) |
Equal-length named columns retained as an Arrow table |
rows.from_numpy(array, columns=...) |
An explicit two-dimensional array converted lazily to named records |
rows.from_dataframe(frame) |
The dataframe interchange protocol |
rows.from_polars(frame) |
A Polars DataFrame or LazyFrame |
rows.from_parquet(source) |
Parquet data with optional projection and filtering |
rows.from_db(connect, query) |
A DB-API query using a connection factory |
rows.from_sqlite(database, query) |
A SQLite query |
Path, opener, and database adapters open their resources only when the pipeline executes. An already-open CSV or JSONL handle is caller-owned and one-shot; fpstreams neither rewinds nor closes it. A zero-argument opener is replayable, and fpstreams closes each handle it returns. Dataframe interchange conversion and Polars LazyFrame collection are also deferred. An Arrow C stream provider is the exception: its stream is imported once at construction and treated as one-shot. Adapter docstrings list the exact options and return types.
Use from_csv() when Python csv.DictReader compatibility and string-valued
cells matter. Use scan_csv() for typed inference and wide analytical scans.
Query projection follows conversion of every caller-visible CSV column. A
select() query still reports conversion errors in columns it does not return.
Pass ConvertOptions(include_columns=[...]) explicitly to restrict which columns
the CSV reader converts. Arrow's incremental reader is single-threaded and
freezes inferred types after its first byte block, so pass ReadOptions or
ConvertOptions.column_types when the default inference is not appropriate.
The primary flow(source) entry automatically recognizes PyArrow, pandas, and
Polars objects and standard __arrow_c_stream__ or __dataframe__ providers.
rows(source) uses the same dispatch path, including Arrow priority for an
object that implements both protocols. The named factories remain useful when
you want to make the adapter or its options explicit.
flow.scan_csv() and flow.from_parquet() return Flow, while the corresponding
Rows factories return the explicit relational view.
Bounded JSONL and spreadsheet-safe CSV
rows.from_jsonl() limits each physical line to 8 MiB by default. An oversized
record raises BufferLimitError before JSON parsing; binary inputs are checked
before decoding as well. Adjust the limit with max_record_bytes; use None
only when unlimited records from a trusted local file are intentional.
CSV writers preserve raw cells by default. Use
to_csv(..., spreadsheet_safe=True) for untrusted text that will be opened in a
spreadsheet. A string whose first non-whitespace character is =, +, -, or
@ receives a leading single quote. Non-string values are unchanged. CSV and
JSON/JSONL writers return None.
Since 2.2.0, this protection also covers header cells without changing
the keys used to read records. to_csv() and to_jsonl() also accept atomic=True
and if_exists="error"; a failed export preserves the old file. to_jsonl(default=...)
can serialize dates or other custom values. These additions were introduced in 2.2.0; see
file output for examples and limits.
Rows to_csv() is the record writer and exposes fieldnames, include_header,
and extrasaction. Flow to_csv() instead writes arbitrary items and accepts an
optional header.
Column collection and custom iteration (2.5.0)
to_columns() collects fields in encounter order and fills missing cells with
None:
from fpstreams import rows
assert rows([{"b": 1}, {"a": 2}]).to_columns() == {
"b": [1, None],
"a": [None, 2],
}
In the current source checkout, custom Rows or Flow iteration remains
authoritative. to_columns() closes the iterator it opens on normal completion
or conversion failure. A conversion error remains primary if closing also
fails; the cleanup error is attached as a note. A close failure without a
conversion error is propagated. This custom-iterator close fix is included in 2.5.0
and is not included in the 2.4.1 package.
Arrow and dataframe output
Rows accepts Arrow inputs and can also produce Arrow and dataframe outputs:
arrow_batches()emits bounded PyArrow RecordBatches lazily;to_arrow()materializes a PyArrow Table;__arrow_c_stream__()exports the standard Arrow PyCapsule stream protocol;to_pandas()andto_polars()materialize their respective dataframe types;to_parquet()writes bounded row groups and publishes the local file atomically.
Direct retained Arrow sources can reuse native batches. Joins and aggregates are evaluated first because their output has no equivalent linear source view.
In the 2.5.0 source, arrow_batches(schema=None) and explicit-schema calls
both respect custom Rows/Flow iteration. The native-path decision is made when
consumption starts, so an iteration method changed after construction but before
consumption is observed. Unmodified native inputs can still reuse buffers;
custom iteration uses the existing bounded row-to-batch conversion. Without a
schema, the first output batch determines the schema for later batches.
from contextlib import closing
import pyarrow as pa
from fpstreams import Rows, rows
class AdjustedRows(Rows):
def __iter__(self):
with closing(super().__iter__()) as values:
for row in values:
yield {"value": row["value"] + 10}
table = pa.table({"value": [1, 2, 3]})
source = AdjustedRows(rows.from_arrow(table).to_flow())
for schema in (None, table.schema):
batches = source.arrow_batches(batch_size=2, schema=schema).to_list()
assert [batch.num_rows for batch in batches] == [2, 1]
assert pa.Table.from_batches(batches).to_pylist() == [
{"value": 11}, {"value": 12}, {"value": 13},
]
Terminal consumption closes owned iterators on exhaustion, failure, or short
circuit. When manually iterating batches, close the returned iterator on early
exit. A custom iterator must also close any iterator it opens itself, as in the
closing block above. The default-schema iteration fix is included in 2.5.0.
Record transformations
rename(a="x", b="y") renames both top-level fields; names absent from a
record are ignored. A destination that duplicates another output field raises
ValueError when that record is consumed.
For ordinary Python dictionary rows, with_columns() makes a shallow top-level
copy: existing nested values retain their identities. For dataclass rows it
uses dataclasses.asdict(), which recursively copies values and can propagate
copy errors. Computed selectors read the original row, not earlier additions in
the same call. Other row types and columnar adapters may have different
conversion behavior.
Dotted string selectors split at each dot. Use expression indexing for a list index or a literal key containing a dot:
from fpstreams import col, rows
record = {"items": [{"id": 42}], "a": {"d.e": 7}}
assert rows([record]).select(
first=col("items")[0]["id"], dotted=col("a")["d.e"]
).to_list() == [{"first": 42, "dotted": 7}]
Nulls, missing fields, and NaN
In ordinary Python row comparisons, col("x") == None matches a present
None, and col("x").isin([1, 2, None]) includes None. This differs from
SQL's three-valued null comparisons. A missing x raises SelectionError in
these expressions. Explicit dataset filters supplied to an adapter follow that
adapter's semantics; do not assume a Python expression and an external filter
use the same null rules.
Rows.fill_nulls(x=value) fills both a missing x and a present None;
col("x").fill_null(value) first selects x, so a missing field raises
SelectionError. fillna and dropna are aliases of fill_nulls and
drop_nulls: for ordinary Python rows they handle None, not floating-point
NaN. Unlike pandas' similarly named methods, these aliases do not treat NaN as
missing.
In ordinary in-memory joins, None keys match one another. In a left join, a
right-hand field whose value is None cannot by itself distinguish a matching
row containing None from an unmatched row padded with None; retain a known
non-null right-side marker when that distinction matters.
For ordinary Python dictionary rows, grouping and joining NaN keys can depend on object identity: repeated use of one NaN object can match, while distinct NaN objects do not. Arrow and Polars adapters can materialize separate Python NaN objects, so grouping after conversion may differ. These examples describe Python row behavior and conversions, not a guarantee for every source, backend, or NaN type.
Safer joins
A duplicate lookup key can multiply records without warning. Declare the relationship you expect when duplicates would indicate bad data:
In the 2.5.0 in-memory Python join path, errors from a key's custom hash or equality operation
propagate unchanged. Unhashable keys raise TypeError.
customers = [
{"customer_id": 1, "name": "Ada"},
{"customer_id": 2, "name": "Lin"},
]
enriched = (
rows(orders)
.join(
customers,
on="customer_id",
how="left",
validate="m:1",
)
.to_list()
)
This many-to-one join permits several orders per customer and requires unique
customer keys. Use 1:m for a unique left side, 1:1 when both sides must
be unique, or m:m to permit duplicates on both sides. Partitioned joins use
the same checks, and errors name the duplicate side and key.
In the ordinary in-memory path, inner, left, semi, and anti joins index the
entire right input before reading the left; even an empty left input or a
downstream take(1) does not avoid that right-side read. The right input must
therefore be finite for these operations to produce output. Semi and anti joins
retain qualifying left records, including duplicates. Ordinary right and full
joins materialize both sides. Spill and other execution paths have their own
resource behavior.
Partitioning is governed by SpillLimits. Its defaults are:
| Limit | Default |
|---|---|
| Rows in one loaded partition | 100,000 |
| Serialized bytes in one loaded partition | 64 MiB |
| Matches for one join key | 100,000 |
| Total output rows | 1,000,000 |
| Recursive repartition levels | 3 |
Oversized partitions are repartitioned with a new deterministic salt. If skew
still exceeds the configured depth, or match, group-state, or output expansion
exceeds another limit, the operation raises BufferLimitError before loading an
unbounded bucket. Temporary files are removed. Pass a custom SpillLimits to
join(..., partitions=..., limits=...) or group_by(...).spill(limits=...).
Highly skewed or many-to-many input may exceed these limits even with spill enabled.
Null filtering and lookup failures (2.5.0)
The missing argument is a source-candidate addition and is not in 2.4.1.
For drop_nulls(), the default missing="legacy" treats missing selected fields
as None. A custom callable selector's SelectionError propagates unchanged. For a RowExpr,
errors raised by map() callbacks and directly supplied evaluators also
propagate, while missing generated fields are treated as None. Attribute
lookup TypeError is preserved as a SelectionError with its original cause;
an attribute getter's own SelectionError propagates unchanged. This applies
to string field and path lookups as well. A RowExpr subclass that overrides
__call__ is treated as a user callback. Errors from overloaded operators
propagate, including nested expression failures. Attribute AttributeError and
KeyError raised inside a mapping lookup may remain indistinguishable from a
missing field. This compatibility policy cannot validate arbitrary row protocols.
Use missing="raise" to propagate every selected lookup failure. None remains
null and how="any" / how="all" retain their left-to-right short circuiting,
so a later selector may never run. String and generated RowExpr lookups keep
their usual SelectionError and original exception in __cause__; explicit
callables propagate the exception object they raise. This policy disables the
direct-field optimization that treats missing keys as null. With no selectors,
both policies inspect the record's values as before.
To migrate a custom protocol, supply a callable that returns None for its
explicit absence signal and lets other failures propagate. For example, assume
record.lookup("value") raises the protocol's AbsentField only for absence.
def optional_value(record):
try:
return record.lookup("value")
except AbsentField:
return None
clean = rows(records).drop_nulls(optional_value, missing="raise")
An internal AttributeError or KeyError then propagates unchanged. If the
protocol uses the same exception for absence and internal failure, the callable
must use another reliable signal supplied by that protocol. missing="raise"
does not infer the difference. Existing calls keep missing="legacy" behavior;
migration is opt-in. Each reached selector is called once per row.
Methods
fpstreams.Rows
Bases: RowsIOMixin[T], Generic[T]
A lazy record pipeline with joins, grouping, and data-system adapters.
to_columns
Transpose rows into encounter-ordered, None-padded column lists.
Returns:
| Type | Description |
|---|---|
dict[str, list[Any]]
|
A field-to-list mapping with one aligned entry for every consumed row. |
to_pandas
Materialize all rows as a pandas DataFrame through bounded Arrow conversion.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
batch_size
|
int
|
Maximum source rows converted in each intermediate Arrow batch. |
65536
|
schema
|
Any
|
Optional Arrow schema fixing field order, types, and allowed columns. |
None
|
Returns:
| Type | Description |
|---|---|
Any
|
A pandas DataFrame containing all rows; the Rows pipeline is fully consumed. |
to_numpy
Materialize selected record values as a two-dimensional NumPy array.
Without selectors, mapping fields are aligned in first-seen order and missing fields
become None. With selectors, every selector follows the same field, index, path,
expression, and SelectionError behavior as the rest of Rows.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
*selectors
|
Selector
|
Optional selectors defining output columns in encounter order. |
()
|
dtype
|
Any
|
Optional dtype forwarded to NumPy conversion. |
None
|
copy
|
bool | None
|
NumPy copy policy: |
None
|
Returns:
| Type | Description |
|---|---|
Any
|
A two-dimensional ndarray with one row per consumed record. |
arrow_batches
Return a lazy Flow of bounded PyArrow RecordBatch objects.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
batch_size
|
int
|
Maximum source rows retained for one emitted RecordBatch. |
65536
|
schema
|
Any
|
Optional fixed schema; later fields outside it are rejected. |
None
|
Returns:
| Type | Description |
|---|---|
Flow[Any]
|
A Flow that closes its upstream iterator when exhausted, failed, or short-circuited. |
to_arrow
Materialize all rows as one PyArrow Table.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
batch_size
|
int
|
Maximum rows converted in each intermediate RecordBatch. |
65536
|
schema
|
Any
|
Optional schema used for conversion and for an empty result. |
None
|
Returns:
| Type | Description |
|---|---|
Any
|
A Table containing every row; direct Arrow sources may reuse native batches. |
polars_batches
Return a lazy Flow of Polars DataFrames converted from bounded Arrow batches.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
batch_size
|
int
|
Maximum source rows represented by each emitted DataFrame. |
65536
|
schema
|
Any
|
Optional Arrow schema fixing conversion fields and types. |
None
|
Returns:
| Type | Description |
|---|---|
Flow[Any]
|
A Flow that preserves batch order and inherits upstream cleanup behavior. |
to_polars
Materialize all rows as one Polars DataFrame through Arrow.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
batch_size
|
int
|
Maximum rows converted in each intermediate Arrow batch. |
65536
|
schema
|
Any
|
Optional Arrow schema fixing conversion fields and types. |
None
|
Returns:
| Type | Description |
|---|---|
Any
|
A non-rechunked Polars DataFrame containing the fully consumed pipeline. |
to_csv
to_csv(
path: str | PathLike[str],
*,
fieldnames: Iterable[str] | None = None,
encoding: str = "utf-8",
include_header: bool = True,
extrasaction: Literal["raise", "ignore"] = "raise",
spreadsheet_safe: bool = False,
atomic: bool = False,
if_exists: Literal["replace", "error"] = "replace",
) -> None
Consume rows into a CSV file incrementally and return None.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
path
|
str | PathLike[str]
|
Destination file opened for text overwrite. |
required |
fieldnames
|
Iterable[str] | None
|
Output order; inferred from the first row when omitted. |
None
|
encoding
|
str
|
Text encoding used to write the destination. |
'utf-8'
|
include_header
|
bool
|
Write fieldnames before data rows when true. |
True
|
extrasaction
|
Literal['raise', 'ignore']
|
Raise or ignore fields absent from fieldnames. |
'raise'
|
spreadsheet_safe
|
bool
|
Prefix formula-like cells, including headers, without changing record field lookup. |
False
|
atomic
|
bool
|
Publish after the writer and source close successfully. Writing or source-cleanup failures preserve the old target. |
False
|
if_exists
|
Literal['replace', 'error']
|
With atomic=True, replace the target or fail if it already exists. |
'replace'
|
to_jsonl
to_jsonl(
path: str | PathLike[str],
*,
encoding: str = "utf-8",
ensure_ascii: bool = False,
default: Callable[[Any], Any] | None = None,
atomic: bool = False,
if_exists: Literal["replace", "error"] = "replace",
) -> None
Consume rows into an overwritten file as one JSON object per line.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
path
|
str | PathLike[str]
|
Destination JSON Lines file. |
required |
encoding
|
str
|
Text encoding used to write the destination. |
'utf-8'
|
ensure_ascii
|
bool
|
Escape non-ASCII code points when true. |
False
|
default
|
Callable[[Any], Any] | None
|
Optional serializer called for objects the JSON encoder cannot handle. |
None
|
atomic
|
bool
|
Publish after the writer and source close successfully. Writing or source-cleanup failures preserve the old target. |
False
|
if_exists
|
Literal['replace', 'error']
|
With atomic=True, replace the target or fail if it already exists. |
'replace'
|
to_parquet
to_parquet(
path: str | PathLike[str],
*,
if_exists: Literal["error", "replace"] = "error",
batch_size: int = 65536,
schema: Any = None,
compression: Any = "zstd",
use_dictionary: Any = True,
write_statistics: Any = True,
writer_options: Mapping[str, Any] | None = None,
) -> int
Stream rows to a local temporary Parquet file and publish it atomically.
The destination changes only after all bounded row groups are written; failures remove the temporary file.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
path
|
str | PathLike[str]
|
Local destination path; URI-style paths are rejected. |
required |
if_exists
|
Literal['error', 'replace']
|
Error before consuming rows, or atomically replace the target. |
'error'
|
batch_size
|
int
|
Maximum rows converted and written per row group. |
65536
|
schema
|
Any
|
Optional Arrow schema, required to write an empty pipeline. Nonempty rows require at least one column to preserve their row count. |
None
|
compression
|
Any
|
Compression setting passed to PyArrow ParquetWriter. |
'zstd'
|
use_dictionary
|
Any
|
Dictionary-encoding setting passed to ParquetWriter. |
True
|
write_statistics
|
Any
|
Column-statistics setting passed to ParquetWriter. |
True
|
writer_options
|
Mapping[str, Any] | None
|
Extra ParquetWriter options except reserved arguments. |
None
|
Returns:
| Type | Description |
|---|---|
int
|
The number of source rows published. |
to_db
to_db(
connect: ConnectionFactory,
statement: str,
*,
parameters: Callable[[T], Any] | None = None,
batch_size: int = 1000,
) -> int
Submit mapped rows with DB-API executemany batches in one transaction.
Commit after all rows succeed or roll back on error; always close the iterator, cursor, and connection.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
connect
|
ConnectionFactory
|
Zero-argument factory for the transaction's connection. |
required |
statement
|
str
|
Statement passed to cursor.executemany(). |
required |
parameters
|
Callable[[T], Any] | None
|
Optional callable mapping each row to bound parameters. |
None
|
batch_size
|
int
|
Maximum parameter sets in each executemany() call. |
1000
|
Returns:
| Type | Description |
|---|---|
int
|
The number of source rows submitted, independent of cursor.rowcount. |
to_sqlite
to_sqlite(
database: str | PathLike[str],
table: str,
*,
if_exists: Literal[
"append", "fail", "replace"
] = "append",
conflict: Literal[
"error", "ignore", "replace"
] = "error",
columns: Iterable[str] | None = None,
schema: Mapping[str, str] | None = None,
batch_size: int = 1000,
timeout: float = 5.0,
uri: bool = False,
) -> int
Insert rows into a SQLite table in bounded batches and one transaction.
Replace-mode DDL and inserts roll back together; all owned resources close on every exit path. Table and column identifiers use SQLite's ASCII case rules; source record field lookup keeps the configured names. Duplicate aliases for one column raise DuplicateKeyError before inserts.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
database
|
str | PathLike[str]
|
SQLite path or URI passed to sqlite3.connect(). |
required |
table
|
str
|
Destination table identifier, validated and quoted as data. |
required |
if_exists
|
Literal['append', 'fail', 'replace']
|
Append, fail if present, or transactionally replace the table. |
'append'
|
conflict
|
Literal['error', 'ignore', 'replace']
|
Use ordinary INSERT, INSERT OR IGNORE, or INSERT OR REPLACE. |
'error'
|
columns
|
Iterable[str] | None
|
Optional ordered projection; extra source fields are omitted. |
None
|
schema
|
Mapping[str, str] | None
|
Optional mapping from field names to supported SQLite type names. |
None
|
batch_size
|
int
|
Maximum bindings in each executemany() call. |
1000
|
timeout
|
float
|
Seconds sqlite3 waits for a locked database. |
5.0
|
uri
|
bool
|
Interpret database as a SQLite URI when true. |
False
|
Returns:
| Type | Description |
|---|---|
int
|
Source records submitted, including records ignored by SQLite conflicts. |
from_csv
staticmethod
from_csv(
path: str
| PathLike[str]
| TextIO
| Callable[[], TextIO],
*,
encoding: str = "utf-8",
**format_parameters: Any,
) -> Rows[dict[str, Any]]
Read CSV rows lazily from a path, caller-owned handle, or owned opener.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
path
|
str | PathLike[str] | TextIO | Callable[[], TextIO]
|
A path reopened for every execution, an already-open text handle consumed once without being closed, or a zero-argument opener whose returned handle is closed after each execution. |
required |
encoding
|
str
|
Text encoding used only when fpstreams opens a path. |
'utf-8'
|
**format_parameters
|
Any
|
Keyword options forwarded to csv.DictReader, such as dialect, delimiter, or quoting. |
{}
|
Returns:
| Type | Description |
|---|---|
Rows[dict[str, Any]]
|
Lazy dictionaries keyed by the unique CSV header. Open handles can be consumed once; paths and opener functions can be read again. |
scan_csv
staticmethod
scan_csv(
path: str | PathLike[str],
*,
batch_size: int = 65536,
read_options: Any = None,
parse_options: Any = None,
convert_options: Any = None,
memory_pool: Any = None,
) -> Rows[dict[str, Any]]
Lazily scan typed CSV batches using Arrow conversion.
Unlike from_csv(), this Arrow reader infers non-string types.
PyArrow options can fix parsing and conversion behavior when inference is unsuitable.
The incremental Arrow reader is single-threaded and freezes inferred types after its
first byte block; use read_options or convert_options to control those choices.
Query projection runs after conversion of every caller-visible column. Selecting fewer
columns does not suppress conversion errors in other columns. Use convert_options
to restrict the reader's columns explicitly when that behavior is intended.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
path
|
str | PathLike[str]
|
Local CSV path reopened for each iteration. |
required |
batch_size
|
int
|
Maximum rows exposed by each retained Arrow batch. |
65536
|
read_options
|
Any
|
Optional |
None
|
parse_options
|
Any
|
Optional |
None
|
convert_options
|
Any
|
Optional |
None
|
memory_pool
|
Any
|
Optional PyArrow memory pool used by the reader. |
None
|
Returns:
| Type | Description |
|---|---|
Rows[dict[str, Any]]
|
Reusable typed rows. This adapter requires the |
from_jsonl
staticmethod
from_jsonl(
path: str
| PathLike[str]
| TextIO
| BinaryIO
| Callable[[], TextIO | BinaryIO],
*,
encoding: str = "utf-8",
max_record_bytes: int | None = 8 * 1024 * 1024,
) -> Rows[dict[str, Any]]
Read JSON objects lazily from a path, caller-owned handle, or owned opener.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
path
|
str | PathLike[str] | TextIO | BinaryIO | Callable[[], TextIO | BinaryIO]
|
A path reopened for every execution, an already-open text or binary handle consumed once without being closed, or a zero-argument opener whose returned handle is closed after each execution. |
required |
encoding
|
str
|
Encoding used for paths and binary handles, and for byte accounting on text handles. |
'utf-8'
|
max_record_bytes
|
int | None
|
Encoded-byte limit per line, or None for no limit. |
8 * 1024 * 1024
|
Returns:
| Type | Description |
|---|---|
Rows[dict[str, Any]]
|
Lazy dictionary rows. Open handles can be consumed once; paths and opener functions can be read again. Duplicate keys and non-object records raise errors during consumption. |
from_arrow
staticmethod
Adapt an Arrow object or C Stream provider to dictionary rows.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
source
|
Any
|
Reusable PyArrow Table/RecordBatch, one-shot RecordBatchReader, or an
object implementing |
required |
batch_size
|
int
|
Maximum rows converted from each Arrow batch slice. |
65536
|
Returns:
| Type | Description |
|---|---|
Rows[dict[str, Any]]
|
Lazy Rows; reader-backed inputs may be consumed only once and are closed afterward. |
from_columns
staticmethod
Adapt an explicit mapping of independent columns through a retained Arrow table.
from_numpy
staticmethod
Adapt a two-dimensional NumPy array to replayable dictionary rows.
NumPy conversion happens at construction, but each array row is converted to Python
scalar values only when consumed. An existing ndarray is retained by reference, while
other array-like inputs follow numpy.asarray conversion semantics.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
array
|
Any
|
Two-dimensional ndarray or array-like input accepted by |
required |
columns
|
Iterable[str] | None
|
Unique non-empty string names matching the array width. Defaults to
|
None
|
Returns:
| Type | Description |
|---|---|
Rows[dict[str, Any]]
|
Lazy, replayable Rows whose records follow the retained array's row order. |
from_dataframe
staticmethod
from_dataframe(
frame: Any,
*,
batch_size: int = 65536,
allow_copy: bool = True,
) -> Rows[dict[str, Any]]
Adapt an object implementing the dataframe interchange protocol through PyArrow.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
frame
|
Any
|
Object providing dataframe(), optionally with an Arrow C stream. |
required |
batch_size
|
int
|
Maximum rows converted from each Arrow batch. |
65536
|
allow_copy
|
bool
|
Permit interchange conversion to allocate copied buffers. |
True
|
Returns:
| Type | Description |
|---|---|
Rows[dict[str, Any]]
|
Lazy Rows that perform dataframe-to-Arrow conversion when iterated. |
from_polars
staticmethod
from_polars(
frame: Any,
*,
batch_size: int = 65536,
maintain_order: bool = True,
engine: Any = "auto",
) -> Rows[dict[str, Any]]
Adapt an eager Polars DataFrame or batch-collected LazyFrame to dictionary rows.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
frame
|
Any
|
Polars DataFrame or LazyFrame to slice or collect. |
required |
batch_size
|
int
|
Rows requested per eager slice or lazy collection batch. |
65536
|
maintain_order
|
bool
|
Preserve LazyFrame row order while collecting batches. |
True
|
engine
|
Any
|
Polars engine used only for LazyFrame batch collection. |
'auto'
|
Returns:
| Type | Description |
|---|---|
Rows[dict[str, Any]]
|
Lazy reusable Rows; a LazyFrame is collected again for each iteration. |
from_parquet
staticmethod
from_parquet(
source: Any,
*,
columns: Iterable[str] | None = None,
filter: Any = None,
batch_size: int = 65536,
use_threads: bool = True,
filesystem: Any = None,
partitioning: Any = None,
) -> Rows[dict[str, Any]]
Build reusable rows from a fresh PyArrow dataset scanner per iteration.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
source
|
Any
|
PyArrow Dataset or dataset source accepted by pyarrow.dataset(). |
required |
columns
|
Iterable[str] | None
|
Unique projected column names, or None for all columns. |
None
|
filter
|
Any
|
PyArrow dataset expression pushed into the scanner. |
None
|
batch_size
|
int
|
Maximum rows requested from each scanner batch. |
65536
|
use_threads
|
bool
|
Allow the PyArrow scanner to use worker threads. |
True
|
filesystem
|
Any
|
Optional PyArrow filesystem for resolving the source. |
None
|
partitioning
|
Any
|
Optional dataset partitioning specification. |
None
|
Returns:
| Type | Description |
|---|---|
Rows[dict[str, Any]]
|
Lazy dictionary rows with projection and filtering performed by PyArrow. |
from_db
staticmethod
from_db(
connect: ConnectionFactory,
query: str,
parameters: DBParameters = None,
*,
batch_size: int = 1000,
) -> Rows[dict[str, Any]]
Build a reiterable DB-API query source that owns its connections and cursors.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
connect
|
ConnectionFactory
|
Zero-argument factory called once per iteration for a new connection. |
required |
query
|
str
|
Statement executed by each newly opened cursor. |
required |
parameters
|
DBParameters
|
Optional mapping or positional values passed to cursor.execute(). |
None
|
batch_size
|
int
|
Maximum rows requested by each cursor.fetchmany() call. |
1000
|
Returns:
| Type | Description |
|---|---|
Rows[dict[str, Any]]
|
Lazy rows that close the cursor and connection on exhaustion, error, or early stop. |
from_sqlite
staticmethod
from_sqlite(
database: str | PathLike[str],
query: str,
parameters: DBParameters = None,
*,
batch_size: int = 1000,
timeout: float = 5.0,
uri: bool = False,
) -> Rows[dict[str, Any]]
Build a reiterable SQLite query source that owns one connection per iteration.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
database
|
str | PathLike[str]
|
SQLite path or URI passed to sqlite3.connect(). |
required |
query
|
str
|
Statement executed by each newly opened cursor. |
required |
parameters
|
DBParameters
|
Optional mapping or positional values passed to cursor.execute(). |
None
|
batch_size
|
int
|
Maximum rows requested by each cursor.fetchmany() call. |
1000
|
timeout
|
float
|
Seconds sqlite3 waits for a locked database. |
5.0
|
uri
|
bool
|
Interpret database as a SQLite URI when true. |
False
|
Returns:
| Type | Description |
|---|---|
Rows[dict[str, Any]]
|
Lazy dictionary rows that close their cursor and connection after iteration. |
to_flow
Return the underlying Flow without copying data or changing the plan.
Returns:
| Type | Description |
|---|---|
Flow[T]
|
The same lazy Flow that owns this Rows view's source and operations. |
explain
Describe this record pipeline's execution plan without consuming it.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
terminal
|
TerminalName
|
Terminal operation included in engine selection and validation. |
'iterate'
|
Returns:
| Type | Description |
|---|---|
PlanExplanation
|
The same structured explanation produced by the underlying Flow. |
to_list
Execute the record pipeline and collect its rows.
Returns:
| Type | Description |
|---|---|
list[T]
|
A list containing the consumed results in encounter order. |
run_with_report
Run one eager Rows terminal and return its value with query-owned metrics.
count
Consume the pipeline and count every emitted row.
Returns:
| Type | Description |
|---|---|
int
|
The number of rows remaining after all lazy transformations. |
with_engine
Return equivalent lazy Rows requesting auto, Python, or native Flow execution.
concat
Emit these rows followed by each supplied record source in order.
Concatenation is lazy and preserves every input record as-is. It does not align fields, fill missing values, or infer a common schema.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
*others
|
Iterable[T] | Flow[T] | Rows[T]
|
Rows, Flow, or record iterables opened only after earlier inputs finish. |
()
|
Returns:
| Type | Description |
|---|---|
Rows[T]
|
A Rows view over the ordered concatenation, or this same view when no source is given. |
first
Return the first row and close upstream without requesting an unnecessary tail.
Returns:
| Type | Description |
|---|---|
T
|
The first emitted row. |
Raises:
| Type | Description |
|---|---|
EmptyFlowError
|
If the pipeline emits no rows. |
last
Consume the pipeline and return its final row, raising EmptyFlowError when empty.
Returns:
| Type | Description |
|---|---|
T
|
The last emitted row. |
take
Return a lazy prefix that stops and closes upstream after at most count rows.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
count
|
int
|
Nonnegative maximum number of rows to emit. |
required |
Returns:
| Type | Description |
|---|---|
Rows[T]
|
New Rows preserving encounter order; zero emits nothing. |
skip
Return lazy rows after discarding the first count upstream items.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
count
|
int
|
Nonnegative number of rows to consume without emitting. |
required |
Returns:
| Type | Description |
|---|---|
Rows[T]
|
New Rows containing the remaining encounter-ordered rows. |
unique_by
Keep the first row for each distinct selected key in encounter order.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
selector
|
Selector
|
Field, path, index, expression, or callable producing a hashable key. |
required |
Returns:
| Type | Description |
|---|---|
Rows[T]
|
Lazy Rows whose later duplicate keys are omitted. |
filter
Keep rows for which predicate returns a truthy result.
The predicate runs lazily in encounter order, and the parent Rows pipeline remains unchanged.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
predicate
|
Callable[[T], bool]
|
Callable evaluated once for each upstream row reached. |
required |
Returns:
| Type | Description |
|---|---|
Rows[T]
|
New lazy Rows containing only matching rows. |
map
Map rows into an ordinary Flow whose output may have any shape.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
function
|
Callable[[T], R]
|
Lazily transforms each row into one output value. |
required |
Returns:
| Type | Description |
|---|---|
Flow[R]
|
A Flow of transformed values. Call row operations on that Flow to re-enter Rows. |
flat_map
Map rows to iterables and flatten them into an ordinary Flow.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
function
|
Callable[[T], Iterable[R]]
|
Lazily transforms each row into zero or more output values. |
required |
Returns:
| Type | Description |
|---|---|
Flow[R]
|
A Flow of flattened values. Call row operations on that Flow to re-enter Rows. |
where
Require the optional predicate and every named field equality.
Named fields are compiled once, then selected lazily from each consumed row.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
predicate
|
Callable[[T], bool] | None
|
Optional callable that must return truthy for a row. |
None
|
**equalities
|
Any
|
Top-level or dotted field paths mapped to required values. |
{}
|
Returns:
| Type | Description |
|---|---|
Rows[T]
|
New lazy Rows containing rows that satisfy all supplied conditions. |
with_columns
Copy each row and add or replace fields evaluated against the original row.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
**columns
|
Selector
|
Output field names mapped to selectors or RowExpr values. |
{}
|
Returns:
| Type | Description |
|---|---|
Rows[dict[str, Any]]
|
Lazy dictionary Rows; computed columns do not observe earlier additions. |
rename
Rename top-level fields while rejecting collisions in each output record.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
**columns
|
str
|
Existing field names mapped to nonempty destination names. |
{}
|
Returns:
| Type | Description |
|---|---|
Rows[dict[str, Any]]
|
Lazy copied dictionaries; unmapped fields retain their names and order. |
drop
Copy each row without the named top-level fields.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
*columns
|
str
|
Field names to omit; absent names are ignored. |
()
|
Returns:
| Type | Description |
|---|---|
Rows[dict[str, Any]]
|
Lazy dictionary Rows preserving the order of retained fields. |
cast
Convert existing named fields with one callable per field.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
**columns
|
Callable[[Any], Any]
|
Field names mapped to callable value converters. |
{}
|
Returns:
| Type | Description |
|---|---|
Rows[dict[str, Any]]
|
Lazy copied dictionaries; a missing field raises SelectionError when consumed. |
fill_nulls
Replace missing or None named fields with constants or RowExpr results.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
**replacements
|
object
|
Field names mapped to literal values or row expressions. |
{}
|
Returns:
| Type | Description |
|---|---|
Rows[dict[str, Any]]
|
Lazy copied dictionaries; non-None existing values are preserved. |
drop_nulls
drop_nulls(
*selectors: Selector,
how: Literal["any", "all"] = "any",
missing: Literal["legacy", "raise"] = "legacy",
) -> Rows[T]
Drop rows according to None values in selected fields or the whole record.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
*selectors
|
Selector
|
Fields to inspect; omitted means every field in each record. |
()
|
how
|
Literal['any', 'all']
|
"any" drops on one null; "all" requires every inspected value to be null. |
'any'
|
missing
|
Literal['legacy', 'raise']
|
"legacy" retains historical missing-field handling. "raise" propagates lookup failures; use a callable returning None to define actual absence for custom row protocols. None values remain null. |
'legacy'
|
Returns:
| Type | Description |
|---|---|
Rows[T]
|
Lazy Rows using the selected null and lookup-failure policies. |
explode
explode(
selector: Selector,
*,
into: str | None = None,
outer: bool = False,
) -> Rows[dict[str, Any]]
Expand a selected iterable into one copied row per element.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
selector
|
Selector
|
Selector returning a non-string iterable or None. |
required |
into
|
str | None
|
Output field name; required for non-top-level selectors. |
None
|
outer
|
bool
|
Emit one row with None when the selected value is None or empty. |
False
|
Returns:
| Type | Description |
|---|---|
Rows[dict[str, Any]]
|
Lazy flattened dictionary Rows that close upstream on downstream stop. |
unnest
Replace one top-level nested record with its fields.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
column
|
str
|
Non-dotted field name containing a supported record-like value. |
required |
prefix
|
str
|
Text prepended to every promoted nested field. |
''
|
Returns:
| Type | Description |
|---|---|
Rows[dict[str, Any]]
|
Lazy copied dictionaries; output-name collisions raise DuplicateKeyError. |
unpivot
unpivot(
*columns: str,
names_to: str = "variable",
values_to: str = "value",
) -> Rows[dict[str, Any]]
Convert selected top-level fields from wide form into name/value rows.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
*columns
|
str
|
Unique fields expanded in the given order. |
()
|
names_to
|
str
|
Noncolliding output field for each former column name. |
'variable'
|
values_to
|
str
|
Noncolliding output field for each former column value. |
'value'
|
Returns:
| Type | Description |
|---|---|
Rows[dict[str, Any]]
|
Lazy Rows emitting len(columns) records per input row. |
pivot
pivot(
*,
index: Selector | tuple[Selector, ...],
columns: Selector,
values: Selector,
aggregate: str | Callable[[Any, Any], Any] = "error",
fill: Any = None,
) -> Rows[dict[str, Any]]
Materialize long-form rows into encounter-ordered wide records.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
index
|
Selector | tuple[Selector, ...]
|
Selector or selector tuple defining each output row and its key fields. |
required |
columns
|
Selector
|
Selector whose values become dynamic output field names. |
required |
values
|
Selector
|
Selector producing each pivot cell value. |
required |
aggregate
|
str | Callable[[Any, Any], Any]
|
Duplicate-cell policy: error, first, last, sum, or a reducer callable. |
'error'
|
fill
|
Any
|
Value inserted for missing cells among discovered columns. |
None
|
Returns:
| Type | Description |
|---|---|
Rows[dict[str, Any]]
|
Lazy pipeline that builds the full pivot only when consumed. |
select
Project positional and named selectors into new dictionaries.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
*selectors
|
str | int
|
String paths or integer indexes; output names are derived automatically. |
()
|
**named
|
Selector
|
Explicit output names mapped to any supported selector. |
{}
|
Returns:
| Type | Description |
|---|---|
Rows[dict[str, Any]]
|
Lazy projected Rows; duplicate derived or explicit names are rejected immediately. |
sort_by
sort_by(
selector: Selector,
*,
reverse: bool = False,
buffer_size: int | None = None,
tempdir: str | PathLike[str] | None = None,
) -> Rows[T]
Sort rows by a selected key, in memory or through bounded external runs.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
selector
|
Selector
|
Field, path, index, expression, or callable producing the sort key. |
required |
reverse
|
bool
|
Emit descending order when true. |
False
|
buffer_size
|
int | None
|
None for full in-memory sort, or positive rows per spilled run. |
None
|
tempdir
|
str | PathLike[str] | None
|
Parent directory for automatically cleaned external-sort files. |
None
|
Returns:
| Type | Description |
|---|---|
Rows[T]
|
Lazy stably sorted Rows. |
external_sort_by
external_sort_by(
selector: Selector,
*,
reverse: bool = False,
buffer_size: int = 100000,
tempdir: str | PathLike[str] | None = None,
) -> Rows[T]
Sort rows stably with bounded in-memory runs and temporary files.
Each run holds at most buffer_size rows; the lazy merge closes upstream and removes temporary files after completion, failure, or downstream short-circuit.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
selector
|
Selector
|
Field, path, index, expression, or callable producing the sort key. |
required |
reverse
|
bool
|
Emit descending order when true. |
False
|
buffer_size
|
int
|
Positive maximum rows held in each sorted run. |
100000
|
tempdir
|
str | PathLike[str] | None
|
Parent directory for automatically cleaned run files. |
None
|
Returns:
| Type | Description |
|---|---|
Rows[T]
|
Lazy externally sorted Rows. |
aggregate
Run named Aggregators and return a one-row pipeline.
The computation is deferred and produces a Rows pipeline containing one result record.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
**aggregations
|
Collector[Any, Any, Any]
|
Named collectors evaluated during the same traversal. |
{}
|
Returns:
| Type | Description |
|---|---|
Rows[dict[str, Any]]
|
A lazy one-row pipeline containing the named results. |
group_by
Describe grouped aggregation by positional and/or explicitly named selectors.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
*selectors
|
Selector
|
Keys named from field paths or as key_N for other selector types. |
()
|
**named
|
Selector
|
Explicit output key names mapped to supported selectors. |
{}
|
Returns:
| Type | Description |
|---|---|
GroupedRows[T]
|
GroupedRows configuration; no source rows are read until aggregate() is consumed. |
group_by_sorted
Group adjacent records whose selected keys are already ascending.
Keys must be exact int, str, bytes, or nonempty flat tuples of these types. Their shape must stay fixed. Type and order checks cover consumed rows, including one row of lookahead. No sorting is performed.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
*selectors
|
Selector
|
Keys named with the same rules as group_by(). |
()
|
**named
|
Selector
|
Explicit output key names and selectors. |
{}
|
Returns:
| Type | Description |
|---|---|
GroupedRows[T]
|
A deferred grouping using current aggregate state plus one lookahead row. Aggregators that collect values can retain additional state. Spill is unsupported. |
join_sorted
join_sorted(
other: Iterable[Any] | Flow[Any] | Rows[Any],
*,
on: JoinSelector | None = None,
left_on: JoinSelector | None = None,
right_on: JoinSelector | None = None,
how: str = "inner",
suffix: str = "_right",
validate: JoinValidation = "m:m",
max_right_group_rows: int = 100000,
max_matches_per_left: int = 100000,
max_output_rows: int = 1000000,
) -> Rows[dict[str, Any]]
Join ascending inputs while buffering one right key group.
Keys must be exact int, str, bytes, or flat tuples of these types. Inner/left output columns come from the first right record; later new columns fail. Ordering and cardinality checks cover consumed rows only. Finite row budgets raise BufferLimitError before the excess output. Added in version 2.2.0.
join
join(
other: Iterable[Any] | Flow[Any] | Rows[Any],
*,
on: JoinSelector | None = None,
left_on: JoinSelector | None = None,
right_on: JoinSelector | None = None,
how: str = "inner",
suffix: str = "_right",
validate: JoinValidation = "m:m",
partitions: int | None = None,
tempdir: str | PathLike[str] | None = None,
limits: SpillLimits | None = None,
) -> Rows[dict[str, Any]]
Join this record pipeline with another source.
Joins are lazy and preserve stable input order. Inner, left, semi, and anti joins stream the left source after indexing the right source. Right and full joins materialize both sides. Set partitions to use bounded-memory hash partitioning through temporary files.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
other
|
Iterable[Any] | Flow[Any] | Rows[Any]
|
The record iterable, Flow, or Rows pipeline to join. |
required |
on
|
JoinSelector | None
|
A selector used for both left and right keys. |
None
|
left_on
|
JoinSelector | None
|
The left key selector when the two sides use different fields. |
None
|
right_on
|
JoinSelector | None
|
The right key selector when the two sides use different fields. |
None
|
how
|
str
|
One of inner, left, right, full, semi, or anti. |
'inner'
|
suffix
|
str
|
Text appended to conflicting right-side field names. |
'_right'
|
validate
|
JoinValidation
|
Expected key cardinality. 1:m requires unique left keys, m:1 requires unique right keys, 1:1 requires both, and m:m permits duplicates on both sides. |
'm:m'
|
partitions
|
int | None
|
Number of hash partitions for bounded-memory execution. Must be between 2 and 256. |
None
|
tempdir
|
str | PathLike[str] | None
|
Parent directory for temporary partition files. Requires partitions. |
None
|
limits
|
SpillLimits | None
|
Finite partition, match, and output budgets for spilled execution. |
None
|
Returns:
| Type | Description |
|---|---|
Rows[dict[str, Any]]
|
Lazy dictionary Rows that execute the selected in-memory or spilled join when consumed. |
Raises:
| Type | Description |
|---|---|
ValueError
|
If selectors, modes, partition options, or key cardinality are invalid. |
TypeError
|
If a key is unhashable or spilled data cannot be serialized. |
DuplicateKeyError
|
If suffixing would create an ambiguous output field. |
BufferLimitError
|
If spilled execution exceeds a configured resource budget. |
GroupedRows
group_by() returns a grouped plan. Call aggregate() directly, or call
spill() first to use partitioned temporary storage.
Ordinary Python grouping is global, not adjacent: keys in separated rows can
belong to the same group. In ordinary dictionary-row samples a None key is
kept, while a missing selected key raises SelectionError. group_by_sorted()
instead groups adjacent ascending keys and applies the constraints below.
In the 2.5.0 in-memory Python grouping path, errors from a key's custom hash or equality
operation propagate unchanged. Unhashable keys raise TypeError.
Since 2.2.0, Python grouping uses the current selector if a source or callback changes its code or closure. The previous field shortcut could keep using an old field and merge distinct groups.
Grouping with one collector also uses the current step after truth-testing a
custom done result. Temporary states and unused keys are released at the same
points as in the general collector path, including when the output iterator is
closed early. Release callbacks can therefore affect the next row or finisher as
expected.
fpstreams.tabular.GroupedRows
Bases: Generic[T]
A deferred grouping that chooses in-memory or partitioned aggregation.
spill
spill(
partitions: int = 32,
*,
tempdir: str | PathLike[str] | None = None,
limits: SpillLimits | None = None,
) -> GroupedRows[T]
Return grouping configured to aggregate through bounded temporary partitions.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
partitions
|
int
|
Hash-partition count from 2 through 256. |
32
|
tempdir
|
str | PathLike[str] | None
|
Parent directory for automatically removed spill files. |
None
|
limits
|
SpillLimits | None
|
Partition, group-state, output, and repartition budgets. |
None
|
Returns:
| Type | Description |
|---|---|
GroupedRows[T]
|
A new GroupedRows configuration; call aggregate() to obtain a lazy pipeline. |
aggregate
Compute named aggregations independently for each group.
Grouping and aggregation remain deferred until the returned Rows pipeline is consumed.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
**aggregations
|
Collector[Any, Any, Any]
|
Named collectors evaluated during the same traversal. |
{}
|
Returns:
| Type | Description |
|---|---|
Rows[dict[str, Any]]
|
A lazy row pipeline containing one aggregate record per group. |
Grouping sorted records
group_by_sorted() uses the same key names and .aggregate() API as group_by().
The input must already be ascending by exact builtin int, str, bytes, or nonempty
flat tuple keys with a fixed type at each position. It checks consumed rows and
one row of lookahead; an unordered or unsupported key raises during consumption.
from fpstreams import agg, flow
result = (
flow([{"region": "eu", "amount": 2}, {"region": "eu", "amount": 3}])
.rows().group_by_sorted("region").aggregate(total=agg.sum("amount"))
.to_list()
)
assert result == [{"region": "eu", "total": 5}]
Added in 2.2.0. It buffers no
complete group. Aggregators that collect values still grow their own state;
.spill() raises ValueError in this mode. Ordinary group_by() is unchanged.
Joining sorted records
join_sorted(other, on=..., how="inner", validate="m:m") accepts ascending
inputs and supports inner, left, semi, and anti joins. It preserves left order
and the order of matching right records. Both sides must contain records,
including semi and anti joins. The supported key types are the same as
group_by_sorted().
The keyword-only budgets are max_right_group_rows=100_000,
max_matches_per_left=100_000, and max_output_rows=1_000_000. Each must be a
positive integer. Exceeding a budget raises BufferLimitError; already yielded
rows remain yielded. Semi joins enumerate at most one match per left row;
anti output does not count as a match. The right-group limit applies to every mode.
Inner and left joins take the right column list from the first right record.
Later records may omit columns but cannot introduce new ones. This restriction
allows output to start without scanning the entire right source. Suffixes and
shared key columns follow ordinary join() rules. Validation supports m:m,
m:1, 1:m, and 1:1, and checks the consumed prefix. Neither method infers sorting
or changes ordinary join() execution. This API was added in 2.2.0.
Like merge_sorted(), a sorted join acquires source iterators in its composed
branches before pulling rows. It rejects iterator aliases and closes acquired
iterators once; source-opening callbacks therefore run before key callbacks.
Before emitting a match, it buffers the matching right key group and may read
the first row of the next group. A short-circuited consumer only checks ordering
and cardinality for the rows actually consumed, including any lookahead.