continuo-python-runtime 0.6.0__py3-none-any.whl → 0.7.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/contract/kinds.py +18 -3
- continuo_python_runtime/contract/loader.py +9 -0
- continuo_python_runtime/contract/merge.py +7 -1
- continuo_python_runtime/contract/model.py +2 -1
- continuo_python_runtime/contract/secret_ref.py +31 -0
- continuo_python_runtime/harness.py +2 -1
- {continuo_python_runtime-0.6.0.dist-info → continuo_python_runtime-0.7.0.dist-info}/METADATA +9 -3
- {continuo_python_runtime-0.6.0.dist-info → continuo_python_runtime-0.7.0.dist-info}/RECORD +12 -11
- {continuo_python_runtime-0.6.0.dist-info → continuo_python_runtime-0.7.0.dist-info}/WHEEL +0 -0
- {continuo_python_runtime-0.6.0.dist-info → continuo_python_runtime-0.7.0.dist-info}/entry_points.txt +0 -0
- {continuo_python_runtime-0.6.0.dist-info → continuo_python_runtime-0.7.0.dist-info}/licenses/LICENSE +0 -0
- {continuo_python_runtime-0.6.0.dist-info → continuo_python_runtime-0.7.0.dist-info}/licenses/NOTICE +0 -0
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
"""Per-kind contract rules: which fields a node of each kind must carry.
|
|
2
2
|
|
|
3
|
-
Each kind is one ``KindRules`` entry composed of a script policy
|
|
4
|
-
|
|
5
|
-
contract branches on the kind's name.
|
|
3
|
+
Each kind is one ``KindRules`` entry composed of a script policy, a reads rule,
|
|
4
|
+
and whether it may name a Secret. Adding a kind means adding one entry to
|
|
5
|
+
``RULES``; nothing that parses a contract branches on the kind's name.
|
|
6
6
|
"""
|
|
7
7
|
|
|
8
8
|
from __future__ import annotations
|
|
@@ -88,17 +88,32 @@ class CsvRead(ReadsRule):
|
|
|
88
88
|
return dict(reads)
|
|
89
89
|
|
|
90
90
|
|
|
91
|
+
class NoReads(ReadsRule):
|
|
92
|
+
"""No declared reads: the node's script fetches its own data."""
|
|
93
|
+
|
|
94
|
+
def validate(
|
|
95
|
+
self, reads: Any, label: str, *, dialect: str | None, check_reads: bool
|
|
96
|
+
) -> dict[str, str]:
|
|
97
|
+
if reads is None or (isinstance(reads, dict) and not reads):
|
|
98
|
+
return {}
|
|
99
|
+
raise ContractError(
|
|
100
|
+
f"{label}: a python-api node declares no 'reads' (its script fetches its own data)"
|
|
101
|
+
)
|
|
102
|
+
|
|
103
|
+
|
|
91
104
|
@dataclass(frozen=True)
|
|
92
105
|
class KindRules:
|
|
93
106
|
"""What a node of one kind must declare."""
|
|
94
107
|
|
|
95
108
|
script_required: bool
|
|
96
109
|
reads: ReadsRule
|
|
110
|
+
secret_allowed: bool = False
|
|
97
111
|
|
|
98
112
|
|
|
99
113
|
RULES: dict[str, KindRules] = {
|
|
100
114
|
"python-node": KindRules(script_required=True, reads=SqlReads()),
|
|
101
115
|
"python-csv": KindRules(script_required=False, reads=CsvRead()),
|
|
116
|
+
"python-api": KindRules(script_required=True, reads=NoReads(), secret_allowed=True),
|
|
102
117
|
}
|
|
103
118
|
|
|
104
119
|
|
|
@@ -21,6 +21,7 @@ from continuo_python_runtime.contract.model import (
|
|
|
21
21
|
Column,
|
|
22
22
|
Node,
|
|
23
23
|
)
|
|
24
|
+
from continuo_python_runtime.contract.secret_ref import validate_secret_ref
|
|
24
25
|
from continuo_python_runtime.errors import ContractError
|
|
25
26
|
from continuo_python_runtime.types import parse_sql_type
|
|
26
27
|
|
|
@@ -38,6 +39,7 @@ _ALLOWED_KEYS = {
|
|
|
38
39
|
"output_columns",
|
|
39
40
|
"config",
|
|
40
41
|
"content_hash",
|
|
42
|
+
"secret_ref",
|
|
41
43
|
}
|
|
42
44
|
|
|
43
45
|
_REQUIRED_STRING_FIELDS = ("schema", "table", "owner", "schedule")
|
|
@@ -221,6 +223,12 @@ def parse_node(
|
|
|
221
223
|
raw.get("reads"), label, dialect=dialect, check_reads=check_reads
|
|
222
224
|
)
|
|
223
225
|
|
|
226
|
+
secret_ref = ""
|
|
227
|
+
if "secret_ref" in raw:
|
|
228
|
+
if not rules.secret_allowed:
|
|
229
|
+
raise ContractError(f"{label}: 'secret_ref' is not allowed for kind {kind}")
|
|
230
|
+
secret_ref = validate_secret_ref(raw["secret_ref"], label)
|
|
231
|
+
|
|
224
232
|
raw_columns = raw.get("output_columns")
|
|
225
233
|
if not isinstance(raw_columns, list) or not raw_columns:
|
|
226
234
|
raise ContractError(f"{label}: 'output_columns' must be a non-empty list")
|
|
@@ -285,6 +293,7 @@ def parse_node(
|
|
|
285
293
|
config=config,
|
|
286
294
|
content_hash=content_hash,
|
|
287
295
|
kind=kind,
|
|
296
|
+
secret_ref=secret_ref,
|
|
288
297
|
)
|
|
289
298
|
|
|
290
299
|
|
|
@@ -22,7 +22,7 @@ def node_entry(node: Node) -> dict:
|
|
|
22
22
|
four (source_hash, shared_code_hash, config_hash, content_hash). The
|
|
23
23
|
nullable field is always present in output_columns.
|
|
24
24
|
"""
|
|
25
|
-
|
|
25
|
+
entry = {
|
|
26
26
|
"schema": node.schema,
|
|
27
27
|
"table": node.table,
|
|
28
28
|
"owner": node.owner,
|
|
@@ -43,6 +43,11 @@ def node_entry(node: Node) -> dict:
|
|
|
43
43
|
"extra_columns": node.extra_columns,
|
|
44
44
|
"config": dict(node.config),
|
|
45
45
|
}
|
|
46
|
+
# Only a kind that may name a Secret ever carries one; emitting the key
|
|
47
|
+
# only when set keeps every other node's config_hash unchanged.
|
|
48
|
+
if node.secret_ref:
|
|
49
|
+
entry["secret_ref"] = node.secret_ref
|
|
50
|
+
return entry
|
|
46
51
|
|
|
47
52
|
|
|
48
53
|
def _lint_node_closure(
|
|
@@ -131,6 +136,7 @@ def _csv_hash_inputs(node: Node, repo_root: Path) -> tuple[bytes, list[bytes]]:
|
|
|
131
136
|
_HASH_INPUTS: dict[str, Callable[[Node, Path], tuple[bytes, list[bytes]]]] = {
|
|
132
137
|
"python-node": _script_hash_inputs,
|
|
133
138
|
"python-csv": _csv_hash_inputs,
|
|
139
|
+
"python-api": _script_hash_inputs,
|
|
134
140
|
}
|
|
135
141
|
|
|
136
142
|
|
|
@@ -6,7 +6,7 @@ from typing import Any
|
|
|
6
6
|
# Module-level constants
|
|
7
7
|
CRITICALITIES = frozenset({"REGULATORY", "CORE", "SECONDARY"})
|
|
8
8
|
EXTRA_COLUMNS_POLICIES = frozenset({"raise", "warn"})
|
|
9
|
-
KINDS = frozenset({"python-node", "python-csv"})
|
|
9
|
+
KINDS = frozenset({"python-node", "python-csv", "python-api"})
|
|
10
10
|
DEFAULT_KIND = "python-node"
|
|
11
11
|
CONTRACT_VERSION = 1
|
|
12
12
|
|
|
@@ -37,6 +37,7 @@ class Node:
|
|
|
37
37
|
config: dict[str, Any] = field(default_factory=dict)
|
|
38
38
|
content_hash: str | None = None
|
|
39
39
|
kind: str = DEFAULT_KIND
|
|
40
|
+
secret_ref: str = ""
|
|
40
41
|
|
|
41
42
|
@property
|
|
42
43
|
def relation(self) -> str:
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
"""The name of the Kubernetes Secret a node's pod receives as env vars.
|
|
2
|
+
|
|
3
|
+
The ``continuo-api-`` prefix keeps a contract from naming any other Secret in
|
|
4
|
+
the namespace (the warehouse credentials, the platform's own credentials).
|
|
5
|
+
continuo's topology-controller and execution-controller enforce the same
|
|
6
|
+
pattern; this check gives the author the error in CI instead.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from __future__ import annotations
|
|
10
|
+
|
|
11
|
+
import re
|
|
12
|
+
|
|
13
|
+
from continuo_python_runtime.errors import ContractError
|
|
14
|
+
|
|
15
|
+
SECRET_REF_PATTERN = re.compile(r"^continuo-api-[a-z0-9]([-a-z0-9]*[a-z0-9])?$")
|
|
16
|
+
SECRET_REF_MAX_LEN = 253
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def validate_secret_ref(value: object, label: str) -> str:
|
|
20
|
+
"""Return ``value`` if it is a valid ``continuo-api-*`` Secret name."""
|
|
21
|
+
if (
|
|
22
|
+
not isinstance(value, str)
|
|
23
|
+
or len(value) > SECRET_REF_MAX_LEN
|
|
24
|
+
or not SECRET_REF_PATTERN.fullmatch(value)
|
|
25
|
+
):
|
|
26
|
+
raise ContractError(
|
|
27
|
+
f"{label}: 'secret_ref' must be a Kubernetes Secret name starting with "
|
|
28
|
+
f"'continuo-api-' (lowercase letters, digits, '-'; at most "
|
|
29
|
+
f"{SECRET_REF_MAX_LEN} chars), got {value!r}"
|
|
30
|
+
)
|
|
31
|
+
return value
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"""Container entrypoint: dispatches a single node's script and writes its output.
|
|
2
2
|
|
|
3
|
-
``run_node`` is the sole write sink for a python
|
|
3
|
+
``run_node`` is the sole write sink for a python node. It resolves the
|
|
4
4
|
node from the contract, loads and executes its script inside a
|
|
5
5
|
:class:`~continuo_python_runtime.context.RunContext`, conforms the result to
|
|
6
6
|
the declared schema, and writes it through the runtime adapter. Exactly one
|
|
@@ -227,6 +227,7 @@ def _produce_from_csv(
|
|
|
227
227
|
|
|
228
228
|
_PRODUCERS: dict[str, Callable[[Node, Any, Path, CsvSourceReader | None], Any]] = {
|
|
229
229
|
"python-node": _produce_from_script,
|
|
230
|
+
"python-api": _produce_from_script,
|
|
230
231
|
"python-csv": _produce_from_csv,
|
|
231
232
|
}
|
|
232
233
|
|
{continuo_python_runtime-0.6.0.dist-info → continuo_python_runtime-0.7.0.dist-info}/METADATA
RENAMED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: continuo-python-runtime
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.7.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
|
|
@@ -179,6 +179,12 @@ produced, never how it is checked or written.
|
|
|
179
179
|
header but not declared are governed by the same `extra_columns` policy as
|
|
180
180
|
a script node's output — `raise` (default) fails the run, `warn` drops
|
|
181
181
|
them and logs a warning. See `template/contracts/example_csv.yml`.
|
|
182
|
+
- **`python-api`** — a script node that fetches its own data (typically over
|
|
183
|
+
HTTP) and declares no `reads:`. `ctx.read` has nothing to return. It may name
|
|
184
|
+
one Kubernetes Secret with `secret_ref: continuo-api-<name>`; continuo
|
|
185
|
+
attaches that Secret to the node's pod, so each of its keys is an env var the
|
|
186
|
+
script reads with `os.environ[...]`. The contract never holds a secret value
|
|
187
|
+
or env var name; the Secret itself is created in the cluster by the operator.
|
|
182
188
|
|
|
183
189
|
## The script API
|
|
184
190
|
|
|
@@ -275,9 +281,9 @@ A domain repo picks its warehouse engine by which base image it builds
|
|
|
275
281
|
`FROM`:
|
|
276
282
|
|
|
277
283
|
```dockerfile
|
|
278
|
-
FROM ghcr.io/carolsimone/continuo-python-runtime-postgres:v0.
|
|
284
|
+
FROM ghcr.io/carolsimone/continuo-python-runtime-postgres:v0.7.0
|
|
279
285
|
# or
|
|
280
|
-
FROM ghcr.io/carolsimone/continuo-python-runtime-trino:v0.
|
|
286
|
+
FROM ghcr.io/carolsimone/continuo-python-runtime-trino:v0.7.0
|
|
281
287
|
```
|
|
282
288
|
|
|
283
289
|
The engine is part of the image **name**; the tag is the bare version, so
|
|
@@ -6,25 +6,26 @@ continuo_python_runtime/context.py,sha256=K-9XXYlQ4OZ250rCOq6EJNq7fg_D5sK6tOcu7D
|
|
|
6
6
|
continuo_python_runtime/csv_loader.py,sha256=a5Hwu-YIzIiko8X1_vqShsnzjs2-OsKEUSCHLXg_dio,3346
|
|
7
7
|
continuo_python_runtime/csv_source.py,sha256=7g8Tu6ieiJLvWH4Ep4nwYlOdGkjeePr12K5UHuO8TNc,4136
|
|
8
8
|
continuo_python_runtime/errors.py,sha256=nfZzfBhQ2ajm7USAWKKaGY2uUlh8eNjNPNYYuaOJgZE,1032
|
|
9
|
-
continuo_python_runtime/harness.py,sha256=
|
|
9
|
+
continuo_python_runtime/harness.py,sha256=vsoahI38OCaCEpg9Il7xcHYcsXp6A5cxY_fjCC8xGJk,13046
|
|
10
10
|
continuo_python_runtime/hashing.py,sha256=70iXQb0CmjGqniTPofXY6WapydQ7aK2XOk5dzpEf-zk,3354
|
|
11
11
|
continuo_python_runtime/lint.py,sha256=M59UtCHHVQnJKNolIkvkRk_AgGAFvxD9zkKAMMK-7GI,12984
|
|
12
12
|
continuo_python_runtime/types.py,sha256=_ssxNE2Lq5782n9_Ihs-u_6ybCCy8XaL6iYbE35tBug,5057
|
|
13
13
|
continuo_python_runtime/contract/__init__.py,sha256=S79x6j_P0CmaSuCGzeWoK1U0507KvD2MZXwQl9DFWMM,37
|
|
14
|
-
continuo_python_runtime/contract/kinds.py,sha256=
|
|
15
|
-
continuo_python_runtime/contract/loader.py,sha256=
|
|
16
|
-
continuo_python_runtime/contract/merge.py,sha256=
|
|
17
|
-
continuo_python_runtime/contract/model.py,sha256
|
|
14
|
+
continuo_python_runtime/contract/kinds.py,sha256=Ht-hCT7R5A1tEnPlbGL-b9FyJQBvpDWrXK9PBt7yA28,4938
|
|
15
|
+
continuo_python_runtime/contract/loader.py,sha256=9HsV7dJhCwudjOPXBapYFKiZtslyXIkiZ_WfeuLh5-E,14858
|
|
16
|
+
continuo_python_runtime/contract/merge.py,sha256=Zw--_-v3ERE02Nu7qqLtbHa3Hr__0a1SF-2PRqUfXAY,7594
|
|
17
|
+
continuo_python_runtime/contract/model.py,sha256=WB_Qw4koP8BEwuiAZwIEsKFQRa_6QHHBK4xi4eGvPQo,1082
|
|
18
18
|
continuo_python_runtime/contract/paths.py,sha256=SRn70pJkDAIpXw0IcMU2iNKPKvct8w_SZP2ec-lV91M,1458
|
|
19
|
+
continuo_python_runtime/contract/secret_ref.py,sha256=96jh2G81clpabiEWGIsxbd9r5GKEGLb4Sa1-WJ9G59Q,1131
|
|
19
20
|
continuo_python_runtime/csv_readers/__init__.py,sha256=9TKvjC37C77uRaJQZuO0--zVj2OvuNwpJE6nWx7j2S8,806
|
|
20
21
|
continuo_python_runtime/csv_readers/https.py,sha256=tMJ9k5F0b9SAydpon0ZlOznfjvjhF2BBLltyVmvRneE,4998
|
|
21
22
|
continuo_python_runtime/csv_readers/s3.py,sha256=zmvHs3TxlN2alPKrdaHzPyREmkve2nBiAsHRXXe1mE0,2246
|
|
22
23
|
continuo_python_runtime/validation/__init__.py,sha256=hz5oGXsoaUoygR0_pBby9PS6cEfrre518EE0HGB58eE,79
|
|
23
24
|
continuo_python_runtime/validation/runner.py,sha256=7pbksf7MXfTNcdklCOkOXg9qfRiJtyI_dbgbL3a9LWk,14072
|
|
24
25
|
continuo_python_runtime/validation/s3.py,sha256=q8nRrT9R7L1-M4dSlDnkL7CUJMPmq86MHY_UKYFlEf8,1785
|
|
25
|
-
continuo_python_runtime-0.
|
|
26
|
-
continuo_python_runtime-0.
|
|
27
|
-
continuo_python_runtime-0.
|
|
28
|
-
continuo_python_runtime-0.
|
|
29
|
-
continuo_python_runtime-0.
|
|
30
|
-
continuo_python_runtime-0.
|
|
26
|
+
continuo_python_runtime-0.7.0.dist-info/METADATA,sha256=Uo2XMCZawYbKgpER3qDn-eF_0cKJUZTultaxxcBAhuQ,19281
|
|
27
|
+
continuo_python_runtime-0.7.0.dist-info/WHEEL,sha256=W3fkpkm7-wf9vBI5Z-7s0eWkeM-spu78I8Neb98DeEg,87
|
|
28
|
+
continuo_python_runtime-0.7.0.dist-info/entry_points.txt,sha256=ZvIwSm9f9B9ZmZMf6CyF5Zt4QONn8rqaIk-u-B1CF2Y,70
|
|
29
|
+
continuo_python_runtime-0.7.0.dist-info/licenses/LICENSE,sha256=hfXfFCk-8gnps4YvHA6bjCNaSYqOrq2gAdqkXYycAQc,11345
|
|
30
|
+
continuo_python_runtime-0.7.0.dist-info/licenses/NOTICE,sha256=ykgYyQkAMx3uas90FZHzt0aTca9IgJQCLpZ4d74Pl3U,465
|
|
31
|
+
continuo_python_runtime-0.7.0.dist-info/RECORD,,
|
|
File without changes
|
{continuo_python_runtime-0.6.0.dist-info → continuo_python_runtime-0.7.0.dist-info}/entry_points.txt
RENAMED
|
File without changes
|
{continuo_python_runtime-0.6.0.dist-info → continuo_python_runtime-0.7.0.dist-info}/licenses/LICENSE
RENAMED
|
File without changes
|
{continuo_python_runtime-0.6.0.dist-info → continuo_python_runtime-0.7.0.dist-info}/licenses/NOTICE
RENAMED
|
File without changes
|