fpstreams 2.2.0__tar.gz → 2.3.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {fpstreams-2.2.0 → fpstreams-2.3.0}/CHANGELOG.md +46 -1
- {fpstreams-2.2.0 → fpstreams-2.3.0}/PKG-INFO +20 -1
- {fpstreams-2.2.0 → fpstreams-2.3.0}/README.md +19 -0
- {fpstreams-2.2.0 → fpstreams-2.3.0}/pyproject.toml +1 -1
- {fpstreams-2.2.0 → fpstreams-2.3.0}/rust/Cargo.lock +1 -1
- {fpstreams-2.2.0 → fpstreams-2.3.0}/rust/Cargo.toml +1 -1
- {fpstreams-2.2.0 → fpstreams-2.3.0}/src/fpstreams/__init__.py +3 -2
- {fpstreams-2.2.0 → fpstreams-2.3.0}/src/fpstreams/execution/_rows_fusion.py +50 -0
- {fpstreams-2.2.0 → fpstreams-2.3.0}/src/fpstreams/execution/sorted_streams.py +22 -1
- {fpstreams-2.2.0 → fpstreams-2.3.0}/src/fpstreams/execution/sync_ops.py +39 -16
- {fpstreams-2.2.0 → fpstreams-2.3.0}/src/fpstreams/planning/explain.py +15 -1
- {fpstreams-2.2.0 → fpstreams-2.3.0}/src/fpstreams/planning/native.py +75 -35
- {fpstreams-2.2.0 → fpstreams-2.3.0}/src/fpstreams/planning/sync.py +3 -0
- {fpstreams-2.2.0 → fpstreams-2.3.0}/src/fpstreams/storage/spill_store.py +5 -1
- {fpstreams-2.2.0 → fpstreams-2.3.0}/src/fpstreams/streams/async_flow.py +94 -2
- {fpstreams-2.2.0 → fpstreams-2.3.0}/src/fpstreams/streams/flow.py +98 -40
- {fpstreams-2.2.0 → fpstreams-2.3.0}/src/fpstreams/tabular/__init__.py +3 -1
- {fpstreams-2.2.0 → fpstreams-2.3.0}/src/fpstreams/tabular/factory.py +5 -13
- {fpstreams-2.2.0 → fpstreams-2.3.0}/LICENSE +0 -0
- {fpstreams-2.2.0 → fpstreams-2.3.0}/rust/build.rs +0 -0
- {fpstreams-2.2.0 → fpstreams-2.3.0}/rust/src/common.rs +0 -0
- {fpstreams-2.2.0 → fpstreams-2.3.0}/rust/src/float/affine_pair.rs +0 -0
- {fpstreams-2.2.0 → fpstreams-2.3.0}/rust/src/float/endpoints.rs +0 -0
- {fpstreams-2.2.0 → fpstreams-2.3.0}/rust/src/float.rs +0 -0
- {fpstreams-2.2.0 → fpstreams-2.3.0}/rust/src/integer/endpoints.rs +0 -0
- {fpstreams-2.2.0 → fpstreams-2.3.0}/rust/src/integer.rs +0 -0
- {fpstreams-2.2.0 → fpstreams-2.3.0}/rust/src/lib.rs +0 -0
- {fpstreams-2.2.0 → fpstreams-2.3.0}/rust/src/numeric_mean.rs +0 -0
- {fpstreams-2.2.0 → fpstreams-2.3.0}/rust/src/numpy_export.rs +0 -0
- {fpstreams-2.2.0 → fpstreams-2.3.0}/rust/src/numpy_group/buffer.rs +0 -0
- {fpstreams-2.2.0 → fpstreams-2.3.0}/rust/src/numpy_group.rs +0 -0
- {fpstreams-2.2.0 → fpstreams-2.3.0}/rust/src/pair/expr.rs +0 -0
- {fpstreams-2.2.0 → fpstreams-2.3.0}/rust/src/pair/prefix.rs +0 -0
- {fpstreams-2.2.0 → fpstreams-2.3.0}/rust/src/pair/row_filter.rs +0 -0
- {fpstreams-2.2.0 → fpstreams-2.3.0}/rust/src/pair/unique.rs +0 -0
- {fpstreams-2.2.0 → fpstreams-2.3.0}/rust/src/pair/value_filter.rs +0 -0
- {fpstreams-2.2.0 → fpstreams-2.3.0}/rust/src/pair/value_map.rs +0 -0
- {fpstreams-2.2.0 → fpstreams-2.3.0}/rust/src/pair.rs +0 -0
- {fpstreams-2.2.0 → fpstreams-2.3.0}/rust/src/pivot.rs +0 -0
- {fpstreams-2.2.0 → fpstreams-2.3.0}/rust/src/records.rs +0 -0
- {fpstreams-2.2.0 → fpstreams-2.3.0}/rust/src/relational/adapters/namedtuple.rs +0 -0
- {fpstreams-2.2.0 → fpstreams-2.3.0}/rust/src/relational/adapters.rs +0 -0
- {fpstreams-2.2.0 → fpstreams-2.3.0}/rust/src/relational/global_numeric.rs +0 -0
- {fpstreams-2.2.0 → fpstreams-2.3.0}/rust/src/relational/group_numeric.rs +0 -0
- {fpstreams-2.2.0 → fpstreams-2.3.0}/rust/src/relational/group_pair_expr.rs +0 -0
- {fpstreams-2.2.0 → fpstreams-2.3.0}/rust/src/relational/join_callable/many.rs +0 -0
- {fpstreams-2.2.0 → fpstreams-2.3.0}/rust/src/relational/join_callable/unique.rs +0 -0
- {fpstreams-2.2.0 → fpstreams-2.3.0}/rust/src/relational/join_callable.rs +0 -0
- {fpstreams-2.2.0 → fpstreams-2.3.0}/rust/src/relational/join_exact.rs +0 -0
- {fpstreams-2.2.0 → fpstreams-2.3.0}/rust/src/relational/join_i64.rs +0 -0
- {fpstreams-2.2.0 → fpstreams-2.3.0}/rust/src/relational.rs +0 -0
- {fpstreams-2.2.0 → fpstreams-2.3.0}/rust/src/relational_fixed/composite.rs +0 -0
- {fpstreams-2.2.0 → fpstreams-2.3.0}/rust/src/relational_fixed/global_multi/same_field.rs +0 -0
- {fpstreams-2.2.0 → fpstreams-2.3.0}/rust/src/relational_fixed/global_multi.rs +0 -0
- {fpstreams-2.2.0 → fpstreams-2.3.0}/rust/src/relational_fixed/group_multi.rs +0 -0
- {fpstreams-2.2.0 → fpstreams-2.3.0}/rust/src/relational_fixed/single.rs +0 -0
- {fpstreams-2.2.0 → fpstreams-2.3.0}/rust/src/relational_fixed.rs +0 -0
- {fpstreams-2.2.0 → fpstreams-2.3.0}/rust/src/scalar_sort.rs +0 -0
- {fpstreams-2.2.0 → fpstreams-2.3.0}/rust/src/scalar_unique.rs +0 -0
- {fpstreams-2.2.0 → fpstreams-2.3.0}/rust/src/select.rs +0 -0
- {fpstreams-2.2.0 → fpstreams-2.3.0}/rust/src/tests/adapters.rs +0 -0
- {fpstreams-2.2.0 → fpstreams-2.3.0}/rust/src/tests/group_exact/pairs.rs +0 -0
- {fpstreams-2.2.0 → fpstreams-2.3.0}/rust/src/tests/group_exact/rows.rs +0 -0
- {fpstreams-2.2.0 → fpstreams-2.3.0}/rust/src/tests/group_exact.rs +0 -0
- {fpstreams-2.2.0 → fpstreams-2.3.0}/rust/src/tests/group_fixed.rs +0 -0
- {fpstreams-2.2.0 → fpstreams-2.3.0}/rust/src/tests/join_callable/behavior.rs +0 -0
- {fpstreams-2.2.0 → fpstreams-2.3.0}/rust/src/tests/join_callable/direct.rs +0 -0
- {fpstreams-2.2.0 → fpstreams-2.3.0}/rust/src/tests/join_callable.rs +0 -0
- {fpstreams-2.2.0 → fpstreams-2.3.0}/rust/src/tests/join_exact.rs +0 -0
- {fpstreams-2.2.0 → fpstreams-2.3.0}/rust/src/tests/numeric/buffers.rs +0 -0
- {fpstreams-2.2.0 → fpstreams-2.3.0}/rust/src/tests/numeric/kernels.rs +0 -0
- {fpstreams-2.2.0 → fpstreams-2.3.0}/rust/src/tests/numeric/reductions.rs +0 -0
- {fpstreams-2.2.0 → fpstreams-2.3.0}/rust/src/tests/numeric.rs +0 -0
- {fpstreams-2.2.0 → fpstreams-2.3.0}/rust/src/tests/numpy_group.rs +0 -0
- {fpstreams-2.2.0 → fpstreams-2.3.0}/rust/src/tests.rs +0 -0
- {fpstreams-2.2.0 → fpstreams-2.3.0}/rust/src/unnest.rs +0 -0
- {fpstreams-2.2.0 → fpstreams-2.3.0}/rust/src/unpivot.rs +0 -0
- {fpstreams-2.2.0 → fpstreams-2.3.0}/src/fpstreams/_native.pyi +0 -0
- {fpstreams-2.2.0 → fpstreams-2.3.0}/src/fpstreams/_provenance.py +0 -0
- {fpstreams-2.2.0 → fpstreams-2.3.0}/src/fpstreams/aggregate.py +0 -0
- {fpstreams-2.2.0 → fpstreams-2.3.0}/src/fpstreams/async_flow.py +0 -0
- {fpstreams-2.2.0 → fpstreams-2.3.0}/src/fpstreams/collecting/__init__.py +0 -0
- {fpstreams-2.2.0 → fpstreams-2.3.0}/src/fpstreams/collecting/_collector_base.py +0 -0
- {fpstreams-2.2.0 → fpstreams-2.3.0}/src/fpstreams/collecting/aggregate_program.py +0 -0
- {fpstreams-2.2.0 → fpstreams-2.3.0}/src/fpstreams/collecting/aggregation.py +0 -0
- {fpstreams-2.2.0 → fpstreams-2.3.0}/src/fpstreams/collecting/collector.py +0 -0
- {fpstreams-2.2.0 → fpstreams-2.3.0}/src/fpstreams/collecting/program.py +0 -0
- {fpstreams-2.2.0 → fpstreams-2.3.0}/src/fpstreams/collecting/reducer.py +0 -0
- {fpstreams-2.2.0 → fpstreams-2.3.0}/src/fpstreams/collecting/statistics.py +0 -0
- {fpstreams-2.2.0 → fpstreams-2.3.0}/src/fpstreams/collectors.py +0 -0
- {fpstreams-2.2.0 → fpstreams-2.3.0}/src/fpstreams/column.py +0 -0
- {fpstreams-2.2.0 → fpstreams-2.3.0}/src/fpstreams/errors.py +0 -0
- {fpstreams-2.2.0 → fpstreams-2.3.0}/src/fpstreams/exceptions.py +0 -0
- {fpstreams-2.2.0 → fpstreams-2.3.0}/src/fpstreams/execution/__init__.py +0 -0
- {fpstreams-2.2.0 → fpstreams-2.3.0}/src/fpstreams/execution/_pair_aggregate.py +0 -0
- {fpstreams-2.2.0 → fpstreams-2.3.0}/src/fpstreams/execution/_pair_dict.py +0 -0
- {fpstreams-2.2.0 → fpstreams-2.3.0}/src/fpstreams/execution/_pair_row_filter.py +0 -0
- {fpstreams-2.2.0 → fpstreams-2.3.0}/src/fpstreams/execution/_scalar_fusion.py +0 -0
- {fpstreams-2.2.0 → fpstreams-2.3.0}/src/fpstreams/execution/arrow.py +0 -0
- {fpstreams-2.2.0 → fpstreams-2.3.0}/src/fpstreams/execution/async_iterators.py +0 -0
- {fpstreams-2.2.0 → fpstreams-2.3.0}/src/fpstreams/execution/async_map.py +0 -0
- {fpstreams-2.2.0 → fpstreams-2.3.0}/src/fpstreams/execution/async_merge.py +0 -0
- {fpstreams-2.2.0 → fpstreams-2.3.0}/src/fpstreams/execution/async_ops.py +0 -0
- {fpstreams-2.2.0 → fpstreams-2.3.0}/src/fpstreams/execution/async_prefetch.py +0 -0
- {fpstreams-2.2.0 → fpstreams-2.3.0}/src/fpstreams/execution/async_queue.py +0 -0
- {fpstreams-2.2.0 → fpstreams-2.3.0}/src/fpstreams/execution/async_scheduler.py +0 -0
- {fpstreams-2.2.0 → fpstreams-2.3.0}/src/fpstreams/execution/async_timers.py +0 -0
- {fpstreams-2.2.0 → fpstreams-2.3.0}/src/fpstreams/execution/native.py +0 -0
- {fpstreams-2.2.0 → fpstreams-2.3.0}/src/fpstreams/execution/numpy_group.py +0 -0
- {fpstreams-2.2.0 → fpstreams-2.3.0}/src/fpstreams/execution/numpy_prefix.py +0 -0
- {fpstreams-2.2.0 → fpstreams-2.3.0}/src/fpstreams/execution/physical.py +0 -0
- {fpstreams-2.2.0 → fpstreams-2.3.0}/src/fpstreams/execution/relational/__init__.py +0 -0
- {fpstreams-2.2.0 → fpstreams-2.3.0}/src/fpstreams/execution/relational/arrow_global.py +0 -0
- {fpstreams-2.2.0 → fpstreams-2.3.0}/src/fpstreams/execution/relational/arrow_group.py +0 -0
- {fpstreams-2.2.0 → fpstreams-2.3.0}/src/fpstreams/execution/relational/arrow_group_rows.py +0 -0
- {fpstreams-2.2.0 → fpstreams-2.3.0}/src/fpstreams/execution/relational/join.py +0 -0
- {fpstreams-2.2.0 → fpstreams-2.3.0}/src/fpstreams/execution/sorting.py +0 -0
- {fpstreams-2.2.0 → fpstreams-2.3.0}/src/fpstreams/execution/sync.py +0 -0
- {fpstreams-2.2.0 → fpstreams-2.3.0}/src/fpstreams/expr.py +0 -0
- {fpstreams-2.2.0 → fpstreams-2.3.0}/src/fpstreams/expressions/__init__.py +0 -0
- {fpstreams-2.2.0 → fpstreams-2.3.0}/src/fpstreams/expressions/_codegen.py +0 -0
- {fpstreams-2.2.0 → fpstreams-2.3.0}/src/fpstreams/expressions/_row_codegen.py +0 -0
- {fpstreams-2.2.0 → fpstreams-2.3.0}/src/fpstreams/expressions/program.py +0 -0
- {fpstreams-2.2.0 → fpstreams-2.3.0}/src/fpstreams/expressions/row.py +0 -0
- {fpstreams-2.2.0 → fpstreams-2.3.0}/src/fpstreams/expressions/row_eval.py +0 -0
- {fpstreams-2.2.0 → fpstreams-2.3.0}/src/fpstreams/expressions/row_ir.py +0 -0
- {fpstreams-2.2.0 → fpstreams-2.3.0}/src/fpstreams/expressions/scalar.py +0 -0
- {fpstreams-2.2.0 → fpstreams-2.3.0}/src/fpstreams/expressions/selectors.py +0 -0
- {fpstreams-2.2.0 → fpstreams-2.3.0}/src/fpstreams/expressions/typed_ir.py +0 -0
- {fpstreams-2.2.0 → fpstreams-2.3.0}/src/fpstreams/functional.py +0 -0
- {fpstreams-2.2.0 → fpstreams-2.3.0}/src/fpstreams/gather.py +0 -0
- {fpstreams-2.2.0 → fpstreams-2.3.0}/src/fpstreams/io_safety.py +0 -0
- {fpstreams-2.2.0 → fpstreams-2.3.0}/src/fpstreams/option.py +0 -0
- {fpstreams-2.2.0 → fpstreams-2.3.0}/src/fpstreams/physical/__init__.py +0 -0
- {fpstreams-2.2.0 → fpstreams-2.3.0}/src/fpstreams/physical/async_plan.py +0 -0
- {fpstreams-2.2.0 → fpstreams-2.3.0}/src/fpstreams/physical/compiled.py +0 -0
- {fpstreams-2.2.0 → fpstreams-2.3.0}/src/fpstreams/physical/kernel_cache.py +0 -0
- {fpstreams-2.2.0 → fpstreams-2.3.0}/src/fpstreams/physical/plan.py +0 -0
- {fpstreams-2.2.0 → fpstreams-2.3.0}/src/fpstreams/physical/relational.py +0 -0
- {fpstreams-2.2.0 → fpstreams-2.3.0}/src/fpstreams/planning/__init__.py +0 -0
- {fpstreams-2.2.0 → fpstreams-2.3.0}/src/fpstreams/planning/_pair_stages.py +0 -0
- {fpstreams-2.2.0 → fpstreams-2.3.0}/src/fpstreams/planning/arrow.py +0 -0
- {fpstreams-2.2.0 → fpstreams-2.3.0}/src/fpstreams/planning/arrow_source.py +0 -0
- {fpstreams-2.2.0 → fpstreams-2.3.0}/src/fpstreams/planning/async_.py +0 -0
- {fpstreams-2.2.0 → fpstreams-2.3.0}/src/fpstreams/planning/async_utils.py +0 -0
- {fpstreams-2.2.0 → fpstreams-2.3.0}/src/fpstreams/planning/compiler.py +0 -0
- {fpstreams-2.2.0 → fpstreams-2.3.0}/src/fpstreams/planning/gather.py +0 -0
- {fpstreams-2.2.0 → fpstreams-2.3.0}/src/fpstreams/planning/logical.py +0 -0
- {fpstreams-2.2.0 → fpstreams-2.3.0}/src/fpstreams/planning/numpy.py +0 -0
- {fpstreams-2.2.0 → fpstreams-2.3.0}/src/fpstreams/planning/pair_i64_expression.py +0 -0
- {fpstreams-2.2.0 → fpstreams-2.3.0}/src/fpstreams/planning/plan_cache.py +0 -0
- {fpstreams-2.2.0 → fpstreams-2.3.0}/src/fpstreams/planning/semantic_analyzer.py +0 -0
- {fpstreams-2.2.0 → fpstreams-2.3.0}/src/fpstreams/planning/semantic_rules.py +0 -0
- {fpstreams-2.2.0 → fpstreams-2.3.0}/src/fpstreams/planning/semantics.py +0 -0
- {fpstreams-2.2.0 → fpstreams-2.3.0}/src/fpstreams/planning/source.py +0 -0
- {fpstreams-2.2.0 → fpstreams-2.3.0}/src/fpstreams/primitives/__init__.py +0 -0
- {fpstreams-2.2.0 → fpstreams-2.3.0}/src/fpstreams/primitives/option.py +0 -0
- {fpstreams-2.2.0 → fpstreams-2.3.0}/src/fpstreams/primitives/result.py +0 -0
- {fpstreams-2.2.0 → fpstreams-2.3.0}/src/fpstreams/py.typed +0 -0
- {fpstreams-2.2.0 → fpstreams-2.3.0}/src/fpstreams/result.py +0 -0
- {fpstreams-2.2.0 → fpstreams-2.3.0}/src/fpstreams/runtime/__init__.py +0 -0
- {fpstreams-2.2.0 → fpstreams-2.3.0}/src/fpstreams/runtime/_distinct.py +0 -0
- {fpstreams-2.2.0 → fpstreams-2.3.0}/src/fpstreams/runtime/failpoints.py +0 -0
- {fpstreams-2.2.0 → fpstreams-2.3.0}/src/fpstreams/runtime/files.py +0 -0
- {fpstreams-2.2.0 → fpstreams-2.3.0}/src/fpstreams/runtime/iterators.py +0 -0
- {fpstreams-2.2.0 → fpstreams-2.3.0}/src/fpstreams/runtime/limits.py +0 -0
- {fpstreams-2.2.0 → fpstreams-2.3.0}/src/fpstreams/runtime/metrics.py +0 -0
- {fpstreams-2.2.0 → fpstreams-2.3.0}/src/fpstreams/runtime/query.py +0 -0
- {fpstreams-2.2.0 → fpstreams-2.3.0}/src/fpstreams/runtime/report.py +0 -0
- {fpstreams-2.2.0 → fpstreams-2.3.0}/src/fpstreams/runtime/resources.py +0 -0
- {fpstreams-2.2.0 → fpstreams-2.3.0}/src/fpstreams/runtime/spill.py +0 -0
- {fpstreams-2.2.0 → fpstreams-2.3.0}/src/fpstreams/runtime/tasks.py +0 -0
- {fpstreams-2.2.0 → fpstreams-2.3.0}/src/fpstreams/storage/__init__.py +0 -0
- {fpstreams-2.2.0 → fpstreams-2.3.0}/src/fpstreams/storage/codec.py +0 -0
- {fpstreams-2.2.0 → fpstreams-2.3.0}/src/fpstreams/streams/__init__.py +0 -0
- {fpstreams-2.2.0 → fpstreams-2.3.0}/src/fpstreams/streams/_async_identity.py +0 -0
- {fpstreams-2.2.0 → fpstreams-2.3.0}/src/fpstreams/streams/_flow_structural_list.py +0 -0
- {fpstreams-2.2.0 → fpstreams-2.3.0}/src/fpstreams/streams/_flow_unique_list.py +0 -0
- {fpstreams-2.2.0 → fpstreams-2.3.0}/src/fpstreams/streams/async_terminals.py +0 -0
- {fpstreams-2.2.0 → fpstreams-2.3.0}/src/fpstreams/streams/flow_terminals.py +0 -0
- {fpstreams-2.2.0 → fpstreams-2.3.0}/src/fpstreams/streams/pairs.py +0 -0
- {fpstreams-2.2.0 → fpstreams-2.3.0}/src/fpstreams/tabular/_text_sources.py +0 -0
- {fpstreams-2.2.0 → fpstreams-2.3.0}/src/fpstreams/tabular/arrow.py +0 -0
- {fpstreams-2.2.0 → fpstreams-2.3.0}/src/fpstreams/tabular/dataframe.py +0 -0
- {fpstreams-2.2.0 → fpstreams-2.3.0}/src/fpstreams/tabular/grouped.py +0 -0
- {fpstreams-2.2.0 → fpstreams-2.3.0}/src/fpstreams/tabular/io.py +0 -0
- {fpstreams-2.2.0 → fpstreams-2.3.0}/src/fpstreams/tabular/join.py +0 -0
- {fpstreams-2.2.0 → fpstreams-2.3.0}/src/fpstreams/tabular/numpy.py +0 -0
- {fpstreams-2.2.0 → fpstreams-2.3.0}/src/fpstreams/tabular/polars.py +0 -0
- {fpstreams-2.2.0 → fpstreams-2.3.0}/src/fpstreams/tabular/records.py +0 -0
- {fpstreams-2.2.0 → fpstreams-2.3.0}/src/fpstreams/tabular/rows.py +0 -0
- {fpstreams-2.2.0 → fpstreams-2.3.0}/src/fpstreams/tabular/spill.py +0 -0
- {fpstreams-2.2.0 → fpstreams-2.3.0}/src/fpstreams/tabular/spill_io.py +0 -0
- {fpstreams-2.2.0 → fpstreams-2.3.0}/src/fpstreams/tabular/spill_limits.py +0 -0
- {fpstreams-2.2.0 → fpstreams-2.3.0}/src/fpstreams/tabular/sql.py +0 -0
- {fpstreams-2.2.0 → fpstreams-2.3.0}/src/fpstreams/tabular/sqlite_sink.py +0 -0
- {fpstreams-2.2.0 → fpstreams-2.3.0}/src/fpstreams/testing.py +0 -0
|
@@ -3,7 +3,52 @@
|
|
|
3
3
|
This file records user-visible and compatibility-relevant changes in fpstreams 2,
|
|
4
4
|
including changed defaults.
|
|
5
5
|
|
|
6
|
-
##
|
|
6
|
+
## 2.3.0 - 2026-10-15
|
|
7
|
+
|
|
8
|
+
### Fixed
|
|
9
|
+
|
|
10
|
+
- Async callback return types resolve correctly for `AsyncFlow.map()`,
|
|
11
|
+
`map_async()`, and `map_batches()`. Mixed immediate and awaitable returns
|
|
12
|
+
retain the resolved element type.
|
|
13
|
+
|
|
14
|
+
### Changed
|
|
15
|
+
|
|
16
|
+
- Supported single-stage Python `with_columns()` and `select()` queries reuse a
|
|
17
|
+
loop template with separate callback bindings for each execution, reducing
|
|
18
|
+
fixed setup cost while retaining computed-column live selectors and projection
|
|
19
|
+
snapshots for each row.
|
|
20
|
+
- Explicit sorted grouping with one key avoids a temporary selector generator
|
|
21
|
+
and wrapper tuple per row while preserving live callbacks, key validation,
|
|
22
|
+
lookahead, and callback error behavior.
|
|
23
|
+
|
|
24
|
+
### Added
|
|
25
|
+
|
|
26
|
+
- `Flow.map_batches(function, size=...)` and `AsyncFlow.map_batches(...)` call a
|
|
27
|
+
mapper once per tuple batch, including a shorter final batch. Each call emits
|
|
28
|
+
one result. Sync batches inherit `parallel()` settings; async batches support
|
|
29
|
+
concurrency, ordering, per-batch timeouts, and bounded buffering.
|
|
30
|
+
- `Flow.map_parallel()` and `Flow.parallel()` accept a caller-owned `executor`.
|
|
31
|
+
Queries cancel their queued work and wait for their own running mappers while
|
|
32
|
+
leaving the shared pool open. Existing worker/backend defaults remain in place
|
|
33
|
+
when no executor is supplied.
|
|
34
|
+
- Streaming analytics examples show generator input, valid weighted denominators,
|
|
35
|
+
parameterized SQLite selection, and atomic writes across multiple batches.
|
|
36
|
+
|
|
37
|
+
## 2.2.1 - 2026-10-07
|
|
38
|
+
|
|
39
|
+
### Fixed
|
|
40
|
+
|
|
41
|
+
- Editors resolve the public `rows` factory directly, preserving method completion,
|
|
42
|
+
parameter hints, and docstrings instead of treating it as the record module.
|
|
43
|
+
- Core `flow()` and `rows()` factories retain iterable element types without optional
|
|
44
|
+
data adapters installed. A shared series protocol preserves one-dimensional series
|
|
45
|
+
typing without importing Polars into the core type declarations.
|
|
46
|
+
|
|
47
|
+
### Changed
|
|
48
|
+
|
|
49
|
+
- Automatic planning skips NumPy buffer probes for exact list, tuple, and range
|
|
50
|
+
sources. Python identity terminal plans on exact containers also avoid a temporary
|
|
51
|
+
decision object, reducing the fixed cost of small workloads.
|
|
7
52
|
|
|
8
53
|
## 2.2.0 - 2026-10-06
|
|
9
54
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: fpstreams
|
|
3
|
-
Version: 2.
|
|
3
|
+
Version: 2.3.0
|
|
4
4
|
Classifier: Development Status :: 4 - Beta
|
|
5
5
|
Classifier: Programming Language :: Python :: 3
|
|
6
6
|
Classifier: Programming Language :: Python :: 3.11
|
|
@@ -66,6 +66,15 @@ Lazy Python pipelines for iterables and records, with bounded async concurrency.
|
|
|
66
66
|
|
|
67
67
|
Python 3.11 or newer is required.
|
|
68
68
|
|
|
69
|
+
The current stable release is [2.3.0](https://github.com/steventimes/fpstreams/releases/tag/v2.3.0).
|
|
70
|
+
It adds explicit `map_batches()` for sync and async pipelines, reusable mapping
|
|
71
|
+
executors, and async callback typing fixes. See the
|
|
72
|
+
[changelog](https://github.com/steventimes/fpstreams/blob/master/CHANGELOG.md) for its scope and the
|
|
73
|
+
[performance guide](https://steventimes.github.io/fpstreams/user-guide/performance/#process-mapping-and-explicit-batches)
|
|
74
|
+
for batch and executor examples. The
|
|
75
|
+
[roadmap](https://steventimes.github.io/fpstreams/roadmap/#next-development-steps)
|
|
76
|
+
lists the next optimization candidates and their acceptance criteria.
|
|
77
|
+
|
|
69
78
|
## Install
|
|
70
79
|
|
|
71
80
|
Install the latest stable release:
|
|
@@ -87,6 +96,16 @@ python -m pip install "fpstreams[data]" # NumPy, pandas, and PyArrow
|
|
|
87
96
|
python -m pip install "fpstreams[polars]" # Polars and PyArrow
|
|
88
97
|
~~~
|
|
89
98
|
|
|
99
|
+
## Editor support
|
|
100
|
+
|
|
101
|
+
fpstreams includes inline type annotations, docstrings, and the `py.typed` marker.
|
|
102
|
+
Select the Python interpreter where fpstreams is installed in your editor to get
|
|
103
|
+
method completions, parameter hints, and hover documentation.
|
|
104
|
+
|
|
105
|
+
Version 2.2.1 fixes `rows` factory completion and keeps core
|
|
106
|
+
iterable element types when optional data adapters are absent. For example,
|
|
107
|
+
`flow([1, 2, 3])` is inferred as `Flow[int]`.
|
|
108
|
+
|
|
90
109
|
## Quick start
|
|
91
110
|
|
|
92
111
|
This example filters paid orders, groups them by region, and reports both the
|
|
@@ -11,6 +11,15 @@ Lazy Python pipelines for iterables and records, with bounded async concurrency.
|
|
|
11
11
|
|
|
12
12
|
Python 3.11 or newer is required.
|
|
13
13
|
|
|
14
|
+
The current stable release is [2.3.0](https://github.com/steventimes/fpstreams/releases/tag/v2.3.0).
|
|
15
|
+
It adds explicit `map_batches()` for sync and async pipelines, reusable mapping
|
|
16
|
+
executors, and async callback typing fixes. See the
|
|
17
|
+
[changelog](https://github.com/steventimes/fpstreams/blob/master/CHANGELOG.md) for its scope and the
|
|
18
|
+
[performance guide](https://steventimes.github.io/fpstreams/user-guide/performance/#process-mapping-and-explicit-batches)
|
|
19
|
+
for batch and executor examples. The
|
|
20
|
+
[roadmap](https://steventimes.github.io/fpstreams/roadmap/#next-development-steps)
|
|
21
|
+
lists the next optimization candidates and their acceptance criteria.
|
|
22
|
+
|
|
14
23
|
## Install
|
|
15
24
|
|
|
16
25
|
Install the latest stable release:
|
|
@@ -32,6 +41,16 @@ python -m pip install "fpstreams[data]" # NumPy, pandas, and PyArrow
|
|
|
32
41
|
python -m pip install "fpstreams[polars]" # Polars and PyArrow
|
|
33
42
|
~~~
|
|
34
43
|
|
|
44
|
+
## Editor support
|
|
45
|
+
|
|
46
|
+
fpstreams includes inline type annotations, docstrings, and the `py.typed` marker.
|
|
47
|
+
Select the Python interpreter where fpstreams is installed in your editor to get
|
|
48
|
+
method completions, parameter hints, and hover documentation.
|
|
49
|
+
|
|
50
|
+
Version 2.2.1 fixes `rows` factory completion and keeps core
|
|
51
|
+
iterable element types when optional data adapters are absent. For example,
|
|
52
|
+
`flow([1, 2, 3])` is inferred as `Flow[int]`.
|
|
53
|
+
|
|
35
54
|
## Quick start
|
|
36
55
|
|
|
37
56
|
This example filters paid orders, groups them by region, and reports both the
|
|
@@ -39,9 +39,10 @@ from .streams import (
|
|
|
39
39
|
flow,
|
|
40
40
|
pairs,
|
|
41
41
|
)
|
|
42
|
-
from .tabular import Rows, SpillLimits
|
|
42
|
+
from .tabular import Rows, SpillLimits
|
|
43
|
+
from .tabular.factory import rows as rows
|
|
43
44
|
|
|
44
|
-
__version__ = "2.
|
|
45
|
+
__version__ = "2.3.0"
|
|
45
46
|
Stream = Flow
|
|
46
47
|
ParallelStream = Flow
|
|
47
48
|
|
|
@@ -5,6 +5,7 @@ from __future__ import annotations
|
|
|
5
5
|
import ast
|
|
6
6
|
from collections.abc import Callable, Iterator
|
|
7
7
|
from itertools import chain
|
|
8
|
+
from types import FunctionType
|
|
8
9
|
from typing import Any, cast
|
|
9
10
|
|
|
10
11
|
from ..errors import SelectionError
|
|
@@ -74,6 +75,33 @@ RowsFilterSink = Callable[[list[Any], Iterator[Any]], tuple[Any | None, bool]]
|
|
|
74
75
|
I64RowFilterPlan = tuple[str, tuple[tuple[int, int], ...], bool]
|
|
75
76
|
|
|
76
77
|
|
|
78
|
+
def _row_map_loop_template(
|
|
79
|
+
_source: Iterator[Any],
|
|
80
|
+
_fpstreams_slot_0: Callable[[Any], tuple[bool, Any]],
|
|
81
|
+
_fpstreams_slot_1: Callable[[Any], type[Any]],
|
|
82
|
+
_fpstreams_slot_2: type[dict[str, Any]],
|
|
83
|
+
_fpstreams_slot_3: type[StopIteration],
|
|
84
|
+
_fpstreams_slot_4: Callable[[Any], Any],
|
|
85
|
+
_fpstreams_slot_5: type[StopIteration],
|
|
86
|
+
) -> Iterator[Any]:
|
|
87
|
+
"""Keep the one-stage generated loop's dispatch, bindings, and row lifetimes."""
|
|
88
|
+
for _row in _source:
|
|
89
|
+
if _fpstreams_slot_1(_row) is not _fpstreams_slot_2:
|
|
90
|
+
try:
|
|
91
|
+
_fallback = _fpstreams_slot_0(_row)
|
|
92
|
+
except _fpstreams_slot_3:
|
|
93
|
+
return
|
|
94
|
+
if _fallback[0]:
|
|
95
|
+
yield _fallback[1]
|
|
96
|
+
continue
|
|
97
|
+
_current = _row
|
|
98
|
+
try:
|
|
99
|
+
_current = _fpstreams_slot_4(_current)
|
|
100
|
+
except _fpstreams_slot_5:
|
|
101
|
+
return
|
|
102
|
+
yield _current
|
|
103
|
+
|
|
104
|
+
|
|
77
105
|
def execute_rows_fusion(
|
|
78
106
|
iterator: Iterator[Any],
|
|
79
107
|
operations: tuple[RowsOperation, ...],
|
|
@@ -391,6 +419,28 @@ class _RowsLoopBuilder:
|
|
|
391
419
|
|
|
392
420
|
def compile(self) -> RowsLoop:
|
|
393
421
|
"""Compile a generator whose globals expose no builtins or source-text interpolation."""
|
|
422
|
+
if (
|
|
423
|
+
len(self._operations) == 1
|
|
424
|
+
and isinstance(operation := self._operations[0], MapOp)
|
|
425
|
+
and self._descriptors[0].kind in {"select", "with_columns"}
|
|
426
|
+
):
|
|
427
|
+
slots = (
|
|
428
|
+
_fallback_processor(self._operations),
|
|
429
|
+
type,
|
|
430
|
+
dict,
|
|
431
|
+
StopIteration,
|
|
432
|
+
operation.function,
|
|
433
|
+
StopIteration,
|
|
434
|
+
)
|
|
435
|
+
namespace: dict[str, Any] = {"__builtins__": {}, "_fpstreams_slots": slots}
|
|
436
|
+
loop = FunctionType(
|
|
437
|
+
_row_map_loop_template.__code__,
|
|
438
|
+
namespace,
|
|
439
|
+
name="_fpstreams_rows_loop",
|
|
440
|
+
argdefs=slots,
|
|
441
|
+
)
|
|
442
|
+
namespace["_fpstreams_rows_loop"] = loop
|
|
443
|
+
return cast(RowsLoop, loop)
|
|
394
444
|
body = self._loop_body()
|
|
395
445
|
return cast(
|
|
396
446
|
RowsLoop,
|
|
@@ -21,6 +21,11 @@ class _End:
|
|
|
21
21
|
|
|
22
22
|
_EOF = _End()
|
|
23
23
|
_UNSET = object()
|
|
24
|
+
_BUILTIN_TUPLE: type[Any] = tuple
|
|
25
|
+
_BUILTIN_LEN = len
|
|
26
|
+
_BUILTIN_TYPE = type
|
|
27
|
+
_BUILTIN_STOP_ITERATION = StopIteration
|
|
28
|
+
_BUILTIN_RUNTIME_ERROR = RuntimeError
|
|
24
29
|
|
|
25
30
|
|
|
26
31
|
class _KeyShape:
|
|
@@ -146,8 +151,24 @@ def aggregate_sorted_rows(
|
|
|
146
151
|
"""Finish adjacent groups without buffering their rows or future groups."""
|
|
147
152
|
source = values if isinstance(values, Source) else Source.from_iterable(values)
|
|
148
153
|
|
|
154
|
+
single = (
|
|
155
|
+
_BUILTIN_TYPE(keys) is cast(Any, _BUILTIN_TUPLE)
|
|
156
|
+
and _BUILTIN_LEN(keys) == 1
|
|
157
|
+
and _BUILTIN_TYPE(keys[0]) is cast(Any, _BUILTIN_TUPLE)
|
|
158
|
+
and _BUILTIN_LEN(keys[0]) == 2
|
|
159
|
+
)
|
|
160
|
+
|
|
149
161
|
def select_key(row: Any) -> Any:
|
|
150
|
-
|
|
162
|
+
if single and tuple is _BUILTIN_TUPLE:
|
|
163
|
+
try:
|
|
164
|
+
selected_key = keys[0][1](row)
|
|
165
|
+
except _BUILTIN_STOP_ITERATION as error:
|
|
166
|
+
raise _BUILTIN_RUNTIME_ERROR("generator raised StopIteration") from error
|
|
167
|
+
if len is _BUILTIN_LEN:
|
|
168
|
+
return selected_key
|
|
169
|
+
selected = (selected_key,)
|
|
170
|
+
else:
|
|
171
|
+
selected = tuple(selector(row) for _name, selector in keys)
|
|
151
172
|
return selected[0] if len(selected) == 1 else selected
|
|
152
173
|
|
|
153
174
|
cursor = _SortedCursor(source, select_key, side="input")
|
|
@@ -8,9 +8,10 @@ up those local resources themselves.
|
|
|
8
8
|
from __future__ import annotations
|
|
9
9
|
|
|
10
10
|
from collections import deque
|
|
11
|
-
from collections.abc import Callable, Iterator
|
|
11
|
+
from collections.abc import Callable, Iterable, Iterator
|
|
12
12
|
from concurrent.futures import (
|
|
13
13
|
FIRST_COMPLETED,
|
|
14
|
+
Executor,
|
|
14
15
|
Future,
|
|
15
16
|
ProcessPoolExecutor,
|
|
16
17
|
ThreadPoolExecutor,
|
|
@@ -86,29 +87,30 @@ def _map_parallel(iterator: Iterator[Any], operation: ParallelMapOp) -> Iterator
|
|
|
86
87
|
At most buffer futures remain queued. Ordered mode awaits futures in submission
|
|
87
88
|
order; unordered mode yields completed sets as they become available. Callback
|
|
88
89
|
failures propagate through Future.result, and early exit cancels queued work
|
|
89
|
-
before waiting for
|
|
90
|
+
before waiting for this map's running callbacks. Only an owned pool is shut down.
|
|
90
91
|
"""
|
|
91
|
-
executor =
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
max_workers=operation.workers
|
|
96
|
-
|
|
92
|
+
executor = operation.executor
|
|
93
|
+
owned = executor is None
|
|
94
|
+
if executor is None:
|
|
95
|
+
executor = (
|
|
96
|
+
ThreadPoolExecutor(max_workers=operation.workers)
|
|
97
|
+
if operation.backend == "thread"
|
|
98
|
+
else ProcessPoolExecutor(
|
|
99
|
+
max_workers=operation.workers,
|
|
100
|
+
mp_context=get_context("spawn"),
|
|
101
|
+
)
|
|
97
102
|
)
|
|
98
|
-
)
|
|
99
103
|
if operation.ordered:
|
|
100
104
|
pending: deque[Future[Any]] = deque()
|
|
101
105
|
try:
|
|
102
106
|
for item in iterator:
|
|
103
107
|
pending.append(executor.submit(operation.function, item))
|
|
104
108
|
if len(pending) >= operation.buffer:
|
|
105
|
-
yield pending
|
|
109
|
+
yield _pop_parallel_result(pending)
|
|
106
110
|
while pending:
|
|
107
|
-
yield pending
|
|
111
|
+
yield _pop_parallel_result(pending)
|
|
108
112
|
finally:
|
|
109
|
-
|
|
110
|
-
future.cancel()
|
|
111
|
-
executor.shutdown(wait=True, cancel_futures=True)
|
|
113
|
+
_close_parallel_work(executor, pending, owned=owned)
|
|
112
114
|
return
|
|
113
115
|
|
|
114
116
|
pending_set: set[Future[Any]] = set()
|
|
@@ -124,9 +126,30 @@ def _map_parallel(iterator: Iterator[Any], operation: ParallelMapOp) -> Iterator
|
|
|
124
126
|
for future in done:
|
|
125
127
|
yield future.result()
|
|
126
128
|
finally:
|
|
127
|
-
|
|
128
|
-
|
|
129
|
+
_close_parallel_work(executor, pending_set, owned=owned)
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
def _pop_parallel_result(pending: deque[Future[Any]]) -> Any:
|
|
133
|
+
"""Keep the awaited future registered until its result settles."""
|
|
134
|
+
result = pending[0].result()
|
|
135
|
+
pending.popleft()
|
|
136
|
+
return result
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
def _close_parallel_work(
|
|
140
|
+
executor: Executor, futures: Iterable[Future[Any]], *, owned: bool
|
|
141
|
+
) -> None:
|
|
142
|
+
"""Cancel this map's queued work and wait without taking ownership of a borrowed pool."""
|
|
143
|
+
pending = tuple(futures)
|
|
144
|
+
for future in pending:
|
|
145
|
+
future.cancel()
|
|
146
|
+
if owned:
|
|
129
147
|
executor.shutdown(wait=True, cancel_futures=True)
|
|
148
|
+
return
|
|
149
|
+
running = tuple(future for future in pending if not future.cancelled())
|
|
150
|
+
if running:
|
|
151
|
+
# Waiting does not surface unconsumed callback errors or swallow caller interrupts.
|
|
152
|
+
wait(running)
|
|
130
153
|
|
|
131
154
|
|
|
132
155
|
def _tap(iterator: Iterator[Any], operation: TapOp) -> Iterator[Any]:
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
|
|
5
|
+
from concurrent.futures import ProcessPoolExecutor, ThreadPoolExecutor
|
|
5
6
|
from dataclasses import dataclass
|
|
6
7
|
from typing import Any
|
|
7
8
|
|
|
@@ -24,6 +25,17 @@ from .sync import FilterOp, MapOp, Operation, ParallelMapOp, TapOp
|
|
|
24
25
|
_FUSABLE = (MapOp, FilterOp, TapOp)
|
|
25
26
|
|
|
26
27
|
|
|
28
|
+
def _parallel_backend(operation: ParallelMapOp) -> str:
|
|
29
|
+
"""Describe the executor actually used, including caller-owned pools."""
|
|
30
|
+
if operation.executor is None:
|
|
31
|
+
return operation.backend
|
|
32
|
+
if isinstance(operation.executor, ThreadPoolExecutor):
|
|
33
|
+
return "thread"
|
|
34
|
+
if isinstance(operation.executor, ProcessPoolExecutor):
|
|
35
|
+
return "process"
|
|
36
|
+
return "executor"
|
|
37
|
+
|
|
38
|
+
|
|
27
39
|
def _python_stages(operations: tuple[Operation, ...]) -> list[dict[str, Any]]:
|
|
28
40
|
"""Return Python stages, fusing adjacent map/filter/tap nodes."""
|
|
29
41
|
stages: list[dict[str, Any]] = []
|
|
@@ -49,7 +61,9 @@ def _python_stages(operations: tuple[Operation, ...]) -> list[dict[str, Any]]:
|
|
|
49
61
|
stages.append(
|
|
50
62
|
{
|
|
51
63
|
"engine": (
|
|
52
|
-
|
|
64
|
+
_parallel_backend(operation)
|
|
65
|
+
if isinstance(operation, ParallelMapOp)
|
|
66
|
+
else "python"
|
|
53
67
|
),
|
|
54
68
|
"operations": [operation.name],
|
|
55
69
|
"fused": False,
|
|
@@ -44,6 +44,9 @@ _I64_MIN = -(2**63)
|
|
|
44
44
|
_I64_MAX = 2**63 - 1
|
|
45
45
|
_AUTO_THRESHOLD = 8
|
|
46
46
|
_AUTO_IDENTITY_I64_SUM_THRESHOLD = 65_536
|
|
47
|
+
_AUTO_IDENTITY_CONTAINER_REASON = (
|
|
48
|
+
"identity list/tuple stays in Python below its direct native crossover"
|
|
49
|
+
)
|
|
47
50
|
_COPY_SHORT_CIRCUIT_LIMIT = 1_024
|
|
48
51
|
_COPY_SHORT_CIRCUIT_RATIO = 64
|
|
49
52
|
_ALLOCATING_I64_MAP_ROOTS = frozenset({"add", "sub", "mul", "floordiv", "neg"})
|
|
@@ -108,61 +111,65 @@ def validate_terminal(terminal: str) -> TerminalName:
|
|
|
108
111
|
|
|
109
112
|
|
|
110
113
|
def _terminal_metadata(
|
|
111
|
-
decision: EngineDecision,
|
|
114
|
+
decision: EngineDecision | None,
|
|
112
115
|
plan: Pipeline,
|
|
113
116
|
terminal: TerminalName,
|
|
114
117
|
*,
|
|
115
118
|
source_is_container: bool | None = None,
|
|
119
|
+
python_reason: str = "",
|
|
116
120
|
) -> EngineDecision:
|
|
117
|
-
"""Attach
|
|
121
|
+
"""Attach terminal metadata, constructing a Python-only decision in one allocation."""
|
|
118
122
|
source = plan.source.native_data
|
|
119
|
-
|
|
123
|
+
engine = "python" if decision is None else decision.engine
|
|
124
|
+
program = None if decision is None else decision.program
|
|
125
|
+
crosses_native_boundary = engine in {"native", "hybrid"}
|
|
120
126
|
if source_is_container is None:
|
|
121
127
|
source_is_container = (
|
|
122
128
|
isinstance(source, (list, tuple)) or _numpy_buffer_kind(source) is not None
|
|
123
129
|
)
|
|
124
130
|
metadata_count = terminal == "count" and exact_count(plan) is not None
|
|
125
131
|
direct_i64_container_sum = (
|
|
126
|
-
|
|
127
|
-
and
|
|
128
|
-
and
|
|
129
|
-
and not
|
|
132
|
+
engine == "native"
|
|
133
|
+
and program is not None
|
|
134
|
+
and program.kind == "i64"
|
|
135
|
+
and not program.stages
|
|
130
136
|
and type(source) in (list, tuple)
|
|
131
137
|
and terminal == "sum"
|
|
132
138
|
)
|
|
133
139
|
direct_exact_container_mean = (
|
|
134
|
-
|
|
135
|
-
and
|
|
136
|
-
and not
|
|
140
|
+
engine == "native"
|
|
141
|
+
and program is not None
|
|
142
|
+
and not program.stages
|
|
137
143
|
and type(source) in (list, tuple)
|
|
138
144
|
and terminal == "mean"
|
|
139
145
|
and _exact_number_mean_available()
|
|
140
146
|
)
|
|
141
147
|
range_metadata = (
|
|
142
|
-
|
|
148
|
+
engine == "native"
|
|
143
149
|
and type(source) is range
|
|
144
150
|
and not plan.operations
|
|
145
151
|
and terminal in {"sum", "min", "max", "last"}
|
|
146
152
|
)
|
|
147
153
|
probe_path = (
|
|
148
|
-
|
|
149
|
-
and
|
|
154
|
+
engine == "native"
|
|
155
|
+
and program is not None
|
|
150
156
|
and type(source) in (list, tuple)
|
|
151
157
|
and terminal in {"first", "any", "all"}
|
|
152
|
-
and _container_probe_available(
|
|
158
|
+
and _container_probe_available(program.kind)
|
|
153
159
|
)
|
|
160
|
+
decision_reason = python_reason if decision is None else decision.reason
|
|
154
161
|
reason = (
|
|
155
|
-
f"{
|
|
162
|
+
f"{decision_reason}; bounded probe; only undecided fallback bulk-copies"
|
|
156
163
|
if probe_path
|
|
157
|
-
else f"{
|
|
164
|
+
else f"{decision_reason}; retained range terminal uses constant-time metadata"
|
|
158
165
|
if range_metadata
|
|
159
|
-
else
|
|
166
|
+
else decision_reason
|
|
160
167
|
)
|
|
161
168
|
return EngineDecision(
|
|
162
|
-
|
|
169
|
+
engine,
|
|
163
170
|
reason,
|
|
164
|
-
|
|
165
|
-
decision.native_operation_count,
|
|
171
|
+
program,
|
|
172
|
+
0 if decision is None else decision.native_operation_count,
|
|
166
173
|
# An undecided bounded probe restarts the legacy bulk adapter, and numeric
|
|
167
174
|
# buffer kernels may snapshot exporters before detached computation.
|
|
168
175
|
scans_source=crosses_native_boundary and source_is_container and not metadata_count,
|
|
@@ -173,7 +180,7 @@ def _terminal_metadata(
|
|
|
173
180
|
and not direct_i64_container_sum
|
|
174
181
|
and not direct_exact_container_mean
|
|
175
182
|
),
|
|
176
|
-
materializes=terminal == "list" or
|
|
183
|
+
materializes=terminal == "list" or engine == "hybrid",
|
|
177
184
|
complexity=(
|
|
178
185
|
"O(1)"
|
|
179
186
|
if metadata_count or range_metadata
|
|
@@ -234,6 +241,9 @@ def _numpy_f64_buffer(source: object) -> Any | None:
|
|
|
234
241
|
|
|
235
242
|
def _numpy_buffer_kind(source: object) -> NativeKind | None:
|
|
236
243
|
"""Return the exact native representation exposed by an explicit NumPy column."""
|
|
244
|
+
source_type = type(source)
|
|
245
|
+
if source_type is list or source_type is tuple or source_type is range:
|
|
246
|
+
return None
|
|
237
247
|
if _numpy_i64_buffer(source) is not None:
|
|
238
248
|
return "i64"
|
|
239
249
|
if _numpy_f64_buffer(source) is not None:
|
|
@@ -783,6 +793,45 @@ def _buffer_short_circuit_decision(
|
|
|
783
793
|
return None
|
|
784
794
|
|
|
785
795
|
|
|
796
|
+
def _identity_source_terminal_context(
|
|
797
|
+
plan: Pipeline,
|
|
798
|
+
terminal: TerminalName,
|
|
799
|
+
source: object,
|
|
800
|
+
) -> tuple[EngineDecision | None, NativeKind | None, bool]:
|
|
801
|
+
"""Choose exact-container routes before considering numeric buffer metadata."""
|
|
802
|
+
source_type = type(source)
|
|
803
|
+
if (
|
|
804
|
+
plan.engine == "auto"
|
|
805
|
+
and (source_type is list or source_type is tuple)
|
|
806
|
+
and terminal not in {"mean", "statistics"}
|
|
807
|
+
and not _auto_direct_i64_sum_candidate(source, terminal)
|
|
808
|
+
):
|
|
809
|
+
return (
|
|
810
|
+
_terminal_metadata(
|
|
811
|
+
None,
|
|
812
|
+
plan,
|
|
813
|
+
terminal,
|
|
814
|
+
source_is_container=True,
|
|
815
|
+
python_reason=_AUTO_IDENTITY_CONTAINER_REASON,
|
|
816
|
+
),
|
|
817
|
+
None,
|
|
818
|
+
True,
|
|
819
|
+
)
|
|
820
|
+
buffer_kind = _numpy_buffer_kind(source)
|
|
821
|
+
source_is_container = isinstance(source, (list, tuple)) or buffer_kind is not None
|
|
822
|
+
return (
|
|
823
|
+
_forced_native_exact_count_decision(
|
|
824
|
+
plan,
|
|
825
|
+
terminal,
|
|
826
|
+
source,
|
|
827
|
+
buffer_kind,
|
|
828
|
+
source_is_container=source_is_container,
|
|
829
|
+
),
|
|
830
|
+
buffer_kind,
|
|
831
|
+
source_is_container,
|
|
832
|
+
)
|
|
833
|
+
|
|
834
|
+
|
|
786
835
|
def _select_operation_terminal_engine(
|
|
787
836
|
plan: Pipeline,
|
|
788
837
|
terminal: TerminalName,
|
|
@@ -850,17 +899,11 @@ def select_terminal_engine(plan: Pipeline, terminal: TerminalName) -> EngineDeci
|
|
|
850
899
|
source = plan.source.native_data
|
|
851
900
|
if retained_one_shot := _retained_one_shot_decision(plan):
|
|
852
901
|
return _terminal_metadata(retained_one_shot, plan, terminal)
|
|
853
|
-
buffer_kind =
|
|
854
|
-
|
|
855
|
-
metadata_count = _forced_native_exact_count_decision(
|
|
856
|
-
plan,
|
|
857
|
-
terminal,
|
|
858
|
-
source,
|
|
859
|
-
buffer_kind,
|
|
860
|
-
source_is_container=source_is_container,
|
|
902
|
+
source_decision, buffer_kind, source_is_container = _identity_source_terminal_context(
|
|
903
|
+
plan, terminal, source
|
|
861
904
|
)
|
|
862
|
-
if
|
|
863
|
-
return
|
|
905
|
+
if source_decision is not None:
|
|
906
|
+
return source_decision
|
|
864
907
|
if buffered := _buffer_short_circuit_decision(plan, terminal, buffer_kind):
|
|
865
908
|
return _terminal_metadata(
|
|
866
909
|
buffered,
|
|
@@ -875,10 +918,7 @@ def select_terminal_engine(plan: Pipeline, terminal: TerminalName) -> EngineDeci
|
|
|
875
918
|
and not _auto_direct_i64_sum_candidate(source, terminal)
|
|
876
919
|
):
|
|
877
920
|
return _terminal_metadata(
|
|
878
|
-
EngineDecision(
|
|
879
|
-
"python",
|
|
880
|
-
"identity list/tuple stays in Python below its direct native crossover",
|
|
881
|
-
),
|
|
921
|
+
EngineDecision("python", _AUTO_IDENTITY_CONTAINER_REASON),
|
|
882
922
|
plan,
|
|
883
923
|
terminal,
|
|
884
924
|
source_is_container=source_is_container,
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
|
|
5
5
|
from collections.abc import Callable
|
|
6
|
+
from concurrent.futures import Executor
|
|
6
7
|
from dataclasses import dataclass
|
|
7
8
|
from os import PathLike
|
|
8
9
|
from typing import Any, Literal
|
|
@@ -22,6 +23,7 @@ class ParallelSettings:
|
|
|
22
23
|
backend: ParallelBackend
|
|
23
24
|
ordered: bool
|
|
24
25
|
buffer: int
|
|
26
|
+
executor: Executor | None = None
|
|
25
27
|
|
|
26
28
|
|
|
27
29
|
@dataclass(frozen=True, slots=True)
|
|
@@ -42,6 +44,7 @@ class ParallelMapOp:
|
|
|
42
44
|
ordered: bool
|
|
43
45
|
buffer: int
|
|
44
46
|
name: str = "map_parallel"
|
|
47
|
+
executor: Executor | None = None
|
|
45
48
|
|
|
46
49
|
|
|
47
50
|
@dataclass(frozen=True, slots=True)
|
|
@@ -271,7 +271,11 @@ class SpillStore:
|
|
|
271
271
|
yield from self.read_path(run.path)
|
|
272
272
|
|
|
273
273
|
def read_path(self, path: Path) -> Iterator[object]:
|
|
274
|
-
"""Read a
|
|
274
|
+
"""Read a query-owned framed path through tracked I/O.
|
|
275
|
+
|
|
276
|
+
Callers supply paths produced by this store. This reader does not validate
|
|
277
|
+
registration or make pickle safe for externally supplied files.
|
|
278
|
+
"""
|
|
275
279
|
from ..runtime.failpoints import hit
|
|
276
280
|
from ..runtime.resources import _add_cleanup_failure
|
|
277
281
|
|