fpstreams 2.2.1__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.1 → fpstreams-2.3.0}/CHANGELOG.md +30 -1
  2. {fpstreams-2.2.1 → fpstreams-2.3.0}/PKG-INFO +10 -1
  3. {fpstreams-2.2.1 → fpstreams-2.3.0}/README.md +9 -0
  4. {fpstreams-2.2.1 → fpstreams-2.3.0}/pyproject.toml +1 -1
  5. {fpstreams-2.2.1 → fpstreams-2.3.0}/rust/Cargo.lock +1 -1
  6. {fpstreams-2.2.1 → fpstreams-2.3.0}/rust/Cargo.toml +1 -1
  7. {fpstreams-2.2.1 → fpstreams-2.3.0}/src/fpstreams/__init__.py +1 -1
  8. {fpstreams-2.2.1 → fpstreams-2.3.0}/src/fpstreams/execution/_rows_fusion.py +50 -0
  9. {fpstreams-2.2.1 → fpstreams-2.3.0}/src/fpstreams/execution/sorted_streams.py +22 -1
  10. {fpstreams-2.2.1 → fpstreams-2.3.0}/src/fpstreams/execution/sync_ops.py +39 -16
  11. {fpstreams-2.2.1 → fpstreams-2.3.0}/src/fpstreams/planning/explain.py +15 -1
  12. {fpstreams-2.2.1 → fpstreams-2.3.0}/src/fpstreams/planning/sync.py +3 -0
  13. {fpstreams-2.2.1 → fpstreams-2.3.0}/src/fpstreams/storage/spill_store.py +5 -1
  14. {fpstreams-2.2.1 → fpstreams-2.3.0}/src/fpstreams/streams/async_flow.py +94 -2
  15. {fpstreams-2.2.1 → fpstreams-2.3.0}/src/fpstreams/streams/flow.py +84 -28
  16. {fpstreams-2.2.1 → fpstreams-2.3.0}/LICENSE +0 -0
  17. {fpstreams-2.2.1 → fpstreams-2.3.0}/rust/build.rs +0 -0
  18. {fpstreams-2.2.1 → fpstreams-2.3.0}/rust/src/common.rs +0 -0
  19. {fpstreams-2.2.1 → fpstreams-2.3.0}/rust/src/float/affine_pair.rs +0 -0
  20. {fpstreams-2.2.1 → fpstreams-2.3.0}/rust/src/float/endpoints.rs +0 -0
  21. {fpstreams-2.2.1 → fpstreams-2.3.0}/rust/src/float.rs +0 -0
  22. {fpstreams-2.2.1 → fpstreams-2.3.0}/rust/src/integer/endpoints.rs +0 -0
  23. {fpstreams-2.2.1 → fpstreams-2.3.0}/rust/src/integer.rs +0 -0
  24. {fpstreams-2.2.1 → fpstreams-2.3.0}/rust/src/lib.rs +0 -0
  25. {fpstreams-2.2.1 → fpstreams-2.3.0}/rust/src/numeric_mean.rs +0 -0
  26. {fpstreams-2.2.1 → fpstreams-2.3.0}/rust/src/numpy_export.rs +0 -0
  27. {fpstreams-2.2.1 → fpstreams-2.3.0}/rust/src/numpy_group/buffer.rs +0 -0
  28. {fpstreams-2.2.1 → fpstreams-2.3.0}/rust/src/numpy_group.rs +0 -0
  29. {fpstreams-2.2.1 → fpstreams-2.3.0}/rust/src/pair/expr.rs +0 -0
  30. {fpstreams-2.2.1 → fpstreams-2.3.0}/rust/src/pair/prefix.rs +0 -0
  31. {fpstreams-2.2.1 → fpstreams-2.3.0}/rust/src/pair/row_filter.rs +0 -0
  32. {fpstreams-2.2.1 → fpstreams-2.3.0}/rust/src/pair/unique.rs +0 -0
  33. {fpstreams-2.2.1 → fpstreams-2.3.0}/rust/src/pair/value_filter.rs +0 -0
  34. {fpstreams-2.2.1 → fpstreams-2.3.0}/rust/src/pair/value_map.rs +0 -0
  35. {fpstreams-2.2.1 → fpstreams-2.3.0}/rust/src/pair.rs +0 -0
  36. {fpstreams-2.2.1 → fpstreams-2.3.0}/rust/src/pivot.rs +0 -0
  37. {fpstreams-2.2.1 → fpstreams-2.3.0}/rust/src/records.rs +0 -0
  38. {fpstreams-2.2.1 → fpstreams-2.3.0}/rust/src/relational/adapters/namedtuple.rs +0 -0
  39. {fpstreams-2.2.1 → fpstreams-2.3.0}/rust/src/relational/adapters.rs +0 -0
  40. {fpstreams-2.2.1 → fpstreams-2.3.0}/rust/src/relational/global_numeric.rs +0 -0
  41. {fpstreams-2.2.1 → fpstreams-2.3.0}/rust/src/relational/group_numeric.rs +0 -0
  42. {fpstreams-2.2.1 → fpstreams-2.3.0}/rust/src/relational/group_pair_expr.rs +0 -0
  43. {fpstreams-2.2.1 → fpstreams-2.3.0}/rust/src/relational/join_callable/many.rs +0 -0
  44. {fpstreams-2.2.1 → fpstreams-2.3.0}/rust/src/relational/join_callable/unique.rs +0 -0
  45. {fpstreams-2.2.1 → fpstreams-2.3.0}/rust/src/relational/join_callable.rs +0 -0
  46. {fpstreams-2.2.1 → fpstreams-2.3.0}/rust/src/relational/join_exact.rs +0 -0
  47. {fpstreams-2.2.1 → fpstreams-2.3.0}/rust/src/relational/join_i64.rs +0 -0
  48. {fpstreams-2.2.1 → fpstreams-2.3.0}/rust/src/relational.rs +0 -0
  49. {fpstreams-2.2.1 → fpstreams-2.3.0}/rust/src/relational_fixed/composite.rs +0 -0
  50. {fpstreams-2.2.1 → fpstreams-2.3.0}/rust/src/relational_fixed/global_multi/same_field.rs +0 -0
  51. {fpstreams-2.2.1 → fpstreams-2.3.0}/rust/src/relational_fixed/global_multi.rs +0 -0
  52. {fpstreams-2.2.1 → fpstreams-2.3.0}/rust/src/relational_fixed/group_multi.rs +0 -0
  53. {fpstreams-2.2.1 → fpstreams-2.3.0}/rust/src/relational_fixed/single.rs +0 -0
  54. {fpstreams-2.2.1 → fpstreams-2.3.0}/rust/src/relational_fixed.rs +0 -0
  55. {fpstreams-2.2.1 → fpstreams-2.3.0}/rust/src/scalar_sort.rs +0 -0
  56. {fpstreams-2.2.1 → fpstreams-2.3.0}/rust/src/scalar_unique.rs +0 -0
  57. {fpstreams-2.2.1 → fpstreams-2.3.0}/rust/src/select.rs +0 -0
  58. {fpstreams-2.2.1 → fpstreams-2.3.0}/rust/src/tests/adapters.rs +0 -0
  59. {fpstreams-2.2.1 → fpstreams-2.3.0}/rust/src/tests/group_exact/pairs.rs +0 -0
  60. {fpstreams-2.2.1 → fpstreams-2.3.0}/rust/src/tests/group_exact/rows.rs +0 -0
  61. {fpstreams-2.2.1 → fpstreams-2.3.0}/rust/src/tests/group_exact.rs +0 -0
  62. {fpstreams-2.2.1 → fpstreams-2.3.0}/rust/src/tests/group_fixed.rs +0 -0
  63. {fpstreams-2.2.1 → fpstreams-2.3.0}/rust/src/tests/join_callable/behavior.rs +0 -0
  64. {fpstreams-2.2.1 → fpstreams-2.3.0}/rust/src/tests/join_callable/direct.rs +0 -0
  65. {fpstreams-2.2.1 → fpstreams-2.3.0}/rust/src/tests/join_callable.rs +0 -0
  66. {fpstreams-2.2.1 → fpstreams-2.3.0}/rust/src/tests/join_exact.rs +0 -0
  67. {fpstreams-2.2.1 → fpstreams-2.3.0}/rust/src/tests/numeric/buffers.rs +0 -0
  68. {fpstreams-2.2.1 → fpstreams-2.3.0}/rust/src/tests/numeric/kernels.rs +0 -0
  69. {fpstreams-2.2.1 → fpstreams-2.3.0}/rust/src/tests/numeric/reductions.rs +0 -0
  70. {fpstreams-2.2.1 → fpstreams-2.3.0}/rust/src/tests/numeric.rs +0 -0
  71. {fpstreams-2.2.1 → fpstreams-2.3.0}/rust/src/tests/numpy_group.rs +0 -0
  72. {fpstreams-2.2.1 → fpstreams-2.3.0}/rust/src/tests.rs +0 -0
  73. {fpstreams-2.2.1 → fpstreams-2.3.0}/rust/src/unnest.rs +0 -0
  74. {fpstreams-2.2.1 → fpstreams-2.3.0}/rust/src/unpivot.rs +0 -0
  75. {fpstreams-2.2.1 → fpstreams-2.3.0}/src/fpstreams/_native.pyi +0 -0
  76. {fpstreams-2.2.1 → fpstreams-2.3.0}/src/fpstreams/_provenance.py +0 -0
  77. {fpstreams-2.2.1 → fpstreams-2.3.0}/src/fpstreams/aggregate.py +0 -0
  78. {fpstreams-2.2.1 → fpstreams-2.3.0}/src/fpstreams/async_flow.py +0 -0
  79. {fpstreams-2.2.1 → fpstreams-2.3.0}/src/fpstreams/collecting/__init__.py +0 -0
  80. {fpstreams-2.2.1 → fpstreams-2.3.0}/src/fpstreams/collecting/_collector_base.py +0 -0
  81. {fpstreams-2.2.1 → fpstreams-2.3.0}/src/fpstreams/collecting/aggregate_program.py +0 -0
  82. {fpstreams-2.2.1 → fpstreams-2.3.0}/src/fpstreams/collecting/aggregation.py +0 -0
  83. {fpstreams-2.2.1 → fpstreams-2.3.0}/src/fpstreams/collecting/collector.py +0 -0
  84. {fpstreams-2.2.1 → fpstreams-2.3.0}/src/fpstreams/collecting/program.py +0 -0
  85. {fpstreams-2.2.1 → fpstreams-2.3.0}/src/fpstreams/collecting/reducer.py +0 -0
  86. {fpstreams-2.2.1 → fpstreams-2.3.0}/src/fpstreams/collecting/statistics.py +0 -0
  87. {fpstreams-2.2.1 → fpstreams-2.3.0}/src/fpstreams/collectors.py +0 -0
  88. {fpstreams-2.2.1 → fpstreams-2.3.0}/src/fpstreams/column.py +0 -0
  89. {fpstreams-2.2.1 → fpstreams-2.3.0}/src/fpstreams/errors.py +0 -0
  90. {fpstreams-2.2.1 → fpstreams-2.3.0}/src/fpstreams/exceptions.py +0 -0
  91. {fpstreams-2.2.1 → fpstreams-2.3.0}/src/fpstreams/execution/__init__.py +0 -0
  92. {fpstreams-2.2.1 → fpstreams-2.3.0}/src/fpstreams/execution/_pair_aggregate.py +0 -0
  93. {fpstreams-2.2.1 → fpstreams-2.3.0}/src/fpstreams/execution/_pair_dict.py +0 -0
  94. {fpstreams-2.2.1 → fpstreams-2.3.0}/src/fpstreams/execution/_pair_row_filter.py +0 -0
  95. {fpstreams-2.2.1 → fpstreams-2.3.0}/src/fpstreams/execution/_scalar_fusion.py +0 -0
  96. {fpstreams-2.2.1 → fpstreams-2.3.0}/src/fpstreams/execution/arrow.py +0 -0
  97. {fpstreams-2.2.1 → fpstreams-2.3.0}/src/fpstreams/execution/async_iterators.py +0 -0
  98. {fpstreams-2.2.1 → fpstreams-2.3.0}/src/fpstreams/execution/async_map.py +0 -0
  99. {fpstreams-2.2.1 → fpstreams-2.3.0}/src/fpstreams/execution/async_merge.py +0 -0
  100. {fpstreams-2.2.1 → fpstreams-2.3.0}/src/fpstreams/execution/async_ops.py +0 -0
  101. {fpstreams-2.2.1 → fpstreams-2.3.0}/src/fpstreams/execution/async_prefetch.py +0 -0
  102. {fpstreams-2.2.1 → fpstreams-2.3.0}/src/fpstreams/execution/async_queue.py +0 -0
  103. {fpstreams-2.2.1 → fpstreams-2.3.0}/src/fpstreams/execution/async_scheduler.py +0 -0
  104. {fpstreams-2.2.1 → fpstreams-2.3.0}/src/fpstreams/execution/async_timers.py +0 -0
  105. {fpstreams-2.2.1 → fpstreams-2.3.0}/src/fpstreams/execution/native.py +0 -0
  106. {fpstreams-2.2.1 → fpstreams-2.3.0}/src/fpstreams/execution/numpy_group.py +0 -0
  107. {fpstreams-2.2.1 → fpstreams-2.3.0}/src/fpstreams/execution/numpy_prefix.py +0 -0
  108. {fpstreams-2.2.1 → fpstreams-2.3.0}/src/fpstreams/execution/physical.py +0 -0
  109. {fpstreams-2.2.1 → fpstreams-2.3.0}/src/fpstreams/execution/relational/__init__.py +0 -0
  110. {fpstreams-2.2.1 → fpstreams-2.3.0}/src/fpstreams/execution/relational/arrow_global.py +0 -0
  111. {fpstreams-2.2.1 → fpstreams-2.3.0}/src/fpstreams/execution/relational/arrow_group.py +0 -0
  112. {fpstreams-2.2.1 → fpstreams-2.3.0}/src/fpstreams/execution/relational/arrow_group_rows.py +0 -0
  113. {fpstreams-2.2.1 → fpstreams-2.3.0}/src/fpstreams/execution/relational/join.py +0 -0
  114. {fpstreams-2.2.1 → fpstreams-2.3.0}/src/fpstreams/execution/sorting.py +0 -0
  115. {fpstreams-2.2.1 → fpstreams-2.3.0}/src/fpstreams/execution/sync.py +0 -0
  116. {fpstreams-2.2.1 → fpstreams-2.3.0}/src/fpstreams/expr.py +0 -0
  117. {fpstreams-2.2.1 → fpstreams-2.3.0}/src/fpstreams/expressions/__init__.py +0 -0
  118. {fpstreams-2.2.1 → fpstreams-2.3.0}/src/fpstreams/expressions/_codegen.py +0 -0
  119. {fpstreams-2.2.1 → fpstreams-2.3.0}/src/fpstreams/expressions/_row_codegen.py +0 -0
  120. {fpstreams-2.2.1 → fpstreams-2.3.0}/src/fpstreams/expressions/program.py +0 -0
  121. {fpstreams-2.2.1 → fpstreams-2.3.0}/src/fpstreams/expressions/row.py +0 -0
  122. {fpstreams-2.2.1 → fpstreams-2.3.0}/src/fpstreams/expressions/row_eval.py +0 -0
  123. {fpstreams-2.2.1 → fpstreams-2.3.0}/src/fpstreams/expressions/row_ir.py +0 -0
  124. {fpstreams-2.2.1 → fpstreams-2.3.0}/src/fpstreams/expressions/scalar.py +0 -0
  125. {fpstreams-2.2.1 → fpstreams-2.3.0}/src/fpstreams/expressions/selectors.py +0 -0
  126. {fpstreams-2.2.1 → fpstreams-2.3.0}/src/fpstreams/expressions/typed_ir.py +0 -0
  127. {fpstreams-2.2.1 → fpstreams-2.3.0}/src/fpstreams/functional.py +0 -0
  128. {fpstreams-2.2.1 → fpstreams-2.3.0}/src/fpstreams/gather.py +0 -0
  129. {fpstreams-2.2.1 → fpstreams-2.3.0}/src/fpstreams/io_safety.py +0 -0
  130. {fpstreams-2.2.1 → fpstreams-2.3.0}/src/fpstreams/option.py +0 -0
  131. {fpstreams-2.2.1 → fpstreams-2.3.0}/src/fpstreams/physical/__init__.py +0 -0
  132. {fpstreams-2.2.1 → fpstreams-2.3.0}/src/fpstreams/physical/async_plan.py +0 -0
  133. {fpstreams-2.2.1 → fpstreams-2.3.0}/src/fpstreams/physical/compiled.py +0 -0
  134. {fpstreams-2.2.1 → fpstreams-2.3.0}/src/fpstreams/physical/kernel_cache.py +0 -0
  135. {fpstreams-2.2.1 → fpstreams-2.3.0}/src/fpstreams/physical/plan.py +0 -0
  136. {fpstreams-2.2.1 → fpstreams-2.3.0}/src/fpstreams/physical/relational.py +0 -0
  137. {fpstreams-2.2.1 → fpstreams-2.3.0}/src/fpstreams/planning/__init__.py +0 -0
  138. {fpstreams-2.2.1 → fpstreams-2.3.0}/src/fpstreams/planning/_pair_stages.py +0 -0
  139. {fpstreams-2.2.1 → fpstreams-2.3.0}/src/fpstreams/planning/arrow.py +0 -0
  140. {fpstreams-2.2.1 → fpstreams-2.3.0}/src/fpstreams/planning/arrow_source.py +0 -0
  141. {fpstreams-2.2.1 → fpstreams-2.3.0}/src/fpstreams/planning/async_.py +0 -0
  142. {fpstreams-2.2.1 → fpstreams-2.3.0}/src/fpstreams/planning/async_utils.py +0 -0
  143. {fpstreams-2.2.1 → fpstreams-2.3.0}/src/fpstreams/planning/compiler.py +0 -0
  144. {fpstreams-2.2.1 → fpstreams-2.3.0}/src/fpstreams/planning/gather.py +0 -0
  145. {fpstreams-2.2.1 → fpstreams-2.3.0}/src/fpstreams/planning/logical.py +0 -0
  146. {fpstreams-2.2.1 → fpstreams-2.3.0}/src/fpstreams/planning/native.py +0 -0
  147. {fpstreams-2.2.1 → fpstreams-2.3.0}/src/fpstreams/planning/numpy.py +0 -0
  148. {fpstreams-2.2.1 → fpstreams-2.3.0}/src/fpstreams/planning/pair_i64_expression.py +0 -0
  149. {fpstreams-2.2.1 → fpstreams-2.3.0}/src/fpstreams/planning/plan_cache.py +0 -0
  150. {fpstreams-2.2.1 → fpstreams-2.3.0}/src/fpstreams/planning/semantic_analyzer.py +0 -0
  151. {fpstreams-2.2.1 → fpstreams-2.3.0}/src/fpstreams/planning/semantic_rules.py +0 -0
  152. {fpstreams-2.2.1 → fpstreams-2.3.0}/src/fpstreams/planning/semantics.py +0 -0
  153. {fpstreams-2.2.1 → fpstreams-2.3.0}/src/fpstreams/planning/source.py +0 -0
  154. {fpstreams-2.2.1 → fpstreams-2.3.0}/src/fpstreams/primitives/__init__.py +0 -0
  155. {fpstreams-2.2.1 → fpstreams-2.3.0}/src/fpstreams/primitives/option.py +0 -0
  156. {fpstreams-2.2.1 → fpstreams-2.3.0}/src/fpstreams/primitives/result.py +0 -0
  157. {fpstreams-2.2.1 → fpstreams-2.3.0}/src/fpstreams/py.typed +0 -0
  158. {fpstreams-2.2.1 → fpstreams-2.3.0}/src/fpstreams/result.py +0 -0
  159. {fpstreams-2.2.1 → fpstreams-2.3.0}/src/fpstreams/runtime/__init__.py +0 -0
  160. {fpstreams-2.2.1 → fpstreams-2.3.0}/src/fpstreams/runtime/_distinct.py +0 -0
  161. {fpstreams-2.2.1 → fpstreams-2.3.0}/src/fpstreams/runtime/failpoints.py +0 -0
  162. {fpstreams-2.2.1 → fpstreams-2.3.0}/src/fpstreams/runtime/files.py +0 -0
  163. {fpstreams-2.2.1 → fpstreams-2.3.0}/src/fpstreams/runtime/iterators.py +0 -0
  164. {fpstreams-2.2.1 → fpstreams-2.3.0}/src/fpstreams/runtime/limits.py +0 -0
  165. {fpstreams-2.2.1 → fpstreams-2.3.0}/src/fpstreams/runtime/metrics.py +0 -0
  166. {fpstreams-2.2.1 → fpstreams-2.3.0}/src/fpstreams/runtime/query.py +0 -0
  167. {fpstreams-2.2.1 → fpstreams-2.3.0}/src/fpstreams/runtime/report.py +0 -0
  168. {fpstreams-2.2.1 → fpstreams-2.3.0}/src/fpstreams/runtime/resources.py +0 -0
  169. {fpstreams-2.2.1 → fpstreams-2.3.0}/src/fpstreams/runtime/spill.py +0 -0
  170. {fpstreams-2.2.1 → fpstreams-2.3.0}/src/fpstreams/runtime/tasks.py +0 -0
  171. {fpstreams-2.2.1 → fpstreams-2.3.0}/src/fpstreams/storage/__init__.py +0 -0
  172. {fpstreams-2.2.1 → fpstreams-2.3.0}/src/fpstreams/storage/codec.py +0 -0
  173. {fpstreams-2.2.1 → fpstreams-2.3.0}/src/fpstreams/streams/__init__.py +0 -0
  174. {fpstreams-2.2.1 → fpstreams-2.3.0}/src/fpstreams/streams/_async_identity.py +0 -0
  175. {fpstreams-2.2.1 → fpstreams-2.3.0}/src/fpstreams/streams/_flow_structural_list.py +0 -0
  176. {fpstreams-2.2.1 → fpstreams-2.3.0}/src/fpstreams/streams/_flow_unique_list.py +0 -0
  177. {fpstreams-2.2.1 → fpstreams-2.3.0}/src/fpstreams/streams/async_terminals.py +0 -0
  178. {fpstreams-2.2.1 → fpstreams-2.3.0}/src/fpstreams/streams/flow_terminals.py +0 -0
  179. {fpstreams-2.2.1 → fpstreams-2.3.0}/src/fpstreams/streams/pairs.py +0 -0
  180. {fpstreams-2.2.1 → fpstreams-2.3.0}/src/fpstreams/tabular/__init__.py +0 -0
  181. {fpstreams-2.2.1 → fpstreams-2.3.0}/src/fpstreams/tabular/_text_sources.py +0 -0
  182. {fpstreams-2.2.1 → fpstreams-2.3.0}/src/fpstreams/tabular/arrow.py +0 -0
  183. {fpstreams-2.2.1 → fpstreams-2.3.0}/src/fpstreams/tabular/dataframe.py +0 -0
  184. {fpstreams-2.2.1 → fpstreams-2.3.0}/src/fpstreams/tabular/factory.py +0 -0
  185. {fpstreams-2.2.1 → fpstreams-2.3.0}/src/fpstreams/tabular/grouped.py +0 -0
  186. {fpstreams-2.2.1 → fpstreams-2.3.0}/src/fpstreams/tabular/io.py +0 -0
  187. {fpstreams-2.2.1 → fpstreams-2.3.0}/src/fpstreams/tabular/join.py +0 -0
  188. {fpstreams-2.2.1 → fpstreams-2.3.0}/src/fpstreams/tabular/numpy.py +0 -0
  189. {fpstreams-2.2.1 → fpstreams-2.3.0}/src/fpstreams/tabular/polars.py +0 -0
  190. {fpstreams-2.2.1 → fpstreams-2.3.0}/src/fpstreams/tabular/records.py +0 -0
  191. {fpstreams-2.2.1 → fpstreams-2.3.0}/src/fpstreams/tabular/rows.py +0 -0
  192. {fpstreams-2.2.1 → fpstreams-2.3.0}/src/fpstreams/tabular/spill.py +0 -0
  193. {fpstreams-2.2.1 → fpstreams-2.3.0}/src/fpstreams/tabular/spill_io.py +0 -0
  194. {fpstreams-2.2.1 → fpstreams-2.3.0}/src/fpstreams/tabular/spill_limits.py +0 -0
  195. {fpstreams-2.2.1 → fpstreams-2.3.0}/src/fpstreams/tabular/sql.py +0 -0
  196. {fpstreams-2.2.1 → fpstreams-2.3.0}/src/fpstreams/tabular/sqlite_sink.py +0 -0
  197. {fpstreams-2.2.1 → fpstreams-2.3.0}/src/fpstreams/testing.py +0 -0
