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,262 @@
1
+ # A consistent SQLite snapshot through the existing ports
2
+
3
+ [Handbook](index.md) · [Extension ports](extending.md) · [Recipes](recipes.md)
4
+
5
+ **For architects and data engineers:** capture related tables from an existing
6
+ SQLite database once, supply them through `SnapshotClient`, and let the existing
7
+ `SnapshotDataSource` check and record the evidence. No benchmark, new framework,
8
+ remote service, ORM or core dependency is introduced.
9
+
10
+ The wheel and source distribution carry two complete external application files:
11
+
12
+ - `examples/integrations/sqlite_snapshot.py`: the small producer and detached client
13
+ - `examples/integrations/sqlite_snapshot_demo.py`: a synthetic runnable integration
14
+
15
+ They intentionally live outside `equiframe`. The producer is a concrete SQLite
16
+ DB-API example, not a promise that every DB-API database has SQLite's transaction
17
+ semantics. The core snapshot exports remain experimental; existing manifest 1.0,
18
+ fingerprints, imports and benchmark aliases are unchanged.
19
+
20
+ ## Install an identified artifact
21
+
22
+ The producer needs Python's standard-library `sqlite3`, the identified
23
+ EquiFrame wheel and its `arrow` dependencies. The demo also executes a native
24
+ DuckDB join, so it needs the `duckdb` extra. It does not require pandas, Polars,
25
+ scikit-learn, network access, credentials or any real data to run.
26
+
27
+ Use the [verified first-use installation](recipes.md#1-obtain-verify-and-install-the-kit)
28
+ in a fresh supported Python environment outside the checkout. Commands below
29
+ show 3.12; use the minor recorded by your interpreter-specific kit. Its hash-pinned
30
+ `requirements.lock` covers this demo as well as the other recipes; pandas/pytest
31
+ in that shared kit are not new dependencies of this producer. Copy **both**
32
+ integration files from the installed wheel's resources (shown below) or the
33
+ corresponding verified source distribution into a new working directory.
34
+ Use a registry artifact only after verifying its project, version and identity;
35
+ the string `0.12.4` alone does not identify bytes. An offline kit is optional,
36
+ not a separate public-release channel promised by this documentation.
37
+
38
+ For example, after verifying the private kit, use these POSIX commands from
39
+ outside the checkout. `KIT` is the extracted verified first-use kit:
40
+
41
+ ```bash
42
+ python3.12 -m venv sqlite-example-env
43
+ sqlite-example-env/bin/python -m pip install --require-hashes --only-binary=:all: \
44
+ -r "$KIT/requirements.lock"
45
+ sqlite-example-env/bin/python -m pip install --no-index \
46
+ "$KIT/wheels/equiframe-0.12.4-py3-none-any.whl"
47
+ sqlite-example-env/bin/python -m pip check
48
+ mkdir sqlite-example-work
49
+ sqlite-example-env/bin/python - <<'PY'
50
+ from importlib.resources import files
51
+ from pathlib import Path
52
+
53
+ source = files("equiframe").joinpath("examples/integrations")
54
+ for name in ("sqlite_snapshot.py", "sqlite_snapshot_demo.py"):
55
+ Path("sqlite-example-work", name).write_bytes(source.joinpath(name).read_bytes())
56
+ PY
57
+ cd sqlite-example-work
58
+ ../sqlite-example-env/bin/python sqlite_snapshot_demo.py --case good --output good
59
+ ```
60
+
61
+ The first pip command deliberately downloads pinned dependencies from the
62
+ configured index. For offline use, obtain the same reviewed dependency wheels
63
+ and add `--no-index --find-links /absolute/path/wheelhouse`. See the kit guide for
64
+ Windows environment commands; this example does not independently qualify a
65
+ Windows, macOS or ARM64 platform. Run the script normally: unlike single-file
66
+ recipes, these two files require their containing directory on Python's import
67
+ path, which `python -I script.py` deliberately omits.
68
+
69
+ Focused development checks used Python **3.12.14**, SQLite **3.53.1**, PyArrow
70
+ **25.0.1**, DuckDB **1.5.6**, Pydantic **2.13.5**, Typer **0.27.2** and tzdata
71
+ **2026.4** on Linux. Exact transitive versions/hashes are the unchanged candidate
72
+ lock and shared first-use lock. These are observed versions, not minimum support
73
+ claims. Record the final commit and wheel/sdist hashes alongside a new run.
74
+
75
+ ## Three runnable outcomes
76
+
77
+ Every case requires a **new** output directory; reuse fails before changing it.
78
+ All data is synthetic: two accounts and three selected ledger entries. A fourth
79
+ entry belongs to another cohort and is excluded by a bound SQL parameter.
80
+
81
+ ```bash
82
+ python sqlite_snapshot_demo.py --case good --output good-new
83
+ # execution=completed; comparison=equivalent; exit=0
84
+
85
+ python sqlite_snapshot_demo.py --case different --output different-new
86
+ # execution=completed; comparison=different; exit=1
87
+
88
+ python sqlite_snapshot_demo.py --case corrupt --output corrupt-new
89
+ # execution=invalid_input; comparison=not_evaluable; exit=3
90
+ ```
91
+
92
+ The literal expected output is independently authored in `expected_output()`:
93
+ `A / Café / 1.10 / USD`, `B / Café / NULL / USD`, and
94
+ `C / NULL / -0.01 / EUR`. It is never generated by the SQLite query or DuckDB
95
+ join. The discrepancy changes A to `1.11`; currencies stay separate columns and
96
+ no aggregate total hides the record-level difference. The controlled failure
97
+ changes one downloaded byte while preserving the original manifest. The existing
98
+ consumer rejects the SHA-256 mismatch before exposing the table to DuckDB.
99
+
100
+ Each output directory contains:
101
+
102
+ - `synthetic.sqlite`: only the demo's small synthetic source database
103
+ - `execution/<run-id>/`: native execution evidence, pinned manifest, input
104
+ artifacts and, on success, the joined output
105
+ - `comparison/`: complete captured comparison evidence for successful execution
106
+ - `report/`: default-redacted JSON/text/HTML report, written before the assertion
107
+ - `receipt.json`: case, identity/fingerprint, explicit states and exit code
108
+
109
+ Failed execution has no successful output/comparison/report. Its receipt states
110
+ `not_evaluable` as the demo's gate status; it does not fabricate an evaluated
111
+ `ComparisonResult`. Inspect the execution's failure diagnostics. A producer
112
+ exception before execution raises normally and yields no published snapshot
113
+ client; incomplete output folders must not be accepted as successful runs.
114
+
115
+ Acceptance is the process exit code **0** and a successful
116
+ `assert_equivalent(comparison)`, not existence of a report, a completed join or
117
+ truthiness of a result. A matching synthetic expected output establishes this
118
+ fixture's behavior, not independent human review, business correctness in
119
+ production, or scientific evidence.
120
+
121
+ ## Use your own trusted queries
122
+
123
+ The producer keeps the native boundary explicit:
124
+
125
+ ```python
126
+ from pathlib import Path
127
+ import pyarrow as pa
128
+ import equiframe as ef
129
+ from sqlite_snapshot import SQLiteQuery, capture_sqlite_snapshot
130
+
131
+ client = capture_sqlite_snapshot(
132
+ Path("existing.sqlite"),
133
+ snapshot_id="ledger.2026-10-06.001",
134
+ queries=(
135
+ SQLiteQuery(
136
+ name="entries",
137
+ sql="SELECT id, amount FROM entries WHERE cohort = ? ORDER BY id",
138
+ parameters=("reviewed-cohort",),
139
+ schema=pa.schema(
140
+ [
141
+ pa.field("id", pa.string(), nullable=False),
142
+ pa.field("amount", pa.decimal128(10, 2)),
143
+ ]
144
+ ),
145
+ ),
146
+ ),
147
+ domain_schema_revision="ledger-schema.1",
148
+ extraction_revision="reviewed-query.1",
149
+ provenance=("Authorized ledger capture; exact amounts stored as TEXT",),
150
+ )
151
+ source = ef.SnapshotDataSource(
152
+ "ledger.2026-10-06.001",
153
+ client=client,
154
+ expected_domain_schema_revision="ledger-schema.1",
155
+ )
156
+ # Pass source to the existing execute, migrate or run_workflow API.
157
+ ```
158
+
159
+ All related tables must be included in **one** capture call. Two calls open two
160
+ transactions and do not establish cross-call consistency. The producer owns a
161
+ new read-only connection to the existing main database, explicitly begins one
162
+ native SQLite transaction and reads every query inside it. The first native
163
+ read anchors the snapshot. In WAL mode, another connection can commit while
164
+ this reader continues seeing the original table versions; the functional test
165
+ does exactly this between the two SELECTs. In rollback-journal mode writers can
166
+ wait on readers. The producer does not change journal mode or configure writers.
167
+
168
+ After every query succeeds, the producer releases the read transaction with
169
+ rollback and closes the connection before returning detached Arrow IPC bytes.
170
+ The same cleanup runs on SQL, schema, conversion and budget failures. Cursors
171
+ are closed explicitly. It does not borrow, commit or close a caller-owned
172
+ connection. Later database changes or removal do not change the captured client.
173
+ There is no implicit recapture, retry or `latest` resolution.
174
+
175
+ Choose a distinct `snapshot_id` for each publication and meaningful schema/query
176
+ revisions. This process-local example has no global ID registry, persistent
177
+ catalog, lease or restart recovery. Its opaque artifact URIs are identifiers
178
+ under `example.invalid`, never fetched as URLs. Their per-capture namespace keeps
179
+ references distinct, so separate captures can have different fingerprints even
180
+ with identical rows. Fixed identity means the chosen client/manifest stays
181
+ unchanged for a consuming run, not that a human label authenticates a producer.
182
+
183
+ ## Deliberately supported schema
184
+
185
+ The SQL cursor names/order must exactly match the declared Arrow schema.
186
+ SQLite's dynamic value types are checked before Arrow conversion:
187
+
188
+ | Declared Arrow type | Required non-NULL SQLite value | Behavior |
189
+ |---|---|---|
190
+ | `int64` | INTEGER / Python `int` | Exact signed 64-bit value; REAL/text casts are refused |
191
+ | `string` | TEXT / Python `str` | Unicode preserved; no normalization or coercion |
192
+ | `decimal128(p, s)` with `0 <= s <= p` | Plain decimal TEXT | Explicit Decimal parse, exact scale/precision check, no float intermediary |
193
+
194
+ NULL is preserved only for nullable fields. Duplicates, empty tables, column
195
+ order and returned row order are preserved in the capture; the demo comparison
196
+ explicitly uses bag semantics. Declare `ORDER BY` where query order matters.
197
+ Decimal negative zero is refused because Arrow decimal cannot retain its sign.
198
+ Exponents, NaN/Infinity strings, REAL money, overflowing precision and values
199
+ requiring rounding are refused. Do not cast existing REAL money to TEXT and
200
+ assume that this recovers precision already lost in storage.
201
+
202
+ Dates, timestamps/time zones, BLOBs, floats/NaNs, other integer widths, structured
203
+ types, custom SQLite converters and Arrow field/schema metadata are outside this
204
+ small producer's supported subset. They fail explicitly instead of being
205
+ silently coerced. This does not narrow the core's separate
206
+ [semantic support](semantics.md). Adding a new producer type needs its own exact
207
+ mapping, independent expectations and rejection tests.
208
+
209
+ `SnapshotDataSource` then independently checks downloaded size/SHA-256, row
210
+ count, names/order, physical/logical type and declared non-nullability. It saves
211
+ the complete manifest and fingerprint, including when selecting fewer tables.
212
+ The producer's declared provenance/revisions are copied into that manifest;
213
+ they are assertions by the trusted application, not verified origin claims.
214
+
215
+ ## Operational limits and privacy
216
+
217
+ Keep this implementation for small, trusted datasets. Defaults are 10,000 rows
218
+ per table, 256 rows per fetch, 16 MiB total serialized payload and a five-second
219
+ SQLite lock wait. The byte budget is checked **after** Arrow serialization;
220
+ queries, fetches, string/Decimal conversion and Arrow allocation can consume
221
+ resources before checks. Complete detached payloads remain in memory. Lock
222
+ timeout is not query timeout. No hard memory, execution-time, disk, process or
223
+ network quota is provided; no cancellation/streaming service is implemented.
224
+
225
+ The SQLite database, application SQL, injected client and dependencies must be
226
+ trusted. `mode=ro`, SELECT/WITH admission, bound values and hashes do not make
227
+ this a sandbox, authenticate an author or establish a no-egress guarantee. The
228
+ transaction's consistency claim covers the ordinary persistent main database
229
+ tables in these queries; it does not synchronize remote systems, attached
230
+ databases, volatile functions, external virtual tables or filesystem effects.
231
+
232
+ SQL text, parameters and the source database pathname are not included in the
233
+ snapshot manifest by this producer. Query/database exceptions may still expose
234
+ application details to a caller; handle them according to your logging policy.
235
+ Provenance is caller-authored and raw SQLite/Arrow/comparison evidence can contain
236
+ private values. Use authorized private directories and keep synthetic examples
237
+ separate from real data. The report's redaction does not redact or delete raw
238
+ artifacts. This example neither implements José's service nor defines Jhair's
239
+ DW, publication rules or acceptance criteria.
240
+
241
+ ## Run the focused checks
242
+
243
+ From the repository root in the existing locked development environment:
244
+
245
+ ```bash
246
+ uv run --locked --all-extras ruff check examples/integrations tests/test_sqlite_snapshot_example.py
247
+ uv run --locked --all-extras ruff format --check examples/integrations tests/test_sqlite_snapshot_example.py
248
+ uv run --locked --all-extras mypy --python-version 3.12 examples/integrations/sqlite_snapshot.py examples/integrations/sqlite_snapshot_demo.py
249
+ uv run --locked --all-extras pytest -q tests/test_sqlite_snapshot_example.py
250
+ ```
251
+
252
+ The tests cover native concurrent WAL consistency, lifecycle cleanup on success
253
+ and errors, fixed manifest selection/provenance, schema-preserving empty/NULL
254
+ tables, controlled size/hash/schema/row/nullability mismatches, exact conversion
255
+ admission and the copied standalone demo's three exit codes. These are ordinary
256
+ functional checks, not a security assessment, production load test, final
257
+ artifact verification or new platform matrix. Final candidate distribution
258
+ verification remains a separate gate.
259
+
260
+ Existing orchestrators can call the Python API inside a task or run the demo's
261
+ CLI-shaped gate as a subprocess, retain its private evidence, and propagate
262
+ nonzero exits. No scheduler or new orchestration framework is required.
@@ -0,0 +1,49 @@
1
+ # Inputs from files, SQL and API responses
2
+
3
+ `LocalDataSource` accepts existing `FileInput` (CSV/JSON/JSONL), `ArtifactInput`
4
+ (hashed Parquet/Arrow), `ArrowInput` and the new experimental `SQLiteInput` and
5
+ `StreamInput`. Every input exposes `load(context)` and produces a `LoadedTable`.
6
+ Use the same declared columns, null, Decimal and timestamp policies regardless
7
+ of where the data came from. Ingestion retains original bytes and conversion
8
+ receipts; a rejected or quarantined input fails execution.
9
+
10
+ `SQLiteInput(path, query, spec, parameters=(), batch_rows=4096)` reads one trusted
11
+ parameterized SELECT/WITH in an owned read-only transaction. Its `IngestionSpec`
12
+ uses JSONL with direct record mappings. Decimal fields require SQLite TEXT;
13
+ REAL cannot recover exact monetary values. Textual identifiers, duplicate rows
14
+ and NULLs remain visible. BLOB and ambiguous column names are rejected. It does
15
+ not manage a remote warehouse or claim another database provider's isolation.
16
+
17
+ `StreamInput(open_stream, spec, expected_sha256=None, batch_rows=4096)` consumes
18
+ an explicitly injected binary response context manager. The caller implements
19
+ API authentication, transport and timeouts. EquiFrame never opens an implicit
20
+ URL. It closes the response on success or failure, checks byte budgets and an
21
+ optional separately supplied digest, then uses the existing typed ingestion.
22
+ Blocking transport calls remain the caller's responsibility. JSON documents
23
+ still materialize; CSV/JSONL parsing is incremental. Final tables and public
24
+ conversion receipts materialize within cooperative budgets, not a hard RSS cap.
25
+
26
+ `RecordBatchInput(open_batches, schema)` accepts an explicit Arrow batch reader
27
+ context manager. Every batch must match the exact schema, including metadata;
28
+ nonnullable fields cannot contain NULL. Row and Arrow-byte budgets accumulate
29
+ across all batches before constructing the final chunked table. The reader is
30
+ closed after success, schema failure, cancellation or budget exhaustion. The
31
+ final table still materializes; this is not a disk-spilling execution engine.
32
+
33
+ `CompositeDataSource({"logical_name": (provider, "provider_table")})` binds
34
+ independent `DataSource` implementations to the names of one contract. Bindings
35
+ are copied, tables are checked and failures propagate. No plugin is discovered
36
+ or imported implicitly. Providers own their immutable snapshots, authentication
37
+ and type fidelity. Composition does not create an atomic snapshot across
38
+ databases or APIs. `LocalDataSource` also accepts external objects with
39
+ `load(context) -> LoadedTable`, so a new input need not modify core.
40
+
41
+ SQLite additionally checks cancellation/timeouts through its native progress
42
+ handler every 1000 VM instructions, including before the first row is produced.
43
+ Its busy lock wait uses a 5-second SQLite timeout; interruption remains
44
+ cooperative. Stream reads and external batch retrieval remain caller-controlled
45
+ blocking operations and require transport timeouts supplied by that caller.
46
+
47
+ These adapters are library inputs. Untrusted SQL/callbacks are not sandboxed.
48
+ Register external inputs explicitly; document their type, consistency and size
49
+ guarantees before using them as interchangeable sources.
@@ -0,0 +1,107 @@
1
+ # EquiFrame más universal en cinco pasos
2
+
3
+ [Inicio](inicio.md) · [Fuentes](universal-sources.md) · [Extensiones](extending.md)
4
+
5
+ EquiFrame 0.12.4 permite cambiar la entrada conservando un
6
+ contrato y un plan nativo común. El siguiente recorrido utiliza datos sintéticos
7
+ y un esperado literal independiente. No necesita benchmark, servicios remotos,
8
+ credenciales, GitHub Actions ni recursos de pago.
9
+
10
+ 1. **Entradas y contrato común.** CSV, JSON/JSONL, Parquet y Arrow reutilizan los
11
+ adaptadores existentes. `SQLiteInput` añade consultas locales parametrizadas
12
+ de solo lectura. `StreamInput` recibe respuestas binarias de un cliente
13
+ proporcionado por el consumidor. Las columnas, tipos, claves, ausencias,
14
+ monedas, fechas y duplicados siguen siendo decisiones explícitas del contrato.
15
+ 2. **Operaciones nativas.** Se reutilizan filtros, proyecciones, conversiones
16
+ exactas admitidas, agregaciones, ordenamientos y seis tipos de join. El nuevo
17
+ `Window` añade numeración y valores anteriores/siguientes por partición o
18
+ sobre toda la entrada: `row_number`, `lag` y `lead`, con offset declarado.
19
+ Pandas, Polars y DuckDB ejecutan cada uno su implementación nativa.
20
+ 3. **Significado de los datos.** Las ventanas conservan tipos y unidades, valores
21
+ extremos, precisión Decimal, NULL/NaN y zonas horarias admitidas. Los empates
22
+ se rechazan para impedir elecciones arbitrarias. Un tipo incompatible o una
23
+ capacidad no admitida se explica antes de ejecutar; un fallo nunca aprueba.
24
+ 4. **Extensiones y límites.** `CompositeDataSource` vincula proveedores externos
25
+ a nombres lógicos. `RecordBatchInput` verifica esquemas Arrow explícitos y
26
+ límites acumulados. SQLite comprueba cancelación y tiempo durante consultas
27
+ largas. Un nuevo proveedor implementa `table_names` y `resolve`; un nuevo
28
+ input local implementa `load(context)`. No necesita modificar el núcleo.
29
+ 5. **Primer uso completo.** El ejemplo instalado compara seis entradas bajo el
30
+ mismo contrato y plan en los tres motores: 18 ejecuciones por caso. Guarda
31
+ capturas, comprobaciones, reportes y un recibo; distingue coincidencia,
32
+ discrepancia e integridad fallida.
33
+
34
+ ## Instala y ejecuta el ejemplo
35
+
36
+ Usa un wheel identificado por revisión y SHA-256. El código propio tiene
37
+ licencia MIT; los componentes de terceros conservan sus términos. La publicación
38
+ pública sigue pendiente. Desde una carpeta nueva fuera del checkout:
39
+
40
+ ```powershell
41
+ py -3.12 -m venv .venv
42
+ .\.venv\Scripts\python.exe -m pip install 'C:\ruta\equiframe-0.12.4-py3-none-any.whl[engines]'
43
+ .\.venv\Scripts\python.exe -m pip check
44
+ .\.venv\Scripts\python.exe -m equiframe.examples.integrations.universal_first_use --output caso-correcto
45
+ .\.venv\Scripts\python.exe -m equiframe.examples.integrations.universal_first_use --case different --output caso-distinto
46
+ .\.venv\Scripts\python.exe -m equiframe.examples.integrations.universal_first_use --case corrupt --output caso-danado
47
+ ```
48
+
49
+ En Linux/macOS sustituye el intérprete por `python3.12` y el ejecutable del
50
+ entorno por `.venv/bin/python`. Son instrucciones de uso; cada entorno requiere
51
+ su propia ejecución registrada. No presupongas validación nativa de macOS.
52
+
53
+ Los directorios de salida deben ser nuevos. El caso correcto termina con código
54
+ 0 y 18 comparaciones equivalentes. `different` altera únicamente SQLite de
55
+ 12.30 a 12.31: produce discrepancias en los tres motores y termina con código 1.
56
+ `corrupt` declara un digest incorrecto para la respuesta de API: rechaza esa
57
+ entrada en los tres motores, no compara sus resultados y termina con código 3.
58
+ Cada caso conserva un `receipt.json`; los casos evaluados también conservan
59
+ reportes bajo cada combinación de motor y fuente. El cliente API del ejemplo
60
+ es una respuesta en memoria explícitamente sintética.
61
+
62
+ ## Cambia únicamente la configuración de entrada
63
+
64
+ ```python
65
+ source = ef.LocalDataSource({"input": ef.FileInput(csv_path, csv_spec)})
66
+ # La alternativa usa las mismas columnas declaradas con formato JSONL:
67
+ source = ef.LocalDataSource(
68
+ {
69
+ "input": ef.SQLiteInput(
70
+ database,
71
+ "SELECT id, sequence, amount FROM entries WHERE cohort = ?",
72
+ jsonl_spec,
73
+ ("demo",),
74
+ )
75
+ }
76
+ )
77
+ ```
78
+
79
+ El contrato y el plan no cambian. La especificación de lectura sí declara el
80
+ formato, los campos y las conversiones. No se infieren monedas ni se convierten
81
+ SQL REAL a Decimal exacto. Para un API real, pasa un context manager de respuesta
82
+ a `StreamInput`; tu cliente controla autenticación y timeouts. Para otro motor
83
+ SQL, implementa el puerto con tipos e identidad explícitos y comprueba su
84
+ consistencia; este candidato no incluye adaptadores PostgreSQL/MySQL.
85
+
86
+ ## Límites comprobables
87
+
88
+ El IR y las ventanas admitidas forman parte del contrato estable; los proveedores,
89
+ las fuentes extendidas y la orquestación conservan su alcance experimental.
90
+ Las capacidades exactas y las
91
+ restricciones de ventanas están en [migration-ir](migration-ir.md) y en
92
+ `get_adapter_capabilities()`. DuckDB materializa y comprueba la ventana antes de
93
+ filtros o proyecciones posteriores, para que no oculten sus errores. No hay
94
+ frames arbitrarios ni agregaciones de ventana admitidos.
95
+
96
+ Los lotes reducen y controlan la lectura; las tablas finales y los recibos de
97
+ ingestión todavía se materializan. Los límites son cooperativos, no una cuota
98
+ dura de RAM ni procesamiento distribuido. Los bloqueos de transporte requieren
99
+ timeouts del cliente. Combinar proveedores no crea una transacción común entre
100
+ bases o APIs. Los contratos admiten dominios distintos, pero un ejemplo
101
+ sintético no acredita adopción real en salud, finanzas ni producción.
102
+
103
+ Los cinco pasos de implementación no son un porcentaje de Universalidad
104
+ UV01–UV12 ni una declaración de compatibilidad universal al 100 %. Windows,
105
+ Linux y macOS, versiones de Python y arquitecturas requieren recibos del mismo
106
+ candidato. La matriz histórica, licencias, revisión humana y publicación no se
107
+ cierran mediante estas mejoras técnicas.