calc-flow-python 2026.9.25__cp314-abi3-win_amd64.whl

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 (50) hide show
  1. calc_flow/__init__.py +248 -0
  2. calc_flow/_compat.py +103 -0
  3. calc_flow/_native.pyd +4 -0
  4. calc_flow/_native.pyi +387 -0
  5. calc_flow/_stream_inputs.py +233 -0
  6. calc_flow/array.py +1337 -0
  7. calc_flow/asof_join_spec.py +126 -0
  8. calc_flow/capabilities.py +829 -0
  9. calc_flow/compute.py +278 -0
  10. calc_flow/config.py +221 -0
  11. calc_flow/errors.py +25 -0
  12. calc_flow/join_spec.py +157 -0
  13. calc_flow/pipeline.py +1288 -0
  14. calc_flow/py.typed +0 -0
  15. calc_flow/runtime.py +1075 -0
  16. calc_flow/store.py +140 -0
  17. calc_flow/stream.py +390 -0
  18. calc_flow/symbolic/__init__.py +69 -0
  19. calc_flow/symbolic/_generated_rolling_kernels.py +23 -0
  20. calc_flow/symbolic/analyzer.py +2788 -0
  21. calc_flow/symbolic/asof.py +212 -0
  22. calc_flow/symbolic/asof_analysis.py +215 -0
  23. calc_flow/symbolic/domains.py +77 -0
  24. calc_flow/symbolic/errors.py +67 -0
  25. calc_flow/symbolic/expr.py +850 -0
  26. calc_flow/symbolic/late_output.py +256 -0
  27. calc_flow/symbolic/lower/__init__.py +31 -0
  28. calc_flow/symbolic/lower/asof.py +86 -0
  29. calc_flow/symbolic/lower/bindings.py +69 -0
  30. calc_flow/symbolic/lower/event_windows.py +477 -0
  31. calc_flow/symbolic/lower/late_output.py +118 -0
  32. calc_flow/symbolic/lower/planners.py +1278 -0
  33. calc_flow/symbolic/lower/program.py +840 -0
  34. calc_flow/symbolic/lower/schema.py +170 -0
  35. calc_flow/symbolic/lower/segments.py +871 -0
  36. calc_flow/symbolic/lower/sql.py +423 -0
  37. calc_flow/symbolic/lower/strategies.py +1522 -0
  38. calc_flow/symbolic/nodes.py +626 -0
  39. calc_flow/symbolic/ops.py +1305 -0
  40. calc_flow/symbolic/optimizer.py +692 -0
  41. calc_flow/symbolic/program.py +494 -0
  42. calc_flow/symbolic/sql.py +36 -0
  43. calc_flow/symbolic/types.py +110 -0
  44. calc_flow/symbolic/windows.py +177 -0
  45. calc_flow/udf.py +19 -0
  46. calc_flow_python-2026.9.25.dist-info/METADATA +318 -0
  47. calc_flow_python-2026.9.25.dist-info/RECORD +50 -0
  48. calc_flow_python-2026.9.25.dist-info/WHEEL +5 -0
  49. calc_flow_python-2026.9.25.dist-info/licenses/LICENSE +202 -0
  50. calc_flow_python-2026.9.25.dist-info/sboms/calc-flow-python.cyclonedx.json +10082 -0