@@ -3,7 +3,36 @@
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.
7
36
 
8
37
  ## 2.2.1 - 2026-10-07
9
38
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: fpstreams
3
- Version: 2.2.1
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:
@@ -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:
@@ -4,7 +4,7 @@ build-backend = "maturin"
4
4
 
5
5
  [project]
6
6
  name = "fpstreams"
7
- version = "2.2.1"
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.1"
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.1"
3
+ version = "2.3.0"
4
4
  edition = "2024"
5
5
  rust-version = "1.85"
6
6
  description = "Native fused execution kernels for fpstreams."
@@ -42,7 +42,7 @@ from .streams import (
42
42
  from .tabular import Rows, SpillLimits
43
43
  from .tabular.factory import rows as rows
44
44
 
45
- __version__ = "2.2.1"
45
+ __version__ = "2.3.0"
46
46
  Stream = Flow
47
47
  ParallelStream = Flow
48
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,
@@ -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
 
@@ -6,7 +6,7 @@ import asyncio
6
6
  import operator
7
7
  import os
8
8
  from collections.abc import AsyncIterable, AsyncIterator, Awaitable, Callable, Iterable
9
- from typing import Any, Generic, TypeVar, cast
9
+ from typing import Any, Generic, TypeVar, cast, overload
10
10
 
11
11
  from ..expressions.selectors import Selector, compile_selector
12
12
  from ..planning.async_ import (
@@ -340,6 +340,28 @@ class AsyncFlow(AsyncFlowTerminalsMixin[T], Generic[T]):
340
340
  )
341
341
  )
