equiframe 0.12.4__py3-none-any.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 (170) hide show
  1. equiframe/__init__.py +832 -0
  2. equiframe/__main__.py +3 -0
  3. equiframe/_base.py +84 -0
  4. equiframe/_cli_support.py +125 -0
  5. equiframe/_comparison_io.py +161 -0
  6. equiframe/_dataset_provenance.py +146 -0
  7. equiframe/_decimal.py +22 -0
  8. equiframe/_diff_capture.py +433 -0
  9. equiframe/_doctor.py +123 -0
  10. equiframe/_document_io.py +61 -0
  11. equiframe/_documents.py +79 -0
  12. equiframe/_duckdb_transport.py +149 -0
  13. equiframe/_error_reporting.py +52 -0
  14. equiframe/_first_use.py +102 -0
  15. equiframe/_path_compat.py +26 -0
  16. equiframe/_physical.py +133 -0
  17. equiframe/_r_review.R +308 -0
  18. equiframe/_regular_io.py +167 -0
  19. equiframe/_report_format_v1.py +298 -0
  20. equiframe/_report_io.py +591 -0
  21. equiframe/_stability_inventory.py +1421 -0
  22. equiframe/_timezones.py +257 -0
  23. equiframe/_vendor/cyclonedx/LICENSE +201 -0
  24. equiframe/_vendor/cyclonedx/PROVENANCE.md +18 -0
  25. equiframe/_vendor/cyclonedx/bom-1.6.schema.json +5673 -0
  26. equiframe/_vendor/cyclonedx/jsf-0.82.schema.json +240 -0
  27. equiframe/_vendor/cyclonedx/spdx.schema.json +712 -0
  28. equiframe/adapters.py +454 -0
  29. equiframe/assertion_types.py +122 -0
  30. equiframe/assertions.py +271 -0
  31. equiframe/benchmark.py +101 -0
  32. equiframe/bundle.py +240 -0
  33. equiframe/cli.py +693 -0
  34. equiframe/comparison.py +937 -0
  35. equiframe/comparison_report.py +660 -0
  36. equiframe/comparison_types.py +257 -0
  37. equiframe/compatibility.py +546 -0
  38. equiframe/contracts.py +879 -0
  39. equiframe/datasets.py +1301 -0
  40. equiframe/decision.py +34 -0
  41. equiframe/destinations.py +295 -0
  42. equiframe/detector_types.py +431 -0
  43. equiframe/detectors.py +389 -0
  44. equiframe/diff.py +192 -0
  45. equiframe/diff_contracts.py +523 -0
  46. equiframe/docs/adoption.md +329 -0
  47. equiframe/docs/api.md +534 -0
  48. equiframe/docs/arquitecto.md +143 -0
  49. equiframe/docs/choosing-api.md +84 -0
  50. equiframe/docs/cientifico.md +180 -0
  51. equiframe/docs/compatibility.md +254 -0
  52. equiframe/docs/contributing.md +171 -0
  53. equiframe/docs/dependencies.md +237 -0
  54. equiframe/docs/eda.md +166 -0
  55. equiframe/docs/extending.md +158 -0
  56. equiframe/docs/fast-ci.md +52 -0
  57. equiframe/docs/first-use.md +78 -0
  58. equiframe/docs/index.md +142 -0
  59. equiframe/docs/ingestion.md +145 -0
  60. equiframe/docs/inicio.md +129 -0
  61. equiframe/docs/installed-private.md +97 -0
  62. equiframe/docs/journeys.md +362 -0
  63. equiframe/docs/lectura-conversion.md +211 -0
  64. equiframe/docs/licensing.md +17 -0
  65. equiframe/docs/maintenance.md +233 -0
  66. equiframe/docs/migration-ir.md +333 -0
  67. equiframe/docs/option-audit.md +45 -0
  68. equiframe/docs/outcome-gates.md +134 -0
  69. equiframe/docs/performance.md +263 -0
  70. equiframe/docs/piloto-simulado.md +56 -0
  71. equiframe/docs/quickstart.md +156 -0
  72. equiframe/docs/r_review.md +212 -0
  73. equiframe/docs/recipes.md +248 -0
  74. equiframe/docs/releasing.md +183 -0
  75. equiframe/docs/security.md +218 -0
  76. equiframe/docs/semantics.md +274 -0
  77. equiframe/docs/sqlite-snapshots.md +262 -0
  78. equiframe/docs/universal-sources.md +49 -0
  79. equiframe/docs/universalidad-cinco-pasos.md +107 -0
  80. equiframe/docs/upgrading.md +538 -0
  81. equiframe/duckdb_capture.py +410 -0
  82. equiframe/eda/__init__.py +36 -0
  83. equiframe/eda/__main__.py +3 -0
  84. equiframe/eda/_backend.py +612 -0
  85. equiframe/eda/_inventory.py +49 -0
  86. equiframe/eda/_paths.py +49 -0
  87. equiframe/eda/_report.py +291 -0
  88. equiframe/eda/api.py +535 -0
  89. equiframe/eda/cli.py +102 -0
  90. equiframe/eda/models.py +239 -0
  91. equiframe/errors.py +83 -0
  92. equiframe/evidence.py +375 -0
  93. equiframe/examples/integrations/eda_generic.py +60 -0
  94. equiframe/examples/integrations/outcome_gates.py +94 -0
  95. equiframe/examples/integrations/sqlite_snapshot.py +246 -0
  96. equiframe/examples/integrations/sqlite_snapshot_demo.py +226 -0
  97. equiframe/examples/integrations/universal_first_use.py +296 -0
  98. equiframe/examples/journeys/README.md +31 -0
  99. equiframe/examples/journeys/__init__.py +1 -0
  100. equiframe/examples/journeys/a_pandas_refactor.py +88 -0
  101. equiframe/examples/journeys/b_native_migration.py +181 -0
  102. equiframe/examples/journeys/c_retail_etl.py +460 -0
  103. equiframe/examples/journeys/common.py +136 -0
  104. equiframe/examples/journeys/d_financial_reconciliation.py +96 -0
  105. equiframe/examples/journeys/e_telemetry.py +135 -0
  106. equiframe/examples/journeys/f_data_science_preparation.py +84 -0
  107. equiframe/examples/journeys/g_external_snapshots.py +197 -0
  108. equiframe/examples/journeys/h_portable_ci.py +129 -0
  109. equiframe/examples/journeys/requirements.lock +249 -0
  110. equiframe/examples/journeys/verify_journeys.py +117 -0
  111. equiframe/examples/load_convert_compare.py +85 -0
  112. equiframe/execution.py +355 -0
  113. equiframe/execution_types.py +177 -0
  114. equiframe/ingestion.py +744 -0
  115. equiframe/local.py +126 -0
  116. equiframe/migration/__init__.py +94 -0
  117. equiframe/migration/_physical.py +8 -0
  118. equiframe/migration/backends/_timestamp_transport.py +109 -0
  119. equiframe/migration/backends/duckdb.py +651 -0
  120. equiframe/migration/backends/duckdb_decimal.py +125 -0
  121. equiframe/migration/backends/duckdb_time.py +87 -0
  122. equiframe/migration/backends/pandas.py +544 -0
  123. equiframe/migration/backends/polars.py +915 -0
  124. equiframe/migration/backends/polars_decimal.py +231 -0
  125. equiframe/migration/io.py +198 -0
  126. equiframe/migration/ports.py +96 -0
  127. equiframe/migration/runner.py +545 -0
  128. equiframe/migration/runner_types.py +405 -0
  129. equiframe/migration/types.py +354 -0
  130. equiframe/migration/validation.py +619 -0
  131. equiframe/normalization.py +358 -0
  132. equiframe/pipeline.py +274 -0
  133. equiframe/pipeline_types.py +247 -0
  134. equiframe/ports.py +39 -0
  135. equiframe/preprocessing.py +110 -0
  136. equiframe/profile_types.py +343 -0
  137. equiframe/profiles.py +447 -0
  138. equiframe/py.typed +0 -0
  139. equiframe/r_review.py +706 -0
  140. equiframe/reduction.py +518 -0
  141. equiframe/registry.py +121 -0
  142. equiframe/report_rendering.py +366 -0
  143. equiframe/report_types.py +294 -0
  144. equiframe/reporting.py +26 -0
  145. equiframe/rule_types.py +178 -0
  146. equiframe/rules.py +819 -0
  147. equiframe/schemas/r-execution-binding-1.0.schema.json +46 -0
  148. equiframe/schemas/r-review-binding-1.0.schema.json +28 -0
  149. equiframe/schemas/r-review-options-1.0.schema.json +38 -0
  150. equiframe/schemas/r-review-result-1.0.schema.json +207 -0
  151. equiframe/schemas/reviewed-decision-1.0.schema.json +3394 -0
  152. equiframe/serialization.py +152 -0
  153. equiframe/snapshot.py +296 -0
  154. equiframe/sources.py +283 -0
  155. equiframe/tables.py +459 -0
  156. equiframe/types.py +71 -0
  157. equiframe/values.py +252 -0
  158. equiframe/verification_request.py +35 -0
  159. equiframe/verify.py +799 -0
  160. equiframe/verify_types.py +255 -0
  161. equiframe/workflow.py +510 -0
  162. equiframe/workflow_request.py +38 -0
  163. equiframe-0.12.4.dist-info/METADATA +268 -0
  164. equiframe-0.12.4.dist-info/RECORD +170 -0
  165. equiframe-0.12.4.dist-info/WHEEL +4 -0
  166. equiframe-0.12.4.dist-info/entry_points.txt +3 -0
  167. equiframe-0.12.4.dist-info/licenses/LICENSE +21 -0
  168. equiframe-0.12.4.dist-info/licenses/THIRD_PARTY_NOTICES.md +42 -0
  169. equiframe-0.12.4.dist-info/licenses/tools/cyclonedx/LICENSE +201 -0
  170. equiframe-0.12.4.dist-info/licenses/tools/cyclonedx/PROVENANCE.md +18 -0
