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.
Files changed (197) hide show
  1. {fpstreams-2.2.0 → fpstreams-2.3.0}/CHANGELOG.md +46 -1
  2. {fpstreams-2.2.0 → fpstreams-2.3.0}/PKG-INFO +20 -1
  3. {fpstreams-2.2.0 → fpstreams-2.3.0}/README.md +19 -0
  4. {fpstreams-2.2.0 → fpstreams-2.3.0}/pyproject.toml +1 -1
  5. {fpstreams-2.2.0 → fpstreams-2.3.0}/rust/Cargo.lock +1 -1
  6. {fpstreams-2.2.0 → fpstreams-2.3.0}/rust/Cargo.toml +1 -1
  7. {fpstreams-2.2.0 → fpstreams-2.3.0}/src/fpstreams/__init__.py +3 -2
  8. {fpstreams-2.2.0 → fpstreams-2.3.0}/src/fpstreams/execution/_rows_fusion.py +50 -0
  9. {fpstreams-2.2.0 → fpstreams-2.3.0}/src/fpstreams/execution/sorted_streams.py +22 -1
  10. {fpstreams-2.2.0 → fpstreams-2.3.0}/src/fpstreams/execution/sync_ops.py +39 -16
  11. {fpstreams-2.2.0 → fpstreams-2.3.0}/src/fpstreams/planning/explain.py +15 -1
  12. {fpstreams-2.2.0 → fpstreams-2.3.0}/src/fpstreams/planning/native.py +75 -35
  13. {fpstreams-2.2.0 → fpstreams-2.3.0}/src/fpstreams/planning/sync.py +3 -0
  14. {fpstreams-2.2.0 → fpstreams-2.3.0}/src/fpstreams/storage/spill_store.py +5 -1
  15. {fpstreams-2.2.0 → fpstreams-2.3.0}/src/fpstreams/streams/async_flow.py +94 -2
  16. {fpstreams-2.2.0 → fpstreams-2.3.0}/src/fpstreams/streams/flow.py +98 -40
  17. {fpstreams-2.2.0 → fpstreams-2.3.0}/src/fpstreams/tabular/__init__.py +3 -1
  18. {fpstreams-2.2.0 → fpstreams-2.3.0}/src/fpstreams/tabular/factory.py +5 -13
  19. {fpstreams-2.2.0 → fpstreams-2.3.0}/LICENSE +0 -0
  20. {fpstreams-2.2.0 → fpstreams-2.3.0}/rust/build.rs +0 -0
  21. {fpstreams-2.2.0 → fpstreams-2.3.0}/rust/src/common.rs +0 -0
  22. {fpstreams-2.2.0 → fpstreams-2.3.0}/rust/src/float/affine_pair.rs +0 -0
  23. {fpstreams-2.2.0 → fpstreams-2.3.0}/rust/src/float/endpoints.rs +0 -0
  24. {fpstreams-2.2.0 → fpstreams-2.3.0}/rust/src/float.rs +0 -0
  25. {fpstreams-2.2.0 → fpstreams-2.3.0}/rust/src/integer/endpoints.rs +0 -0
  26. {fpstreams-2.2.0 → fpstreams-2.3.0}/rust/src/integer.rs +0 -0
  27. {fpstreams-2.2.0 → fpstreams-2.3.0}/rust/src/lib.rs +0 -0
  28. {fpstreams-2.2.0 → fpstreams-2.3.0}/rust/src/numeric_mean.rs +0 -0
  29. {fpstreams-2.2.0 → fpstreams-2.3.0}/rust/src/numpy_export.rs +0 -0
  30. {fpstreams-2.2.0 → fpstreams-2.3.0}/rust/src/numpy_group/buffer.rs +0 -0
  31. {fpstreams-2.2.0 → fpstreams-2.3.0}/rust/src/numpy_group.rs +0 -0
  32. {fpstreams-2.2.0 → fpstreams-2.3.0}/rust/src/pair/expr.rs +0 -0
  33. {fpstreams-2.2.0 → fpstreams-2.3.0}/rust/src/pair/prefix.rs +0 -0
  34. {fpstreams-2.2.0 → fpstreams-2.3.0}/rust/src/pair/row_filter.rs +0 -0
  35. {fpstreams-2.2.0 → fpstreams-2.3.0}/rust/src/pair/unique.rs +0 -0
  36. {fpstreams-2.2.0 → fpstreams-2.3.0}/rust/src/pair/value_filter.rs +0 -0
  37. {fpstreams-2.2.0 → fpstreams-2.3.0}/rust/src/pair/value_map.rs +0 -0
  38. {fpstreams-2.2.0 → fpstreams-2.3.0}/rust/src/pair.rs +0 -0
  39. {fpstreams-2.2.0 → fpstreams-2.3.0}/rust/src/pivot.rs +0 -0
  40. {fpstreams-2.2.0 → fpstreams-2.3.0}/rust/src/records.rs +0 -0
  41. {fpstreams-2.2.0 → fpstreams-2.3.0}/rust/src/relational/adapters/namedtuple.rs +0 -0
  42. {fpstreams-2.2.0 → fpstreams-2.3.0}/rust/src/relational/adapters.rs +0 -0
  43. {fpstreams-2.2.0 → fpstreams-2.3.0}/rust/src/relational/global_numeric.rs +0 -0
  44. {fpstreams-2.2.0 → fpstreams-2.3.0}/rust/src/relational/group_numeric.rs +0 -0
  45. {fpstreams-2.2.0 → fpstreams-2.3.0}/rust/src/relational/group_pair_expr.rs +0 -0
  46. {fpstreams-2.2.0 → fpstreams-2.3.0}/rust/src/relational/join_callable/many.rs +0 -0
  47. {fpstreams-2.2.0 → fpstreams-2.3.0}/rust/src/relational/join_callable/unique.rs +0 -0
  48. {fpstreams-2.2.0 → fpstreams-2.3.0}/rust/src/relational/join_callable.rs +0 -0
  49. {fpstreams-2.2.0 → fpstreams-2.3.0}/rust/src/relational/join_exact.rs +0 -0
  50. {fpstreams-2.2.0 → fpstreams-2.3.0}/rust/src/relational/join_i64.rs +0 -0
  51. {fpstreams-2.2.0 → fpstreams-2.3.0}/rust/src/relational.rs +0 -0
  52. {fpstreams-2.2.0 → fpstreams-2.3.0}/rust/src/relational_fixed/composite.rs +0 -0
  53. {fpstreams-2.2.0 → fpstreams-2.3.0}/rust/src/relational_fixed/global_multi/same_field.rs +0 -0
  54. {fpstreams-2.2.0 → fpstreams-2.3.0}/rust/src/relational_fixed/global_multi.rs +0 -0
  55. {fpstreams-2.2.0 → fpstreams-2.3.0}/rust/src/relational_fixed/group_multi.rs +0 -0
  56. {fpstreams-2.2.0 → fpstreams-2.3.0}/rust/src/relational_fixed/single.rs +0 -0
  57. {fpstreams-2.2.0 → fpstreams-2.3.0}/rust/src/relational_fixed.rs +0 -0
  58. {fpstreams-2.2.0 → fpstreams-2.3.0}/rust/src/scalar_sort.rs +0 -0
  59. {fpstreams-2.2.0 → fpstreams-2.3.0}/rust/src/scalar_unique.rs +0 -0
  60. {fpstreams-2.2.0 → fpstreams-2.3.0}/rust/src/select.rs +0 -0
  61. {fpstreams-2.2.0 → fpstreams-2.3.0}/rust/src/tests/adapters.rs +0 -0
  62. {fpstreams-2.2.0 → fpstreams-2.3.0}/rust/src/tests/group_exact/pairs.rs +0 -0
  63. {fpstreams-2.2.0 → fpstreams-2.3.0}/rust/src/tests/group_exact/rows.rs +0 -0
  64. {fpstreams-2.2.0 → fpstreams-2.3.0}/rust/src/tests/group_exact.rs +0 -0
  65. {fpstreams-2.2.0 → fpstreams-2.3.0}/rust/src/tests/group_fixed.rs +0 -0
  66. {fpstreams-2.2.0 → fpstreams-2.3.0}/rust/src/tests/join_callable/behavior.rs +0 -0
  67. {fpstreams-2.2.0 → fpstreams-2.3.0}/rust/src/tests/join_callable/direct.rs +0 -0
  68. {fpstreams-2.2.0 → fpstreams-2.3.0}/rust/src/tests/join_callable.rs +0 -0
  69. {fpstreams-2.2.0 → fpstreams-2.3.0}/rust/src/tests/join_exact.rs +0 -0
  70. {fpstreams-2.2.0 → fpstreams-2.3.0}/rust/src/tests/numeric/buffers.rs +0 -0
  71. {fpstreams-2.2.0 → fpstreams-2.3.0}/rust/src/tests/numeric/kernels.rs +0 -0
  72. {fpstreams-2.2.0 → fpstreams-2.3.0}/rust/src/tests/numeric/reductions.rs +0 -0
  73. {fpstreams-2.2.0 → fpstreams-2.3.0}/rust/src/tests/numeric.rs +0 -0
  74. {fpstreams-2.2.0 → fpstreams-2.3.0}/rust/src/tests/numpy_group.rs +0 -0
  75. {fpstreams-2.2.0 → fpstreams-2.3.0}/rust/src/tests.rs +0 -0
  76. {fpstreams-2.2.0 → fpstreams-2.3.0}/rust/src/unnest.rs +0 -0
  77. {fpstreams-2.2.0 → fpstreams-2.3.0}/rust/src/unpivot.rs +0 -0
  78. {fpstreams-2.2.0 → fpstreams-2.3.0}/src/fpstreams/_native.pyi +0 -0
  79. {fpstreams-2.2.0 → fpstreams-2.3.0}/src/fpstreams/_provenance.py +0 -0
  80. {fpstreams-2.2.0 → fpstreams-2.3.0}/src/fpstreams/aggregate.py +0 -0
  81. {fpstreams-2.2.0 → fpstreams-2.3.0}/src/fpstreams/async_flow.py +0 -0
  82. {fpstreams-2.2.0 → fpstreams-2.3.0}/src/fpstreams/collecting/__init__.py +0 -0
  83. {fpstreams-2.2.0 → fpstreams-2.3.0}/src/fpstreams/collecting/_collector_base.py +0 -0
  84. {fpstreams-2.2.0 → fpstreams-2.3.0}/src/fpstreams/collecting/aggregate_program.py +0 -0
  85. {fpstreams-2.2.0 → fpstreams-2.3.0}/src/fpstreams/collecting/aggregation.py +0 -0
  86. {fpstreams-2.2.0 → fpstreams-2.3.0}/src/fpstreams/collecting/collector.py +0 -0
  87. {fpstreams-2.2.0 → fpstreams-2.3.0}/src/fpstreams/collecting/program.py +0 -0
  88. {fpstreams-2.2.0 → fpstreams-2.3.0}/src/fpstreams/collecting/reducer.py +0 -0
  89. {fpstreams-2.2.0 → fpstreams-2.3.0}/src/fpstreams/collecting/statistics.py +0 -0
  90. {fpstreams-2.2.0 → fpstreams-2.3.0}/src/fpstreams/collectors.py +0 -0
  91. {fpstreams-2.2.0 → fpstreams-2.3.0}/src/fpstreams/column.py +0 -0
  92. {fpstreams-2.2.0 → fpstreams-2.3.0}/src/fpstreams/errors.py +0 -0
  93. {fpstreams-2.2.0 → fpstreams-2.3.0}/src/fpstreams/exceptions.py +0 -0
  94. {fpstreams-2.2.0 → fpstreams-2.3.0}/src/fpstreams/execution/__init__.py +0 -0
  95. {fpstreams-2.2.0 → fpstreams-2.3.0}/src/fpstreams/execution/_pair_aggregate.py +0 -0
  96. {fpstreams-2.2.0 → fpstreams-2.3.0}/src/fpstreams/execution/_pair_dict.py +0 -0
  97. {fpstreams-2.2.0 → fpstreams-2.3.0}/src/fpstreams/execution/_pair_row_filter.py +0 -0
  98. {fpstreams-2.2.0 → fpstreams-2.3.0}/src/fpstreams/execution/_scalar_fusion.py +0 -0
  99. {fpstreams-2.2.0 → fpstreams-2.3.0}/src/fpstreams/execution/arrow.py +0 -0
  100. {fpstreams-2.2.0 → fpstreams-2.3.0}/src/fpstreams/execution/async_iterators.py +0 -0
  101. {fpstreams-2.2.0 → fpstreams-2.3.0}/src/fpstreams/execution/async_map.py +0 -0
  102. {fpstreams-2.2.0 → fpstreams-2.3.0}/src/fpstreams/execution/async_merge.py +0 -0
  103. {fpstreams-2.2.0 → fpstreams-2.3.0}/src/fpstreams/execution/async_ops.py +0 -0
  104. {fpstreams-2.2.0 → fpstreams-2.3.0}/src/fpstreams/execution/async_prefetch.py +0 -0
  105. {fpstreams-2.2.0 → fpstreams-2.3.0}/src/fpstreams/execution/async_queue.py +0 -0
  106. {fpstreams-2.2.0 → fpstreams-2.3.0}/src/fpstreams/execution/async_scheduler.py +0 -0
  107. {fpstreams-2.2.0 → fpstreams-2.3.0}/src/fpstreams/execution/async_timers.py +0 -0
  108. {fpstreams-2.2.0 → fpstreams-2.3.0}/src/fpstreams/execution/native.py +0 -0
  109. {fpstreams-2.2.0 → fpstreams-2.3.0}/src/fpstreams/execution/numpy_group.py +0 -0
  110. {fpstreams-2.2.0 → fpstreams-2.3.0}/src/fpstreams/execution/numpy_prefix.py +0 -0
  111. {fpstreams-2.2.0 → fpstreams-2.3.0}/src/fpstreams/execution/physical.py +0 -0
  112. {fpstreams-2.2.0 → fpstreams-2.3.0}/src/fpstreams/execution/relational/__init__.py +0 -0
  113. {fpstreams-2.2.0 → fpstreams-2.3.0}/src/fpstreams/execution/relational/arrow_global.py +0 -0
  114. {fpstreams-2.2.0 → fpstreams-2.3.0}/src/fpstreams/execution/relational/arrow_group.py +0 -0
  115. {fpstreams-2.2.0 → fpstreams-2.3.0}/src/fpstreams/execution/relational/arrow_group_rows.py +0 -0
  116. {fpstreams-2.2.0 → fpstreams-2.3.0}/src/fpstreams/execution/relational/join.py +0 -0
  117. {fpstreams-2.2.0 → fpstreams-2.3.0}/src/fpstreams/execution/sorting.py +0 -0
  118. {fpstreams-2.2.0 → fpstreams-2.3.0}/src/fpstreams/execution/sync.py +0 -0
  119. {fpstreams-2.2.0 → fpstreams-2.3.0}/src/fpstreams/expr.py +0 -0
  120. {fpstreams-2.2.0 → fpstreams-2.3.0}/src/fpstreams/expressions/__init__.py +0 -0
  121. {fpstreams-2.2.0 → fpstreams-2.3.0}/src/fpstreams/expressions/_codegen.py +0 -0
  122. {fpstreams-2.2.0 → fpstreams-2.3.0}/src/fpstreams/expressions/_row_codegen.py +0 -0
  123. {fpstreams-2.2.0 → fpstreams-2.3.0}/src/fpstreams/expressions/program.py +0 -0
  124. {fpstreams-2.2.0 → fpstreams-2.3.0}/src/fpstreams/expressions/row.py +0 -0
  125. {fpstreams-2.2.0 → fpstreams-2.3.0}/src/fpstreams/expressions/row_eval.py +0 -0
  126. {fpstreams-2.2.0 → fpstreams-2.3.0}/src/fpstreams/expressions/row_ir.py +0 -0
  127. {fpstreams-2.2.0 → fpstreams-2.3.0}/src/fpstreams/expressions/scalar.py +0 -0
  128. {fpstreams-2.2.0 → fpstreams-2.3.0}/src/fpstreams/expressions/selectors.py +0 -0
  129. {fpstreams-2.2.0 → fpstreams-2.3.0}/src/fpstreams/expressions/typed_ir.py +0 -0
  130. {fpstreams-2.2.0 → fpstreams-2.3.0}/src/fpstreams/functional.py +0 -0
  131. {fpstreams-2.2.0 → fpstreams-2.3.0}/src/fpstreams/gather.py +0 -0
  132. {fpstreams-2.2.0 → fpstreams-2.3.0}/src/fpstreams/io_safety.py +0 -0
  133. {fpstreams-2.2.0 → fpstreams-2.3.0}/src/fpstreams/option.py +0 -0
  134. {fpstreams-2.2.0 → fpstreams-2.3.0}/src/fpstreams/physical/__init__.py +0 -0
  135. {fpstreams-2.2.0 → fpstreams-2.3.0}/src/fpstreams/physical/async_plan.py +0 -0
  136. {fpstreams-2.2.0 → fpstreams-2.3.0}/src/fpstreams/physical/compiled.py +0 -0
  137. {fpstreams-2.2.0 → fpstreams-2.3.0}/src/fpstreams/physical/kernel_cache.py +0 -0
  138. {fpstreams-2.2.0 → fpstreams-2.3.0}/src/fpstreams/physical/plan.py +0 -0
  139. {fpstreams-2.2.0 → fpstreams-2.3.0}/src/fpstreams/physical/relational.py +0 -0
  140. {fpstreams-2.2.0 → fpstreams-2.3.0}/src/fpstreams/planning/__init__.py +0 -0
  141. {fpstreams-2.2.0 → fpstreams-2.3.0}/src/fpstreams/planning/_pair_stages.py +0 -0
  142. {fpstreams-2.2.0 → fpstreams-2.3.0}/src/fpstreams/planning/arrow.py +0 -0
  143. {fpstreams-2.2.0 → fpstreams-2.3.0}/src/fpstreams/planning/arrow_source.py +0 -0
  144. {fpstreams-2.2.0 → fpstreams-2.3.0}/src/fpstreams/planning/async_.py +0 -0
  145. {fpstreams-2.2.0 → fpstreams-2.3.0}/src/fpstreams/planning/async_utils.py +0 -0
  146. {fpstreams-2.2.0 → fpstreams-2.3.0}/src/fpstreams/planning/compiler.py +0 -0
  147. {fpstreams-2.2.0 → fpstreams-2.3.0}/src/fpstreams/planning/gather.py +0 -0
  148. {fpstreams-2.2.0 → fpstreams-2.3.0}/src/fpstreams/planning/logical.py +0 -0
  149. {fpstreams-2.2.0 → fpstreams-2.3.0}/src/fpstreams/planning/numpy.py +0 -0
  150. {fpstreams-2.2.0 → fpstreams-2.3.0}/src/fpstreams/planning/pair_i64_expression.py +0 -0
  151. {fpstreams-2.2.0 → fpstreams-2.3.0}/src/fpstreams/planning/plan_cache.py +0 -0
  152. {fpstreams-2.2.0 → fpstreams-2.3.0}/src/fpstreams/planning/semantic_analyzer.py +0 -0
  153. {fpstreams-2.2.0 → fpstreams-2.3.0}/src/fpstreams/planning/semantic_rules.py +0 -0
  154. {fpstreams-2.2.0 → fpstreams-2.3.0}/src/fpstreams/planning/semantics.py +0 -0
  155. {fpstreams-2.2.0 → fpstreams-2.3.0}/src/fpstreams/planning/source.py +0 -0
  156. {fpstreams-2.2.0 → fpstreams-2.3.0}/src/fpstreams/primitives/__init__.py +0 -0
  157. {fpstreams-2.2.0 → fpstreams-2.3.0}/src/fpstreams/primitives/option.py +0 -0
  158. {fpstreams-2.2.0 → fpstreams-2.3.0}/src/fpstreams/primitives/result.py +0 -0
  159. {fpstreams-2.2.0 → fpstreams-2.3.0}/src/fpstreams/py.typed +0 -0
  160. {fpstreams-2.2.0 → fpstreams-2.3.0}/src/fpstreams/result.py +0 -0
  161. {fpstreams-2.2.0 → fpstreams-2.3.0}/src/fpstreams/runtime/__init__.py +0 -0
  162. {fpstreams-2.2.0 → fpstreams-2.3.0}/src/fpstreams/runtime/_distinct.py +0 -0
  163. {fpstreams-2.2.0 → fpstreams-2.3.0}/src/fpstreams/runtime/failpoints.py +0 -0
  164. {fpstreams-2.2.0 → fpstreams-2.3.0}/src/fpstreams/runtime/files.py +0 -0
  165. {fpstreams-2.2.0 → fpstreams-2.3.0}/src/fpstreams/runtime/iterators.py +0 -0
  166. {fpstreams-2.2.0 → fpstreams-2.3.0}/src/fpstreams/runtime/limits.py +0 -0
  167. {fpstreams-2.2.0 → fpstreams-2.3.0}/src/fpstreams/runtime/metrics.py +0 -0
  168. {fpstreams-2.2.0 → fpstreams-2.3.0}/src/fpstreams/runtime/query.py +0 -0
  169. {fpstreams-2.2.0 → fpstreams-2.3.0}/src/fpstreams/runtime/report.py +0 -0
  170. {fpstreams-2.2.0 → fpstreams-2.3.0}/src/fpstreams/runtime/resources.py +0 -0
  171. {fpstreams-2.2.0 → fpstreams-2.3.0}/src/fpstreams/runtime/spill.py +0 -0
  172. {fpstreams-2.2.0 → fpstreams-2.3.0}/src/fpstreams/runtime/tasks.py +0 -0
  173. {fpstreams-2.2.0 → fpstreams-2.3.0}/src/fpstreams/storage/__init__.py +0 -0
  174. {fpstreams-2.2.0 → fpstreams-2.3.0}/src/fpstreams/storage/codec.py +0 -0
  175. {fpstreams-2.2.0 → fpstreams-2.3.0}/src/fpstreams/streams/__init__.py +0 -0
  176. {fpstreams-2.2.0 → fpstreams-2.3.0}/src/fpstreams/streams/_async_identity.py +0 -0
  177. {fpstreams-2.2.0 → fpstreams-2.3.0}/src/fpstreams/streams/_flow_structural_list.py +0 -0
  178. {fpstreams-2.2.0 → fpstreams-2.3.0}/src/fpstreams/streams/_flow_unique_list.py +0 -0
  179. {fpstreams-2.2.0 → fpstreams-2.3.0}/src/fpstreams/streams/async_terminals.py +0 -0
  180. {fpstreams-2.2.0 → fpstreams-2.3.0}/src/fpstreams/streams/flow_terminals.py +0 -0
  181. {fpstreams-2.2.0 → fpstreams-2.3.0}/src/fpstreams/streams/pairs.py +0 -0
  182. {fpstreams-2.2.0 → fpstreams-2.3.0}/src/fpstreams/tabular/_text_sources.py +0 -0
  183. {fpstreams-2.2.0 → fpstreams-2.3.0}/src/fpstreams/tabular/arrow.py +0 -0
  184. {fpstreams-2.2.0 → fpstreams-2.3.0}/src/fpstreams/tabular/dataframe.py +0 -0
  185. {fpstreams-2.2.0 → fpstreams-2.3.0}/src/fpstreams/tabular/grouped.py +0 -0
  186. {fpstreams-2.2.0 → fpstreams-2.3.0}/src/fpstreams/tabular/io.py +0 -0
  187. {fpstreams-2.2.0 → fpstreams-2.3.0}/src/fpstreams/tabular/join.py +0 -0
  188. {fpstreams-2.2.0 → fpstreams-2.3.0}/src/fpstreams/tabular/numpy.py +0 -0
  189. {fpstreams-2.2.0 → fpstreams-2.3.0}/src/fpstreams/tabular/polars.py +0 -0
  190. {fpstreams-2.2.0 → fpstreams-2.3.0}/src/fpstreams/tabular/records.py +0 -0
  191. {fpstreams-2.2.0 → fpstreams-2.3.0}/src/fpstreams/tabular/rows.py +0 -0
  192. {fpstreams-2.2.0 → fpstreams-2.3.0}/src/fpstreams/tabular/spill.py +0 -0
  193. {fpstreams-2.2.0 → fpstreams-2.3.0}/src/fpstreams/tabular/spill_io.py +0 -0
  194. {fpstreams-2.2.0 → fpstreams-2.3.0}/src/fpstreams/tabular/spill_limits.py +0 -0
  195. {fpstreams-2.2.0 → fpstreams-2.3.0}/src/fpstreams/tabular/sql.py +0 -0
  196. {fpstreams-2.2.0 → fpstreams-2.3.0}/src/fpstreams/tabular/sqlite_sink.py +0 -0
  197. {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
- ## Unreleased
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.2.0
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
@@ -4,7 +4,7 @@ build-backend = "maturin"
4
4
 
5
5
  [project]
6
6
  name = "fpstreams"
7
- version = "2.2.0"
7
+ version = "2.3.0"
8
8
  description = "Lazy Python pipelines for iterables and records, with bounded async concurrency."
9
9
  readme = "README.md"
10
10
  authors = [
@@ -4,7 +4,7 @@ version = 4
4
4
 
5
5
  [[package]]
6
6
  name = "fpstreams-native"
7
- version = "2.2.0"
7
+ version = "2.3.0"
8
8
  dependencies = [
9
9
  "pyo3",
10
10
  "pyo3-build-config",
@@ -1,6 +1,6 @@
1
1
  [package]
2
2
  name = "fpstreams-native"
3
- version = "2.2.0"
3
+ version = "2.3.0"
4
4
  edition = "2024"
5
5
  rust-version = "1.85"
6
6
  description = "Native fused execution kernels for fpstreams."
@@ -39,9 +39,10 @@ from .streams import (
39
39
  flow,
40
40
  pairs,
41
41
  )
42
- from .tabular import Rows, SpillLimits, rows
42
+ from .tabular import Rows, SpillLimits
43
+ from .tabular.factory import rows as rows
43
44
 
44
- __version__ = "2.2.0"
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
- selected = tuple(selector(row) for _name, selector in keys)
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 executor shutdown.
90
+ before waiting for this map's running callbacks. Only an owned pool is shut down.
90
91
  """
91
- executor = (
92
- ThreadPoolExecutor(max_workers=operation.workers)
93
- if operation.backend == "thread"
94
- else ProcessPoolExecutor(
95
- max_workers=operation.workers,
96
- mp_context=get_context("spawn"),
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.popleft().result()
109
+ yield _pop_parallel_result(pending)
106
110
  while pending:
107
- yield pending.popleft().result()
111
+ yield _pop_parallel_result(pending)
108
112
  finally:
109
- for future in pending:
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
- for future in pending_set:
128
- future.cancel()
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
- operation.backend if isinstance(operation, ParallelMapOp) else "python"
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 worst-case data movement and complexity metadata to a terminal decision."""
121
+ """Attach terminal metadata, constructing a Python-only decision in one allocation."""
118
122
  source = plan.source.native_data
119
- crosses_native_boundary = decision.engine in {"native", "hybrid"}
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
- decision.engine == "native"
127
- and decision.program is not None
128
- and decision.program.kind == "i64"
129
- and not decision.program.stages
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
- decision.engine == "native"
135
- and decision.program is not None
136
- and not decision.program.stages
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
- decision.engine == "native"
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
- decision.engine == "native"
149
- and decision.program is not None
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(decision.program.kind)
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"{decision.reason}; bounded probe; only undecided fallback bulk-copies"
162
+ f"{decision_reason}; bounded probe; only undecided fallback bulk-copies"
156
163
  if probe_path
157
- else f"{decision.reason}; retained range terminal uses constant-time metadata"
164
+ else f"{decision_reason}; retained range terminal uses constant-time metadata"
158
165
  if range_metadata
159
- else decision.reason
166
+ else decision_reason
160
167
  )
161
168
  return EngineDecision(
162
- decision.engine,
169
+ engine,
163
170
  reason,
164
- decision.program,
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 decision.engine == "hybrid",
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 = _numpy_buffer_kind(source)
854
- source_is_container = isinstance(source, (list, tuple)) or buffer_kind is not None
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 metadata_count is not None:
863
- return metadata_count
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 registered framed path through the query's tracked I/O boundary."""
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