continuo-python-runtime 0.4.0__py3-none-any.whl → 0.5.0__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.
@@ -18,6 +18,11 @@ Dispatches on ``VALIDATION_OP`` env var (default ``build_from_sql``):
18
18
  that dropped a column the script reads fails the release gate, then the output
19
19
  table is materialized empty from the declared typed columns and the declared
20
20
  physical layout.
21
+ - ``check_binds``: for a dbt test's compiled SQL (``CANDIDATE_SQL_URI``, fetched
22
+ the same way as ``build_from_sql``). EXPLAINs it against the candidate schema
23
+ via the engine adapter and creates nothing — a test whose compiled SQL names
24
+ a column a candidate change dropped or renamed fails the release at its
25
+ source, before any table is built.
21
26
 
22
27
  The engine adapter is discovered from the single installed
23
28
  ``continuo_engine.adapters`` entry point — each runner image installs exactly one.
@@ -108,7 +113,7 @@ def load_candidate_spec() -> dict:
108
113
  return spec
109
114
 
110
115
 
111
- _NODE_OPS = ("build_from_sql", "clone_from_prod", "build_from_columns")
116
+ _NODE_OPS = ("build_from_sql", "clone_from_prod", "build_from_columns", "check_binds")
112
117
  _SCHEMA_OPS = ("ensure_schema", "drop_schema")
113
118
 
114
119
 
@@ -116,10 +121,13 @@ def main() -> None:
116
121
  """Run one validation op end to end; exits non-zero on failure.
117
122
 
118
123
  Node ops (``build_from_sql``/``clone_from_prod``/``build_from_columns``)
119
- materialize one empty node table and require ``TABLE_NAME``. Schema ops
120
- (``ensure_schema``/``drop_schema``) act on the whole candidate schema —
121
- the executor schedules them as one-shot engine-image Jobs to own the
122
- candidate-schema lifecycle without connecting to the warehouse itself —
124
+ materialize one empty node table and require ``TABLE_NAME``. ``check_binds``
125
+ is also a node op and requires ``TABLE_NAME`` (for identity only, in the
126
+ result block's ``unique_id``) but creates nothing: it bind-checks a dbt
127
+ test's compiled SQL against the candidate schema with the engine's EXPLAIN.
128
+ Schema ops (``ensure_schema``/``drop_schema``) act on the whole candidate
129
+ schema — the executor schedules them as one-shot engine-image Jobs to own
130
+ the candidate-schema lifecycle without connecting to the warehouse itself —
123
131
  and take no table.
124
132
  """
