continuo-python-runtime 0.2.0__py3-none-any.whl → 0.3.1__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.
@@ -73,6 +73,13 @@ def main(argv: list[str] | None = None) -> int:
73
73
  "run", help="Execute a node script (container entrypoint)"
74
74
  )
75
75
 
76
+ # validation-op subcommand
77
+ subparsers.add_parser(
78
+ "validation-op",
79
+ help="Execute one blue/green validation operation (container command; "
80
+ "driven by VALIDATION_OP and the engine Secret env)",
81
+ )
82
+
76
83
  args = parser.parse_args(argv)
77
84
 
78
85
  try:
@@ -88,6 +95,10 @@ def main(argv: list[str] | None = None) -> int:
88
95
  return cmd_lint(args.path)
89
96
  elif args.command == "run":
90
97
  return cmd_run()
98
+ elif args.command == "validation-op":
99
+ from continuo_python_runtime.validation.runner import main as validation_op_main
100
+ validation_op_main()
101
+ return 0
91
102
  except HarnessError as exc:
92
103
  logger.error("%s", exc)
93
104
  return 1
@@ -10,13 +10,13 @@ from continuo_python_runtime.contract.model import Node
10
10
  from continuo_python_runtime.errors import ReadError
11
11
 
12
12
  if TYPE_CHECKING:
13
- from continuo_validation_contract.port import RuntimeAdapter # type: ignore[import-untyped]
13
+ from continuo_engine_contract.port import WarehouseAdapter # type: ignore[import-untyped]
14
14
 
15
15
 
16
16
  class RunContext:
17
17
  """Manages declared read access for a node execution."""
18
18
 
19
- def __init__(self, node: Node, adapter: "RuntimeAdapter") -> None:
19
+ def __init__(self, node: Node, adapter: "WarehouseAdapter") -> None:
20
20
  """Initialize context with a node and adapter.
21
21
 
22
22
  Args:
@@ -11,7 +11,7 @@ from pathlib import Path
11
11
  from typing import Any
12
12
 
13
13
  import yaml
14
- from continuo_validation_contract.sql import ensure_single_read # type: ignore[import-untyped]
14
+ from continuo_engine_contract.sql import ensure_single_read # type: ignore[import-untyped]
15
15
  from sqlglot import Dialect
16
16
  from sqlglot.errors import TokenError
17
17
 
@@ -149,7 +149,7 @@ def parse_node(
149
149
  ``source`` is the originating filename; it appears in every error message.
150
150
  ``dialect`` is a sqlglot dialect name (e.g. ``"postgres"``, ``"trino"``)
151
151
  each declared read is checked against via
152
- :func:`~continuo_validation_contract.sql.ensure_single_read`; ``None``
152
+ :func:`~continuo_engine_contract.sql.ensure_single_read`; ``None``
153
153
  (the default) uses sqlglot's dialect-neutral parser.
154
154
 
155
155
  ``check_reads=False`` skips *only* that :func:`ensure_single_read` call —
@@ -306,7 +306,7 @@ def load_contract_dir(
306
306
  """Load and validate every `*.yml`/`*.yaml` contract file under ``path``.
307
307
 
308
308
  ``check_reads=False`` skips the per-read
309
- :func:`~continuo_validation_contract.sql.ensure_single_read` gate and
309
+ :func:`~continuo_engine_contract.sql.ensure_single_read` gate and
310
310
  nothing else; every other rule in :func:`parse_node` and every rule here
