continuo-python-runtime 0.4.1__py3-none-any.whl → 0.6.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.
@@ -0,0 +1,109 @@
1
+ """Per-kind contract rules: which fields a node of each kind must carry.
2
+
3
+ Each kind is one ``KindRules`` entry composed of a script policy and a reads
4
+ rule. Adding a kind means adding one entry to ``RULES``; nothing that parses a
5
+ contract branches on the kind's name.
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+ from abc import ABC, abstractmethod
11
+ from dataclasses import dataclass
12
+ from typing import Any
13
+
14
+ from continuo_engine_contract.sql import ensure_single_read # type: ignore[import-untyped]
15
+ from sqlglot.errors import TokenError
16
+
17
+ from continuo_python_runtime.contract.model import KINDS
18
+ from continuo_python_runtime.csv_source import parse_csv_uri
19
+ from continuo_python_runtime.errors import ContractError
20
+
21
+
22
+ class ReadsRule(ABC):
23
+ """Validates a node's ``reads`` mapping and returns it unchanged."""
24
+
25
+ @abstractmethod
26
+ def validate(
27
+ self, reads: Any, label: str, *, dialect: str | None, check_reads: bool
28
+ ) -> dict[str, str]: ...
29
+
30
+
31
+ class SqlReads(ReadsRule):
32
+ """A non-empty mapping of read name -> single SELECT."""
33
+
34
+ def validate(
35
+ self, reads: Any, label: str, *, dialect: str | None, check_reads: bool
36
+ ) -> dict[str, str]:
37
+ if not isinstance(reads, dict) or not reads:
38
+ raise ContractError(
39
+ f"{label}: 'reads' must be a non-empty mapping of name -> SQL"
40
+ )
41
+ for name, sql in reads.items():
42
+ if not isinstance(name, str) or not name.strip():
43
+ raise ContractError(
44
+ f"{label}: 'reads' name {name!r} must be a non-empty string"
45
+ )
46
+ if not isinstance(sql, str) or not sql.strip():
47
+ raise ContractError(
48
+ f"{label}: 'reads.{name}' must be a non-empty SQL string"
49
+ )
50
+ if not check_reads:
51
+ continue
52
+ try:
53
+ ensure_single_read(sql, dialect)
54
+ except (ValueError, TokenError) as exc:
55
+ # ensure_single_read's own message is phrased for check_binds
56
+ # (its only other caller today), so it's wrapped rather than
57
+ # surfaced bare here. TokenError is also caught: an unterminated
58
+ # string literal or comment fails sqlglot's tokenizer with a
59
+ # TokenError, a SqlglotError sibling of ParseError and not a
60
+ # subclass of ValueError -- despite ensure_single_read's
61
+ # docstring promising every rejection is a ValueError. Only
62
+ # TokenError, not the broader SqlglotError, is caught here: by
63
+ # the time control reaches this point `dialect` has already
64
+ # been validated once in load_contract_dir, so any other
65
+ # SqlglotError a future sqlglot version might raise from this
66
+ # call should surface as itself, not get relabeled as a
67
+ # rejected read.
68
+ raise ContractError(
69
+ f"{label}: 'reads.{name}' must be a single read query ({exc})"
70
+ ) from exc
71
+ return dict(reads)
72
+
73
+
74
+ class CsvRead(ReadsRule):
75
+ """Exactly ``{csv: <uri>}``, the uri in the grammar ``parse_csv_uri`` accepts."""
76
+
77
+ def validate(
78
+ self, reads: Any, label: str, *, dialect: str | None, check_reads: bool
79
+ ) -> dict[str, str]:
80
+ if not isinstance(reads, dict) or set(reads) != {"csv"}:
81
+ raise ContractError(
82
+ f"{label}: a python-csv node's 'reads' must be exactly {{csv: <uri>}}"
83
+ )
84
+ try:
85
+ parse_csv_uri(reads["csv"])
86
+ except (ValueError, TypeError) as exc:
87
+ raise ContractError(f"{label}: invalid csv uri: {exc}") from exc
88
+ return dict(reads)
89
+
90
+
91
+ @dataclass(frozen=True)
92
+ class KindRules:
93
+ """What a node of one kind must declare."""
94
+
95
+ script_required: bool
96
+ reads: ReadsRule
97
+
98
+
99
+ RULES: dict[str, KindRules] = {
100
+ "python-node": KindRules(script_required=True, reads=SqlReads()),
101
+ "python-csv": KindRules(script_required=False, reads=CsvRead()),
102
+ }
103
+
104
+
105
+ def rules_for(kind: object, label: str) -> KindRules:
106
+ """Return the rules for ``kind``; raise ``ContractError`` for any other value."""
107
+ if isinstance(kind, str) and kind in RULES:
108
+ return RULES[kind]
109
+ raise ContractError(f"{label}: 'kind' must be one of {sorted(KINDS)}, got {kind!r}")
@@ -11,18 +11,16 @@ from pathlib import Path
11
11
  from typing import Any
