calc-flow-python 4.0.0__cp313-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.
- calc_flow/__init__.py +169 -0
- calc_flow/_native.pyd +0 -0
- calc_flow/_native.pyi +366 -0
- calc_flow/array.py +1324 -0
- calc_flow/capabilities.py +775 -0
- calc_flow/config.py +219 -0
- calc_flow/errors.py +25 -0
- calc_flow/join_spec.py +156 -0
- calc_flow/pipeline.py +1123 -0
- calc_flow/py.typed +0 -0
- calc_flow/runtime.py +979 -0
- calc_flow/store.py +138 -0
- calc_flow/symbolic/__init__.py +65 -0
- calc_flow/symbolic/_generated_rolling_kernels.py +23 -0
- calc_flow/symbolic/analyzer.py +2280 -0
- calc_flow/symbolic/domains.py +77 -0
- calc_flow/symbolic/errors.py +58 -0
- calc_flow/symbolic/expr.py +662 -0
- calc_flow/symbolic/lower/__init__.py +31 -0
- calc_flow/symbolic/lower/planners.py +1270 -0
- calc_flow/symbolic/lower/program.py +840 -0
- calc_flow/symbolic/lower/segments.py +836 -0
- calc_flow/symbolic/lower/strategies.py +1472 -0
- calc_flow/symbolic/nodes.py +603 -0
- calc_flow/symbolic/ops.py +1155 -0
- calc_flow/symbolic/optimizer.py +600 -0
- calc_flow/symbolic/program.py +377 -0
- calc_flow/symbolic/types.py +110 -0
- calc_flow/symbolic/windows.py +153 -0
- calc_flow/udf.py +19 -0
- calc_flow_python-4.0.0.dist-info/METADATA +376 -0
- calc_flow_python-4.0.0.dist-info/RECORD +35 -0
- calc_flow_python-4.0.0.dist-info/WHEEL +4 -0
- calc_flow_python-4.0.0.dist-info/licenses/LICENSE +202 -0
- calc_flow_python-4.0.0.dist-info/sboms/calc-flow-python.cyclonedx.json +10081 -0
|
@@ -0,0 +1,775 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
from collections.abc import Mapping, Sequence
|
|
5
|
+
from dataclasses import dataclass
|
|
6
|
+
from typing import Any, Literal
|
|
7
|
+
|
|
8
|
+
type BatchKind = Literal["table", "array"]
|
|
9
|
+
type OptionValueType = Literal["string", "integer", "number", "boolean"]
|
|
10
|
+
type ExecutionMode = Literal["batch", "stream"]
|
|
11
|
+
type OutputFinality = Literal["per_row_final", "group_final_append_only", "unproven"]
|
|
12
|
+
type CheckpointSupport = Literal["stateless", "checkpointed_stateful", "unproven"]
|
|
13
|
+
type PartitionContract = Literal["none", "row_axis_independent"]
|
|
14
|
+
|
|
15
|
+
CAPABILITY_SCHEMA_VERSION = 3
|
|
16
|
+
|
|
17
|
+
_EXECUTION_MODES = frozenset(("batch", "stream"))
|
|
18
|
+
_OUTPUT_FINALITIES = frozenset(("per_row_final", "group_final_append_only", "unproven"))
|
|
19
|
+
_CHECKPOINT_SUPPORTS = frozenset(("stateless", "checkpointed_stateful", "unproven"))
|
|
20
|
+
_PARTITION_CONTRACTS = frozenset(("none", "row_axis_independent"))
|
|
21
|
+
_CAPABILITY_RULES = frozenset(
|
|
22
|
+
(
|
|
23
|
+
("array_api_safe_dtype", "1"),
|
|
24
|
+
("elementwise_broadcast", "1"),
|
|
25
|
+
("feature_axis_reduction", "1"),
|
|
26
|
+
("table_matmul_static_rhs", "1"),
|
|
27
|
+
)
|
|
28
|
+
)
|
|
29
|
+
|
|
30
|
+
PORTABLE_ARROW_TYPES = (
|
|
31
|
+
"bool",
|
|
32
|
+
"date32",
|
|
33
|
+
"date64",
|
|
34
|
+
"float32",
|
|
35
|
+
"float64",
|
|
36
|
+
"int8",
|
|
37
|
+
"int16",
|
|
38
|
+
"int32",
|
|
39
|
+
"int64",
|
|
40
|
+
"large_string",
|
|
41
|
+
"string",
|
|
42
|
+
"time32[s]",
|
|
43
|
+
"time64[us]",
|
|
44
|
+
"timestamp[ms]",
|
|
45
|
+
"timestamp[us]",
|
|
46
|
+
"uint8",
|
|
47
|
+
"uint16",
|
|
48
|
+
"uint32",
|
|
49
|
+
"uint64",
|
|
50
|
+
)
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
@dataclass(frozen=True, slots=True)
|
|
54
|
+
class ProviderOption:
|
|
55
|
+
name: str
|
|
56
|
+
value_type: OptionValueType
|
|
57
|
+
required: bool = False
|
|
58
|
+
|
|
59
|
+
def __post_init__(self) -> None:
|
|
60
|
+
if type(self.name) is not str:
|
|
61
|
+
raise TypeError(
|
|
62
|
+
"provider options_schema field name must contain strict data; "
|
|
63
|
+
f"found {type(self.name).__name__}"
|
|
64
|
+
)
|
|
65
|
+
if type(self.value_type) is not str:
|
|
66
|
+
raise TypeError(
|
|
67
|
+
f"provider options_schema field {self.name!r}.value_type must "
|
|
68
|
+
"contain strict data; "
|
|
69
|
+
f"found {type(self.value_type).__name__}"
|
|
70
|
+
)
|
|
71
|
+
if self.value_type not in {"string", "integer", "number", "boolean"}:
|
|
72
|
+
raise ValueError(
|
|
73
|
+
f"provider options_schema field {self.name!r}.value_type must be "
|
|
74
|
+
"string, integer, number, or boolean; "
|
|
75
|
+
f"found {self.value_type}"
|
|
76
|
+
)
|
|
77
|
+
if type(self.required) is not bool:
|
|
78
|
+
raise TypeError(
|
|
79
|
+
f"provider options_schema at {self.name!r}.required must contain "
|
|
80
|
+
f"strict data; found {type(self.required).__name__}"
|
|
81
|
+
)
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
@dataclass(frozen=True, slots=True)
|
|
85
|
+
class ProviderOptionsSchema:
|
|
86
|
+
fields: tuple[ProviderOption, ...] = ()
|
|
87
|
+
additional_properties: Literal[False] = False
|
|
88
|
+
|
|
89
|
+
def __post_init__(self) -> None:
|
|
90
|
+
if type(self.fields) is not tuple or any(
|
|
91
|
+
not isinstance(field, ProviderOption) for field in self.fields
|
|
92
|
+
):
|
|
93
|
+
raise TypeError(
|
|
94
|
+
"provider options_schema fields must be a tuple of "
|
|
95
|
+
"ProviderOption values"
|
|
96
|
+
)
|
|
97
|
+
if self.additional_properties is not False:
|
|
98
|
+
raise ValueError(
|
|
99
|
+
"provider options_schema additional_properties must be False"
|
|
100
|
+
)
|
|
101
|
+
names = tuple(field.name for field in self.fields)
|
|
102
|
+
duplicate = next(
|
|
103
|
+
(name for index, name in enumerate(names) if name in names[:index]),
|
|
104
|
+
None,
|
|
105
|
+
)
|
|
106
|
+
if duplicate is not None:
|
|
107
|
+
raise ValueError(
|
|
108
|
+
f"provider options_schema contains duplicate field {duplicate!r}"
|
|
109
|
+
)
|
|
110
|
+
object.__setattr__(
|
|
111
|
+
self,
|
|
112
|
+
"fields",
|
|
113
|
+
tuple(sorted(self.fields, key=lambda field: field.name)),
|
|
114
|
+
)
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
@dataclass(frozen=True, slots=True)
|
|
118
|
+
class RuntimeSessionScope:
|
|
119
|
+
kind: Literal["runtime_session"]
|
|
120
|
+
session_id: str
|
|
121
|
+
revision: int
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
def _require_exact_str(owner: str, field: str, value: object) -> None:
|
|
125
|
+
if type(value) is not str:
|
|
126
|
+
raise TypeError(
|
|
127
|
+
f"{owner} {field} must be an exact str; found {type(value).__name__}"
|
|
128
|
+
)
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
def _require_exact_bool(owner: str, field: str, value: object) -> None:
|
|
132
|
+
if type(value) is not bool:
|
|
133
|
+
raise TypeError(
|
|
134
|
+
f"{owner} {field} must be an exact bool; found {type(value).__name__}"
|
|
135
|
+
)
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
def _require_port_tuple(owner: str, field: str, value: object) -> None:
|
|
139
|
+
if type(value) is not tuple or any(
|
|
140
|
+
not isinstance(port, ProviderPort) for port in value
|
|
141
|
+
):
|
|
142
|
+
raise TypeError(
|
|
143
|
+
f"{owner} {field} must be a tuple of ProviderPort values; "
|
|
144
|
+
f"found {type(value).__name__}"
|
|
145
|
+
)
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
def _require_modes(owner: str, value: object) -> tuple[ExecutionMode, ...]:
|
|
149
|
+
if type(value) is not tuple or any(type(mode) is not str for mode in value):
|
|
150
|
+
raise TypeError(f"{owner} modes must be a tuple of execution modes")
|
|
151
|
+
if not value:
|
|
152
|
+
raise ValueError(f"{owner} modes must declare at least one execution mode")
|
|
153
|
+
unknown = next((mode for mode in value if mode not in _EXECUTION_MODES), None)
|
|
154
|
+
if unknown is not None:
|
|
155
|
+
raise ValueError(
|
|
156
|
+
f"{owner} execution modes are batch and stream; found {unknown!r}"
|
|
157
|
+
)
|
|
158
|
+
return value # type: ignore[return-value]
|
|
159
|
+
|
|
160
|
+
|
|
161
|
+
def _require_finality(owner: str, value: object) -> OutputFinality:
|
|
162
|
+
if type(value) is not str:
|
|
163
|
+
raise TypeError(
|
|
164
|
+
f"{owner} finality must be an exact str; found {type(value).__name__}"
|
|
165
|
+
)
|
|
166
|
+
if value not in _OUTPUT_FINALITIES:
|
|
167
|
+
raise ValueError(
|
|
168
|
+
"output finality must be per_row_final, group_final_append_only, or "
|
|
169
|
+
f"unproven; found {value!r}"
|
|
170
|
+
)
|
|
171
|
+
return value # type: ignore[return-value]
|
|
172
|
+
|
|
173
|
+
|
|
174
|
+
def _require_state_version(owner: str, support: str, state_version: object) -> None:
|
|
175
|
+
if support != "checkpointed_stateful":
|
|
176
|
+
if state_version is not None:
|
|
177
|
+
raise ValueError(
|
|
178
|
+
f"{owner} state_version must be None unless checkpointed_stateful"
|
|
179
|
+
)
|
|
180
|
+
return
|
|
181
|
+
if state_version is None or (type(state_version) is int and state_version <= 0):
|
|
182
|
+
raise ValueError(
|
|
183
|
+
f"{owner} checkpointed_stateful requires a positive state_version"
|
|
184
|
+
)
|
|
185
|
+
if type(state_version) is not int:
|
|
186
|
+
raise TypeError(
|
|
187
|
+
f"{owner} state_version must be an exact int when "
|
|
188
|
+
f"checkpointed_stateful; found {type(state_version).__name__}"
|
|
189
|
+
)
|
|
190
|
+
|
|
191
|
+
|
|
192
|
+
def _require_checkpoint_support(
|
|
193
|
+
owner: str, support: object, state_version: object, stateful: bool
|
|
194
|
+
) -> None:
|
|
195
|
+
if type(support) is not str:
|
|
196
|
+
raise TypeError(
|
|
197
|
+
f"{owner} checkpoint_support must be an exact str; "
|
|
198
|
+
f"found {type(support).__name__}"
|
|
199
|
+
)
|
|
200
|
+
if support not in _CHECKPOINT_SUPPORTS:
|
|
201
|
+
raise ValueError(
|
|
202
|
+
"checkpoint support must be stateless, checkpointed_stateful, or "
|
|
203
|
+
f"unproven; found {support!r}"
|
|
204
|
+
)
|
|
205
|
+
_require_state_version(owner, support, state_version)
|
|
206
|
+
if support == "stateless" and stateful:
|
|
207
|
+
raise ValueError(f"{owner} stateless capability must set stateful=False")
|
|
208
|
+
|
|
209
|
+
|
|
210
|
+
def _require_state_layouts(
|
|
211
|
+
owner: str, support: str, state_version: int | None, state_layouts: object
|
|
212
|
+
) -> None:
|
|
213
|
+
"""Validate the durable layout inventory behind a state contract.
|
|
214
|
+
|
|
215
|
+
``state_layouts`` enumerates every checkpoint layout the operator
|
|
216
|
+
version may install; a single ``state_version`` cannot express a
|
|
217
|
+
per-declaration layout split such as rolling's EWMA layout v2.
|
|
218
|
+
"""
|
|
219
|
+
|
|
220
|
+
if type(state_layouts) is not tuple:
|
|
221
|
+
raise TypeError(
|
|
222
|
+
f"{owner} state_layouts must be an exact tuple; "
|
|
223
|
+
f"found {type(state_layouts).__name__}"
|
|
224
|
+
)
|
|
225
|
+
if support != "checkpointed_stateful":
|
|
226
|
+
if state_layouts:
|
|
227
|
+
raise ValueError(
|
|
228
|
+
f"{owner} state_layouts must be empty unless checkpointed_stateful"
|
|
229
|
+
)
|
|
230
|
+
return
|
|
231
|
+
_require_stateful_layout_inventory(owner, state_version, state_layouts)
|
|
232
|
+
|
|
233
|
+
|
|
234
|
+
def _require_stateful_layout_inventory(
|
|
235
|
+
owner: str, state_version: int | None, state_layouts: tuple[int, ...]
|
|
236
|
+
) -> None:
|
|
237
|
+
if not state_layouts:
|
|
238
|
+
raise ValueError(
|
|
239
|
+
f"{owner} checkpointed_stateful requires at least one state layout"
|
|
240
|
+
)
|
|
241
|
+
for layout in state_layouts:
|
|
242
|
+
if type(layout) is not int:
|
|
243
|
+
raise TypeError(
|
|
244
|
+
f"{owner} state layouts must be exact positive integers; "
|
|
245
|
+
f"found {type(layout).__name__}"
|
|
246
|
+
)
|
|
247
|
+
if layout <= 0:
|
|
248
|
+
raise ValueError(f"{owner} state layouts must be positive integers")
|
|
249
|
+
_require_ascending_layouts(owner, state_layouts)
|
|
250
|
+
if state_version not in state_layouts:
|
|
251
|
+
raise ValueError(f"{owner} state_layouts must contain state_version")
|
|
252
|
+
|
|
253
|
+
|
|
254
|
+
def _require_ascending_layouts(owner: str, state_layouts: tuple[int, ...]) -> None:
|
|
255
|
+
if any(
|
|
256
|
+
left >= right
|
|
257
|
+
for left, right in zip(state_layouts[:-1], state_layouts[1:], strict=True)
|
|
258
|
+
):
|
|
259
|
+
raise ValueError(
|
|
260
|
+
f"{owner} state_layouts must be strictly ascending without duplicates"
|
|
261
|
+
)
|
|
262
|
+
|
|
263
|
+
|
|
264
|
+
@dataclass(frozen=True, slots=True)
|
|
265
|
+
class CapabilityRule:
|
|
266
|
+
name: str
|
|
267
|
+
version: str
|
|
268
|
+
|
|
269
|
+
def __post_init__(self) -> None:
|
|
270
|
+
if type(self.name) is not str or type(self.version) is not str:
|
|
271
|
+
raise TypeError(
|
|
272
|
+
"capability rule name and version must be exact strings; found "
|
|
273
|
+
f"{type(self.name).__name__} and {type(self.version).__name__}"
|
|
274
|
+
)
|
|
275
|
+
if (self.name, self.version) not in _CAPABILITY_RULES:
|
|
276
|
+
raise ValueError(f"unknown capability rule {self.name}@{self.version}")
|
|
277
|
+
|
|
278
|
+
|
|
279
|
+
@dataclass(frozen=True, slots=True)
|
|
280
|
+
class ProviderArrayRules:
|
|
281
|
+
supported_dtypes: tuple[str, ...]
|
|
282
|
+
safe_dtype_rule: CapabilityRule
|
|
283
|
+
shape_rules: tuple[CapabilityRule, ...]
|
|
284
|
+
|
|
285
|
+
def __post_init__(self) -> None:
|
|
286
|
+
if type(self.supported_dtypes) is not tuple or any(
|
|
287
|
+
type(dtype) is not str for dtype in self.supported_dtypes
|
|
288
|
+
):
|
|
289
|
+
raise TypeError(
|
|
290
|
+
"provider array rules supported_dtypes must be a tuple of str; "
|
|
291
|
+
f"found {type(self.supported_dtypes).__name__}"
|
|
292
|
+
)
|
|
293
|
+
if not isinstance(self.safe_dtype_rule, CapabilityRule):
|
|
294
|
+
raise TypeError(
|
|
295
|
+
"provider array rules safe_dtype_rule must be a CapabilityRule; "
|
|
296
|
+
f"found {type(self.safe_dtype_rule).__name__}"
|
|
297
|
+
)
|
|
298
|
+
if type(self.shape_rules) is not tuple or any(
|
|
299
|
+
not isinstance(rule, CapabilityRule) for rule in self.shape_rules
|
|
300
|
+
):
|
|
301
|
+
raise TypeError(
|
|
302
|
+
"provider array rules shape_rules must be a tuple of "
|
|
303
|
+
f"CapabilityRule values; found {type(self.shape_rules).__name__}"
|
|
304
|
+
)
|
|
305
|
+
object.__setattr__(
|
|
306
|
+
self, "supported_dtypes", tuple(sorted(self.supported_dtypes))
|
|
307
|
+
)
|
|
308
|
+
object.__setattr__(
|
|
309
|
+
self,
|
|
310
|
+
"shape_rules",
|
|
311
|
+
tuple(
|
|
312
|
+
sorted(
|
|
313
|
+
self.shape_rules,
|
|
314
|
+
key=lambda rule: (rule.name, rule.version),
|
|
315
|
+
)
|
|
316
|
+
),
|
|
317
|
+
)
|
|
318
|
+
|
|
319
|
+
|
|
320
|
+
@dataclass(frozen=True, slots=True)
|
|
321
|
+
class _StatelessProviderLifecycle:
|
|
322
|
+
"""Trusted process-local proof attached by the native stream seam."""
|
|
323
|
+
|
|
324
|
+
deterministic: bool
|
|
325
|
+
replay_safe: bool
|
|
326
|
+
supports_static_inputs: bool
|
|
327
|
+
array_rules: ProviderArrayRules
|
|
328
|
+
|
|
329
|
+
|
|
330
|
+
@dataclass(frozen=True, slots=True)
|
|
331
|
+
class OperatorCapability:
|
|
332
|
+
kind: str
|
|
333
|
+
version: str
|
|
334
|
+
input_ports: tuple[ProviderPort, ...]
|
|
335
|
+
output_ports: tuple[ProviderPort, ...]
|
|
336
|
+
modes: tuple[ExecutionMode, ...]
|
|
337
|
+
finality: OutputFinality
|
|
338
|
+
requires_datafusion: bool
|
|
339
|
+
stateful: bool
|
|
340
|
+
microbatch_invariant: bool
|
|
341
|
+
requires_watermark: bool
|
|
342
|
+
checkpoint_support: CheckpointSupport
|
|
343
|
+
state_version: int | None
|
|
344
|
+
deterministic: bool
|
|
345
|
+
replay_safe: bool
|
|
346
|
+
state_layouts: tuple[int, ...] = ()
|
|
347
|
+
|
|
348
|
+
def __post_init__(self) -> None:
|
|
349
|
+
_require_exact_str("operator capability", "kind", self.kind)
|
|
350
|
+
_require_exact_str("operator capability", "version", self.version)
|
|
351
|
+
_require_port_tuple("operator capability", "input_ports", self.input_ports)
|
|
352
|
+
_require_port_tuple("operator capability", "output_ports", self.output_ports)
|
|
353
|
+
_require_modes("operator capability", self.modes)
|
|
354
|
+
_require_finality("operator capability", self.finality)
|
|
355
|
+
_require_exact_bool(
|
|
356
|
+
"operator capability", "requires_datafusion", self.requires_datafusion
|
|
357
|
+
)
|
|
358
|
+
_require_exact_bool("operator capability", "stateful", self.stateful)
|
|
359
|
+
_require_exact_bool(
|
|
360
|
+
"operator capability", "microbatch_invariant", self.microbatch_invariant
|
|
361
|
+
)
|
|
362
|
+
_require_exact_bool(
|
|
363
|
+
"operator capability", "requires_watermark", self.requires_watermark
|
|
364
|
+
)
|
|
365
|
+
_require_exact_bool("operator capability", "deterministic", self.deterministic)
|
|
366
|
+
_require_exact_bool("operator capability", "replay_safe", self.replay_safe)
|
|
367
|
+
_require_checkpoint_support(
|
|
368
|
+
"operator capability",
|
|
369
|
+
self.checkpoint_support,
|
|
370
|
+
self.state_version,
|
|
371
|
+
self.stateful,
|
|
372
|
+
)
|
|
373
|
+
_require_state_layouts(
|
|
374
|
+
"operator capability",
|
|
375
|
+
self.checkpoint_support,
|
|
376
|
+
self.state_version,
|
|
377
|
+
self.state_layouts,
|
|
378
|
+
)
|
|
379
|
+
|
|
380
|
+
|
|
381
|
+
@dataclass(frozen=True, slots=True)
|
|
382
|
+
class UdfCapability:
|
|
383
|
+
provider: str
|
|
384
|
+
name: str
|
|
385
|
+
version: str
|
|
386
|
+
kind: Literal["data_fusion_scalar"]
|
|
387
|
+
input_types: tuple[str, ...]
|
|
388
|
+
return_type: str
|
|
389
|
+
volatility: str
|
|
390
|
+
|
|
391
|
+
|
|
392
|
+
@dataclass(frozen=True, slots=True)
|
|
393
|
+
class ProviderPort:
|
|
394
|
+
name: str
|
|
395
|
+
kind: BatchKind
|
|
396
|
+
required: bool
|
|
397
|
+
|
|
398
|
+
|
|
399
|
+
@dataclass(frozen=True, slots=True)
|
|
400
|
+
class ProviderCapability:
|
|
401
|
+
provider: str
|
|
402
|
+
name: str
|
|
403
|
+
version: str
|
|
404
|
+
input_ports: tuple[ProviderPort, ...]
|
|
405
|
+
output_ports: tuple[ProviderPort, ...]
|
|
406
|
+
options_schema: ProviderOptionsSchema | None
|
|
407
|
+
modes: tuple[ExecutionMode, ...]
|
|
408
|
+
finality: OutputFinality
|
|
409
|
+
stateful: bool
|
|
410
|
+
microbatch_invariant: bool
|
|
411
|
+
requires_watermark: bool
|
|
412
|
+
checkpoint_support: CheckpointSupport
|
|
413
|
+
state_version: int | None
|
|
414
|
+
deterministic: bool
|
|
415
|
+
replay_safe: bool
|
|
416
|
+
supports_static_inputs: bool
|
|
417
|
+
partition_contract: PartitionContract
|
|
418
|
+
array_rules: ProviderArrayRules | None
|
|
419
|
+
|
|
420
|
+
def __post_init__(self) -> None:
|
|
421
|
+
_require_exact_str("provider capability", "provider", self.provider)
|
|
422
|
+
_require_exact_str("provider capability", "version", self.version)
|
|
423
|
+
_require_port_tuple("provider capability", "input_ports", self.input_ports)
|
|
424
|
+
_require_port_tuple("provider capability", "output_ports", self.output_ports)
|
|
425
|
+
_require_modes("provider capability", self.modes)
|
|
426
|
+
_require_finality("provider capability", self.finality)
|
|
427
|
+
_require_exact_bool("provider capability", "stateful", self.stateful)
|
|
428
|
+
_require_exact_bool(
|
|
429
|
+
"provider capability", "microbatch_invariant", self.microbatch_invariant
|
|
430
|
+
)
|
|
431
|
+
_require_exact_bool(
|
|
432
|
+
"provider capability", "requires_watermark", self.requires_watermark
|
|
433
|
+
)
|
|
434
|
+
_require_exact_bool("provider capability", "deterministic", self.deterministic)
|
|
435
|
+
_require_exact_bool("provider capability", "replay_safe", self.replay_safe)
|
|
436
|
+
_require_exact_bool(
|
|
437
|
+
"provider capability", "supports_static_inputs", self.supports_static_inputs
|
|
438
|
+
)
|
|
439
|
+
if type(self.partition_contract) is not str:
|
|
440
|
+
raise TypeError(
|
|
441
|
+
"provider capability partition_contract must be an exact str; "
|
|
442
|
+
f"found {type(self.partition_contract).__name__}"
|
|
443
|
+
)
|
|
444
|
+
if self.partition_contract not in _PARTITION_CONTRACTS:
|
|
445
|
+
raise ValueError(
|
|
446
|
+
"provider capability partition_contract must be none or "
|
|
447
|
+
f"row_axis_independent; found {self.partition_contract!r}"
|
|
448
|
+
)
|
|
449
|
+
if self.array_rules is not None and not isinstance(
|
|
450
|
+
self.array_rules, ProviderArrayRules
|
|
451
|
+
):
|
|
452
|
+
raise TypeError(
|
|
453
|
+
"provider capability array_rules must be a ProviderArrayRules or "
|
|
454
|
+
f"None; found {type(self.array_rules).__name__}"
|
|
455
|
+
)
|
|
456
|
+
_require_checkpoint_support(
|
|
457
|
+
"provider capability",
|
|
458
|
+
self.checkpoint_support,
|
|
459
|
+
self.state_version,
|
|
460
|
+
self.stateful,
|
|
461
|
+
)
|
|
462
|
+
|
|
463
|
+
|
|
464
|
+
@dataclass(frozen=True, slots=True)
|
|
465
|
+
class RuntimeCapabilities:
|
|
466
|
+
schema_version: Literal[3]
|
|
467
|
+
scope: RuntimeSessionScope
|
|
468
|
+
package_version: str
|
|
469
|
+
project_format_versions: tuple[int, ...]
|
|
470
|
+
batch_kinds: tuple[BatchKind, ...]
|
|
471
|
+
portable_arrow_types: tuple[str, ...]
|
|
472
|
+
operators: tuple[OperatorCapability, ...]
|
|
473
|
+
udfs: tuple[UdfCapability, ...]
|
|
474
|
+
providers: tuple[ProviderCapability, ...]
|
|
475
|
+
connectors: tuple[ConnectorCapability, ...]
|
|
476
|
+
|
|
477
|
+
|
|
478
|
+
def runtime_capabilities(
|
|
479
|
+
*,
|
|
480
|
+
session_id: str,
|
|
481
|
+
revision: int,
|
|
482
|
+
package_version: str,
|
|
483
|
+
registrations: Sequence[Mapping[str, Any]],
|
|
484
|
+
) -> RuntimeCapabilities:
|
|
485
|
+
from calc_flow import _native
|
|
486
|
+
|
|
487
|
+
udfs = tuple(
|
|
488
|
+
sorted(
|
|
489
|
+
(
|
|
490
|
+
UdfCapability(
|
|
491
|
+
provider=str(registration["provider"]),
|
|
492
|
+
name=str(registration["name"]),
|
|
493
|
+
version=str(registration["version"]),
|
|
494
|
+
kind="data_fusion_scalar",
|
|
495
|
+
input_types=tuple(registration["input_types"]),
|
|
496
|
+
return_type=str(registration["return_type"]),
|
|
497
|
+
volatility=str(registration["volatility"]),
|
|
498
|
+
)
|
|
499
|
+
for registration in registrations
|
|
500
|
+
if registration["kind"] == "scalar_udf"
|
|
501
|
+
),
|
|
502
|
+
key=lambda item: (item.provider, item.name, item.version),
|
|
503
|
+
)
|
|
504
|
+
)
|
|
505
|
+
|
|
506
|
+
def provider_capability(registration: Mapping[str, Any]) -> ProviderCapability:
|
|
507
|
+
lifecycle = registration.get("stream_lifecycle")
|
|
508
|
+
if isinstance(lifecycle, _StatelessProviderLifecycle):
|
|
509
|
+
modes: tuple[ExecutionMode, ...] = ("batch", "stream")
|
|
510
|
+
finality: OutputFinality = "per_row_final"
|
|
511
|
+
microbatch_invariant = True
|
|
512
|
+
deterministic = lifecycle.deterministic
|
|
513
|
+
replay_safe = lifecycle.replay_safe
|
|
514
|
+
supports_static_inputs = lifecycle.supports_static_inputs
|
|
515
|
+
partition_contract: PartitionContract = "row_axis_independent"
|
|
516
|
+
array_rules: ProviderArrayRules | None = lifecycle.array_rules
|
|
517
|
+
else:
|
|
518
|
+
modes = ("batch",)
|
|
519
|
+
finality = "unproven"
|
|
520
|
+
microbatch_invariant = False
|
|
521
|
+
deterministic = False
|
|
522
|
+
replay_safe = False
|
|
523
|
+
supports_static_inputs = False
|
|
524
|
+
partition_contract = "none"
|
|
525
|
+
array_rules = None
|
|
526
|
+
return ProviderCapability(
|
|
527
|
+
provider=str(registration["provider"]),
|
|
528
|
+
name=str(registration["name"]),
|
|
529
|
+
version=str(registration["version"]),
|
|
530
|
+
input_ports=tuple(
|
|
531
|
+
ProviderPort(str(name), kind, required=True)
|
|
532
|
+
for name, kind in registration.get("input_ports", (("input", "array"),))
|
|
533
|
+
),
|
|
534
|
+
output_ports=tuple(
|
|
535
|
+
ProviderPort(str(name), kind, required=True)
|
|
536
|
+
for name, kind in registration.get(
|
|
537
|
+
"output_ports", (("output", "array"),)
|
|
538
|
+
)
|
|
539
|
+
),
|
|
540
|
+
options_schema=registration.get("options_schema"),
|
|
541
|
+
modes=modes,
|
|
542
|
+
finality=finality,
|
|
543
|
+
stateful=False,
|
|
544
|
+
microbatch_invariant=microbatch_invariant,
|
|
545
|
+
requires_watermark=False,
|
|
546
|
+
checkpoint_support="stateless",
|
|
547
|
+
state_version=None,
|
|
548
|
+
deterministic=deterministic,
|
|
549
|
+
replay_safe=replay_safe,
|
|
550
|
+
supports_static_inputs=supports_static_inputs,
|
|
551
|
+
partition_contract=partition_contract,
|
|
552
|
+
array_rules=array_rules,
|
|
553
|
+
)
|
|
554
|
+
|
|
555
|
+
providers = tuple(
|
|
556
|
+
sorted(
|
|
557
|
+
(
|
|
558
|
+
provider_capability(registration)
|
|
559
|
+
for registration in registrations
|
|
560
|
+
if registration["kind"] == "provider"
|
|
561
|
+
),
|
|
562
|
+
key=lambda item: (item.provider, item.name, item.version),
|
|
563
|
+
)
|
|
564
|
+
)
|
|
565
|
+
return RuntimeCapabilities(
|
|
566
|
+
schema_version=CAPABILITY_SCHEMA_VERSION,
|
|
567
|
+
scope=RuntimeSessionScope(
|
|
568
|
+
kind="runtime_session",
|
|
569
|
+
session_id=session_id,
|
|
570
|
+
revision=revision,
|
|
571
|
+
),
|
|
572
|
+
package_version=package_version,
|
|
573
|
+
project_format_versions=(3,),
|
|
574
|
+
batch_kinds=("array", "table"),
|
|
575
|
+
portable_arrow_types=PORTABLE_ARROW_TYPES,
|
|
576
|
+
operators=(
|
|
577
|
+
OperatorCapability(
|
|
578
|
+
kind="cross_section",
|
|
579
|
+
version="1",
|
|
580
|
+
input_ports=(ProviderPort("input", "table", required=True),),
|
|
581
|
+
output_ports=(ProviderPort("output", "table", required=True),),
|
|
582
|
+
modes=("batch", "stream"),
|
|
583
|
+
finality="group_final_append_only",
|
|
584
|
+
requires_datafusion=False,
|
|
585
|
+
stateful=True,
|
|
586
|
+
microbatch_invariant=True,
|
|
587
|
+
requires_watermark=True,
|
|
588
|
+
checkpoint_support="checkpointed_stateful",
|
|
589
|
+
state_version=1,
|
|
590
|
+
state_layouts=(1,),
|
|
591
|
+
deterministic=True,
|
|
592
|
+
replay_safe=True,
|
|
593
|
+
),
|
|
594
|
+
OperatorCapability(
|
|
595
|
+
kind="expression",
|
|
596
|
+
version="1",
|
|
597
|
+
input_ports=(ProviderPort("input", "table", required=True),),
|
|
598
|
+
output_ports=(ProviderPort("output", "table", required=True),),
|
|
599
|
+
modes=("batch", "stream"),
|
|
600
|
+
finality="per_row_final",
|
|
601
|
+
requires_datafusion=True,
|
|
602
|
+
stateful=False,
|
|
603
|
+
microbatch_invariant=True,
|
|
604
|
+
requires_watermark=False,
|
|
605
|
+
checkpoint_support="stateless",
|
|
606
|
+
state_version=None,
|
|
607
|
+
deterministic=True,
|
|
608
|
+
replay_safe=True,
|
|
609
|
+
),
|
|
610
|
+
OperatorCapability(
|
|
611
|
+
kind="rolling",
|
|
612
|
+
version="1",
|
|
613
|
+
input_ports=(ProviderPort("input", "table", required=True),),
|
|
614
|
+
output_ports=(ProviderPort("output", "table", required=True),),
|
|
615
|
+
modes=("batch", "stream"),
|
|
616
|
+
finality="per_row_final",
|
|
617
|
+
requires_datafusion=False,
|
|
618
|
+
stateful=True,
|
|
619
|
+
microbatch_invariant=True,
|
|
620
|
+
requires_watermark=True,
|
|
621
|
+
checkpoint_support="checkpointed_stateful",
|
|
622
|
+
state_version=1,
|
|
623
|
+
state_layouts=(1, 2),
|
|
624
|
+
deterministic=True,
|
|
625
|
+
replay_safe=True,
|
|
626
|
+
),
|
|
627
|
+
OperatorCapability(
|
|
628
|
+
kind="sql",
|
|
629
|
+
version="1",
|
|
630
|
+
input_ports=(ProviderPort("input", "table", required=True),),
|
|
631
|
+
output_ports=(ProviderPort("output", "table", required=True),),
|
|
632
|
+
modes=("batch", "stream"),
|
|
633
|
+
finality="unproven",
|
|
634
|
+
requires_datafusion=True,
|
|
635
|
+
stateful=False,
|
|
636
|
+
microbatch_invariant=False,
|
|
637
|
+
requires_watermark=False,
|
|
638
|
+
checkpoint_support="stateless",
|
|
639
|
+
state_version=None,
|
|
640
|
+
deterministic=True,
|
|
641
|
+
replay_safe=True,
|
|
642
|
+
),
|
|
643
|
+
OperatorCapability(
|
|
644
|
+
kind="stream_join",
|
|
645
|
+
version="1",
|
|
646
|
+
input_ports=(
|
|
647
|
+
ProviderPort("left", "table", required=True),
|
|
648
|
+
ProviderPort("right", "table", required=True),
|
|
649
|
+
),
|
|
650
|
+
output_ports=(ProviderPort("output", "table", required=True),),
|
|
651
|
+
modes=("stream",),
|
|
652
|
+
finality="unproven",
|
|
653
|
+
requires_datafusion=True,
|
|
654
|
+
stateful=True,
|
|
655
|
+
microbatch_invariant=False,
|
|
656
|
+
requires_watermark=True,
|
|
657
|
+
checkpoint_support="checkpointed_stateful",
|
|
658
|
+
state_version=1,
|
|
659
|
+
state_layouts=(1,),
|
|
660
|
+
deterministic=True,
|
|
661
|
+
replay_safe=True,
|
|
662
|
+
),
|
|
663
|
+
),
|
|
664
|
+
udfs=udfs,
|
|
665
|
+
providers=providers,
|
|
666
|
+
connectors=connector_capabilities(_native.registered_connectors()),
|
|
667
|
+
)
|
|
668
|
+
|
|
669
|
+
|
|
670
|
+
# ---------------------------------------------------------------------------
|
|
671
|
+
# Connector capability surface (M6-08)
|
|
672
|
+
# ---------------------------------------------------------------------------
|
|
673
|
+
|
|
674
|
+
type DeliveryCapabilityKind = Literal["best_effort", "at_least_once", "exactly_once"]
|
|
675
|
+
type ReplayCapabilityKind = Literal["replayable_exact", "unreplayable"]
|
|
676
|
+
type WatermarkSupportKind = Literal["native", "generated_only"]
|
|
677
|
+
type TransactionSupportKind = Literal[
|
|
678
|
+
"none",
|
|
679
|
+
"pre_commit_commit",
|
|
680
|
+
"ledger_idempotent",
|
|
681
|
+
"retry_deduplicated",
|
|
682
|
+
]
|
|
683
|
+
|
|
684
|
+
|
|
685
|
+
@dataclass(frozen=True, slots=True)
|
|
686
|
+
class ConnectorCapabilities:
|
|
687
|
+
delivery: DeliveryCapabilityKind
|
|
688
|
+
replay: ReplayCapabilityKind
|
|
689
|
+
watermark: WatermarkSupportKind
|
|
690
|
+
transaction: TransactionSupportKind
|
|
691
|
+
snapshot: bool
|
|
692
|
+
polling: bool
|
|
693
|
+
cdc: bool
|
|
694
|
+
lookup: bool
|
|
695
|
+
|
|
696
|
+
|
|
697
|
+
@dataclass(frozen=True, slots=True)
|
|
698
|
+
class ConnectorCapability:
|
|
699
|
+
provider: str
|
|
700
|
+
name: str
|
|
701
|
+
version: str
|
|
702
|
+
kind: Literal["source", "sink", "both"]
|
|
703
|
+
capabilities: ConnectorCapabilities
|
|
704
|
+
formats: tuple[str, ...]
|
|
705
|
+
options_schema: Mapping[str, object]
|
|
706
|
+
|
|
707
|
+
def __post_init__(self) -> None:
|
|
708
|
+
if type(self.provider) is not str or not self.provider:
|
|
709
|
+
raise ValueError("connector capability provider must be a non-empty string")
|
|
710
|
+
if type(self.name) is not str or not self.name:
|
|
711
|
+
raise ValueError("connector capability name must be a non-empty string")
|
|
712
|
+
if type(self.version) is not str or not self.version:
|
|
713
|
+
raise ValueError("connector capability version must be a non-empty string")
|
|
714
|
+
if self.kind not in {"source", "sink", "both"}:
|
|
715
|
+
raise ValueError(
|
|
716
|
+
f"connector capability kind must be source, sink, or both; "
|
|
717
|
+
f"found {self.kind}"
|
|
718
|
+
)
|
|
719
|
+
if not isinstance(self.capabilities, ConnectorCapabilities):
|
|
720
|
+
raise TypeError(
|
|
721
|
+
"connector capability capabilities must be a ConnectorCapabilities; "
|
|
722
|
+
f"found {type(self.capabilities).__name__}"
|
|
723
|
+
)
|
|
724
|
+
if not isinstance(self.formats, tuple):
|
|
725
|
+
raise TypeError(
|
|
726
|
+
"connector capability formats must be a tuple of strings; "
|
|
727
|
+
f"found {type(self.formats).__name__}"
|
|
728
|
+
)
|
|
729
|
+
if not isinstance(self.options_schema, Mapping):
|
|
730
|
+
raise TypeError(
|
|
731
|
+
"connector capability options_schema must be a Mapping; "
|
|
732
|
+
f"found {type(self.options_schema).__name__}"
|
|
733
|
+
)
|
|
734
|
+
|
|
735
|
+
|
|
736
|
+
def _parse_options_schema(raw: object) -> dict[str, object]:
|
|
737
|
+
"""Parses the JSON string the native layer serializes into a dict."""
|
|
738
|
+
if isinstance(raw, dict):
|
|
739
|
+
return dict(raw)
|
|
740
|
+
if isinstance(raw, str):
|
|
741
|
+
return dict(json.loads(raw))
|
|
742
|
+
return {}
|
|
743
|
+
|
|
744
|
+
|
|
745
|
+
def connector_capabilities(
|
|
746
|
+
registrations: Sequence[Mapping[str, Any]],
|
|
747
|
+
) -> tuple[ConnectorCapability, ...]:
|
|
748
|
+
"""Builds the sorted tuple of connector capabilities from native data."""
|
|
749
|
+
parsed: list[ConnectorCapability] = []
|
|
750
|
+
for registration in registrations:
|
|
751
|
+
caps_data = registration.get("capabilities", {})
|
|
752
|
+
caps = ConnectorCapabilities(
|
|
753
|
+
delivery=str(caps_data.get("delivery", "at_least_once")),
|
|
754
|
+
replay=str(caps_data.get("replay", "unreplayable")),
|
|
755
|
+
watermark=str(caps_data.get("watermark", "generated_only")),
|
|
756
|
+
transaction=str(caps_data.get("transaction", "none")),
|
|
757
|
+
snapshot=bool(caps_data.get("snapshot", False)),
|
|
758
|
+
polling=bool(caps_data.get("polling", False)),
|
|
759
|
+
cdc=bool(caps_data.get("cdc", False)),
|
|
760
|
+
lookup=bool(caps_data.get("lookup", False)),
|
|
761
|
+
)
|
|
762
|
+
parsed.append(
|
|
763
|
+
ConnectorCapability(
|
|
764
|
+
provider=str(registration["provider"]),
|
|
765
|
+
name=str(registration["name"]),
|
|
766
|
+
version=str(registration["version"]),
|
|
767
|
+
kind=str(registration.get("kind", "both")),
|
|
768
|
+
capabilities=caps,
|
|
769
|
+
formats=tuple(str(f) for f in registration.get("formats", ())),
|
|
770
|
+
options_schema=_parse_options_schema(
|
|
771
|
+
registration.get("options_schema", "{}")
|
|
772
|
+
),
|
|
773
|
+
)
|
|
774
|
+
)
|
|
775
|
+
return tuple(sorted(parsed, key=lambda c: (c.provider, c.name, c.version)))
|