311
311
  (dialect validity, document shape, duplicate relations, "no contract
312
312
  files found") still runs. The runtime
@@ -21,10 +21,10 @@ from pathlib import Path
21
21
  from types import ModuleType
22
22
  from typing import Any
23
23
 
24
- from continuo_validation_contract.port import ( # type: ignore[import-untyped]
25
- discover_runtime_adapter,
24
+ from continuo_engine_contract.port import ( # type: ignore[import-untyped]
25
+ discover_adapter,
26
26
  )
27
- from continuo_validation_contract.result import result_block # type: ignore[import-untyped]
27
+ from continuo_engine_contract.result import result_block # type: ignore[import-untyped]
28
28
 
29
29
  from continuo_python_runtime.conform import conform, to_arrow
30
30
  from continuo_python_runtime.context import RunContext
@@ -141,13 +141,13 @@ def load_script(node: Node, repo_root: Path) -> ModuleType:
141
141
 
142
142
 
143
143
  def build_adapter() -> Any:
144
- """Discover and construct the single installed runtime adapter.
144
+ """Discover and construct the single installed warehouse adapter.
145
145
 
146
146
  Raises:
147
147
  LoadError: If any of the adapter's ``required_env()`` vars are unset
148
148
  or empty in ``os.environ``.
149
149
  """
150
- _, cls = discover_runtime_adapter()
150
+ _, cls = discover_adapter()
151
151
  required = cls.required_env()
152
152
  missing = [key for key in required if not os.environ.get(key)]
153
153
  if missing:
@@ -178,13 +178,12 @@ def _validate_config_early(adapter: Any, node: Node) -> None:
178
178
  but it is called *after* the script has executed and its result has been
179
179
  conformed, so a single typo (``config: {index: [...]}``) burned the whole
180
180
  node run before failing. This is the tripwire that fails it in the first
181
- second instead. Validation-time checking of the same vocabulary is a future
182
- cross-repo dependency (continuo-validation step 3a), and `continuo-runtime
183
- validate` runs on a CI runner where no engine adapter is installed at all,
184
- so this is the earliest point the engine's own rules can be applied.
181
+ second instead. `continuo-runtime validate` cannot do it earlier still: it
182
+ runs on a domain repo's CI runner, where no engine adapter is installed at
183
+ all, so this is the earliest point the engine's own rules can be applied.
185
184
 
186
185
  The call is skipped for an adapter that does not provide ``validate_config``:
187
- the abstract ``RuntimeAdapter`` in the pinned ``continuo-validation-contract``
186
+ the abstract ``WarehouseAdapter`` in ``continuo-engine-contract``
188
187
  does not declare it (this repo ships the harness and both adapters as one
189
188
  coordinated release), and an adapter without it loses only earliness —
190
189
  ``ensure_table`` still fails closed on the same config.
@@ -10,7 +10,7 @@ import re
10
10
  from dataclasses import dataclass
11
11
 
12
12
  import pyarrow as pa # type: ignore[import-untyped]
13
- from continuo_validation_contract.types import validate_column_type # type: ignore[import-untyped]
13
+ from continuo_engine_contract.types import validate_column_type # type: ignore[import-untyped]
14
14
 
15
15
  from continuo_python_runtime.errors import ContractError
16
16
 
@@ -37,7 +37,7 @@ class SqlType:
37
37
  def parse_sql_type(raw: str) -> SqlType:
38
38
  """Parse a SQL type string into a canonical SqlType.
39
39
 
40
- ``continuo_validation_contract.types.validate_column_type`` is the single
40
+ ``continuo_engine_contract.types.validate_column_type`` is the single
41
41
  acceptance authority for the grammar shape (case-insensitive, injection-
42
42
  guarded): it runs first, and anything it rejects is rejected here too. The
43
43
  logic below only extracts precision/scale/length and enforces the NUMERIC
@@ -0,0 +1 @@
1
+ """Blue/green validation runner: op dispatch, artifact fetch, result block."""
@@ -0,0 +1,245 @@
1
+ """Build a single node as an empty table in the candidate schema (blue/green validation).
2
+
3
+ Dispatches on ``VALIDATION_OP`` env var (default ``build_from_sql``):
4
+ - ``build_from_sql``: fetch the node's compiled SQL from S3 (``CANDIDATE_SQL_URI``) and
5
+ materialize it empty (models/snapshots).
6
+ - ``clone_from_prod``: clone an existing prod table's shape empty from ``PROD_SCHEMA``
7
+ (unchanged upstreams, including seeds).
8
+ - ``build_from_columns``: for python nodes, which have no SELECT to shape their output
9
+ from. Fetch the node's validation spec JSON from S3 (``CANDIDATE_SPEC_URI`` —
10
+ ``{"reads": [sql, ...], "output_columns": [{"name","type","nullable"}, ...],
11
+ "config": {...}}``; ``config`` is optional and defaults to ``{}``), bind-check every
12
+ declared read against the candidate schema so an upstream that
13
+ dropped a column the script reads fails the release gate, then materialize the
14
+ output table empty from the declared typed columns and the declared physical layout.
15
+
16
+ The engine adapter is discovered from the single installed
17
+ ``continuo_engine.adapters`` entry point — each runner image installs exactly one.
18
+ stdout is reserved exclusively for the runner's one structured ``result_block``,
19
+ printed as its last line; all diagnostics go to stderr via the ``logging`` module.
20
+ A non-zero exit marks the node failed.
21
+ """
22
+ import json
23
+ import logging
24
+ import os
25
+ import sys
26
+
27
+ from continuo_engine_contract import result # type: ignore[import-untyped]
28
+ from continuo_engine_contract.port import ( # type: ignore[import-untyped]
29
+ AdapterDiscoveryError,
30
+ discover_adapter,
31
+ )
32
+ from continuo_python_runtime.validation import s3
33
+
34
+ logger = logging.getLogger("validation_runner")
35
+
36
+
37
+ def _node_id() -> str:
38
+ """Best-known node identity for a result block: ``NODE_ID`` env, else ``""``.
39
+
40
+ The single source of NODE_ID resolution shared by every block-emitting path.
41
+ ``main`` layers a ``model.{table}`` fallback on top once TABLE_NAME is known;
42
+ the early ``_require`` failures (which may be the missing TABLE_NAME itself)
43
+ cannot, so they fall back to this bare value.
44
+ """
45
+ return os.environ.get("NODE_ID", "")
46
+
47
+
48
+ def _require(name: str) -> str:
49
+ value = os.environ.get(name)
50
+ if not value:
51
+ logger.error("missing required env var %s", name)
52
+ print(
53
+ result.result_block(
54
+ "error", f"missing required env var {name}",
55
+ unique_id=_node_id(),
56
+ ),
57
+ flush=True,
58
+ )
59
+ sys.exit(2)
60
+ return value
61
+
62
+
63
+ def load_candidate_sql() -> str:
64
+ """Fetch this node's candidate SQL from S3 at ``CANDIDATE_SQL_URI``.
65
+
66
+ Returns the raw UTF-8 body (no stripping; the caller normalizes). Returns ``""``
67
+ when ``CANDIDATE_SQL_URI`` is unset/empty (nothing to validate). Raises on
68
+ invalid-URI or S3-download errors so ``main`` maps them to a structured block.
69
+ """
70
+ uri = os.environ.get("CANDIDATE_SQL_URI", "")
71
+ if not uri:
72
+ return ""
73
+ bucket, key = s3.parse_s3_uri(uri)
74
+ client = s3.make_s3_client()
75
+ body = client.get_object(Bucket=bucket, Key=key)["Body"].read()
76
+ decoded: str = body.decode("utf-8")
77
+ return decoded
78
+
79
+
80
+ def load_candidate_spec() -> dict:
81
+ """Fetch this node's validation spec JSON from S3 at ``CANDIDATE_SPEC_URI``.
82
+
83
+ Returns the parsed dict. Raises on unset/empty URI, invalid URI, S3
84
+ download errors, invalid JSON, or JSON that parses to something other than
85
+ an object (e.g. a list, ``null``, a number, or a string) — ``main`` maps
86
+ every failure to a structured block.
87
+ """
88
+ uri = os.environ.get("CANDIDATE_SPEC_URI", "")
89
+ if not uri:
90
+ raise ValueError("CANDIDATE_SPEC_URI is unset or empty")
91
+ bucket, key = s3.parse_s3_uri(uri)
92
+ client = s3.make_s3_client()
93
+ body = client.get_object(Bucket=bucket, Key=key)["Body"].read()
94
+ spec = json.loads(body.decode("utf-8"))
95
+ if not isinstance(spec, dict):
96
+ raise ValueError(
97
+ f"candidate spec must be a JSON object, got {type(spec).__name__}"
98
+ )
99
+ return spec
100
+
101
+
102
+ _NODE_OPS = ("build_from_sql", "clone_from_prod", "build_from_columns")
103
+ _SCHEMA_OPS = ("ensure_schema", "drop_schema")
104
+
105
+
106
+ def main() -> None:
107
+ """Run one validation op end to end; exits non-zero on failure.
108
+
109
+ Node ops (``build_from_sql``/``clone_from_prod``/``build_from_columns``)
110
+ materialize one empty node table and require ``TABLE_NAME``. Schema ops
111
+ (``ensure_schema``/``drop_schema``) act on the whole candidate schema —
112
+ the executor schedules them as one-shot engine-image Jobs to own the
113
+ candidate-schema lifecycle without connecting to the warehouse itself —
114
+ and take no table.
115
+ """
116
+ logging.basicConfig(
117
+ level=logging.INFO, stream=sys.stderr, format="%(levelname)s %(name)s: %(message)s"
118
+ )
119
+ schema = _require("DBT_TARGET_SCHEMA")
120
+ op = os.environ.get("VALIDATION_OP", "build_from_sql")
121
+
122
+ # Gather op-specific inputs and identity BEFORE touching the adapter, surfacing
123
+ # input errors as a structured block (preserves the prior contract + exit codes).
124
+ table = None
125
+ candidate_sql = None
126
+ prod_schema = None
127
+ spec: dict | None = None
128
+ config: dict = {}
129
+ if op in _NODE_OPS:
130
+ table = _require("TABLE_NAME")
131
+ unique_id = _node_id() or f"model.{table}"
132
+ if op == "build_from_sql":
133
+ try:
134
+ raw_sql = load_candidate_sql()
135
+ except Exception as exc:
136
+ uri = os.environ.get("CANDIDATE_SQL_URI", "")
137
+ logger.error("ERROR fetching candidate SQL from %r: %s", uri, exc)
138
+ print(result.result_block("error", str(exc), unique_id=unique_id), flush=True)
139
+ sys.exit(1)
140
+ if not raw_sql:
141
+ logger.error(
142
+ "CANDIDATE_SQL_URI is unset or the object is empty for a "
143
+ "build_from_sql node; cannot validate"
144
+ )
145
+ print(result.result_block("error", "CANDIDATE_SQL_URI is unset or empty",
146
+ unique_id=unique_id), flush=True)
147
+ sys.exit(2)
148
+ candidate_sql = raw_sql
149
+ elif op == "build_from_columns":
150
+ try:
151
+ spec = load_candidate_spec()
152
+ except (ValueError, json.JSONDecodeError) as exc:
153
+ logger.error("invalid candidate spec: %s", exc)
154
+ print(result.result_block("error", str(exc), unique_id=unique_id), flush=True)
155
+ sys.exit(2)
156
+ except Exception as exc:
157
+ uri = os.environ.get("CANDIDATE_SPEC_URI", "")
158
+ logger.error("ERROR fetching candidate spec from %r: %s", uri, exc)
159
+ print(result.result_block("error", str(exc), unique_id=unique_id), flush=True)
160
+ sys.exit(1)
161
+ if not spec.get("output_columns"):
162
+ logger.error("candidate spec has no output_columns; cannot validate")
163
+ print(result.result_block("error", "candidate spec has no output_columns",
164
+ unique_id=unique_id), flush=True)
165
+ sys.exit(2)
166
+ raw_config = spec.get("config")
167
+ if raw_config is not None and not isinstance(raw_config, dict):
168
+ # Engine-blind: the runner only decides that the block is a JSON
169
+ # object. Which keys are legal is the installed adapter's call.
170
+ msg = (
171
+ f"candidate spec 'config' must be a JSON object, "
172
+ f"got {type(raw_config).__name__}"
173
+ )
174
+ logger.error("%s", msg)
175
+ print(result.result_block("error", msg, unique_id=unique_id), flush=True)
176
+ sys.exit(2)
177
+ config = raw_config or {}
178
+ else:
179
+ prod_schema = _require("PROD_SCHEMA")
180
+ elif op in _SCHEMA_OPS:
181
+ unique_id = _node_id() or f"schema.{schema}"
182
+ else:
183
+ unique_id = _node_id() or f"schema.{schema}"
184
+ logger.error("unknown VALIDATION_OP %r", op)
185
+ print(result.result_block("error", f"unknown VALIDATION_OP {op!r}",
186
+ unique_id=unique_id), flush=True)
187
+ sys.exit(2)
188
+
189
+ # Engine selection is discovery, not configuration: the image installs one adapter.
190
+ try:
191
+ engine, adapter_cls = discover_adapter()
192
+ except AdapterDiscoveryError as exc:
193
+ logger.error("%s", exc)
194
+ print(result.result_block("error", str(exc), unique_id=unique_id), flush=True)
195
+ sys.exit(2)
196
+
197
+ missing = [v for v in adapter_cls.required_env() if not os.environ.get(v)]
198
+ if missing:
199
+ msg = f"missing required env for engine {engine!r}: {', '.join(missing)}"
200
+ logger.error("%s", msg)
201
+ print(result.result_block("error", msg, unique_id=unique_id), flush=True)
202
+ sys.exit(2)
203
+
204
+ # close() runs exactly once, in the finally, on every path: the success case,
205
+ # the op error (sys.exit raises SystemExit, which still unwinds finally), and
206
+ # a from_env() failure (adapter stays None). A close() failure only logs — the
207
+ # primary error, if any, is already the SystemExit propagating through.
208
+ adapter = None
209
+ try:
210
+ adapter = adapter_cls.from_env()
211
+ if op == "ensure_schema":
212
+ adapter.ensure_schema(schema)
213
+ elif op == "drop_schema":
214
+ adapter.drop_schema(schema)
215
+ else:
216
+ adapter.ensure_schema(schema)
217
+ assert table is not None, "table must be set for a node op"
218
+ if op == "build_from_sql":
219
+ assert candidate_sql is not None, "candidate_sql must be set for build_from_sql"
220
+ adapter.build_empty_from_sql(schema, table, candidate_sql)
221
+ elif op == "build_from_columns":
222
+ assert spec is not None, "spec must be set for build_from_columns"
223
+ for read_sql in spec.get("reads", []):
224
+ adapter.check_binds(read_sql)
225
+ adapter.build_empty_from_columns(schema, table, spec["output_columns"], config)
226
+ else:
227
+ assert prod_schema is not None, "prod_schema must be set for clone_from_prod"
228
+ adapter.clone_empty_from_prod(schema, prod_schema, table)
229
+ except Exception as exc:
230
+ logger.error("ERROR op=%s on schema %s: %s", op, schema, exc)
231
+ print(result.result_block("error", str(exc), unique_id=unique_id), flush=True)
232
+ sys.exit(1)
233
+ finally:
234
+ if adapter is not None:
235
+ try:
236
+ adapter.close()
237
+ except Exception as close_exc: # never mask the primary outcome
238
+ logger.error("adapter close failed: %s", close_exc)
239
+
240
+ logger.info("ran op=%s on schema=%s (engine=%s)", op, schema, engine)
241
+ print(result.result_block("success", unique_id=unique_id), flush=True)
242
+
243
+
244
+ if __name__ == "__main__":
245
+ main()
@@ -0,0 +1,45 @@
1
+ """S3 helpers for the validation runner.
2
+
3
+ ``parse_s3_uri`` and ``make_s3_client`` back the runner's candidate-SQL fetch
4
+ from ``CANDIDATE_SQL_URI``.
5
+ """
6
+ import os
7
+
8
+ from typing import Any
9
+
10
+ import boto3 # type: ignore[import-untyped]
11
+
12
+
13
+ def parse_s3_uri(uri: str) -> tuple[str, str]:
14
+ """Parse an ``s3://bucket/key`` URI and return ``(bucket, key)``.
15
+
16
+ Raises :class:`ValueError` for non-s3:// URIs, a missing bucket, or a
17
+ missing/empty key (e.g. ``s3://bucket-only`` or ``s3://bucket/``).
18
+ """
19
+ if not uri.startswith("s3://"):
20
+ raise ValueError(f"invalid S3 URI (must start with s3://): {uri!r}")
21
+ bucket, _, key = uri[len("s3://"):].partition("/")
22
+ if not bucket or not key:
23
+ raise ValueError(f"invalid S3 URI (missing bucket or key): {uri!r}")
24
+ return bucket, key
25
+
26
+
27
+ def make_s3_client() -> Any:
28
+ """Construct a boto3 S3 client, letting boto3 resolve credentials itself.
29
+
30
+ Only ``S3_ENDPOINT_URL`` (when set and non-empty, e.g. for MinIO) is passed
31
+ explicitly. Credentials and region are left for boto3's own chain, which
32
+ natively reads ``AWS_ACCESS_KEY_ID``, ``AWS_SECRET_ACCESS_KEY``,
33
+ ``AWS_SESSION_TOKEN``, and ``AWS_DEFAULT_REGION`` from the environment, and
34
+ falls through to IAM roles / web identity / instance profiles when those are
35
+ absent. Passing raw (possibly blank or missing) env values as explicit
36
+ keyword args would instead select empty/explicit credentials or drop
37
+ temporary-credential session tokens, breaking that chain.
38
+
39
+ Tests patch ``s3.boto3`` to intercept the underlying ``client`` call.
40
+ """
41
+ kwargs: dict[str, Any] = {}
42
+ endpoint_url = os.environ.get("S3_ENDPOINT_URL")
43
+ if endpoint_url:
44
+ kwargs["endpoint_url"] = endpoint_url
45
+ return boto3.client("s3", **kwargs)
@@ -1,14 +1,18 @@
1
- Metadata-Version: 2.4
1
+ Metadata-Version: 2.5
2
2
  Name: continuo-python-runtime
3
- Version: 0.2.0
3
+ Version: 0.3.1
4
4
  Summary: Runtime harness, contract tooling, and CI lint for Continuo python nodes.
5
5
  Author: Simone Carolini
6
6
  Maintainer: Simone Carolini
7
+ License-Expression: Apache-2.0
8
+ License-File: LICENSE
9
+ License-File: NOTICE
7
10
  Classifier: Development Status :: 4 - Beta
8
11
  Classifier: Intended Audience :: Developers
9
12
  Classifier: Programming Language :: Python :: 3.14
10
13
  Requires-Python: >=3.14
11
- Requires-Dist: continuo-validation-contract==0.4.0
14
+ Requires-Dist: boto3==1.43.59
15
+ Requires-Dist: continuo-engine-contract==0.7.1
12
16
  Requires-Dist: pyarrow==25.0.0
13
17
  Requires-Dist: pyyaml==6.0.3
14
18
  Requires-Dist: sqlglot==30.15.0
@@ -16,6 +20,10 @@ Description-Content-Type: text/markdown
16
20
 
17
21
  # Continuo Python Runtime
18
22
 
23
+ [![CI](https://github.com/carolsimone/continuo-python-runtime/actions/workflows/ci.yml/badge.svg)](https://github.com/carolsimone/continuo-python-runtime/actions/workflows/ci.yml)
24
+ [![License: Apache 2.0](https://img.shields.io/badge/License-Apache%202.0-blue.svg)](LICENSE)
25
+ [![Release](https://img.shields.io/github/v/release/carolsimone/continuo-python-runtime?label=release)](https://github.com/carolsimone/continuo-python-runtime/releases)
26
+
19
27
  Runtime harness, contract tooling, and CI lint for Continuo python nodes.
20
28
  This repo is what domain data teams (marketing, finance, …) template from to
21
29
  ship a python node into Continuo: write a contract + a `run(ctx)` script,
@@ -24,53 +32,69 @@ and register the release with Continuo.
24
32
 
25
33
  ## What this repo is
26
34
 
27
- Three artifacts come out of this repository:
35
+ Four artifacts come out of this repository:
28
36
 
29
37
  - **The `continuo-python-runtime` PyPI package** — the `continuo-runtime` CLI
30
- (`validate` / `merge` / `hash` / `lint` / `run`) and the harness library
31
- (`conform()`, `RunContext`, the error taxonomy) that domain repos install.
32
- - **Per-engine base images**, one per warehouse engine (`...-postgres`,
33
- `...-trino`), that domain repos build `FROM`. Each image bakes in the
34
- runtime, a single `RuntimeAdapter` for that engine, and the
35
- `continuo-runtime run` entrypoint.
38
+ (`validate` / `merge` / `hash` / `lint` / `run` / `validation-op`) and the
39
+ harness library (`conform()`, `RunContext`, the error taxonomy) that domain
40
+ repos install.
41
+ - **The `continuo-engine-contract` PyPI package** — the `WarehouseAdapter`
42
+ port, the contract schema, the shared SQL/type/config guards, and the
43
+ sentinel result-block format. Adapter authors outside this repo pin it.
44
+ - **Per-engine base images**, one per warehouse engine
45
+ (`continuo-python-runtime-postgres`, `continuo-python-runtime-trino`), that
46
+ domain repos build `FROM`. Each image bakes in the runtime and a single
47
+ `WarehouseAdapter` for that engine, and serves both roles that adapter has:
48
+ the node harness (`ENTRYPOINT ["continuo-runtime"]`, `CMD ["run"]`) and the
49
+ validation runner (`continuo-runtime validation-op`).
36
50
  - **`template/`** — a copy-ready domain repo: `Dockerfile`, `contracts/`,
37
51
  `scripts/`, and the `release.yml` CI/CD workflow.
38
52
 
39
- Base images and the PyPI publication of this package land with this repo's
40
- PR 9 (the image pipeline); until then, install the runtime from git as noted
41
- in `template/.github/workflows/release.yml`.
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.
42
57
 
43
- ### Package map
58
+ ### What this repo owns
44
59
 
45
- Each per-engine base image bakes together three PyPI packages: the harness,
46
- the engine adapter, and the published contract. The harness and both engine
47
- adapters live in *this* repo (`continuo-python-runtime`) as a uv workspace;
48
- `continuo-validation-runners` owns the *validation-side* (offline lint/merge)
49
- counterparts of the same engines, which is a separate concern from the
50
- data-plane adapters here.
60
+ This repository owns the entire python-node surface: the engine contract, both
61
+ engine adapters, the validation runner, and the node harness. The former
62
+ `continuo-validation` repository was merged in — there is no longer a separate
63
+ validation-side port, adapter class, entry-point group, or image. One
64
+ `WarehouseAdapter` per engine serves both the data plane (`fetch` /
65
+ `ensure_table` / `load`) and validation (`ensure_schema` / `drop_schema` /
66
+ `build_empty_from_sql` / `build_empty_from_columns` / `clone_empty_from_prod` /
67
+ `check_binds`), and one image per engine runs both roles.
51
68
 
52
69
  | Package (distribution name) | Module | Lives in | Role |
53
70
  | --- | --- | --- | --- |
54
- | `continuo-python-runtime` | `continuo_python_runtime` | this repo (root) | Harness: CLI, `conform()`, `RunContext`, error taxonomy. |
55
- | `continuo-python-runtime-postgres` | `continuo_python_runtime_postgres` | this repo, `python-runtime-postgres/` | Data-plane `RuntimeAdapter` for Postgres (`fetch`/`ensure_table`/`load`). **Not published to PyPI** — built from source into the image. |
56
- | `continuo-python-runtime-trino` | `continuo_python_runtime_trino` | this repo, `python-runtime-trino/` | Data-plane `RuntimeAdapter` for Trino/Iceberg. **Not published to PyPI** — built from source into the image. |
57
- | `continuo-validation-contract` | `continuo_validation_contract` | `continuo-validation-runners` | The published contract (schema, `RuntimeAdapter` port, result-block format) both sides depend on. |
58
- | `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. |
59
-
60
- All three packages built in this repo resolve `continuo-validation-contract`
61
- from PyPI (`==0.4.0`); the two adapter packages are uv workspace members
62
- (`[tool.uv.workspace]` in the root `pyproject.toml`), so `uv sync
63
- --all-packages --all-groups` at the repo root installs everything for local
64
- development.
65
-
66
- **Only `continuo-python-runtime` is published to PyPI.** The two engine
67
- adapters are built **from source into the runtime images**: `Dockerfile.postgres`
68
- and `Dockerfile.trino` `pip install` them out of the build context, so each
69
- image ships exactly one adapter and the harness discovers it through the
70
- `continuo_runtime.adapters` entry-point group at run time. Nothing installs
71
- them from an index — the harness package does not depend on them, and domain
72
- repos get their adapter by building `FROM` a published base image. They are
73
- still built, type-checked, and tested by CI on every change.
71
+ | `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
+ | `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. |
75
+
76
+ All four are uv workspace members (`[tool.uv.workspace]` in the root
77
+ `pyproject.toml`), so `uv sync --all-packages --all-groups` at the repo root
78
+ installs everything for local development.
79
+
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.
89
+
90
+ ### The result block is a frozen wire contract
91
+
92
+ `continuo_engine_contract.result` writes a sentinel-framed JSON block as the
93
+ last line of stdout, and Continuo's Go side parses it byte-for-byte
94
+ (`pkg/validationresult` in the `continuo` repository). That wire format — the
95
+ sentinel markers, the framing, and the field names inside the block — is
96
+ **frozen**. Reuse it; never change it from this side alone. A change here that
97
+ the Go parser has not been taught is a production outage, not a refactor.
74
98
 
75
99
  ## Quickstart for domain teams
76
100
 
@@ -98,7 +122,7 @@ still built, type-checked, and tested by CI on every change.
98
122
  ### Upgrading an existing domain repo
99
123
 
100
124
  `validate` / `merge` / `hash` now hand every declared read to a real SQL
101
- parser (sqlglot, via `continuo_validation_contract.sql.ensure_single_read`)
125
+ parser (sqlglot, via `continuo_engine_contract.sql.ensure_single_read`)
102
126
  instead of scanning it for a leading `SELECT`/`WITH`. Two things follow for a
103
127
  repo written before this, on its next release: SQL a driver would accept but
104
128
  a parser will not — most commonly a driver-specific bind placeholder like
@@ -138,9 +162,13 @@ def run(ctx):
138
162
  example a polars DataFrame. Returning anything else raises `ScriptError`.
139
163
  - The dataframe library is your choice. No dataframe library is baked into
140
164
  the base image — the package's own runtime dependencies are `pyarrow`,
141
- `PyYAML`, and `continuo-validation-contract`. Add whatever you script
165
+ `PyYAML`, and `continuo-engine-contract`. Add whatever you script
142
166
  against (pandas, polars, …) as a `RUN pip install` line in your own
143
- `Dockerfile`, on top of the base image.
167
+ `Dockerfile`, on top of the base image. The base image runs as non-root
168
+ (uid 65532), which pip cannot install under, so switch to root for the
169
+ install and back afterward — `USER root`, then the `RUN pip install`
170
+ line, then `USER 65532:65532` — since the executor's pod spec expects
171
+ the image to end at uid 65532. See `template/Dockerfile` for the pattern.
144
172
  - Scripts do not import warehouse drivers, write raw SQL literals, or call
145
173
  data-access methods directly — `continuo-runtime lint` rejects those:
146
174
  - forbidden driver imports (`psycopg2`/`sqlalchemy`/`trino`/etc.),
@@ -208,19 +236,20 @@ A domain repo picks its warehouse engine by which base image it builds
208
236
  `FROM`:
209
237
 
210
238
  ```dockerfile
211
- FROM ghcr.io/carolsimone/continuo-python-runtime:v0.2.0-postgres
239
+ FROM ghcr.io/carolsimone/continuo-python-runtime-postgres:v0.3.1
212
240
  # or
213
- FROM ghcr.io/carolsimone/continuo-python-runtime:v0.2.0-trino
241
+ FROM ghcr.io/carolsimone/continuo-python-runtime-trino:v0.3.1
214
242
  ```
215
243
 
216
- Each image bakes in exactly one `RuntimeAdapter` for that engine — installed
217
- from this repo's `python-runtime-postgres/` or `python-runtime-trino/`
218
- package (see the package map above; both adapters live in this repo, not
219
- `continuo-validation-runners`) — registered under the
220
- `continuo_runtime.adapters` entry-point group (entry names `postgres` /
221
- `trino`). The harness discovers it via `discover_runtime_adapter()` at run
222
- time, so a single image serves every node in the service. The executor
223
- injects the
244
+ The engine is part of the image **name**; the tag is the bare version, so
245
+ Continuo's Helm chart can pin an image as `<name>:vX.Y.Z@sha256:<digest>`.
246
+
247
+ Each image bakes in exactly one `WarehouseAdapter` for that engine — installed
248
+ from this repo's `adapters/postgres/` or `adapters/trino/` package (see the
249
+ table above) — registered under the `continuo_engine.adapters` entry-point
250
+ group (entry names `postgres` / `trino`). The runtime discovers it via
251
+ `discover_adapter()` at run time, so a single image serves every node in the
252
+ service and the release-time validation Job for it. The executor injects the
224
253
  warehouse connection as environment variables (engine-native, e.g.
225
254
  `POSTGRES_HOST`/`POSTGRES_DB`/`POSTGRES_USER`) plus the node-selection
226
255
  environment (`NODE_ID`, `TABLE_NAME`, `TARGET_SCHEMA`, and optionally
@@ -0,0 +1,24 @@
1
+ continuo_python_runtime/__init__.py,sha256=otNaXEPsTtRXJV5FLtDUsFO8VD4OC-MLAzwhtimHfOE,70
2
+ continuo_python_runtime/cli.py,sha256=X4JBC4FDpgFOeDdtBbeX9GTq1XTuJCghDpemlR_CPvE,5925
3
+ continuo_python_runtime/closure.py,sha256=6e-IAQp8Lieb0bGKP1F6JE4TgVBJ_COTGXRUult2xeI,12991
4
+ continuo_python_runtime/conform.py,sha256=a9CKCA-gT9Zerzr6c5H8aByTFOUd6uBMo02sNKSDnXg,9199
5
+ continuo_python_runtime/context.py,sha256=K-9XXYlQ4OZ250rCOq6EJNq7fg_D5sK6tOcu7DLZfVg,1856
6
+ continuo_python_runtime/errors.py,sha256=nfZzfBhQ2ajm7USAWKKaGY2uUlh8eNjNPNYYuaOJgZE,1032
7
+ continuo_python_runtime/harness.py,sha256=1b_ze2_AJ9vncY-yj88TgJylVzKwGw1-idxHoaYAymE,12101
8
+ continuo_python_runtime/hashing.py,sha256=70iXQb0CmjGqniTPofXY6WapydQ7aK2XOk5dzpEf-zk,3354
9
+ continuo_python_runtime/lint.py,sha256=M59UtCHHVQnJKNolIkvkRk_AgGAFvxD9zkKAMMK-7GI,12984
10
+ continuo_python_runtime/types.py,sha256=_ssxNE2Lq5782n9_Ihs-u_6ybCCy8XaL6iYbE35tBug,5057
11
+ continuo_python_runtime/contract/__init__.py,sha256=S79x6j_P0CmaSuCGzeWoK1U0507KvD2MZXwQl9DFWMM,37
12
+ continuo_python_runtime/contract/loader.py,sha256=WeKngAe6VH-WxgRTVdCERPMdsGBszJM9RkanUTTs9-w,15611
13
+ continuo_python_runtime/contract/merge.py,sha256=XCCeE2vJUm0KA60d2XsHpjRh5fIdVfFu_HsBzEyUWow,6505
14
+ continuo_python_runtime/contract/model.py,sha256=2hKI04M0WwvTkOy9eDGl2dRZgY3bAuJYktAG-ICI-AU,936
15
+ continuo_python_runtime/contract/paths.py,sha256=SRn70pJkDAIpXw0IcMU2iNKPKvct8w_SZP2ec-lV91M,1458
16
+ continuo_python_runtime/validation/__init__.py,sha256=hz5oGXsoaUoygR0_pBby9PS6cEfrre518EE0HGB58eE,79
17
+ continuo_python_runtime/validation/runner.py,sha256=zHs5CR3Wr9512sh6eY9hpN8NG3weJPYUFRDPJf2BNnc,10875
18
+ continuo_python_runtime/validation/s3.py,sha256=q8nRrT9R7L1-M4dSlDnkL7CUJMPmq86MHY_UKYFlEf8,1785
19
+ continuo_python_runtime-0.3.1.dist-info/METADATA,sha256=CbkAD-RaAJiq31POHm89uDUFgQEYQMN2DCCfwhAJqVo,15416
20
+ continuo_python_runtime-0.3.1.dist-info/WHEEL,sha256=zOwg4jB6zX2kU910N-cMawjivD6tO8NEWvE12je1bVk,87
21
+ continuo_python_runtime-0.3.1.dist-info/entry_points.txt,sha256=ZvIwSm9f9B9ZmZMf6CyF5Zt4QONn8rqaIk-u-B1CF2Y,70
22
+ continuo_python_runtime-0.3.1.dist-info/licenses/LICENSE,sha256=hfXfFCk-8gnps4YvHA6bjCNaSYqOrq2gAdqkXYycAQc,11345
23
+ continuo_python_runtime-0.3.1.dist-info/licenses/NOTICE,sha256=ykgYyQkAMx3uas90FZHzt0aTca9IgJQCLpZ4d74Pl3U,465
24
+ continuo_python_runtime-0.3.1.dist-info/RECORD,,
@@ -1,4 +1,4 @@
1
1
  Wheel-Version: 1.0
2
- Generator: hatchling 1.31.0
2
+ Generator: hatchling 1.32.0
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
@@ -0,0 +1,201 @@
1
+ Apache License
2
+ Version 2.0, January 2004
3
+ http://www.apache.org/licenses/
4
+
5
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
6
+
7
+ 1. Definitions.
8
+
9
+ "License" shall mean the terms and conditions for use, reproduction,
10
+ and distribution as defined by Sections 1 through 9 of this document.
11
+
12
+ "Licensor" shall mean the copyright owner or entity authorized by
13
+ the copyright owner that is granting the License.
14
+
15
+ "Legal Entity" shall mean the union of the acting entity and all
16
+ other entities that control, are controlled by, or are under common
17
+ control with that entity. For the purposes of this definition,
18
+ "control" means (i) the power, direct or indirect, to cause the
19
+ direction or management of such entity, whether by contract or
20
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
21
+ outstanding shares, or (iii) beneficial ownership of such entity.
22
+
23
+ "You" (or "Your") shall mean an individual or Legal Entity
24
+ exercising permissions granted by this License.
25
+
26
+ "Source" form shall mean the preferred form for making modifications,
27
+ including but not limited to software source code, documentation
28
+ source, and configuration files.
29
+
30
+ "Object" form shall mean any form resulting from mechanical
31
+ transformation or translation of a Source form, including but
32
+ not limited to compiled object code, generated documentation,
33
+ and conversions to other media types.
34
+
35
+ "Work" shall mean the work of authorship, whether in Source or
36
+ Object form, made available under the License, as indicated by a
37
+ copyright notice that is included in or attached to the work
38
+ (an example is provided in the Appendix below).
39
+
40
+ "Derivative Works" shall mean any work, whether in Source or Object
41
+ form, that is based on (or derived from) the Work and for which the
42
+ editorial revisions, annotations, elaborations, or other modifications
43
+ represent, as a whole, an original work of authorship. For the
44
+ purposes of this License, Derivative Works shall not include works
45
+ that remain separable from, or merely link (or bind by name) to the
46
+ interfaces of, the Work and Derivative Works thereof.
47
+
48
+ "Contribution" shall mean any work of authorship, including
49
+ the original version of the Work and any modifications or additions
50
+ to that Work or Derivative Works thereof, that is intentionally
51
+ submitted to Licensor for inclusion in the Work by the copyright owner
52
+ or by an individual or Legal Entity authorized to submit on behalf of
53
+ the copyright owner. For the purposes of this definition, "submitted"
54
+ means any form of electronic, verbal, or written communication sent
55
+ to the Licensor or its representatives, including but not limited to
56
+ communication on electronic mailing lists, source code control systems,
57
+ and issue tracking systems that are managed by, or on behalf of, the
58
+ Licensor for the purpose of discussing and improving the Work, but
59
+ excluding communication that is conspicuously marked or otherwise
60
+ designated in writing by the copyright owner as "Not a Contribution."
61
+
62
+ "Contributor" shall mean Licensor and any individual or Legal Entity
63
+ on behalf of whom a Contribution has been received by Licensor and
64
+ subsequently incorporated within the Work.
65
+
66
+ 2. Grant of Copyright License. Subject to the terms and conditions of
67
+ this License, each Contributor hereby grants to You a perpetual,
68
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
69
+ copyright license to reproduce, prepare Derivative Works of,
70
+ publicly display, publicly perform, sublicense, and distribute the
71
+ Work and such Derivative Works in Source or Object form.
72
+
73
+ 3. Grant of Patent License. Subject to the terms and conditions of
74
+ this License, each Contributor hereby grants to You a perpetual,
75
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
76
+ (except as stated in this section) patent license to make, have made,
77
+ use, offer to sell, sell, import, and otherwise transfer the Work,
78
+ where such license applies only to those patent claims licensable
79
+ by such Contributor that are necessarily infringed by their
80
+ Contribution(s) alone or by combination of their Contribution(s)
81
+ with the Work to which such Contribution(s) was submitted. If You
82
+ institute patent litigation against any entity (including a
83
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
84
+ or a Contribution incorporated within the Work constitutes direct
85
+ or contributory patent infringement, then any patent licenses
86
+ granted to You under this License for that Work shall terminate
87
+ as of the date such litigation is filed.
88
+
89
+ 4. Redistribution. You may reproduce and distribute copies of the
90
+ Work or Derivative Works thereof in any medium, with or without
91
+ modifications, and in Source or Object form, provided that You
92
+ meet the following conditions:
93
+
94
+ (a) You must give any other recipients of the Work or
95
+ Derivative Works a copy of this License; and
96
+
97
+ (b) You must cause any modified files to carry prominent notices
98
+ stating that You changed the files; and
99
+
100
+ (c) You must retain, in the Source form of any Derivative Works
101
+ that You distribute, all copyright, patent, trademark, and
102
+ attribution notices from the Source form of the Work,
103
+ excluding those notices that do not pertain to any part of
104
+ the Derivative Works; and
105
+
106
+ (d) If the Work includes a "NOTICE" text file as part of its
107
+ distribution, then any Derivative Works that You distribute must
108
+ include a readable copy of the attribution notices contained
109
+ within such NOTICE file, excluding those notices that do not
110
+ pertain to any part of the Derivative Works, in at least one
111
+ of the following places: within a NOTICE text file distributed
112
+ as part of the Derivative Works; within the Source form or
113
+ documentation, if provided along with the Derivative Works; or,
114
+ within a display generated by the Derivative Works, if and
115
+ wherever such third-party notices normally appear. The contents
116
+ of the NOTICE file are for informational purposes only and
117
+ do not modify the License. You may add Your own attribution
118
+ notices within Derivative Works that You distribute, alongside
119
+ or as an addendum to the NOTICE text from the Work, provided
120
+ that such additional attribution notices cannot be construed
121
+ as modifying the License.
122
+
123
+ You may add Your own copyright statement to Your modifications and
124
+ may provide additional or different license terms and conditions
125
+ for use, reproduction, or distribution of Your modifications, or
126
+ for any such Derivative Works as a whole, provided Your use,
127
+ reproduction, and distribution of the Work otherwise complies with
128
+ the conditions stated in this License.
129
+
130
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
131
+ any Contribution intentionally submitted for inclusion in the Work
132
+ by You to the Licensor shall be under the terms and conditions of
133
+ this License, without any additional terms or conditions.
134
+ Notwithstanding the above, nothing herein shall supersede or modify
135
+ the terms of any separate license agreement you may have executed
136
+ with Licensor regarding such Contributions.
137
+
138
+ 6. Trademarks. This License does not grant permission to use the trade
139
+ names, trademarks, service marks, or product names of the Licensor,
140
+ except as required for reasonable and customary use in describing the
141
+ origin of the Work and reproducing the content of the NOTICE file.
142
+
143
+ 7. Disclaimer of Warranty. Unless required by applicable law or
144
+ agreed to in writing, Licensor provides the Work (and each
145
+ Contributor provides its Contributions) on an "AS IS" BASIS,
146
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
147
+ implied, including, without limitation, any warranties or conditions
148
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
149
+ PARTICULAR PURPOSE. You are solely responsible for determining the
150
+ appropriateness of using or redistributing the Work and assume any
151
+ risks associated with Your exercise of permissions under this License.
152
+
153
+ 8. Limitation of Liability. In no event and under no legal theory,
154
+ whether in tort (including negligence), contract, or otherwise,
155
+ unless required by applicable law (such as deliberate and grossly
156
+ negligent acts) or agreed to in writing, shall any Contributor be
157
+ liable to You for damages, including any direct, indirect, special,
158
+ incidental, or consequential damages of any character arising as a
159
+ result of this License or out of the use or inability to use the
160
+ Work (including but not limited to damages for loss of goodwill,
161
+ work stoppage, computer failure or malfunction, or any and all
162
+ other commercial damages or losses), even if such Contributor
163
+ has been advised of the possibility of such damages.
164
+
165
+ 9. Accepting Warranty or Additional Liability. While redistributing
166
+ the Work or Derivative Works thereof, You may choose to offer,
167
+ and charge a fee for, acceptance of support, warranty, indemnity,
168
+ or other liability obligations and/or rights consistent with this
169
+ License. However, in accepting such obligations, You may act only
170
+ on Your own behalf and on Your sole responsibility, not on behalf
171
+ of any other Contributor, and only if You agree to indemnify,
172
+ defend, and hold each Contributor harmless for any liability
173
+ incurred by, or claims asserted against, such Contributor by reason
174
+ of your accepting any such warranty or additional liability.
175
+
176
+ END OF TERMS AND CONDITIONS
177
+
178
+ APPENDIX: How to apply the Apache License to your work.
179
+
180
+ To apply the Apache License to your work, attach the following
181
+ boilerplate notice, with the fields enclosed by brackets "[]"
182
+ replaced with your own identifying information. (Don't include
183
+ the brackets!) The text should be enclosed in the appropriate
184
+ comment syntax for the file format. We also recommend that a
185
+ file or class name and description of purpose be included on the
186
+ same "printed page" as the copyright notice for easier
187
+ identification within third-party archives.
188
+
189
+ Copyright 2026 Simone Carolini
190
+
191
+ Licensed under the Apache License, Version 2.0 (the "License");
192
+ you may not use this file except in compliance with the License.
193
+ You may obtain a copy of the License at
194
+
195
+ http://www.apache.org/licenses/LICENSE-2.0
196
+
197
+ Unless required by applicable law or agreed to in writing, software
198
+ distributed under the License is distributed on an "AS IS" BASIS,
199
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
200
+ See the License for the specific language governing permissions and
201
+ limitations under the License.
@@ -0,0 +1,11 @@
1
+ Continuo Python Runtime
2
+ Copyright 2026 Simone Carolini
3
+
4
+ Licensed under the Apache License, Version 2.0 (the "License"); you may not use
5
+ this product except in compliance with the License. You may obtain a copy of the
6
+ License in the LICENSE file at the root of this repository, or at:
7
+
8
+ http://www.apache.org/licenses/LICENSE-2.0
9
+
10
+ This product includes third-party software. See `uv.lock` for the full
11
+ dependency inventory and each dependency's declared license.
@@ -1,19 +0,0 @@
1
- continuo_python_runtime/__init__.py,sha256=otNaXEPsTtRXJV5FLtDUsFO8VD4OC-MLAzwhtimHfOE,70
2
- continuo_python_runtime/cli.py,sha256=FlY1GqEKrtx4O34zMzlFVBuM6wAPaFEyAHjhN0lDcdA,5495
3
- continuo_python_runtime/closure.py,sha256=6e-IAQp8Lieb0bGKP1F6JE4TgVBJ_COTGXRUult2xeI,12991
4
- continuo_python_runtime/conform.py,sha256=a9CKCA-gT9Zerzr6c5H8aByTFOUd6uBMo02sNKSDnXg,9199
5
- continuo_python_runtime/context.py,sha256=ZyeN_dA2DihQbegBiiqHj1_EKk79GsxS-tATPVymUPg,1856
6
- continuo_python_runtime/errors.py,sha256=nfZzfBhQ2ajm7USAWKKaGY2uUlh8eNjNPNYYuaOJgZE,1032
7
- continuo_python_runtime/harness.py,sha256=SF5ciKsc1RawAQ41h_TuYqqkOp5mf5SSeELv0_B2EvI,12211
8
- continuo_python_runtime/hashing.py,sha256=70iXQb0CmjGqniTPofXY6WapydQ7aK2XOk5dzpEf-zk,3354
9
- continuo_python_runtime/lint.py,sha256=M59UtCHHVQnJKNolIkvkRk_AgGAFvxD9zkKAMMK-7GI,12984
10
- continuo_python_runtime/types.py,sha256=kecwFUMkfglv0h6fkcXTlHkEHeLziWyzJvBsimy-Bl0,5065
11
- continuo_python_runtime/contract/__init__.py,sha256=S79x6j_P0CmaSuCGzeWoK1U0507KvD2MZXwQl9DFWMM,37
12
- continuo_python_runtime/contract/loader.py,sha256=6w8uC7kZzGKMDj45pXJtWVI7Pa675CsjJG3e37LcelI,15623
13
- continuo_python_runtime/contract/merge.py,sha256=XCCeE2vJUm0KA60d2XsHpjRh5fIdVfFu_HsBzEyUWow,6505
14
- continuo_python_runtime/contract/model.py,sha256=2hKI04M0WwvTkOy9eDGl2dRZgY3bAuJYktAG-ICI-AU,936
15
- continuo_python_runtime/contract/paths.py,sha256=SRn70pJkDAIpXw0IcMU2iNKPKvct8w_SZP2ec-lV91M,1458
16
- continuo_python_runtime-0.2.0.dist-info/METADATA,sha256=Dvmmo8AgQ3gg0jFhJbcSDC3tCagVG_dSy5gfUj6L_bk,13607
17
- continuo_python_runtime-0.2.0.dist-info/WHEEL,sha256=lCkmxWfQsSc9CfIClYeavTdQeEX2toPqufh9gI35EQA,87
18
- continuo_python_runtime-0.2.0.dist-info/entry_points.txt,sha256=ZvIwSm9f9B9ZmZMf6CyF5Zt4QONn8rqaIk-u-B1CF2Y,70
19
- continuo_python_runtime-0.2.0.dist-info/RECORD,,