12
12
 
13
13
  import yaml
14
- from continuo_engine_contract.sql import ensure_single_read # type: ignore[import-untyped]
15
14
  from sqlglot import Dialect
16
- from sqlglot.errors import TokenError
17
15
 
16
+ from continuo_python_runtime.contract.kinds import rules_for
18
17
  from continuo_python_runtime.contract.model import (
19
18
  CRITICALITIES,
19
+ DEFAULT_KIND,
20
20
  EXTRA_COLUMNS_POLICIES,
21
- KINDS,
22
21
  Column,
23
22
  Node,
24
23
  )
25
- from continuo_python_runtime.csv_source import parse_csv_uri
26
24
  from continuo_python_runtime.errors import ContractError
27
25
  from continuo_python_runtime.types import parse_sql_type
28
26
 
@@ -173,11 +171,8 @@ def parse_node(
173
171
  if unknown:
174
172
  raise ContractError(f"{label}: unknown key(s) {sorted(unknown)}")
175
173
 
176
- kind = raw.get("kind", "python-model")
177
- if not isinstance(kind, str) or kind not in KINDS:
178
- raise ContractError(
179
- f"{label}: 'kind' must be one of {sorted(KINDS)}, got {kind!r}"
180
- )
174
+ kind = raw.get("kind", DEFAULT_KIND)
175
+ rules = rules_for(kind, label)
181
176
 
182
177
  for field in _REQUIRED_STRING_FIELDS:
183
178
  value = raw.get(field)
@@ -191,20 +186,20 @@ def parse_node(
191
186
  owner = raw["owner"]
192
187
  schedule = raw["schedule"]
193
188
 
194
- if kind == "python-csv":
195
- if "script" in raw:
196
- raise ContractError(
197
- f"{label}: 'script' is forbidden for kind python-csv "
198
- "(csv nodes are contract-only)"
199
- )
200
- script = ""
201
- else:
189
+ if rules.script_required:
202
190
  raw_script = raw.get("script")
203
191
  if not isinstance(raw_script, str) or not raw_script.strip():
204
192
  raise ContractError(
205
193
  f"{label}: required field 'script' must be a non-empty string"
206
194
  )
207
195
  script = raw_script
196
+ else:
197
+ if "script" in raw:
198
+ raise ContractError(
199
+ f"{label}: 'script' is forbidden for kind {kind} "
200
+ "(it is a contract-only kind)"
201
+ )
202
+ script = ""
208
203
 
209
204
  criticality = raw.get("criticality")
210
205
  if not isinstance(criticality, str) or criticality not in CRITICALITIES:
@@ -222,51 +217,9 @@ def parse_node(
222
217
  f"got {extra_columns!r}"
223
218
  )
224
219
 
225
- reads = raw.get("reads")
226
- if kind == "python-csv":
227
- if not isinstance(reads, dict) or set(reads) != {"csv"}:
228
- raise ContractError(
229
- f"{label}: a python-csv node's 'reads' must be exactly {{csv: <uri>}}"
230
- )
231
- try:
232
- parse_csv_uri(reads["csv"])
233
- except (ValueError, TypeError) as exc:
234
- raise ContractError(f"{label}: invalid csv uri: {exc}") from exc
235
- else:
236
- if not isinstance(reads, dict) or not reads:
237
- raise ContractError(
238
- f"{label}: 'reads' must be a non-empty mapping of name -> SQL"
239
- )
240
- for name, sql in reads.items():
241
- if not isinstance(name, str) or not name.strip():
242
- raise ContractError(
243
- f"{label}: 'reads' name {name!r} must be a non-empty string"
244
- )
245
- if not isinstance(sql, str) or not sql.strip():
246
- raise ContractError(
247
- f"{label}: 'reads.{name}' must be a non-empty SQL string"
248
- )
249
- if not check_reads:
250
- continue
251
- try:
252
- ensure_single_read(sql, dialect)
253
- except (ValueError, TokenError) as exc:
254
- # ensure_single_read's own message is phrased for check_binds
255
- # (its only other caller today), so it's wrapped rather than
256
- # surfaced bare here. TokenError is also caught: an unterminated
257
- # string literal or comment fails sqlglot's tokenizer with a
258
- # TokenError, a SqlglotError sibling of ParseError and not a
259
- # subclass of ValueError -- despite ensure_single_read's
260
- # docstring promising every rejection is a ValueError. Only
261
- # TokenError, not the broader SqlglotError, is caught here: by
262
- # the time control reaches this point `dialect` has already
263
- # been validated once in load_contract_dir, so any other
264
- # SqlglotError a future sqlglot version might raise from this
265
- # call should surface as itself, not get relabeled as a
266
- # rejected read.
267
- raise ContractError(
268
- f"{label}: 'reads.{name}' must be a single read query ({exc})"
269
- ) from exc
220
+ reads = rules.reads.validate(
221
+ raw.get("reads"), label, dialect=dialect, check_reads=check_reads
222
+ )
270
223
 
271
224
  raw_columns = raw.get("output_columns")
272
225
  if not isinstance(raw_columns, list) or not raw_columns:
@@ -325,7 +278,7 @@ def parse_node(
325
278
  schedule=schedule,
326
279
  criticality=criticality,
327
280
  script=script,
328
- reads=dict(reads),
281
+ reads=reads,
329
282
  output_columns=tuple(columns),
330
283
  description=description,
331
284
  extra_columns=extra_columns,
@@ -1,5 +1,6 @@
1
1
  """Contract v1 merger: wire contract builder."""
2
2
 
3
+ from collections.abc import Callable
3
4
  from pathlib import Path
4
5
 
5
6
  import yaml
@@ -110,6 +111,29 @@ def _lint_node_closure(
110
111
  )
111
112
 
112
113
 
114
+ def _script_hash_inputs(node: Node, repo_root: Path) -> tuple[bytes, list[bytes]]:
115
+ """A script node's source is its script plus its in-repo import closure,
116
+ every file of which is held to the closure lint."""
117
+ script_path = resolve_script_path(node.script, repo_root, context=node.relation)
118
+ script_bytes = script_path.read_bytes()
119
+ closure = resolve_closure(script_path, repo_root)
120
+ member_bytes = [member.read_bytes() for member in closure]
121
+ _lint_node_closure(node, repo_root, script_path, script_bytes, closure, member_bytes)
122
+ return script_bytes, member_bytes
123
+
124
+
125
+ def _csv_hash_inputs(node: Node, repo_root: Path) -> tuple[bytes, list[bytes]]:
126
+ """A csv node has no script and no import closure: its source is the uri.
127
+ New file content at the same uri is new data, not a new node version."""
128
+ return node.reads["csv"].encode(), []
129
+
130
+
131
+ _HASH_INPUTS: dict[str, Callable[[Node, Path], tuple[bytes, list[bytes]]]] = {
132
+ "python-node": _script_hash_inputs,
133
+ "python-csv": _csv_hash_inputs,
134
+ }
135
+
136
+
113
137
  def build_wire_contract(
114
138
  contract_dir: Path, repo_root: Path, service: str, *, dialect: str | None = None
115
139
  ) -> dict:
@@ -129,19 +153,8 @@ def build_wire_contract(
129
153
  for node in nodes:
130
154
  entry = node_entry(node)
131
155
 
132
- if node.kind == "python-csv":
133
- # A csv node has no script and no import closure: its source IS
134
- # the uri -- new file content at the same uri is new data, not a
135
- # new node version.
136
- uri_bytes = node.reads["csv"].encode()
137
- entry.update(hash_parts(entry, uri_bytes, []))
138
- else:
139
- script_path = resolve_script_path(node.script, repo_root, context=node.relation)
140
- script_bytes = script_path.read_bytes()
141
- closure = resolve_closure(script_path, repo_root)
142
- member_bytes = [member.read_bytes() for member in closure]
143
- _lint_node_closure(node, repo_root, script_path, script_bytes, closure, member_bytes)
144
- entry.update(hash_parts(entry, script_bytes, member_bytes))
156
+ primary_bytes, member_bytes = _HASH_INPUTS[node.kind](node, repo_root)
157
+ entry.update(hash_parts(entry, primary_bytes, member_bytes))
145
158
 
146
159
  wire_nodes.append(entry)
147
160
 
@@ -6,7 +6,8 @@ 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-model", "python-csv"})
9
+ KINDS = frozenset({"python-node", "python-csv"})
10
+ DEFAULT_KIND = "python-node"
10
11
  CONTRACT_VERSION = 1
11
12
 
12
13
 
@@ -35,7 +36,7 @@ class Node:
35
36
  extra_columns: str = "raise"
36
37
  config: dict[str, Any] = field(default_factory=dict)
37
38
  content_hash: str | None = None
38
- kind: str = "python-model"
39
+ kind: str = DEFAULT_KIND
39
40
 
40
41
  @property
41
42
  def relation(self) -> str:
@@ -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-model node. It resolves the
3
+ ``run_node`` is the sole write sink for a python-node 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
@@ -16,7 +16,7 @@ import logging
16
16
  import os
17
17
  import sys
18
18
  import uuid
19
- from collections.abc import Mapping
19
+ from collections.abc import Callable, Mapping
20
20
  from pathlib import Path
21
21
  from types import ModuleType
22
22
  from typing import Any
@@ -208,15 +208,36 @@ def _validate_config_early(adapter: Any, node: Node) -> None:
208
208
  ) from exc
209
209
 
210
210
 
211
+ def _produce_from_script(
212
+ node: Node, adapter: Any, app_root: Path, reader: CsvSourceReader | None
213
+ ) -> Any:
214
+ """Import the node's script and return its ``run(ctx)`` result as Arrow."""
215
+ with contextlib.redirect_stdout(sys.stderr):
216
+ module = load_script(node, app_root)
217
+ ctx = RunContext(node, adapter)
218
+ return to_arrow(_execute_script(module, ctx))
219
+
220
+
221
+ def _produce_from_csv(
222
+ node: Node, adapter: Any, app_root: Path, reader: CsvSourceReader | None
223
+ ) -> Any:
224
+ """Load the node's declared csv source."""
225
+ return produce_csv(node, reader=reader)
226
+
227
+
228
+ _PRODUCERS: dict[str, Callable[[Node, Any, Path, CsvSourceReader | None], Any]] = {
229
+ "python-node": _produce_from_script,
230
+ "python-csv": _produce_from_csv,
231
+ }
232
+
233
+
211
234
  def run_node(
212
235
  env: Mapping[str, str], adapter: Any = None, reader: CsvSourceReader | None = None
213
236
  ) -> int:
214
237
  """Run a single node end-to-end and print exactly one sentinel result block.
215
238
 
216
- ``reader`` mirrors the ``adapter`` injection seam: when given, it is
217
- threaded into :func:`produce_csv` for a python-csv node instead of
218
- letting that function pick a reader via ``reader_for``. Ignored for a
219
- python-model node.
239
+ ``reader`` is threaded into the python-csv producer instead of letting
240
+ it pick a reader via ``reader_for``; the script producer ignores it.
220
241
 
221
242
  Returns 0 on success, 1 on any :class:`HarnessError`.
222
243
  """
@@ -255,14 +276,7 @@ def run_node(
255
276
 
256
277
  _validate_config_early(active_adapter, node)
257
278
 
258
- if node.kind == "python-csv":
259
- table = produce_csv(node, reader=reader)
260
- else:
261
- with contextlib.redirect_stdout(sys.stderr):
262
- module = load_script(node, app_root)
263
- ctx = RunContext(node, active_adapter)
264
- raw_result = _execute_script(module, ctx)
265
- table = to_arrow(raw_result)
279
+ table = _PRODUCERS[node.kind](node, active_adapter, app_root, reader)
266
280
 
267
281
  conformed = conform(table, node.output_columns, node.extra_columns)
268
282
 
@@ -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.1
3
+ Version: 0.6.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,7 +11,7 @@ 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.74
14
+ Requires-Dist: boto3==1.43.85
15
15
  Requires-Dist: continuo-engine-contract==0.7.3
16
16
  Requires-Dist: pyarrow==25.0.1
17
17
  Requires-Dist: pyyaml==6.0.3
@@ -163,7 +163,7 @@ Every rule below (`extra_columns`, `output_columns`, "Conform rules") applies
163
163
  to both kinds identically — `kind` only changes how the pre-conform table is
164
164
  produced, never how it is checked or written.
165
165
 
166
- - **`python-model`** (the default; the field may be omitted) — a script node.
166
+ - **`python-node`** (the default; the field may be omitted) — a script node.
167
167
  It requires `script:` and a `reads:` map of one or more named SQL queries,
168
168
  as described in "The script API" below.
169
169
  - **`python-csv`** — a contract-only node: it has no script and its `reads:`
@@ -275,9 +275,9 @@ A domain repo picks its warehouse engine by which base image it builds
275
275
  `FROM`:
276
276
 
277
277
  ```dockerfile
278
- FROM ghcr.io/carolsimone/continuo-python-runtime-postgres:v0.4.1
278
+ FROM ghcr.io/carolsimone/continuo-python-runtime-postgres:v0.6.0
279
279
  # or
280
- FROM ghcr.io/carolsimone/continuo-python-runtime-trino:v0.4.1
280
+ FROM ghcr.io/carolsimone/continuo-python-runtime-trino:v0.6.0
281
281
  ```
282
282
 
283
283
  The engine is part of the image **name**; the tag is the bare version, so
@@ -6,24 +6,25 @@ 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=BBJBmmiQ922WVmPi0Xh7CFnXjNxKefPey0YOEHercjs,12634
9
+ continuo_python_runtime/harness.py,sha256=1NDlhHVoDF9Q1jg3LQmhA56XgRZCBUHzcknntgOwqV0,13011
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/loader.py,sha256=oUer1sbaxhUMNQVUuqO5j5vbQCrgwuHOWJzGe0MHSLg,16973
15
- continuo_python_runtime/contract/merge.py,sha256=36awQV8cHGcQJv6XJGC_mC5MuyRS5-7XTVTLw0sjMgE,6903
16
- continuo_python_runtime/contract/model.py,sha256=L7GwSYKrh6Z0J1c1zWFHQw8GzYUUzzWycuBw5Qq5Dfg,1017
14
+ continuo_python_runtime/contract/kinds.py,sha256=nFyCZplwpxYK8AKzKRfJp0-V49jFVZcJAW8Q4-3ezHE,4338
15
+ continuo_python_runtime/contract/loader.py,sha256=1JARlZJSgadN0h_wvvhFV9q5nqwoDbtvwVsAGDSiXg8,14491
16
+ continuo_python_runtime/contract/merge.py,sha256=QjHavaFe_HviR3vPtAmF5-4_3XdmgNbhl-JmrpVM8iI,7323
17
+ continuo_python_runtime/contract/model.py,sha256=-tf5DFqRidMx_Pt8NWyJZ9i8Yystg9-YqBjqHAeX4Xg,1043
17
18
  continuo_python_runtime/contract/paths.py,sha256=SRn70pJkDAIpXw0IcMU2iNKPKvct8w_SZP2ec-lV91M,1458
18
19
  continuo_python_runtime/csv_readers/__init__.py,sha256=9TKvjC37C77uRaJQZuO0--zVj2OvuNwpJE6nWx7j2S8,806
19
20
  continuo_python_runtime/csv_readers/https.py,sha256=tMJ9k5F0b9SAydpon0ZlOznfjvjhF2BBLltyVmvRneE,4998
20
21
  continuo_python_runtime/csv_readers/s3.py,sha256=zmvHs3TxlN2alPKrdaHzPyREmkve2nBiAsHRXXe1mE0,2246
21
22
  continuo_python_runtime/validation/__init__.py,sha256=hz5oGXsoaUoygR0_pBby9PS6cEfrre518EE0HGB58eE,79
22
- continuo_python_runtime/validation/runner.py,sha256=AwYbiFrQubxSm1vfxJJpnp7xfekviUW2anw4LRQQ4Rc,13062
23
+ continuo_python_runtime/validation/runner.py,sha256=7pbksf7MXfTNcdklCOkOXg9qfRiJtyI_dbgbL3a9LWk,14072
23
24
  continuo_python_runtime/validation/s3.py,sha256=q8nRrT9R7L1-M4dSlDnkL7CUJMPmq86MHY_UKYFlEf8,1785
24
- continuo_python_runtime-0.4.1.dist-info/METADATA,sha256=EjG8OIYrN1O51KOsUxIA0Ln6zpsWdaLJheUcCaRkqD8,18811
25
- continuo_python_runtime-0.4.1.dist-info/WHEEL,sha256=zOwg4jB6zX2kU910N-cMawjivD6tO8NEWvE12je1bVk,87
26
- continuo_python_runtime-0.4.1.dist-info/entry_points.txt,sha256=ZvIwSm9f9B9ZmZMf6CyF5Zt4QONn8rqaIk-u-B1CF2Y,70
27
- continuo_python_runtime-0.4.1.dist-info/licenses/LICENSE,sha256=hfXfFCk-8gnps4YvHA6bjCNaSYqOrq2gAdqkXYycAQc,11345
28
- continuo_python_runtime-0.4.1.dist-info/licenses/NOTICE,sha256=ykgYyQkAMx3uas90FZHzt0aTca9IgJQCLpZ4d74Pl3U,465
29
- continuo_python_runtime-0.4.1.dist-info/RECORD,,
25
+ continuo_python_runtime-0.6.0.dist-info/METADATA,sha256=kX73HGlQoFnSWLVicVKPNyVo-ou6HDH4360MHrcduaM,18810
26
+ continuo_python_runtime-0.6.0.dist-info/WHEEL,sha256=W3fkpkm7-wf9vBI5Z-7s0eWkeM-spu78I8Neb98DeEg,87
27
+ continuo_python_runtime-0.6.0.dist-info/entry_points.txt,sha256=ZvIwSm9f9B9ZmZMf6CyF5Zt4QONn8rqaIk-u-B1CF2Y,70
28
+ continuo_python_runtime-0.6.0.dist-info/licenses/LICENSE,sha256=hfXfFCk-8gnps4YvHA6bjCNaSYqOrq2gAdqkXYycAQc,11345
29
+ continuo_python_runtime-0.6.0.dist-info/licenses/NOTICE,sha256=ykgYyQkAMx3uas90FZHzt0aTca9IgJQCLpZ4d74Pl3U,465
30
+ continuo_python_runtime-0.6.0.dist-info/RECORD,,
@@ -1,4 +1,4 @@
1
1
  Wheel-Version: 1.0
2
- Generator: hatchling 1.32.0
2
+ Generator: hatchling 1.32.4
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any