342
342
 
343
+ @overload
344
+ def map_async(
345
+ self,
346
+ function: Callable[[T], Awaitable[R]],
347
+ *,
348
+ concurrency: int = 8,
349
+ ordered: bool = True,
350
+ timeout: float | None = None,
351
+ buffer: int | None = None,
352
+ ) -> AsyncFlow[R]: ...
353
+
354
+ @overload
355
+ def map_async(
356
+ self,
357
+ function: Callable[[T], R | Awaitable[R]],
358
+ *,
359
+ concurrency: int = 8,
360
+ ordered: bool = True,
361
+ timeout: float | None = None,
362
+ buffer: int | None = None,
363
+ ) -> AsyncFlow[R]: ...
364
+
343
365
  def map_async(
344
366
  self,
345
367
  function: Callable[[T], R | Awaitable[R]],
@@ -353,12 +375,14 @@ class AsyncFlow(AsyncFlowTerminalsMixin[T], Generic[T]):
353
375
 
354
376
  Concurrency is bounded. Ordered mode delays later completed results until earlier inputs
355
377
  finish, while the buffer permits completed work to refill active mapper slots.
378
+ Callbacks run on the event loop. Timeout and cancellation are cooperative and cannot
379
+ interrupt a callback that blocks the loop.
356
380
 
357
381
  Args:
358
382
  function: Sync or async mapper called once for each source item.
359
383
  concurrency: Maximum mapper calls in flight.
360
384
  ordered: Emit in source order when true, otherwise in task-completion order.
361
- timeout: Optional per-item deadline covering mapper invocation and awaiting its result.
385
+ timeout: Optional cooperative per-item timeout while awaiting the mapper result.
362
386
  buffer: Maximum submitted results not yet emitted, or twice ``concurrency`` by default.
363
387
 
364
388
  Returns:
@@ -379,6 +403,12 @@ class AsyncFlow(AsyncFlowTerminalsMixin[T], Generic[T]):
379
403
  operation = _MapAsync(function, concurrency, ordered, timeout, buffer)
380
404
  return cast(AsyncFlow[R], self._append(operation))
381
405
 
406
+ @overload
407
+ def map(self, function: Callable[[T], Awaitable[R]]) -> AsyncFlow[R]: ...
408
+
409
+ @overload
410
+ def map(self, function: Callable[[T], R | Awaitable[R]]) -> AsyncFlow[R]: ...
411
+
382
412
  def map(self, function: Callable[[T], R | Awaitable[R]]) -> AsyncFlow[R]:
383
413
  """Apply a synchronous or asynchronous function to each item in order.
384
414
 
@@ -912,6 +942,68 @@ class AsyncFlow(AsyncFlowTerminalsMixin[T], Generic[T]):
912
942
 
913
943
  batch = chunk
914
944
 
945
+ @overload
946
+ def map_batches(
947
+ self,
948
+ function: Callable[[tuple[T, ...]], Awaitable[R]],
949
+ *,
950
+ size: int,
951
+ concurrency: int = 1,
952
+ ordered: bool = True,
953
+ timeout: float | None = None,
954
+ buffer: int | None = None,
955
+ ) -> AsyncFlow[R]: ...
956
+
957
+ @overload
958
+ def map_batches(
959
+ self,
960
+ function: Callable[[tuple[T, ...]], R | Awaitable[R]],
961
+ *,
962
+ size: int,
963
+ concurrency: int = 1,
964
+ ordered: bool = True,
965
+ timeout: float | None = None,
966
+ buffer: int | None = None,
967
+ ) -> AsyncFlow[R]: ...
968
+
969
+ def map_batches(
970
+ self,
971
+ function: Callable[[tuple[T, ...]], R | Awaitable[R]],
972
+ *,
973
+ size: int,
974
+ concurrency: int = 1,
975
+ ordered: bool = True,
976
+ timeout: float | None = None,
977
+ buffer: int | None = None,
978
+ ) -> AsyncFlow[R]:
979
+ """Apply a sync or async function to consecutive tuples, one result per batch.
980
+
981
+ Batches are lazy and include a shorter final batch. Empty input does not call the
982
+ function. Results stay intact; use `flat_map(iter)` to expand returned iterables.
983
+ Awaitable results are resolved before emission. Early exit cancels pending callbacks
984
+ and closes the source through the existing async map scheduler.
985
+ Callbacks run on the event loop: timeout and cancellation are cooperative and cannot
986
+ interrupt a callback that blocks the loop.
987
+
988
+ Args:
989
+ function: Receives up to `size` consecutive items and returns a result or awaitable.
990
+ size: Maximum input items per batch.
991
+ concurrency: Maximum batch callbacks in flight; one by default.
992
+ ordered: Emit in batch order when true, otherwise in completion order.
993
+ timeout: Optional cooperative per-batch timeout while awaiting the callback.
994
+ buffer: Maximum submitted batches not yet emitted; twice concurrency by default.
995
+
996
+ Returns:
997
+ A lazy async flow containing one resolved result per batch.
998
+
999
+ Raises:
1000
+ TypeError: If size does not support the integer index protocol.
1001
+ ValueError: If size, concurrency, buffer, or timeout is not positive.
1002
+ """
1003
+ return self.chunk(size).map_async(
1004
+ function, concurrency=concurrency, ordered=ordered, timeout=timeout, buffer=buffer
1005
+ )
1006
+
915
1007
  def window(self, size: int, *, step: int = 1) -> AsyncFlow[tuple[T, ...]]:
916
1008
  """Emit sliding tuples of size with the requested step.
917
1009