125
133
  logging.basicConfig(
@@ -139,7 +147,7 @@ def main() -> None:
139
147
  if op in _NODE_OPS:
140
148
  table = _require("TABLE_NAME")
141
149
  unique_id = _node_id() or f"model.{table}"
142
- if op == "build_from_sql":
150
+ if op in ("build_from_sql", "check_binds"):
143
151
  try:
144
152
  raw_sql = load_candidate_sql()
145
153
  except Exception as exc:
@@ -150,7 +158,7 @@ def main() -> None:
150
158
  if not raw_sql:
151
159
  logger.error(
152
160
  "CANDIDATE_SQL_URI is unset or the object is empty for a "
153
- "build_from_sql node; cannot validate"
161
+ "%s node; cannot validate", op
154
162
  )
155
163
  print(result.result_block("error", "CANDIDATE_SQL_URI is unset or empty",
156
164
  unique_id=unique_id), flush=True)
@@ -238,6 +246,12 @@ def main() -> None:
238
246
  if op == "build_from_sql":
239
247
  assert candidate_sql is not None, "candidate_sql must be set for build_from_sql"
240
248
  adapter.build_empty_from_sql(schema, table, candidate_sql)
249
+ elif op == "check_binds":
250
+ # A dbt test: EXPLAIN its compiled SQL against the candidate
251
+ # schema. A dropped or renamed column fails here; no table is
252
+ # created and no row is read.
253
+ assert candidate_sql is not None, "candidate_sql must be set for check_binds"
254
+ adapter.check_binds(candidate_sql)
241
255
  elif op == "build_from_columns":
242
256
  assert spec is not None, "spec must be set for build_from_columns"
243
257
  if csv_source:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: continuo-python-runtime
3
- Version: 0.4.0
3
+ Version: 0.5.0
4
4
  Summary: Runtime harness, contract tooling, and CI lint for Continuo python nodes.
5
5
  Author: Simone Carolini
6
6
  Maintainer: Simone Carolini
@@ -11,11 +11,11 @@ Classifier: Development Status :: 4 - Beta
11
11
  Classifier: Intended Audience :: Developers
12
12
  Classifier: Programming Language :: Python :: 3.14
13
13
  Requires-Python: >=3.14
14
- Requires-Dist: boto3==1.43.59
15
- Requires-Dist: continuo-engine-contract==0.7.2
16
- Requires-Dist: pyarrow==25.0.0
14
+ Requires-Dist: boto3==1.43.85
15
+ Requires-Dist: continuo-engine-contract==0.7.3
16
+ Requires-Dist: pyarrow==25.0.1
17
17
  Requires-Dist: pyyaml==6.0.3
18
- Requires-Dist: sqlglot==30.15.0
18
+ Requires-Dist: sqlglot==30.17.0
19
19
  Description-Content-Type: text/markdown
20
20
 
21
21
  # Continuo Python Runtime
@@ -32,7 +32,7 @@ and register the release with Continuo.
32
32
 
33
33
  ## What this repo is
34
34
 
35
- Four artifacts come out of this repository:
35
+ Five artifacts come out of this repository:
36
36
 
37
37
  - **The `continuo-python-runtime` PyPI package** — the `continuo-runtime` CLI
38
38
  (`validate` / `merge` / `hash` / `lint` / `run` / `validation-op`) and the
@@ -41,6 +41,13 @@ Four artifacts come out of this repository:
41
41
  - **The `continuo-engine-contract` PyPI package** — the `WarehouseAdapter`
42
42
  port, the contract schema, the shared SQL/type/config guards, and the
43
43
  sentinel result-block format. Adapter authors outside this repo pin it.
44
+ - **The two engine-adapter PyPI packages** (`continuo-postgres-adapter`,
45
+ `continuo-trino-adapter`) — one `WarehouseAdapter` implementation per
46
+ warehouse engine, each published independently under the same tag. A
47
+ domain repo normally never installs these directly (the engine image
48
+ already has the matching one baked in); they exist as standalone PyPI
49
+ packages for the "build your own container" shape (see below) and for
50
+ third-party adapter authors to reference.
44
51
  - **Per-engine base images**, one per warehouse engine
45
52
  (`continuo-python-runtime-postgres`, `continuo-python-runtime-trino`), that
46
53
  domain repos build `FROM`. Each image bakes in the runtime and a single
@@ -50,10 +57,11 @@ Four artifacts come out of this repository:
50
57
  - **`template/`** — a copy-ready domain repo: `Dockerfile`, `contracts/`,
51
58
  `scripts/`, and the `release.yml` CI/CD workflow.
52
59
 
53
- One `vX.Y.Z` git tag releases all of it: `publish-pypi.yml` builds both
54
- distributions into a single `dist/` and publishes them together, and
55
- `images.yml` builds and pushes both engine images multi-arch under the same
56
- tag.
60
+ One `vX.Y.Z` git tag releases all of it: `publish-pypi.yml` builds all four
61
+ PyPI distributions into a single `dist/` and publishes them together, and
62
+ `images.yml` builds and pushes both engine images — each installing its
63
+ matching pinned adapter version from that same release — multi-arch under
64
+ the same tag.
57
65
 
58
66
  ### What this repo owns
59
67
 
@@ -70,22 +78,27 @@ validation-side port, adapter class, entry-point group, or image. One
70
78
  | --- | --- | --- | --- |
71
79
  | `continuo-python-runtime` | `continuo_python_runtime` | this repo (root) | Harness (CLI, `conform()`, `RunContext`, error taxonomy) **and** the validation runner (`continuo-runtime validation-op`). Published to PyPI. |
72
80
  | `continuo-engine-contract` | `continuo_engine_contract` | this repo, `contract/` | The `WarehouseAdapter` port, contract schema, the SQL/type/config guards adapters must run, and the result-block format. Published to PyPI. |
73
- | `continuo-python-runtime-postgres` | `continuo_python_runtime_postgres` | this repo, `adapters/postgres/` | `PostgresAdapter` — one class, both roles. **Not published to PyPI** — built from source into the image. |
74
- | `continuo-python-runtime-trino` | `continuo_python_runtime_trino` | this repo, `adapters/trino/` | `TrinoAdapter` — one class, both roles, for Trino/Iceberg. **Not published to PyPI** — built from source into the image. |
81
+ | `continuo-postgres-adapter` | `continuo_postgres_adapter` | this repo, `adapters/postgres/` | `PostgresAdapter` — one class, both roles. Published to PyPI. |
82
+ | `continuo-trino-adapter` | `continuo_trino_adapter` | this repo, `adapters/trino/` | `TrinoAdapter` — one class, both roles, for Trino/Iceberg. Published to PyPI. |
75
83
 
76
84
  All four are uv workspace members (`[tool.uv.workspace]` in the root
77
85
  `pyproject.toml`), so `uv sync --all-packages --all-groups` at the repo root
78
86
  installs everything for local development.
79
87
 
80
- **Only `continuo-python-runtime` and `continuo-engine-contract` are published
81
- to PyPI.** The two engine adapters are built **from source into the engine
82
- images**: `Dockerfile.postgres` and `Dockerfile.trino` install them out of the
83
- build context, so each image ships exactly one adapter and the runtime
84
- discovers it through the `continuo_engine.adapters` entry-point group at run
85
- time. Nothing installs them from an index — the harness package does not
86
- depend on them, and domain repos get their adapter by building `FROM` a
87
- published base image. They are still built, type-checked, and tested by CI on
88
- every change.
88
+ **All four packages in the table above are published to PyPI**, under the
89
+ same `vX.Y.Z` tag. The two engine images then **install the matching pinned
90
+ adapter version from PyPI** — `Dockerfile.postgres` installs
91
+ `continuo-postgres-adapter==X.Y.Z`, `Dockerfile.trino` installs
92
+ `continuo-trino-adapter==X.Y.Z` — rather than building it from this repo's
93
+ source tree, so each image still ships exactly one adapter and the runtime
94
+ still discovers it through the `continuo_engine.adapters` entry-point group
95
+ at run time. The image **name** (`continuo-python-runtime-<engine>`) and the
96
+ adapter's pip **distribution** name (`continuo-<engine>-adapter`) are two
97
+ different artifacts of the same adapter — same engine, same version, same
98
+ runtime behavior, different packaging; see "Build your own container" below
99
+ for a build shape that installs the pip package directly instead of `FROM`
100
+ the image. All four packages are still built, type-checked, and tested by CI
101
+ on every change.
89
102
 
90
103
  ### The result block is a frozen wire contract
91
104
 
@@ -262,26 +275,46 @@ A domain repo picks its warehouse engine by which base image it builds
262
275
  `FROM`:
263
276
 
264
277
  ```dockerfile
265
- FROM ghcr.io/carolsimone/continuo-python-runtime-postgres:v0.4.0
278
+ FROM ghcr.io/carolsimone/continuo-python-runtime-postgres:v0.5.0
266
279
  # or
267
- FROM ghcr.io/carolsimone/continuo-python-runtime-trino:v0.4.0
280
+ FROM ghcr.io/carolsimone/continuo-python-runtime-trino:v0.5.0
268
281
  ```
269
282
 
270
283
  The engine is part of the image **name**; the tag is the bare version, so
271
284
  Continuo's Helm chart can pin an image as `<name>:vX.Y.Z@sha256:<digest>`.
272
285
 
273
- Each image bakes in exactly one `WarehouseAdapter` for that engine — installed
274
- from this repo's `adapters/postgres/` or `adapters/trino/` package (see the
275
- table above) — registered under the `continuo_engine.adapters` entry-point
276
- group (entry names `postgres` / `trino`). The runtime discovers it via
277
- `discover_adapter()` at run time, so a single image serves every node in the
278
- service and the release-time validation Job for it. The executor injects the
279
- warehouse connection as environment variables (engine-native, e.g.
286
+ Each image bakes in exactly one `WarehouseAdapter` for that engine — the
287
+ pinned PyPI version of the `continuo-postgres-adapter` or
288
+ `continuo-trino-adapter` package built from this repo's `adapters/postgres/`
289
+ or `adapters/trino/` source (see the table above) — registered under the
290
+ `continuo_engine.adapters` entry-point group (entry names `postgres` /
291
+ `trino`). The runtime discovers it via `discover_adapter()` at run time, so a
292
+ single image serves every node in the service and the release-time
293
+ validation Job for it. The executor injects the warehouse connection as
294
+ environment variables (engine-native, e.g.
280
295
  `POSTGRES_HOST`/`POSTGRES_DB`/`POSTGRES_USER`) plus the node-selection
281
296
  environment (`NODE_ID`, `TABLE_NAME`, `TARGET_SCHEMA`, and optionally
282
297
  `CONTRACT_DIR`/`APP_ROOT`) that `continuo-runtime run` reads to dispatch the
283
298
  right node's script.
284
299
 
300
+ ### Build your own container
301
+
302
+ A domain repo does not have to build `FROM` the published engine image.
303
+ `template/` ships two Dockerfiles for the two build shapes (see
304
+ `template/README.md` § "Choosing a base" for the full comparison):
305
+
306
+ - **Shape 1 — `template/Dockerfile`** — `FROM` the published engine image
307
+ (`continuo-python-runtime-<engine>`), as shown above. Simplest; the image
308
+ already has the runtime and adapter installed and pinned.
309
+ - **Shape 2 — `template/Dockerfile.pip`** — your own base image, installing
310
+ `continuo-python-runtime` and one `continuo-<engine>-adapter` from PyPI
311
+ via a hash-locked `requirements.lock` (`template/requirements.lock`). Use
312
+ this when you must control the base image yourself.
313
+
314
+ Both shapes end up running the same runtime and the same adapter version;
315
+ which one you pick only changes who controls the base OS layer underneath
316
+ them.
317
+
285
318
  ## Further reading
286
319
 
287
320
  - `docs/superpowers/specs/2026-07-31-python-runtime-design.md` — this
@@ -19,11 +19,11 @@ continuo_python_runtime/csv_readers/__init__.py,sha256=9TKvjC37C77uRaJQZuO0--zVj
19
19
  continuo_python_runtime/csv_readers/https.py,sha256=tMJ9k5F0b9SAydpon0ZlOznfjvjhF2BBLltyVmvRneE,4998
20
20
  continuo_python_runtime/csv_readers/s3.py,sha256=zmvHs3TxlN2alPKrdaHzPyREmkve2nBiAsHRXXe1mE0,2246
21
21
  continuo_python_runtime/validation/__init__.py,sha256=hz5oGXsoaUoygR0_pBby9PS6cEfrre518EE0HGB58eE,79
22
- continuo_python_runtime/validation/runner.py,sha256=AwYbiFrQubxSm1vfxJJpnp7xfekviUW2anw4LRQQ4Rc,13062
22
+ continuo_python_runtime/validation/runner.py,sha256=7pbksf7MXfTNcdklCOkOXg9qfRiJtyI_dbgbL3a9LWk,14072
23
23
  continuo_python_runtime/validation/s3.py,sha256=q8nRrT9R7L1-M4dSlDnkL7CUJMPmq86MHY_UKYFlEf8,1785
24
- continuo_python_runtime-0.4.0.dist-info/METADATA,sha256=JtdvcXJZtH27JKMGd5m1Rm6wvch7MzzrsvvAawOQkCU,17023
25
- continuo_python_runtime-0.4.0.dist-info/WHEEL,sha256=zOwg4jB6zX2kU910N-cMawjivD6tO8NEWvE12je1bVk,87
26
- continuo_python_runtime-0.4.0.dist-info/entry_points.txt,sha256=ZvIwSm9f9B9ZmZMf6CyF5Zt4QONn8rqaIk-u-B1CF2Y,70
27
- continuo_python_runtime-0.4.0.dist-info/licenses/LICENSE,sha256=hfXfFCk-8gnps4YvHA6bjCNaSYqOrq2gAdqkXYycAQc,11345
28
- continuo_python_runtime-0.4.0.dist-info/licenses/NOTICE,sha256=ykgYyQkAMx3uas90FZHzt0aTca9IgJQCLpZ4d74Pl3U,465
29
- continuo_python_runtime-0.4.0.dist-info/RECORD,,
24
+ continuo_python_runtime-0.5.0.dist-info/METADATA,sha256=vQisnCASgreJNhw4ryPWuyQvo4dmw6mWEMJH3zZxHx4,18811
25
+ continuo_python_runtime-0.5.0.dist-info/WHEEL,sha256=zOwg4jB6zX2kU910N-cMawjivD6tO8NEWvE12je1bVk,87
26
+ continuo_python_runtime-0.5.0.dist-info/entry_points.txt,sha256=ZvIwSm9f9B9ZmZMf6CyF5Zt4QONn8rqaIk-u-B1CF2Y,70
27
+ continuo_python_runtime-0.5.0.dist-info/licenses/LICENSE,sha256=hfXfFCk-8gnps4YvHA6bjCNaSYqOrq2gAdqkXYycAQc,11345
28
+ continuo_python_runtime-0.5.0.dist-info/licenses/NOTICE,sha256=ykgYyQkAMx3uas90FZHzt0aTca9IgJQCLpZ4d74Pl3U,465
29
+ continuo_python_runtime-0.5.0.dist-info/RECORD,,