continuo-python-runtime 0.1.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.
- continuo_python_runtime/__init__.py +1 -0
- continuo_python_runtime/cli.py +142 -0
- continuo_python_runtime/conform.py +220 -0
- continuo_python_runtime/context.py +62 -0
- continuo_python_runtime/contract/__init__.py +1 -0
- continuo_python_runtime/contract/loader.py +363 -0
- continuo_python_runtime/contract/merge.py +80 -0
- continuo_python_runtime/contract/model.py +39 -0
- continuo_python_runtime/contract/paths.py +38 -0
- continuo_python_runtime/errors.py +36 -0
- continuo_python_runtime/harness.py +208 -0
- continuo_python_runtime/hashing.py +34 -0
- continuo_python_runtime/lint.py +295 -0
- continuo_python_runtime/types.py +208 -0
- continuo_python_runtime-0.1.0.dist-info/METADATA +193 -0
- continuo_python_runtime-0.1.0.dist-info/RECORD +18 -0
- continuo_python_runtime-0.1.0.dist-info/WHEEL +4 -0
- continuo_python_runtime-0.1.0.dist-info/entry_points.txt +2 -0
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
"""SQL type parsing and Arrow type mapping.
|
|
2
|
+
|
|
3
|
+
Parses SQL type strings into a canonical representation and provides mapping
|
|
4
|
+
to PyArrow types for schema generation.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
import re
|
|
10
|
+
from dataclasses import dataclass
|
|
11
|
+
|
|
12
|
+
import pyarrow as pa # type: ignore[import-untyped]
|
|
13
|
+
|
|
14
|
+
from continuo_python_runtime.errors import ContractError
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
@dataclass(frozen=True)
|
|
18
|
+
class SqlType:
|
|
19
|
+
"""Canonical SQL type representation.
|
|
20
|
+
|
|
21
|
+
Attributes:
|
|
22
|
+
base: Canonicalized base type name (one of BIGINT, INTEGER,
|
|
23
|
+
DOUBLE_PRECISION, NUMERIC, VARCHAR, CHAR, TEXT, TIMESTAMP,
|
|
24
|
+
DATE, BOOLEAN).
|
|
25
|
+
precision: For NUMERIC types, the total number of digits.
|
|
26
|
+
scale: For NUMERIC types, the number of digits after the decimal.
|
|
27
|
+
length: For VARCHAR and CHAR types, the maximum length in characters.
|
|
28
|
+
"""
|
|
29
|
+
|
|
30
|
+
base: str
|
|
31
|
+
precision: int | None = None
|
|
32
|
+
scale: int | None = None
|
|
33
|
+
length: int | None = None
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def parse_sql_type(raw: str) -> SqlType:
|
|
37
|
+
"""Parse a SQL type string into a canonical SqlType.
|
|
38
|
+
|
|
39
|
+
Args:
|
|
40
|
+
raw: A SQL type string (case-insensitive).
|
|
41
|
+
|
|
42
|
+
Returns:
|
|
43
|
+
A SqlType with canonicalized base and parsed parameters.
|
|
44
|
+
|
|
45
|
+
Raises:
|
|
46
|
+
ContractError: If the type is unsupported or malformed.
|
|
47
|
+
"""
|
|
48
|
+
raw = raw.strip()
|
|
49
|
+
if not raw:
|
|
50
|
+
raise ContractError("type: empty string is not a valid SQL type")
|
|
51
|
+
|
|
52
|
+
# Normalize to uppercase for parsing
|
|
53
|
+
normalized = raw.upper()
|
|
54
|
+
|
|
55
|
+
# Handle DOUBLE PRECISION specially (has a space). Only the authored
|
|
56
|
+
# spelling with a space is part of the contract grammar; the internal
|
|
57
|
+
# canonical spelling (with an underscore) is not accepted as input even
|
|
58
|
+
# though it is the SqlType.base value produced above.
|
|
59
|
+
if normalized == "DOUBLE PRECISION":
|
|
60
|
+
return SqlType("DOUBLE_PRECISION")
|
|
61
|
+
|
|
62
|
+
# Try to match parametrized types: TYPE(args)
|
|
63
|
+
match = re.match(r"^([A-Z_]+)\s*\((.*)\)$", normalized)
|
|
64
|
+
if match:
|
|
65
|
+
base_name = match.group(1).strip()
|
|
66
|
+
params_str = match.group(2).strip()
|
|
67
|
+
|
|
68
|
+
# Alias handling
|
|
69
|
+
if base_name == "INT":
|
|
70
|
+
base_name = "INTEGER"
|
|
71
|
+
elif base_name == "DECIMAL":
|
|
72
|
+
base_name = "NUMERIC"
|
|
73
|
+
|
|
74
|
+
if base_name == "NUMERIC":
|
|
75
|
+
# Parse NUMERIC(precision, scale) - strict format: \d+,\s*\d+ (spaces only after comma)
|
|
76
|
+
numeric_match = re.match(r"^(\d+),\s*(\d+)$", params_str)
|
|
77
|
+
if not numeric_match:
|
|
78
|
+
raise ContractError(
|
|
79
|
+
f"type: NUMERIC requires precision and scale as unsigned integers, "
|
|
80
|
+
f"got ({params_str})"
|
|
81
|
+
)
|
|
82
|
+
precision = int(numeric_match.group(1))
|
|
83
|
+
scale = int(numeric_match.group(2))
|
|
84
|
+
if not (1 <= precision <= 38):
|
|
85
|
+
raise ContractError(
|
|
86
|
+
f"type: NUMERIC precision must be between 1 and 38, got {precision}"
|
|
87
|
+
)
|
|
88
|
+
if not (0 <= scale <= precision):
|
|
89
|
+
raise ContractError(
|
|
90
|
+
f"type: NUMERIC scale must be between 0 and precision ({precision}), "
|
|
91
|
+
f"got {scale}"
|
|
92
|
+
)
|
|
93
|
+
return SqlType("NUMERIC", precision=precision, scale=scale)
|
|
94
|
+
|
|
95
|
+
elif base_name == "VARCHAR":
|
|
96
|
+
# Parse VARCHAR(length) - strict format: \d+ only
|
|
97
|
+
if "," in params_str:
|
|
98
|
+
raise ContractError(
|
|
99
|
+
f"type: VARCHAR takes a single parameter (length), "
|
|
100
|
+
f"got {params_str!r}"
|
|
101
|
+
)
|
|
102
|
+
varchar_match = re.match(r"^(\d+)$", params_str)
|
|
103
|
+
if not varchar_match:
|
|
104
|
+
raise ContractError(
|
|
105
|
+
f"type: VARCHAR length must be an unsigned integer, "
|
|
106
|
+
f"got {params_str!r}"
|
|
107
|
+
)
|
|
108
|
+
length = int(varchar_match.group(1))
|
|
109
|
+
return SqlType("VARCHAR", length=length)
|
|
110
|
+
|
|
111
|
+
elif base_name == "CHAR":
|
|
112
|
+
# Parse CHAR(length) - strict format: \d+ only
|
|
113
|
+
if "," in params_str:
|
|
114
|
+
raise ContractError(
|
|
115
|
+
f"type: CHAR takes a single parameter (length), "
|
|
116
|
+
f"got {params_str!r}"
|
|
117
|
+
)
|
|
118
|
+
char_match = re.match(r"^(\d+)$", params_str)
|
|
119
|
+
if not char_match:
|
|
120
|
+
raise ContractError(
|
|
121
|
+
f"type: CHAR length must be an unsigned integer, "
|
|
122
|
+
f"got {params_str!r}"
|
|
123
|
+
)
|
|
124
|
+
length = int(char_match.group(1))
|
|
125
|
+
return SqlType("CHAR", length=length)
|
|
126
|
+
|
|
127
|
+
else:
|
|
128
|
+
# No other types accept parameters
|
|
129
|
+
raise ContractError(
|
|
130
|
+
f"type: {base_name} does not accept parameters, got ({params_str})"
|
|
131
|
+
)
|
|
132
|
+
|
|
133
|
+
# No parameters - must be a bare base type
|
|
134
|
+
normalized_base = normalized
|
|
135
|
+
|
|
136
|
+
# Alias handling
|
|
137
|
+
if normalized_base == "INT":
|
|
138
|
+
normalized_base = "INTEGER"
|
|
139
|
+
elif normalized_base == "DECIMAL":
|
|
140
|
+
normalized_base = "NUMERIC"
|
|
141
|
+
|
|
142
|
+
# List of supported bare types
|
|
143
|
+
# Note: "DOUBLE_PRECISION" (underscored) is deliberately excluded here.
|
|
144
|
+
# It is the canonical SqlType.base value, but only the authored spelling
|
|
145
|
+
# "DOUBLE PRECISION" (with a space) is part of the contract grammar and
|
|
146
|
+
# is handled above.
|
|
147
|
+
supported_bare = {
|
|
148
|
+
"BIGINT",
|
|
149
|
+
"INTEGER",
|
|
150
|
+
"TEXT",
|
|
151
|
+
"TIMESTAMP",
|
|
152
|
+
"DATE",
|
|
153
|
+
"BOOLEAN",
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
if normalized_base in supported_bare:
|
|
157
|
+
return SqlType(normalized_base)
|
|
158
|
+
|
|
159
|
+
# Bare VARCHAR and CHAR require a length parameter
|
|
160
|
+
if normalized_base == "VARCHAR":
|
|
161
|
+
raise ContractError(f"type: VARCHAR requires a length parameter, got {raw!r}")
|
|
162
|
+
if normalized_base == "CHAR":
|
|
163
|
+
raise ContractError(f"type: CHAR requires a length parameter, got {raw!r}")
|
|
164
|
+
|
|
165
|
+
# Bare NUMERIC and DECIMAL require parameters
|
|
166
|
+
if normalized_base == "NUMERIC":
|
|
167
|
+
raise ContractError(f"type: NUMERIC requires precision and scale parameters, got {raw!r}")
|
|
168
|
+
|
|
169
|
+
# Unknown type
|
|
170
|
+
raise ContractError(f"type: unknown SQL type {raw!r}")
|
|
171
|
+
|
|
172
|
+
|
|
173
|
+
def arrow_type(t: SqlType) -> pa.DataType:
|
|
174
|
+
"""Map a SqlType to a PyArrow DataType.
|
|
175
|
+
|
|
176
|
+
Args:
|
|
177
|
+
t: A SqlType instance.
|
|
178
|
+
|
|
179
|
+
Returns:
|
|
180
|
+
A PyArrow DataType corresponding to the SQL type.
|
|
181
|
+
|
|
182
|
+
Raises:
|
|
183
|
+
ValueError: If the SqlType is invalid or incomplete.
|
|
184
|
+
"""
|
|
185
|
+
if t.base == "BIGINT":
|
|
186
|
+
return pa.int64()
|
|
187
|
+
elif t.base == "INTEGER":
|
|
188
|
+
return pa.int32()
|
|
189
|
+
elif t.base == "DOUBLE_PRECISION":
|
|
190
|
+
return pa.float64()
|
|
191
|
+
elif t.base == "NUMERIC":
|
|
192
|
+
if t.precision is None or t.scale is None:
|
|
193
|
+
raise ValueError(f"NUMERIC requires precision and scale, got {t}")
|
|
194
|
+
return pa.decimal128(t.precision, t.scale)
|
|
195
|
+
elif t.base == "VARCHAR":
|
|
196
|
+
return pa.string()
|
|
197
|
+
elif t.base == "CHAR":
|
|
198
|
+
return pa.string()
|
|
199
|
+
elif t.base == "TEXT":
|
|
200
|
+
return pa.string()
|
|
201
|
+
elif t.base == "TIMESTAMP":
|
|
202
|
+
return pa.timestamp("us")
|
|
203
|
+
elif t.base == "DATE":
|
|
204
|
+
return pa.date32()
|
|
205
|
+
elif t.base == "BOOLEAN":
|
|
206
|
+
return pa.bool_()
|
|
207
|
+
else:
|
|
208
|
+
raise ValueError(f"unknown SQL type base: {t.base}")
|
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: continuo-python-runtime
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Runtime harness, contract tooling, and CI lint for Continuo python nodes.
|
|
5
|
+
Author: Simone Carolini
|
|
6
|
+
Maintainer: Simone Carolini
|
|
7
|
+
Classifier: Development Status :: 4 - Beta
|
|
8
|
+
Classifier: Intended Audience :: Developers
|
|
9
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
10
|
+
Requires-Python: >=3.14
|
|
11
|
+
Requires-Dist: continuo-validation-contract==0.3.0
|
|
12
|
+
Requires-Dist: pyarrow==25.0.0
|
|
13
|
+
Requires-Dist: pyyaml==6.0.3
|
|
14
|
+
Description-Content-Type: text/markdown
|
|
15
|
+
|
|
16
|
+
# Continuo Python Runtime
|
|
17
|
+
|
|
18
|
+
Runtime harness, contract tooling, and CI lint for Continuo python nodes.
|
|
19
|
+
This repo is what domain data teams (marketing, finance, …) template from to
|
|
20
|
+
ship a python node into Continuo: write a contract + a `run(ctx)` script,
|
|
21
|
+
push to `main`, and CI does the rest — lint, validate, merge, build, publish,
|
|
22
|
+
and register the release with Continuo.
|
|
23
|
+
|
|
24
|
+
## What this repo is
|
|
25
|
+
|
|
26
|
+
Three artifacts come out of this repository:
|
|
27
|
+
|
|
28
|
+
- **The `continuo-python-runtime` PyPI package** — the `continuo-runtime` CLI
|
|
29
|
+
(`validate` / `merge` / `hash` / `lint` / `run`) and the harness library
|
|
30
|
+
(`conform()`, `RunContext`, the error taxonomy) that domain repos install.
|
|
31
|
+
- **Per-engine base images**, one per warehouse engine (`...-postgres`,
|
|
32
|
+
`...-trino`), that domain repos build `FROM`. Each image bakes in the
|
|
33
|
+
runtime, a single `RuntimeAdapter` for that engine, and the
|
|
34
|
+
`continuo-runtime run` entrypoint.
|
|
35
|
+
- **`template/`** — a copy-ready domain repo: `Dockerfile`, `contracts/`,
|
|
36
|
+
`scripts/`, and the `release.yml` CI/CD workflow.
|
|
37
|
+
|
|
38
|
+
Base images and the PyPI publication of this package land with this repo's
|
|
39
|
+
PR 9 (the image pipeline); until then, install the runtime from git as noted
|
|
40
|
+
in `template/.github/workflows/release.yml`.
|
|
41
|
+
|
|
42
|
+
### Package map
|
|
43
|
+
|
|
44
|
+
Each per-engine base image bakes together three PyPI packages: the harness,
|
|
45
|
+
the engine adapter, and the published contract. The harness and both engine
|
|
46
|
+
adapters live in *this* repo (`continuo-python-runtime`) as a uv workspace;
|
|
47
|
+
`continuo-validation-runners` owns the *validation-side* (offline lint/merge)
|
|
48
|
+
counterparts of the same engines, which is a separate concern from the
|
|
49
|
+
data-plane adapters here.
|
|
50
|
+
|
|
51
|
+
| Package (distribution name) | Module | Lives in | Role |
|
|
52
|
+
| --- | --- | --- | --- |
|
|
53
|
+
| `continuo-python-runtime` | `continuo_python_runtime` | this repo (root) | Harness: CLI, `conform()`, `RunContext`, error taxonomy. |
|
|
54
|
+
| `continuo-python-runtime-postgres` | `continuo_python_runtime_postgres` | this repo, `python-runtime-postgres/` | Data-plane `RuntimeAdapter` for Postgres (`fetch`/`ensure_table`/`load`). |
|
|
55
|
+
| `continuo-python-runtime-trino` | `continuo_python_runtime_trino` | this repo, `python-runtime-trino/` | Data-plane `RuntimeAdapter` for Trino/Iceberg. |
|
|
56
|
+
| `continuo-validation-contract` | `continuo_validation_contract` | `continuo-validation-runners` | The published contract (schema, `RuntimeAdapter` port, result-block format) both sides depend on. |
|
|
57
|
+
| `continuo-validation-postgres` / `continuo-validation-trino` | `continuo_validation_postgres` / `continuo_validation_trino` | `continuo-validation-runners` | Validation-side (lint/merge, no live warehouse I/O) adapters — not to be confused with the data-plane adapters above. |
|
|
58
|
+
|
|
59
|
+
All three packages built in this repo resolve `continuo-validation-contract`
|
|
60
|
+
from PyPI (`==0.3.0`); the two adapter packages are uv workspace members
|
|
61
|
+
(`[tool.uv.workspace]` in the root `pyproject.toml`), so `uv sync
|
|
62
|
+
--all-packages --all-groups` at the repo root installs everything for local
|
|
63
|
+
development.
|
|
64
|
+
|
|
65
|
+
## Quickstart for domain teams
|
|
66
|
+
|
|
67
|
+
1. Copy `template/` into a new repository.
|
|
68
|
+
2. Edit `template/.github/workflows/release.yml` and set `SERVICE` to your
|
|
69
|
+
service name (one service name per domain repo).
|
|
70
|
+
3. Configure repository variables in GitHub (Settings → Secrets and
|
|
71
|
+
variables → Actions): `REGISTRY` (your Docker registry), `BUCKET` (your
|
|
72
|
+
S3 bucket for contract artifacts), `RELEASE_ENDPOINT` (the release
|
|
73
|
+
webhook endpoint). `RELEASE_ENDPOINT` is the **base URL** of the Continuo
|
|
74
|
+
API (no `/releases` suffix) — the workflow appends `/releases` itself.
|
|
75
|
+
4. Configure repository secrets: `AWS_ACCESS_KEY_ID` and
|
|
76
|
+
`AWS_SECRET_ACCESS_KEY` for the S3 upload. The template workflow pushes
|
|
77
|
+
the built image to GHCR using the workflow's own `GITHUB_TOKEN` (granted
|
|
78
|
+
`packages: write`) — no registry secret is needed for that. If you point
|
|
79
|
+
`REGISTRY` at a different or private registry, add your own `docker
|
|
80
|
+
login` step to `release.yml`.
|
|
81
|
+
5. Write a contract file under `contracts/` (see
|
|
82
|
+
`template/contracts/example.yml`) and a script under `scripts/` that
|
|
83
|
+
implements `run(ctx)` (see `template/scripts/example.py`).
|
|
84
|
+
6. Push to `main`. The `release.yml` workflow lints the scripts, validates
|
|
85
|
+
and merges the contracts, builds and pushes the image, uploads the merged
|
|
86
|
+
contract to S3, and POSTs the release.
|
|
87
|
+
|
|
88
|
+
## The script API
|
|
89
|
+
|
|
90
|
+
A node script is a Python file with exactly one required entry point:
|
|
91
|
+
|
|
92
|
+
```python
|
|
93
|
+
def run(ctx):
|
|
94
|
+
...
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
- `ctx` is a `RunContext` (`continuo_python_runtime.context.RunContext`).
|
|
98
|
+
Its only method is `ctx.read(name)`, where `name` is one of the read
|
|
99
|
+
names declared under the node's `reads:` map in the contract — reading
|
|
100
|
+
anything else raises `ReadError`. Each declared read is fetched once and
|
|
101
|
+
memoized; `ctx.read(name)` returns a `pyarrow.Table`.
|
|
102
|
+
- `run(ctx)` can return anything Arrow-convertible: a `pyarrow.Table`
|
|
103
|
+
as-is, a pandas `DataFrame` (converted via
|
|
104
|
+
`pa.Table.from_pandas(..., preserve_index=False)`), or any object
|
|
105
|
+
implementing the Arrow C stream protocol (`__arrow_c_stream__`) — for
|
|
106
|
+
example a polars DataFrame. Returning anything else raises `ScriptError`.
|
|
107
|
+
- The dataframe library is your choice. No dataframe library is baked into
|
|
108
|
+
the base image — the package's own runtime dependencies are `pyarrow`,
|
|
109
|
+
`PyYAML`, and `continuo-validation-contract`. Add whatever you script
|
|
110
|
+
against (pandas, polars, …) as a `RUN pip install` line in your own
|
|
111
|
+
`Dockerfile`, on top of the base image.
|
|
112
|
+
- Scripts do not import warehouse drivers, write raw SQL literals, or call
|
|
113
|
+
data-access methods directly — `continuo-runtime lint` rejects those:
|
|
114
|
+
- forbidden driver imports (`psycopg2`/`sqlalchemy`/`trino`/etc.),
|
|
115
|
+
- SQL string literals (in plain strings, f-strings, and `+` concatenation),
|
|
116
|
+
- forbidden data-access calls (`execute`/`read_sql`/etc.), including ones
|
|
117
|
+
reached via a `from ... import` alias (e.g. `from pandas import
|
|
118
|
+
read_sql as rs` then calling `rs(...)`),
|
|
119
|
+
- private/protected attribute access (`obj._x`) — except on `self`/`cls`,
|
|
120
|
+
so a script's own class-private helpers (`self._helper()`) aren't
|
|
121
|
+
flagged.
|
|
122
|
+
|
|
123
|
+
The SQL-literal rule is best-effort: docstrings are exempt, but other
|
|
124
|
+
prose may still occasionally match. The hard guarantees enforced by lint
|
|
125
|
+
are the driver-import and data-access-call rules, together with the fact
|
|
126
|
+
that `RunContext` only exposes `ctx.read()` — all warehouse access goes
|
|
127
|
+
through it.
|
|
128
|
+
- The harness — not the script — performs the write. It calls `conform()`
|
|
129
|
+
on whatever `run()` returned and issues the only INSERT; the script never
|
|
130
|
+
writes directly.
|
|
131
|
+
|
|
132
|
+
## Conform rules
|
|
133
|
+
|
|
134
|
+
`conform()` (`continuo_python_runtime/conform.py`) enforces the node's
|
|
135
|
+
declared `output_columns` on the table `run()` returned, in this order:
|
|
136
|
+
|
|
137
|
+
| Check | Behavior |
|
|
138
|
+
| --- | --- |
|
|
139
|
+
| Duplicate columns | Any duplicate column name in the returned table always raises `ConformError`. |
|
|
140
|
+
| Extra columns | Governed by the node's `extra_columns` policy: `raise` (default) fails the run; `warn` drops the undeclared column(s) and logs a warning. |
|
|
141
|
+
| Missing columns | Any declared column absent from the returned table always raises `ConformError`. |
|
|
142
|
+
| Column order | The table is reselected into the declared column order. |
|
|
143
|
+
| Strict cast | Each column is cast to its declared Arrow type with `safe=True`. Casts pyarrow's `safe=True` would silently accept but that are not value-lossless are rejected before the cast even runs: floating → decimal (rounds to scale), non-boolean → boolean (coerces truthiness), timestamp → date (drops time-of-day). Any other cast failure also raises `ConformError`. |
|
|
144
|
+
| Not-null | A column declared `nullable: false` that contains any null raises `ConformError`. |
|
|
145
|
+
| VARCHAR/CHAR length | A column declared `VARCHAR(n)`/`CHAR(n)` whose longest value exceeds `n` raises `ConformError`. |
|
|
146
|
+
|
|
147
|
+
## Error taxonomy
|
|
148
|
+
|
|
149
|
+
Every runtime failure is one of five `HarnessError` subclasses
|
|
150
|
+
(`continuo_python_runtime/errors.py`). The sentinel result block's message
|
|
151
|
+
is prefixed `<ErrorClass>: ` so failures can be triaged without parsing free
|
|
152
|
+
text.
|
|
153
|
+
|
|
154
|
+
| Class | Meaning | Typical fix target |
|
|
155
|
+
| --- | --- | --- |
|
|
156
|
+
| `ContractError` | Contract missing or invalid, node not found for `NODE_ID`, or the declared script is missing/unreachable. | The contract yaml or the `script:` path. |
|
|
157
|
+
| `ReadError` | `ctx.read()` was called with an undeclared name, or a declared read failed at the warehouse. | The `reads:` map, or the upstream query/warehouse access. |
|
|
158
|
+
| `ScriptError` | `run()` raised, has no callable `run`, or returned a value that isn't Arrow-convertible. | The node script. |
|
|
159
|
+
| `ConformError` | Structural mismatch (extra/missing/duplicate columns), a strict-cast failure, a not-null violation, or a VARCHAR/CHAR overflow. | The script's output shape, or the `output_columns` declaration. |
|
|
160
|
+
| `LoadError` | Adapter construction failed, or the DDL/INSERT failed at the warehouse during the write. | Warehouse connectivity/permissions, or the target table. |
|
|
161
|
+
|
|
162
|
+
## Engine selection
|
|
163
|
+
|
|
164
|
+
A domain repo picks its warehouse engine by which base image it builds
|
|
165
|
+
`FROM`:
|
|
166
|
+
|
|
167
|
+
```dockerfile
|
|
168
|
+
FROM ghcr.io/carolsimone/continuo-python-runtime:v0.1.0-postgres
|
|
169
|
+
# or
|
|
170
|
+
FROM ghcr.io/carolsimone/continuo-python-runtime:v0.1.0-trino
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
Each image bakes in exactly one `RuntimeAdapter` for that engine — installed
|
|
174
|
+
from this repo's `python-runtime-postgres/` or `python-runtime-trino/`
|
|
175
|
+
package (see the package map above; both adapters live in this repo, not
|
|
176
|
+
`continuo-validation-runners`) — registered under the
|
|
177
|
+
`continuo_runtime.adapters` entry-point group (entry names `postgres` /
|
|
178
|
+
`trino`). The harness discovers it via `discover_runtime_adapter()` at run
|
|
179
|
+
time, so a single image serves every node in the service. The executor
|
|
180
|
+
injects the
|
|
181
|
+
warehouse connection as environment variables (engine-native, e.g.
|
|
182
|
+
`POSTGRES_HOST`/`POSTGRES_DB`/`POSTGRES_USER`) plus the node-selection
|
|
183
|
+
environment (`NODE_ID`, `TABLE_NAME`, `TARGET_SCHEMA`, and optionally
|
|
184
|
+
`CONTRACT_DIR`/`APP_ROOT`) that `continuo-runtime run` reads to dispatch the
|
|
185
|
+
right node's script.
|
|
186
|
+
|
|
187
|
+
## Further reading
|
|
188
|
+
|
|
189
|
+
- `docs/superpowers/specs/2026-07-31-python-runtime-design.md` — this
|
|
190
|
+
repo's design.
|
|
191
|
+
- `docs/boundary-contract.md` — the parent design's boundary contract (§13):
|
|
192
|
+
the five surfaces (S3 artifact, `content_hash`, the release call, the
|
|
193
|
+
runtime image, and the domain repo's CI/CD) that this repo implements.
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
continuo_python_runtime/__init__.py,sha256=otNaXEPsTtRXJV5FLtDUsFO8VD4OC-MLAzwhtimHfOE,70
|
|
2
|
+
continuo_python_runtime/cli.py,sha256=Qki4Mfoab74D0DvguZd5yzSfI-FRQDmtvwC0OacxrAc,4860
|
|
3
|
+
continuo_python_runtime/conform.py,sha256=a9CKCA-gT9Zerzr6c5H8aByTFOUd6uBMo02sNKSDnXg,9199
|
|
4
|
+
continuo_python_runtime/context.py,sha256=ZyeN_dA2DihQbegBiiqHj1_EKk79GsxS-tATPVymUPg,1856
|
|
5
|
+
continuo_python_runtime/errors.py,sha256=nfZzfBhQ2ajm7USAWKKaGY2uUlh8eNjNPNYYuaOJgZE,1032
|
|
6
|
+
continuo_python_runtime/harness.py,sha256=YcWy4ummKY3HQYQ4JQ3-UI5jxDgYwJDQlWT88_GRPH4,7504
|
|
7
|
+
continuo_python_runtime/hashing.py,sha256=6D4MntcjaHTEC55IQBBX2Pq9K59D0TWlxTSXGJ5Jsro,1159
|
|
8
|
+
continuo_python_runtime/lint.py,sha256=HgB0BAuiQuS3DgT6BsOoB2M5A-_OKcFsGbaTWhuM7XU,11897
|
|
9
|
+
continuo_python_runtime/types.py,sha256=z1qnBbbaP6nFN2mmRySXM4ZAesv5N6_4E9vZ4kv74HU,7138
|
|
10
|
+
continuo_python_runtime/contract/__init__.py,sha256=S79x6j_P0CmaSuCGzeWoK1U0507KvD2MZXwQl9DFWMM,37
|
|
11
|
+
continuo_python_runtime/contract/loader.py,sha256=wlxI6XZIoUZ4z9rDe1G8s26jbL0tGXnAHPGajTAM0ag,12828
|
|
12
|
+
continuo_python_runtime/contract/merge.py,sha256=WFde9lzrojlMNjuEU7YW8FWOs6VPSrWPY2_Sri0ljZE,2567
|
|
13
|
+
continuo_python_runtime/contract/model.py,sha256=6luq3SJcgshKoG0QsQhJPZ5fzLa8AcGAev9D4jiGgxA,849
|
|
14
|
+
continuo_python_runtime/contract/paths.py,sha256=SRn70pJkDAIpXw0IcMU2iNKPKvct8w_SZP2ec-lV91M,1458
|
|
15
|
+
continuo_python_runtime-0.1.0.dist-info/METADATA,sha256=YQWMZaYiS3gpRnIutzgKhT3KeB5BtUpeMrwi0qKLRUU,10953
|
|
16
|
+
continuo_python_runtime-0.1.0.dist-info/WHEEL,sha256=lCkmxWfQsSc9CfIClYeavTdQeEX2toPqufh9gI35EQA,87
|
|
17
|
+
continuo_python_runtime-0.1.0.dist-info/entry_points.txt,sha256=ZvIwSm9f9B9ZmZMf6CyF5Zt4QONn8rqaIk-u-B1CF2Y,70
|
|
18
|
+
continuo_python_runtime-0.1.0.dist-info/RECORD,,
|