calc_flow/_native.pyi ADDED
@@ -0,0 +1,387 @@
1
+ from __future__ import annotations
2
+
3
+ import asyncio
4
+ from collections.abc import Awaitable, Callable, Mapping, Sequence
5
+ from datetime import datetime
6
+ from typing import Literal, Protocol, TypedDict, final
7
+
8
+ import pyarrow as pa
9
+ from typing_extensions import Never, TypeAlias
10
+
11
+ JSONValue: TypeAlias = (
12
+ None | bool | int | float | str | list["JSONValue"] | dict[str, "JSONValue"]
13
+ )
14
+ _JSONInput: TypeAlias = (
15
+ None | bool | int | float | str | list["_JSONInput"] | Mapping[str, "_JSONInput"]
16
+ )
17
+ StreamingFailureReasonCode: TypeAlias = Literal[
18
+ "join_state_limit_exceeded",
19
+ "join_match_limit_exceeded",
20
+ "join_counter_overflow",
21
+ "join_time_conversion_failed",
22
+ "asof_invalid_input",
23
+ "asof_duplicate_identity",
24
+ "asof_late_row",
25
+ "asof_state_limit_exceeded",
26
+ "asof_workspace_limit_exceeded",
27
+ "asof_output_limit_exceeded",
28
+ "asof_counter_overflow",
29
+ "asof_protocol_error",
30
+ ]
31
+
32
+ class _ArrowCStream(Protocol):
33
+ def __arrow_c_stream__(
34
+ self, requested_schema: object | None = None, /
35
+ ) -> object: ...
36
+
37
+ class CalcFlowError(Exception): ...
38
+
39
+ class ConfigError(CalcFlowError):
40
+ @property
41
+ def issues(self) -> tuple[dict[str, str], ...]: ...
42
+
43
+ class CompileError(CalcFlowError): ...
44
+ class ExecutionError(CalcFlowError): ...
45
+ class ProviderError(ExecutionError): ...
46
+ class CheckpointError(CalcFlowError): ...
47
+ class CancelledError(ExecutionError): ...
48
+
49
+ class StreamingRuntimeError(CalcFlowError):
50
+ @property
51
+ def category(self) -> str: ...
52
+ @property
53
+ def reason_code(self) -> StreamingFailureReasonCode | None: ...
54
+ @property
55
+ def message(self) -> str: ...
56
+ @property
57
+ def job_id(self) -> int | None: ...
58
+ @property
59
+ def epoch(self) -> int | None: ...
60
+ @property
61
+ def checkpoint_phase(self) -> str | None: ...
62
+ @property
63
+ def component_kind(self) -> str | None: ...
64
+ @property
65
+ def component_id(self) -> str | None: ...
66
+ @property
67
+ def diagnostic_id(self) -> int | None: ...
68
+ @property
69
+ def position(self) -> int: ...
70
+
71
+ class CheckpointPublicationUnknownError(StreamingRuntimeError): ...
72
+
73
+ @final
74
+ class ExecutionOptions:
75
+ def __init__(
76
+ self,
77
+ settings: Mapping[str, _JSONInput] | None = ...,
78
+ deadline: datetime | None = ...,
79
+ ) -> None: ...
80
+ @property
81
+ def settings(self) -> dict[str, JSONValue]: ...
82
+ @property
83
+ def deadline(self) -> datetime | None: ...
84
+
85
+ @final
86
+ class ProviderContext:
87
+ def __new__(cls) -> Never:
88
+ """Engine-created; application construction is not public API."""
89
+ ...
90
+ @property
91
+ def settings(self) -> dict[str, JSONValue]: ...
92
+ @property
93
+ def deadline(self) -> datetime | None: ...
94
+
95
+ class _ExecutionCancellation(Protocol):
96
+ def cancel(self) -> None: ...
97
+
98
+ class Batch:
99
+ @staticmethod
100
+ def from_pyarrow(
101
+ table: _ArrowCStream, metadata: Mapping[str, object] | None = None
102
+ ) -> Batch: ...
103
+ @staticmethod
104
+ def from_array(
105
+ array: object,
106
+ *,
107
+ backend: str,
108
+ metadata: Mapping[str, object] | None = None,
109
+ ) -> Batch: ...
110
+ @staticmethod
111
+ def _from_external(
112
+ object: object, backend: str, len: int, metadata: Mapping[str, object]
113
+ ) -> Batch:
114
+ """Build an internal Python-provider batch; this is not a stable public API."""
115
+ ...
116
+ @staticmethod
117
+ def _new_owned_numpy(shape: Sequence[int], dtype: str) -> tuple[object, object]:
118
+ """Allocate private Rust-owned NumPy storage."""
119
+ ...
120
+ @staticmethod
121
+ def _from_owned_array(
122
+ array: object,
123
+ *,
124
+ backend: str,
125
+ token: object | None,
126
+ metadata: Mapping[str, object],
127
+ ) -> Batch:
128
+ """Adopt a trusted provider result; this is not a stable public API."""
129
+ ...
130
+
131
+ def to_pyarrow(self) -> pa.Table: ...
132
+ @property
133
+ def array(self) -> object: ...
134
+ @property
135
+ def backend(self) -> str: ...
136
+ @property
137
+ def kind(self) -> str: ...
138
+ @property
139
+ def num_rows(self) -> int: ...
140
+ @property
141
+ def metadata(self) -> dict[str, object]: ...
142
+
143
+ class Runtime:
144
+ def __init__(self) -> None: ...
145
+ def register_provider(
146
+ self,
147
+ provider: str,
148
+ name: str,
149
+ version: str,
150
+ callback: object,
151
+ *,
152
+ accepts_context: bool = False,
153
+ ) -> None: ...
154
+ def _register_mapping_provider(
155
+ self,
156
+ provider: str,
157
+ name: str,
158
+ version: str,
159
+ callback: object,
160
+ *,
161
+ input_ports: Sequence[tuple[str, str]],
162
+ output_ports: Sequence[tuple[str, str]],
163
+ accepts_context: bool = False,
164
+ ) -> None: ...
165
+ def _register_stateless_stream_provider(
166
+ self,
167
+ provider: str,
168
+ name: str,
169
+ version: str,
170
+ callback: object,
171
+ *,
172
+ microbatch_invariant: bool,
173
+ deterministic: bool,
174
+ replay_safe: bool,
175
+ ) -> None: ...
176
+ def _register_stateless_stream_mapping_provider(
177
+ self,
178
+ provider: str,
179
+ name: str,
180
+ version: str,
181
+ callback: object,
182
+ *,
183
+ input_ports: Sequence[tuple[str, str]],
184
+ output_ports: Sequence[tuple[str, str]],
185
+ microbatch_invariant: bool,
186
+ deterministic: bool,
187
+ replay_safe: bool,
188
+ ) -> None: ...
189
+ def register_scalar_udf(
190
+ self,
191
+ *,
192
+ provider: str,
193
+ name: str,
194
+ version: str,
195
+ input_types: Sequence[str],
196
+ return_type: str,
197
+ volatility: str,
198
+ function: Callable[..., object],
199
+ ) -> None: ...
200
+ def register_kafka_decoder(
201
+ self,
202
+ *,
203
+ name: str,
204
+ version: str,
205
+ function: Callable[[bytes], object],
206
+ ) -> None: ...
207
+ def catalog(self) -> list[dict[str, object]]: ...
208
+ def _infer_sql_schema(
209
+ self, query: str, schemas: Mapping[str, pa.Schema]
210
+ ) -> pa.Schema: ...
211
+ def validation_report(self, project_json: str) -> dict[str, object]: ...
212
+ def compile_project(self, project_json: str) -> ExecutionPlan: ...
213
+ def compile_stream_project(
214
+ self, project_json: str, delivery: Mapping[str, str]
215
+ ) -> StreamExecutionPlan: ...
216
+ def _compile_stream_graph_project(
217
+ self, project_json: str, delivery: Mapping[str, str]
218
+ ) -> StreamExecutionPlan: ...
219
+
220
+ class ExecutionPlan:
221
+ @property
222
+ def name(self) -> str: ...
223
+ @property
224
+ def fingerprint(self) -> str: ...
225
+ def execute(
226
+ self,
227
+ inputs: dict[str, Batch],
228
+ *,
229
+ options: ExecutionOptions | None = None,
230
+ ) -> RunResult: ...
231
+ def execute_async(
232
+ self,
233
+ inputs: dict[str, Batch],
234
+ *,
235
+ options: ExecutionOptions | None = None,
236
+ ) -> Awaitable[RunResult]: ...
237
+ def _execute_async_cancellable(
238
+ self,
239
+ inputs: dict[str, Batch],
240
+ *,
241
+ options: ExecutionOptions | None = None,
242
+ ) -> tuple[asyncio.Future[RunResult], _ExecutionCancellation]: ...
243
+ def snapshot_async(self) -> Awaitable[dict[str, object]]: ...
244
+ def restore_async(self, state_json: str) -> Awaitable[None]: ...
245
+ def reset_async(self) -> Awaitable[None]: ...
246
+
247
+ class StreamExecutionPlan:
248
+ @property
249
+ def name(self) -> str: ...
250
+ @property
251
+ def fingerprint(self) -> str: ...
252
+ @property
253
+ def source_binding_ids(self) -> tuple[str, ...]: ...
254
+ @property
255
+ def static_input_ids(self) -> tuple[str, ...]: ...
256
+ @property
257
+ def sink_binding_ids(self) -> tuple[str, ...]: ...
258
+ @property
259
+ def requirements(self) -> dict[str, str]: ...
260
+
261
+ @final
262
+ class _ManagedCheckpointRuntime:
263
+ def __init__(self, directory: str, /) -> None: ...
264
+
265
+ @final
266
+ class _StreamingRunner:
267
+ def __init__(
268
+ self,
269
+ plan: StreamExecutionPlan,
270
+ sources: Mapping[str, object],
271
+ sinks: Mapping[str, Sequence[object]],
272
+ checkpoints: _ManagedCheckpointRuntime,
273
+ config: Mapping[str, int],
274
+ static_inputs: Mapping[str, object],
275
+ ) -> None: ...
276
+ def start_async(self) -> Awaitable[_StreamingJob]: ...
277
+ def _wait_start_cleanup_async(self) -> Awaitable[None]: ...
278
+ def _release_roots(self) -> None: ...
279
+
280
+ @final
281
+ class _StreamingJob:
282
+ @property
283
+ def id(self) -> int: ...
284
+ def status(self) -> dict[str, object]: ...
285
+ def trigger_checkpoint_async(self) -> Awaitable[int]: ...
286
+ def shutdown_async(self) -> Awaitable[dict[str, object]]: ...
287
+ def cancel_async(self) -> Awaitable[dict[str, object]]: ...
288
+ def wait_async(self) -> Awaitable[dict[str, object]]: ...
289
+ def _release_roots(self) -> None: ...
290
+ def _enable_callback_profiling(self) -> None: ...
291
+ def _take_callback_profile(self) -> str: ...
292
+
293
+ class _FileProjectStore:
294
+ def __init__(self, directory: str) -> None: ...
295
+ def create(self, project_json: str) -> Awaitable[None]: ...
296
+ def put(self, project_json: str) -> Awaitable[None]: ...
297
+ def get(self, project_id: str) -> Awaitable[str]: ...
298
+ def list(self) -> Awaitable[list[str]]: ...
299
+ def delete(self, project_id: str) -> Awaitable[None]: ...
300
+
301
+ def import_project_json(document: bytes) -> str: ...
302
+ def import_project_yaml(document: bytes) -> str: ...
303
+ def export_project_json(project_json: str) -> str: ...
304
+ def export_project_yaml(project_json: str) -> str: ...
305
+
306
+ class _NodeTiming(TypedDict):
307
+ duration_ns: int
308
+ input_rows: dict[str, int]
309
+ output_rows: dict[str, int]
310
+
311
+ class _DataFusionMetric(TypedDict):
312
+ query_id: int
313
+ node_id: str | None
314
+ runtime_acquire_ns: int
315
+ session_state_create_ns: int
316
+ input_adapter_ns: int
317
+ table_register_ns: int
318
+ sql_parse_ns: int
319
+ logical_planning_ns: int
320
+ physical_planning_ns: int
321
+ physical_planning_count: int
322
+ planning_ns: int
323
+ stream_open_ns: int
324
+ execution_to_first_batch_ns: int
325
+ execution_remaining_ns: int
326
+ execution_ns: int
327
+ collect_ns: int
328
+ output_arrow_wrap_ns: int
329
+ audit_ns: int
330
+ metrics_traversal_ns: int
331
+ logical_plan_string_ns: int
332
+ physical_plan_string_ns: int
333
+ batch_envelope_ns: int
334
+ run_result_ns: int
335
+ physical_metric_count: int
336
+ output_partition_count: int
337
+ output_partition_rows: list[int]
338
+ window_partition_count: int
339
+ window_partition_rows: list[int]
340
+ spill_bytes: int
341
+ elapsed_compute_ns: int
342
+ window_compute_ns: int
343
+ repartition_sort_compute_ns: int
344
+ window_operator_count: int
345
+ repartition_operator_count: int
346
+ sort_operator_count: int
347
+ coalesce_operator_count: int
348
+ output_rows: int
349
+ configured_batch_size: int
350
+ parallelism_mode: Literal["fixed", "auto"]
351
+ configured_target_partitions: int
352
+ requested_target_partitions: int
353
+ effective_target_partitions: int
354
+ available_parallelism: int
355
+ max_partitions: int
356
+ min_rows_per_partition: int
357
+ small_rows_threshold: int
358
+ parallelism_decision_reused: bool
359
+ decision_input_rows: int
360
+ decision_active_entities: int | None
361
+ decision_active_entities_source: str
362
+ input_rows: int
363
+ active_entities: int | None
364
+ active_entities_source: str
365
+ partition_limit_reason: str
366
+ rolling_rewrite_enabled: bool
367
+ diagnostics_collected: bool
368
+ rolling_candidate_windows: int
369
+ rolling_rewritten_windows: int
370
+ rolling_fallback_reasons: list[str]
371
+ logical_plan: str
372
+ physical_plan: str
373
+
374
+ class RunResult:
375
+ @property
376
+ def outputs(self) -> dict[str, Batch]: ...
377
+ @property
378
+ def metadata(self) -> dict[str, str]: ...
379
+ @property
380
+ def node_timings(self) -> dict[str, _NodeTiming]: ...
381
+ @property
382
+ def datafusion_metrics(self) -> list[_DataFusionMetric]: ...
383
+
384
+ def project_json_schema() -> str: ...
385
+ def validate_project_json(project_json: str) -> str: ...
386
+ def version() -> str: ...
387
+ def registered_connectors() -> list[dict[str, object]]: ...
@@ -0,0 +1,233 @@
1
+ """Logical input policies and owned iterable adapters for expression streams."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from collections.abc import AsyncIterable, AsyncIterator, Mapping
6
+ from datetime import timedelta
7
+ from typing import NoReturn, Union
8
+
9
+ import pyarrow as pa
10
+ import pyarrow.compute as pc
11
+
12
+ from calc_flow._compat import TypeAliasType, dataclass
13
+ from calc_flow.compute import TableData, _arrow_table, _table_batch
14
+ from calc_flow.runtime import (
15
+ BoundedOutOfOrderness,
16
+ Cursor,
17
+ Data,
18
+ DisabledWatermarks,
19
+ EdgeBudget,
20
+ NativeWatermarkCapability,
21
+ ReplayPositioning,
22
+ SourceBinding,
23
+ SourceCapabilities,
24
+ SourceDeliveryCapability,
25
+ SourceProvidedWatermarks,
26
+ Watermark,
27
+ WatermarkPolicy,
28
+ )
29
+ from calc_flow.symbolic.analyzer import _schema_fields
30
+ from calc_flow.symbolic.expr import Parameter, TableExpr
31
+ from calc_flow.symbolic.lower.schema import _arrow_schema
32
+ from calc_flow.symbolic.nodes import CStr
33
+
34
+ StreamInput = TypeAliasType(
35
+ "StreamInput", Union[AsyncIterable[Union[TableData, Watermark]], SourceBinding]
36
+ )
37
+
38
+ _POLICIES = (SourceProvidedWatermarks, BoundedOutOfOrderness, DisabledWatermarks)
39
+
40
+
41
+ def _capture_watermarks(
42
+ watermarks: WatermarkPolicy | Mapping[str, WatermarkPolicy] | None,
43
+ ) -> WatermarkPolicy | dict[str, WatermarkPolicy] | None:
44
+ return dict(watermarks) if isinstance(watermarks, Mapping) else watermarks
45
+
46
+
47
+ def _policy_mapping(
48
+ watermarks: WatermarkPolicy | Mapping[str, WatermarkPolicy] | None,
49
+ dynamic: set[str],
50
+ ) -> Mapping[str, WatermarkPolicy]:
51
+ if watermarks is None:
52
+ return {}
53
+ if isinstance(watermarks, Mapping):
54
+ return watermarks
55
+ if not isinstance(watermarks, _POLICIES):
56
+ raise TypeError("stream.watermarks: expected supported watermark policy")
57
+ if len(dynamic) != 1:
58
+ raise ValueError("stream.watermarks: multiple inputs require a name mapping")
59
+ return {next(iter(dynamic)): watermarks}
60
+
61
+
62
+ def _selected_policies(
63
+ inputs: Mapping[str, StreamInput | TableData],
64
+ expected: Mapping[str, TableExpr | Parameter],
65
+ watermarks: WatermarkPolicy | Mapping[str, WatermarkPolicy] | None,
66
+ ) -> Mapping[str, WatermarkPolicy]:
67
+ dynamic = {name for name, value in expected.items() if isinstance(value, TableExpr)}
68
+ selected = _policy_mapping(watermarks, dynamic)
69
+ for name, policy in selected.items():
70
+ if name not in dynamic:
71
+ raise ValueError(f"stream.watermarks.{name}: expected dynamic input name")
72
+ if not isinstance(policy, _POLICIES):
73
+ raise TypeError(
74
+ f"stream.watermarks.{name}: expected supported watermark policy"
75
+ )
76
+ if isinstance(inputs[name], SourceBinding):
77
+ raise ValueError(f"stream.watermarks.{name}: cannot override SourceBinding")
78
+ return selected
79
+
80
+
81
+ @dataclass(frozen=True, slots=True)
82
+ class _IterableConfig:
83
+ name: str
84
+ schema: pa.Schema
85
+ budget: EdgeBudget
86
+ policy: WatermarkPolicy
87
+ ordered_column: str | None
88
+
89
+
90
+ def _source_config(
91
+ name: str,
92
+ value: TableExpr,
93
+ budget: EdgeBudget,
94
+ policy: WatermarkPolicy | None,
95
+ ) -> _IterableConfig:
96
+ schema = _arrow_schema(_schema_fields(value._node.attr("schema")))
97
+ event_time = value._node.attr("event_time")
98
+ ordered_column = None
99
+ if policy is None:
100
+ policy = DisabledWatermarks()
101
+ if isinstance(event_time, CStr):
102
+ ordered_column = event_time.value
103
+ policy = BoundedOutOfOrderness(
104
+ ordered_column, timedelta(microseconds=1), timedelta(milliseconds=100)
105
+ )
106
+ return _IterableConfig(name, schema, budget, policy, ordered_column)
107
+
108
+
109
+ class _IterableSource:
110
+ def __init__(
111
+ self,
112
+ source: AsyncIterable[TableData | Watermark],
113
+ config: _IterableConfig,
114
+ ) -> None:
115
+ self._source = source
116
+ self._config = config
117
+ self._path = f"stream.inputs.{config.name}"
118
+ self._iterator: AsyncIterator[TableData | Watermark] | None = None
119
+ self._closed = False
120
+ self._position = 0
121
+ self._last_time: int | None = None
122
+ self.failure: str | None = None
123
+
124
+ def capabilities(self) -> SourceCapabilities:
125
+ watermarks = NativeWatermarkCapability.NEVER_EMITS
126
+ if isinstance(self._config.policy, SourceProvidedWatermarks):
127
+ watermarks = NativeWatermarkCapability.EMITS_NATIVE
128
+ return SourceCapabilities(
129
+ ReplayPositioning.UNSUPPORTED,
130
+ SourceDeliveryCapability.LOSSY,
131
+ max_batch_rows=self._config.budget.max_rows,
132
+ max_batch_bytes=self._config.budget.max_bytes,
133
+ schema=self._config.schema,
134
+ native_watermarks=watermarks,
135
+ )
136
+
137
+ async def open(self, cursor: Cursor | None) -> None:
138
+ self._iterator = self._source.__aiter__()
139
+
140
+ async def next(self) -> Data | Watermark | None:
141
+ if self._iterator is None:
142
+ raise RuntimeError(f"{self._path}: source is not open")
143
+ try:
144
+ data = await self._iterator.__anext__()
145
+ except StopAsyncIteration:
146
+ return None
147
+ if isinstance(data, Watermark):
148
+ return self._watermark(data)
149
+ self._validate_batch(self._arrow_input(data))
150
+ batch = _table_batch(data, self._path)
151
+ self._position += 1
152
+ return Data(batch, Cursor(self._position.to_bytes(16, "big"), {}))
153
+
154
+ def _arrow_input(self, data: TableData) -> pa.Table | pa.RecordBatch:
155
+ try:
156
+ return _arrow_table(data, self._path)
157
+ except TypeError:
158
+ self._reject(": expected Arrow Table, RecordBatch or table Batch")
159
+
160
+ def _watermark(self, value: Watermark) -> Watermark:
161
+ if not isinstance(self._config.policy, SourceProvidedWatermarks):
162
+ self._reject(": Watermark requires SourceProvidedWatermarks")
163
+ return value
164
+
165
+ def _validate_batch(self, table: pa.Table | pa.RecordBatch) -> None:
166
+ if not table.schema.equals(self._config.schema):
167
+ self._reject(".schema: expected declared Arrow schema")
168
+ if table.num_rows > self._config.budget.max_rows:
169
+ self._reject(": batch exceeds edge_budget.max_rows")
170
+ if table.nbytes > self._config.budget.max_bytes:
171
+ self._reject(": batch exceeds edge_budget.max_bytes")
172
+ if self._config.ordered_column is not None:
173
+ self._validate_time(table[self._config.ordered_column])
174
+
175
+ def _validate_time(self, column: pa.ChunkedArray | pa.Array) -> None:
176
+ if column.null_count:
177
+ self._reject(".event_time: expected non-null event time")
178
+ # Compare in the declared unit without datetime/range conversion. Native
179
+ # generation retains its checked conversion and minimum-minus-delay error.
180
+ values = column.cast(pa.int64())
181
+ if not len(values):
182
+ return
183
+ if self._is_ordered(values):
184
+ self._last_time = values[len(values) - 1].as_py()
185
+ return
186
+ self._reject_out_of_order(values)
187
+
188
+ def _is_ordered(self, values: pa.ChunkedArray | pa.Array) -> bool:
189
+ if self._last_time is not None and values[0].as_py() < self._last_time:
190
+ return False
191
+ decreases = pc.less(values.slice(1), values.slice(0, len(values) - 1))
192
+ return not pc.any(decreases).as_py()
193
+
194
+ def _reject_out_of_order(self, values: pa.ChunkedArray | pa.Array) -> NoReturn:
195
+ previous = self._last_time
196
+ for current in values.to_pylist():
197
+ if previous is not None and current < previous:
198
+ self._reject(
199
+ ".event_time: expected nondecreasing event time; select an "
200
+ "explicit watermark policy for out-of-order input"
201
+ )
202
+ previous = current
203
+ raise RuntimeError(f"{self._path}.event_time: ordering violation not found")
204
+
205
+ def _reject(self, message: str) -> NoReturn:
206
+ self.failure = self._path + message
207
+ raise ValueError(self.failure)
208
+
209
+ async def close(self) -> None:
210
+ if self._closed or self._iterator is None:
211
+ return
212
+ self._closed = True
213
+ close = getattr(self._iterator, "aclose", None)
214
+ if close is not None:
215
+ await close()
216
+
217
+
218
+ def _source_binding(
219
+ data: StreamInput,
220
+ name: str,
221
+ value: TableExpr,
222
+ budget: EdgeBudget,
223
+ policy: WatermarkPolicy | None,
224
+ ) -> tuple[SourceBinding, _IterableSource | None]:
225
+ if isinstance(data, SourceBinding):
226
+ return data, None
227
+ if not isinstance(data, AsyncIterable):
228
+ raise TypeError(
229
+ f"stream.inputs.{name}: expected async iterable or SourceBinding"
230
+ )
231
+ config = _source_config(name, value, budget, policy)
232
+ source = _IterableSource(data, config)
233
+ return SourceBinding(source, watermark_policy=config.policy), source