@@ -0,0 +1,546 @@
1
+ """Inspectable compatibility commitments, separate from execution admission.
2
+
3
+ These records describe implemented boundaries; they do not run a probe, register
4
+ plugins, import optional engines, or certify a dependency/platform combination.
5
+ Use runtime validation and a measured conformity receipt for those questions.
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+ from typing import Annotated, Literal, cast
11
+
12
+ from pydantic import BeforeValidator
13
+
14
+ from equiframe._base import FrozenModel, Text, strict_bool_literal, strict_int_literal
15
+ from equiframe._stability_inventory import (
16
+ CLI_INVENTORY,
17
+ EXPERIMENTAL_API,
18
+ SCHEMA_INVENTORY,
19
+ STABLE_API,
20
+ STABLE_FEATURE_SCOPES,
21
+ )
22
+ from equiframe.adapters import DuckDBAdapter, PandasAdapter, PolarsAdapter
23
+ from equiframe.execution_types import EngineCapabilities
24
+
25
+
26
+ class CompatibilityPolicy(FrozenModel):
27
+ """Prospective policy, effective at the STEP6 candidate; not release history."""
28
+
29
+ policy_version: Literal["1.0"] = "1.0"
30
+ stable_notice_minor_releases: Annotated[Literal[2], BeforeValidator(strict_int_literal)] = 2
31
+ stable_notice_days: Annotated[Literal[90], BeforeValidator(strict_int_literal)] = 90
32
+ notice_threshold: Literal["both"] = "both"
33
+ applies_during_zero_major: Annotated[Literal[True], BeforeValidator(strict_bool_literal)] = True
34
+ notices: tuple[Text, ...] = (
35
+ "CHANGELOG entry with affected surfaces, replacement and migration example",
36
+ "DeprecationWarning at the deprecated Python entry point, when callable",
37
+ "stderr notice for a deprecated CLI invocation, without changing its exit code",
38
+ )
39
+ stable_breaking_release: Literal["major_or_announced_zero_major_minor"] = (
40
+ "major_or_announced_zero_major_minor"
41
+ )
42
+ experimental_change_notice: Literal["changelog_and_migration_in_same_release"] = (
43
+ "changelog_and_migration_in_same_release"
44
+ )
45
+ fail_closed_exception: Text = (
46
+ "Security, corrupted evidence or false-equivalence fixes may immediately reject "
47
+ "previously accepted inputs. Publish affected scope, reason, replacement/recovery "
48
+ "and a regression test; never reinterpret a failure as success."
49
+ )
50
+ persisted_format_rule: Text = (
51
+ "Do not reinterpret an existing schema version or rewrite old evidence. "
52
+ "Incompatible writes require a new format version and an explicit migration. "
53
+ "Reader withdrawal follows the stable notice window even for experimental producers; "
54
+ "security/correctness rejection remains fail-closed. Unknown versions are rejected."
55
+ )
56
+ dependency_support: Text = (
57
+ "Declared Python and dependency ranges are admission metadata, not tested cells. "
58
+ "Only an exact recorded candidate, environment, dependency set and corpus run "
59
+ "establish observed conformity; no external certification is implied."
60
+ )
61
+
62
+
63
+ class StabilityRecord(FrozenModel):
64
+ """One explicitly classified public name, command or persisted surface."""
65
+
66
+ surface: Literal["api", "cli", "format", "evidence", "presentation", "namespace"]
67
+ name: Text
68
+ status: Literal["stable", "experimental", "unsupported"]
69
+ commitment: Text
70
+ parameters: tuple[Text, ...] = ()
71
+ exit_codes: tuple[int, ...] = ()
72
+ schema_sha256: str | None = None
73
+
74
+
75
+ class CapabilityStage(FrozenModel):
76
+ """Conditional capability of one stage, never a Cartesian-product claim."""
77
+
78
+ stage: Literal["capture", "comparison", "native_execution", "migration"]
79
+ status: Literal["supported_with_conditions", "rejected", "not_applicable"]
80
+ scope: Text
81
+ conditions: tuple[Text, ...]
82
+ losses: tuple[Text, ...]
83
+ implementation: tuple[Text, ...]
84
+ tests: tuple[Text, ...]
85
+
86
+
87
+ class AdapterCapability(FrozenModel):
88
+ """Descriptive register entry; EngineCapabilities remains the runtime port."""
89
+
90
+ adapter: Literal["arrow", "pandas", "polars", "duckdb"]
91
+ required_extra: Literal["arrow", "pandas", "polars", "duckdb"]
92
+ component: Literal["packages/equiframe"] = "packages/equiframe"
93
+ ownership_reference: Literal["docs/TEAM_INTEGRATION.md#1-propiedad-y-dependencias"] = (
94
+ "docs/TEAM_INTEGRATION.md#1-propiedad-y-dependencias"
95
+ )
96
+ maintainer: Text | None = None
97
+ maintenance_status: Literal["unassigned", "assigned"] = "unassigned"
98
+ evidence_scope: Literal["repository_corpus_only_no_external_certification"] = (
99
+ "repository_corpus_only_no_external_certification"
100
+ )
101
+ runtime: EngineCapabilities | None
102
+ stages: tuple[CapabilityStage, ...]
103
+
104
+
105
+ def get_compatibility_policy() -> CompatibilityPolicy:
106
+ """Return the current prospective deprecation and format-reader commitment."""
107
+ return CompatibilityPolicy()
108
+
109
+
110
+ _STABLE_SCOPE_COMMITMENTS = {
111
+ "datasets": (
112
+ "Local bytes and effective read options identify the source; ambiguous structure "
113
+ "requires an explicit choice. Conversion validates complete values, column order "
114
+ "and admitted types without implicit loss; mutated captures are rejected. "
115
+ "Use close/context management for owned DuckDB resources. Nested GeoJSON transport "
116
+ "does not imply nested comparison support; no filesystem sandbox is promised."
117
+ ),
118
+ "capture": (
119
+ "Only explicit trusted DuckDB SQL/relation capture is admitted, with caller/session "
120
+ "ownership, native type checks and cooperative budgets; connections, callbacks and "
121
+ "SQL strings are not inferred as direct comparison inputs."
122
+ ),
123
+ "migration": (
124
+ "The closed IR, validation diagnostics and per-engine capability checks define "
125
+ "the admitted subset. Built-in engines execute their own compiled plans "
126
+ "on verified inputs; "
127
+ "unsupported plans and semantic loss fail closed. Compilation/execution success "
128
+ "does not establish equivalence or correctness of an independent expected result. "
129
+ "No arbitrary Python/SQL translation or hard process-memory limit is promised."
130
+ ),
131
+ "detection": (
132
+ "Built-in fit/scoring consumes validated profile features, not selection labels or "
133
+ "engine metadata; callers remain responsible for meaningful feature/normal selection. "
134
+ "Fit, normal calibration and inference remain separate; unavailable support is "
135
+ "not no_alert, and H preserves rule alerts. API/format compatibility does not "
136
+ "promise scientific accuracy, cross-version fitted-model parity or speed."
137
+ ),
138
+ "r_review": (
139
+ "Language R independently reviews verified transport under its admitted exact "
140
+ "contract subset; absence, unsupported policy and disagreement cannot establish "
141
+ "acceptance. The execution finalizer verifies artifact/profile/IF bindings. "
142
+ "The low-level finalizer requires caller-associated component decisions. "
143
+ "Rscript is external and process timeout excludes prior Python capture time."
144
+ ),
145
+ }
146
+
147
+
148
+ def _stable_api_commitment(name: str) -> str:
149
+ base = "Documented signature, typed values and semantic/error contract; stable notice window"
150
+ for scope, names in STABLE_FEATURE_SCOPES:
151
+ if name in names:
152
+ return base + "; " + _STABLE_SCOPE_COMMITMENTS[scope]
153
+ return base
154
+
155
+
156
+ def get_stability_register() -> tuple[StabilityRecord, ...]:
157
+ """Return the exhaustive, explicit core API/CLI/schema classification.
158
+
159
+ Export names and CLI options are deliberately not discovered at runtime:
160
+ adding a surface must also classify it, and drift tests enforce this rule.
161
+ Experimental does not weaken format integrity or safe failure semantics.
162
+ """
163
+ records = [
164
+ StabilityRecord(
165
+ surface="api",
166
+ name="equiframe." + name,
167
+ status=cast('Literal["stable", "experimental"]', status),
168
+ commitment=(
169
+ _stable_api_commitment(name)
170
+ if status == "stable"
171
+ else "Producer/algorithm or extension surface may evolve; "
172
+ "explicit release migration"
173
+ ),
174
+ )
175
+ for names, status in ((STABLE_API, "stable"), (EXPERIMENTAL_API, "experimental"))
176
+ for name in names
177
+ ]
178
+ records.extend(
179
+ StabilityRecord(
180
+ surface="cli",
181
+ name="equiframe " + name,
182
+ status=cast('Literal["stable", "experimental"]', status),
183
+ commitment=(
184
+ "Command, listed parameters and exit meanings; help/error prose is not byte-stable"
185
+ ),
186
+ parameters=parameters,
187
+ exit_codes=codes,
188
+ )
189
+ for name, status, parameters, codes in CLI_INVENTORY
190
+ )
191
+ records.extend(
192
+ StabilityRecord(
193
+ surface="format",
194
+ name=name,
195
+ status=cast('Literal["stable", "experimental"]', status),
196
+ commitment=(
197
+ "Versioned JSON shape and semantics; reader withdrawal uses the stable notice "
198
+ "window independently of producer maturity; no automatic historical rewrite"
199
+ ),
200
+ schema_sha256=digest,
201
+ )
202
+ for name, status, digest in SCHEMA_INVENTORY
203
+ )
204
+ records.extend(
205
+ (
206
+ StabilityRecord(
207
+ surface="format",
208
+ name="equiframe-metadata-json-v1",
209
+ status="stable",
210
+ commitment="Contract canonicalization bytes and fingerprint algorithm; not JCS",
211
+ ),
212
+ StabilityRecord(
213
+ surface="evidence",
214
+ name="comparison.json/differences.jsonl/diff-contract.json",
215
+ status="stable",
216
+ commitment=(
217
+ "Versioned comparison/difference/declaration semantics; existing readers "
218
+ "validate recorded evidence without executing a source; raw URI portability "
219
+ "is not guaranteed"
220
+ ),
221
+ ),
222
+ StabilityRecord(
223
+ surface="evidence",
224
+ name="comparison-report/evidence-manifest.json",
225
+ status="stable",
226
+ commitment=(
227
+ "Closed report inventory, verified hashes, disclosure policy and completion; "
228
+ "reader verification preserves historical bytes and never authenticates authors"
229
+ ),
230
+ ),
231
+ StabilityRecord(
232
+ surface="evidence",
233
+ name="pipeline/migration/workflow-local-layout",
234
+ status="experimental",
235
+ commitment=(
236
+ "Producer layout may evolve; recorded versions retain the reader notice "
237
+ "window; integrity, completion and semantic decisions remain separate"
238
+ ),
239
+ ),
240
+ StabilityRecord(
241
+ surface="presentation",
242
+ name="HTML/text/notebook/error-prose",
243
+ status="experimental",
244
+ commitment=(
245
+ "Visual layout, wording, CSS and bytes may change; schema-tagged JSON "
246
+ "and recorded hashes are the interchange surfaces; no HTML scraping contract"
247
+ ),
248
+ ),
249
+ StabilityRecord(
250
+ surface="namespace",
251
+ name="equiframe_benchmark",
252
+ status="experimental",
253
+ commitment=(
254
+ "Separate distribution and experimental protocol; this core register "
255
+ "does not enumerate or certify its API or scientific results"
256
+ ),
257
+ ),
258
+ StabilityRecord(
259
+ surface="namespace",
260
+ name="equiframe.eda",
261
+ status="experimental",
262
+ commitment=(
263
+ "Optional standalone diagnostics; exact nested API names/signatures/shapes "
264
+ "are explicitly inventoried in equiframe.eda._inventory. No change to "
265
+ "migration/Verify or detectors; unavailable metrics are not zero alerts."
266
+ ),
267
+ ),
268
+ StabilityRecord(
269
+ surface="namespace",
270
+ name="equiframe.benchmark",
271
+ status="unsupported",
272
+ commitment=(
273
+ "Quarantined helpers raise UnsupportedBenchmarkError; CLI exits 2 "
274
+ "except help. No evaluation, migration or replacement compatibility promised"
275
+ ),
276
+ ),
277
+ )
278
+ )
279
+ return tuple(sorted(records, key=lambda record: (record.surface, record.name)))
280
+
281
+
282
+ _ENGINE_CLASS = {"pandas": "Pandas", "polars": "Polars", "duckdb": "DuckDB"}
283
+
284
+ _SCALAR_TYPES = (
285
+ "Scalar NULL, bool, signed/unsigned integers, float32/64, text, fixed-scale decimal, "
286
+ "date32/date64 and timestamps only as admitted by each capture path; nested, binary, "
287
+ "dictionary/category and extension semantics are not silently decoded"
288
+ )
289
+ _LOST_BEFORE_CAPTURE = (
290
+ "NULL/NaN, original per-cell timezone offset/provider, decimal signed zero or exponent "
291
+ "distinctions already erased by the input representation cannot be reconstructed"
292
+ )
293
+ _COMPARISON = CapabilityStage(
294
+ stage="comparison",
295
+ status="supported_with_conditions",
296
+ scope="Materialized captures compared by the complete comparison kernel; no native query",
297
+ conditions=(
298
+ "Contract output schema, explicit keys or bag/sequence policy; incomplete/duplicate keys "
299
+ "and invalid expected references never establish equivalence",
300
+ "Exact physical types by default; authorized lossless mapping, index ignore or column "
301
+ "order relaxation needs an explicit policy/justification",
302
+ "Numeric/absence/temporal policy is applied only where declared; budgets can produce "
303
+ "not_evaluable; no all-types/all-policies Cartesian-product support claim",
304
+ ),
305
+ losses=(_LOST_BEFORE_CAPTURE,),
306
+ implementation=("equiframe.comparison.compare", "equiframe.diff.diff"),
307
+ tests=(
308
+ "tests/test_comparison.py::test_absence_nan_empty_zero_are_distinct",
309
+ "tests/test_comparison.py::test_permutation_and_multiplicity_property",
310
+ "tests/test_comparison.py::test_budget_exhaustion_never_equivalent",
311
+ ),
312
+ )
313
+ _COMMON_MIGRATION = (
314
+ "Closed IR: Scan, Project, Filter, widening Cast, inner/left/right/full/semi/anti EquiJoin "
315
+ "with matchable-key cardinality, Aggregate, Sort, Concatenate, deterministic Deduplicate "
316
+ "and deterministic Window (row_number, exact-type lag/lead)",
317
+ "Signed/unsigned 8/16/32/64-bit integer add/subtract/multiply reject overflow; "
318
+ "Decimal precision <=38; exact multiply declares summed scales; DecimalRound declares "
319
+ "precision/scale and rounding; division/localization have per-engine capability boundaries",
320
+ "Timestamp transport preserves declared unit/zone; float join/group/sort/dedup/window keys and "
321
+ "floating sum/min/max remain rejected; count_valid may consume float",
322
+ "Fallible expressions inside Coalesce, Conditional or boolean short-circuit forms "
323
+ "require preceding Project checkpoints; integer division is rejected",
324
+ "No arbitrary Python/SQL translation, remote execution, implicit casts or cross-engine "
325
+ "fallback; validate_ir and both compiler checks remain authoritative",
326
+ )
327
+ _MIGRATION_TESTS = (
328
+ "tests/test_native_integer_arithmetic.py::test_native_integer_results_preserve_exact_width_and_bag",
329
+ "tests/test_migration_backends.py::test_each_native_adapter_is_invoked_once_without_other_backend_fallback",
330
+ "tests/test_migration_backends.py::test_native_decimal_sum_currency_groups_and_all_null",
331
+ "tests/test_migration_semantic_directions.py::test_semantic_native_directions_and_independent_validation_scope",
332
+ "tests/test_migration_product_semantics.py::test_join_cardinality_composite_key_option_matrix",
333
+ "tests/test_migration_product_semantics.py::test_timestamp_ir_exact_unit_zone_transport",
334
+ "tests/test_migration_product_semantics.py::test_decimal_multiply_exact_scale_and_round",
335
+ "tests/test_migration_windows.py::test_six_migration_directions_verify_windows_against_literal_expected",
336
+ "tests/test_window_semantics.py::test_native_lag_preserves_values_and_literal_column_names",
337
+ )
338
+
339
+
340
+ def _native_stage(engine: str) -> CapabilityStage:
341
+ details = {
342
+ "pandas": (
343
+ "Trusted PandasTransformation callback on isolated DataFrames; no lazy return",
344
+ "Arrow-backed decimal up to precision 76; meaningful index retained separately; "
345
+ "zero-column tables with rows are rejected",
346
+ ),
347
+ "polars": (
348
+ "Trusted PolarsTransformation callback; DataFrame/LazyFrame output is collected",
349
+ "decimal128 precision <=38 with nonnegative scale; date64 rejected; timestamp "
350
+ "seconds transported as milliseconds only after int64 overflow guard",
351
+ ),
352
+ "duckdb": (
353
+ "Trusted SELECT/WITH or compiler-generated native checkpoints in a fresh "
354
+ ":memory: connection; no external SQL access",
355
+ "decimal128 precision <=38 with nonnegative scale; zoned ns, mismatched session "
356
+ "zone, temporal infinity sentinels, casefold column collisions and zero columns "
357
+ "are rejected; zoned s/ms must fit finite int64 microseconds; native output "
358
+ "HUGEINT/UHUGEINT and unknown/extended types are rejected before Arrow export",
359
+ ),
360
+ }
361
+ scope, restrictions = details[engine]
362
+ return CapabilityStage(
363
+ stage="native_execution",
364
+ status="supported_with_conditions",
365
+ scope=scope,
366
+ conditions=(
367
+ restrictions,
368
+ "Pandas meaningful index requires an explicit columns/discard decision before "
369
+ "transport to other engines; DuckDB input table identifiers are ASCII names",
370
+ "Every input conversion must preserve row/column counts; a zero-column input "
371
+ "cannot silently become a zero-row table on an installed native version",
372
+ "Callbacks/SQL are trusted code, not a sandbox; budgets/checkpoints are cooperative "
373
+ "and DuckDB memory_limit is not a whole-process RSS cap",
374
+ ),
375
+ losses=(
376
+ _LOST_BEFORE_CAPTURE,
377
+ "Observed output order is captured but SQL without ORDER BY does not guarantee it",
378
+ ),
379
+ implementation=(
380
+ f"equiframe.adapters.{_ENGINE_CLASS[engine]}Adapter",
381
+ "equiframe.adapters._input_capabilities",
382
+ ),
383
+ tests=(
384
+ "tests/test_execution.py::test_five_families_native",
385
+ "tests/test_execution_regressions.py::test_decimal_storage_width_and_negative_scale_capabilities",
386
+ "tests/test_execution.py::test_index_requires_explicit_transport_policy",
387
+ "tests/test_execution_regressions.py::test_zero_column_tables_keep_cardinality_or_reject_explicitly",
388
+ ),
389
+ )
390
+
391
+
392
+ def get_adapter_capabilities() -> tuple[AdapterCapability, ...]:
393
+ """Return four immutable stage-separated capability descriptions.
394
+
395
+ Entries are declarations tied to implementation and repository test nodes,
396
+ not claims that those nodes ran in the caller's environment. The named
397
+ maintainer is responsible for EquiFrame's adapter implementations, not the
398
+ third-party engines. Native runtime capabilities are derived from the real
399
+ adapters without importing optional dependencies.
400
+ """
401
+ captures = {
402
+ "arrow": (
403
+ "Eager pyarrow.Table direct diff capture; IPC/Parquet through explicit artifact inputs",
404
+ "Direct diff rejects semantic schema/field metadata; only actual base Tables; "
405
+ "physical full validation and detached IPC snapshots; row/byte budgets",
406
+ "test_arrow_retains_schema_null_nan_decimal_and_timezone",
407
+ ),
408
+ "pandas": (
409
+ "Eager pandas.DataFrame direct diff capture; native index captured separately",
410
+ "Plain scalar columns and recognized extension dtypes only; attrs, named column "
411
+ "axis, category/order metadata, object timestamps, heterogeneous numeric objects, "
412
+ "signed-zero/nonfinite or mixed-exponent Decimal and subclasses are rejected",
413
+ "test_pandas_decimal_and_zoned_timestamp_capture_is_exact",
414
+ ),
415
+ "polars": (
416
+ "Eager polars.DataFrame direct diff capture; independently detached Arrow buffers",
417
+ "LazyFrame direct diff is rejected without collect; categories, nested/object/int128 "
418
+ "and unknown extension semantics rejected; explicit execution can collect lazy output",
419
+ "test_polars_eager_preserves_null_nan_decimal_and_zones",
420
+ ),
421
+ "duckdb": (
422
+ "Direct diff consumes an explicitly supplied DuckDBPyRelation with bounded "
423
+ "Arrow IPC capture; capture_duckdb executes one trusted SELECT/WITH",
424
+ "Direct relation submission authorizes execution under the caller-owned "
425
+ "plan/session, with native type admission, row/byte budgets and cooperative "
426
+ "cancellation. SQL strings/connections/readers are not inferred as inputs. "
427
+ "Explicit SQL capture additionally requires exclusive borrowed ownership or a "
428
+ "private session and disables "
429
+ "extension autoload/install; finite temporal values are required",
430
+ "test_explicit_duckdb_relation_capture_is_bounded_and_consumed_once",
431
+ ),
432
+ }
433
+ native = {
434
+ "pandas": PandasAdapter().capabilities(),
435
+ "polars": PolarsAdapter().capabilities(),
436
+ "duckdb": DuckDBAdapter().capabilities(),
437
+ }
438
+ records = []
439
+ for engine in ("arrow", "pandas", "polars", "duckdb"):
440
+ scope, restrictions, test = captures[engine]
441
+ capture = CapabilityStage(
442
+ stage="capture",
443
+ status="supported_with_conditions",
444
+ scope=scope,
445
+ conditions=(
446
+ _SCALAR_TYPES,
447
+ restrictions,
448
+ "Every accepted conversion must preserve row/column counts. In particular, "
449
+ "a zero-column table with rows is accepted only if the installed conversion "
450
+ "preserves cardinality; otherwise fail closed rather than compare zero rows",
451
+ ),
452
+ losses=(_LOST_BEFORE_CAPTURE,),
453
+ implementation=(
454
+ "equiframe._diff_capture.capture_native",
455
+ "equiframe.tables.check_arrow",
456
+ )
457
+ + (("equiframe.duckdb_capture.capture_duckdb",) if engine == "duckdb" else ()),
458
+ tests=("tests/test_diff_capture.py::" + test,)
459
+ + (
460
+ ("tests/test_diff_capture.py::test_polars_direct_capture_rejects_lost_cardinality",)
461
+ if engine == "polars"
462
+ else (
463
+ "tests/test_duckdb_capture.py::test_capture_survives_source_change_and_connection_close",
464
+ )
465
+ if engine == "duckdb"
466
+ else ()
467
+ ),
468
+ )
469
+ if engine == "arrow":
470
+ execution = CapabilityStage(
471
+ stage="native_execution",
472
+ status="not_applicable",
473
+ scope="Arrow is a capture/transport input; no built-in Arrow execution adapter",
474
+ conditions=("Supply an explicit trusted EngineAdapter for a new engine",),
475
+ losses=(),
476
+ implementation=("equiframe.adapters.native_adapter",),
477
+ tests=(
478
+ "tests/test_compatibility_register.py::test_arrow_has_no_builtin_execution_or_compiler",
479
+ ),
480
+ )
481
+ migration = CapabilityStage(
482
+ stage="migration",
483
+ status="not_applicable",
484
+ scope="Arrow feeds captured inputs; it is not a built-in migration target",
485
+ conditions=("Only pandas, polars and duckdb compilers are registered by default",),
486
+ losses=(),
487
+ implementation=("equiframe.migration.ports.native_compiler_registry",),
488
+ tests=(
489
+ "tests/test_compatibility_register.py::test_arrow_has_no_builtin_execution_or_compiler",
490
+ ),
491
+ )
492
+ else:
493
+ execution = _native_stage(engine)
494
+ specific = {
495
+ "pandas": "Pandas retains Arrow dtypes/null masks, exact or explicitly rounded "
496
+ "Decimal division, timestamp s/ms/us/ns and explicit DST localization",
497
+ "polars": "Polars IR transports unchanged timestamp int64 ticks in s/ms/us/ns "
498
+ "and declared zones through an explicit codec; native transition-table/POSIX-rule "
499
+ "localization from pinned installed tzdata resources; "
500
+ "exact/rounded native coefficient "
501
+ "Decimal division through precision 38; "
502
+ "Decimal multiply uses checked native coefficients with summed output scales; "
503
+ "native sums "
504
+ "retain a conservative bound and localization declares DST policies",
505
+ "duckdb": "DuckDB IR transports unchanged timestamp int64 ticks in s/ms/us/ns "
506
+ "and declared zones through an explicit codec; native ICU localization with "
507
+ "explicit fold/gap policies; "
508
+ "exact/rounded native HUGEINT Decimal division through precision 38; "
509
+ "explicit DecimalRound supports half_even and "
510
+ "half_away_from_zero; native eager checkpoints materialize relational nodes "
511
+ "and nested arithmetic/rounding/localization before later pruning; "
512
+ "compiled programs require a compatible native checkpoint adapter",
513
+ }
514
+ migration = CapabilityStage(
515
+ stage="migration",
516
+ status="supported_with_conditions",
517
+ scope="Closed-IR native compilation and pairwise execution on identical captures",
518
+ conditions=(*_COMMON_MIGRATION, specific[engine]),
519
+ losses=("No declared loss is silently accepted; unsupported plans fail closed",),
520
+ implementation=(
521
+ "equiframe.migration.validation.validate_ir",
522
+ "equiframe.migration.runner.check_migration",
523
+ f"equiframe.migration.backends.{engine}.{_ENGINE_CLASS[engine]}Compiler",
524
+ ),
525
+ tests=_MIGRATION_TESTS
526
+ + (
527
+ (
528
+ "tests/test_duckdb_checkpoints.py::test_native_guards_survive_later_empty_filter",
529
+ "tests/test_duckdb_checkpoints.py::test_non_native_adapter_is_rejected_before_provider_even_without_timestamps",
530
+ "tests/test_duckdb_checkpoints.py::test_transport_budget_checks_materialized_intermediate_before_empty_output",
531
+ )
532
+ if engine == "duckdb"
533
+ else ()
534
+ ),
535
+ )
536
+ records.append(
537
+ AdapterCapability(
538
+ adapter=engine,
539
+ required_extra=engine,
540
+ maintainer="Hernán Troya (htroya); EquiFrame adapter implementation only",
541
+ maintenance_status="assigned",
542
+ runtime=native.get(engine),
543
+ stages=(capture, _COMPARISON, execution, migration),
544
+ )
545
+ )
546
+ return tuple(records)