chtypes 2.0.0.dev2__py3-none-any.whl → 2.0.0.dev3__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.
- chtypes/__init__.py +4 -0
- chtypes/_abi2/_decls.py +2 -2
- chtypes/_abi2/_errmap.py +1 -1
- chtypes/_abi2/_vocab.py +30 -5
- chtypes/_decode.py +27 -1
- chtypes/_ocifetch/_channel.py +72 -0
- chtypes/_ocifetch/_ensure.py +20 -2
- chtypes/_ocifetch/_http.py +18 -0
- chtypes/_ocifetch/_oci.py +30 -2
- chtypes/results.py +35 -1
- {chtypes-2.0.0.dev2.dist-info → chtypes-2.0.0.dev3.dist-info}/METADATA +2 -2
- {chtypes-2.0.0.dev2.dist-info → chtypes-2.0.0.dev3.dist-info}/RECORD +15 -15
- {chtypes-2.0.0.dev2.dist-info → chtypes-2.0.0.dev3.dist-info}/WHEEL +0 -0
- {chtypes-2.0.0.dev2.dist-info → chtypes-2.0.0.dev3.dist-info}/entry_points.txt +0 -0
- {chtypes-2.0.0.dev2.dist-info → chtypes-2.0.0.dev3.dist-info}/licenses/LICENSE +0 -0
chtypes/__init__.py
CHANGED
|
@@ -53,6 +53,7 @@ from ._abi2._vocab import (
|
|
|
53
53
|
DocFlags,
|
|
54
54
|
FilterOutcome,
|
|
55
55
|
Format,
|
|
56
|
+
MergeReason,
|
|
56
57
|
Outcome,
|
|
57
58
|
Reason,
|
|
58
59
|
Source,
|
|
@@ -95,6 +96,7 @@ from .errors import (
|
|
|
95
96
|
from .library import Block, Filter, Library, Schema, Server, open_unverified
|
|
96
97
|
from .registry import FetchOptions, Registry, Resolved, TrustedKey, cache_root, search_dirs
|
|
97
98
|
from .results import (
|
|
99
|
+
AtMergeEntry,
|
|
98
100
|
BatchResult,
|
|
99
101
|
BuildInfo,
|
|
100
102
|
Capabilities,
|
|
@@ -138,6 +140,7 @@ __all__ = [
|
|
|
138
140
|
"ArtifactPinnedError",
|
|
139
141
|
"ArtifactUnpublishedError",
|
|
140
142
|
"ArtifactUntrustedError",
|
|
143
|
+
"AtMergeEntry",
|
|
141
144
|
"BatchResult",
|
|
142
145
|
"Block",
|
|
143
146
|
"BuildInfo",
|
|
@@ -166,6 +169,7 @@ __all__ = [
|
|
|
166
169
|
"Header",
|
|
167
170
|
"InternalError",
|
|
168
171
|
"Library",
|
|
172
|
+
"MergeReason",
|
|
169
173
|
"Outcome",
|
|
170
174
|
"Reason",
|
|
171
175
|
"Registry",
|
chtypes/_abi2/_decls.py
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
# GENERATED by scripts/abi-v1/gen.py --major 2 from spec/abi-v2/abi.json (CHS_ABI_FINGERPRINT sha256:
|
|
1
|
+
# GENERATED by scripts/abi-v1/gen.py --major 2 from spec/abi-v2/abi.json (CHS_ABI_FINGERPRINT sha256:4129dccc3f4e698dedb0e99cae413f5240085f633c5a1244e2c55bf7a7bb6c3a) — DO NOT EDIT # noqa: E501
|
|
2
2
|
"""The Python binding's generated ABI v2 layer: ctypes signatures, the handle
|
|
3
3
|
classes, the two-phase resolve functions and invoke_by_name(), the test-only
|
|
4
4
|
dispatcher the conformance suite drives from tests/fixtures/abi-v2/cases.json.
|
|
@@ -19,7 +19,7 @@ from dataclasses import dataclass, field
|
|
|
19
19
|
from . import _errmap
|
|
20
20
|
|
|
21
21
|
CHS_ABI_VERSION = 2
|
|
22
|
-
CHS_ABI_FINGERPRINT = "sha256:
|
|
22
|
+
CHS_ABI_FINGERPRINT = "sha256:4129dccc3f4e698dedb0e99cae413f5240085f633c5a1244e2c55bf7a7bb6c3a"
|
|
23
23
|
|
|
24
24
|
# The description's stability: "unstable" while this generation is being
|
|
25
25
|
# designed, so CHS_ABI_FINGERPRINT moves with every change, and "locked" after
|
chtypes/_abi2/_errmap.py
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
# GENERATED by scripts/abi-v1/gen.py --major 2 from spec/abi-v2/abi.json (CHS_ABI_FINGERPRINT sha256:
|
|
1
|
+
# GENERATED by scripts/abi-v1/gen.py --major 2 from spec/abi-v2/abi.json (CHS_ABI_FINGERPRINT sha256:4129dccc3f4e698dedb0e99cae413f5240085f633c5a1244e2c55bf7a7bb6c3a) — DO NOT EDIT # noqa: E501
|
|
2
2
|
"""spec/abi-v2/sdk.json's error-mapping tables, as references to the
|
|
3
3
|
public classes (python/src/chtypes/errors.py, re-exported by
|
|
4
4
|
python/src/chtypes/_abi2/_errors.py). Only the MAPPING is generated: the
|
chtypes/_abi2/_vocab.py
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
# GENERATED by scripts/abi-v1/gen.py --major 2 from spec/abi-v2/abi.json (CHS_ABI_FINGERPRINT sha256:
|
|
1
|
+
# GENERATED by scripts/abi-v1/gen.py --major 2 from spec/abi-v2/abi.json (CHS_ABI_FINGERPRINT sha256:4129dccc3f4e698dedb0e99cae413f5240085f633c5a1244e2c55bf7a7bb6c3a) — DO NOT EDIT # noqa: E501
|
|
2
2
|
"""The Python binding's generated vocabularies, from spec/abi-v2/abi.json: the C
|
|
3
3
|
enums (`Format`, `Status`), the document vocabularies (`Outcome`, `FilterOutcome`,
|
|
4
|
-
`Verdict`, `DefaultKind`, `Reason`, `Source`, `DiscoverQueryParam`) with the
|
|
5
|
-
description attaches to each value (`Format.ch_name`, `Reason` lossy,
|
|
6
|
-
is_stored, `Verdict.answered`), and the `DocFlags` groups. No other file in
|
|
7
|
-
package keeps a copy of any of it.
|
|
4
|
+
`Verdict`, `DefaultKind`, `MergeReason`, `Reason`, `Source`, `DiscoverQueryParam`) with the
|
|
5
|
+
facts the description attaches to each value (`Format.ch_name`, `Reason` lossy,
|
|
6
|
+
`Source` is_stored, `Verdict.answered`), and the `DocFlags` groups. No other file in
|
|
7
|
+
this package keeps a copy of any of it.
|
|
8
8
|
|
|
9
9
|
Rule r3 (spec/abi-v2/docs.md): every vocabulary has an unknown(n) member, and a
|
|
10
10
|
value the description does not list reads as it, carrying the raw value, for that
|
|
@@ -225,6 +225,30 @@ class DefaultKind(StrEnum):
|
|
|
225
225
|
return cls(text)
|
|
226
226
|
|
|
227
227
|
|
|
228
|
+
class MergeReason(StrEnum):
|
|
229
|
+
"""What a merge would do to one row of the part an INSERT writes, as a batch's
|
|
230
|
+
`at_merge` reports it."""
|
|
231
|
+
|
|
232
|
+
TTL_DELETE = "ttl_delete"
|
|
233
|
+
TTL_COLUMN_RESET = "ttl_column_reset"
|
|
234
|
+
|
|
235
|
+
@classmethod
|
|
236
|
+
def _missing_(cls, value: object) -> MergeReason | None:
|
|
237
|
+
return _unknown_str(cls, value)
|
|
238
|
+
|
|
239
|
+
@property
|
|
240
|
+
def known(self) -> bool:
|
|
241
|
+
"""Whether the description lists this value: False for exactly the unknown(n)
|
|
242
|
+
member an unlisted value reads as (rule r3)."""
|
|
243
|
+
return type(self)._value2member_map_.get(self._value_) is self
|
|
244
|
+
|
|
245
|
+
@classmethod
|
|
246
|
+
def of(cls, text: str) -> MergeReason:
|
|
247
|
+
"""Map a document's spelling. One the description does not list is kept as
|
|
248
|
+
this vocabulary's unknown(n) member, carrying it (rule r3)."""
|
|
249
|
+
return cls(text)
|
|
250
|
+
|
|
251
|
+
|
|
228
252
|
class DiscoverQueryParam(StrEnum):
|
|
229
253
|
"""A query parameter of the SQL `discover_query` returns, which the caller binds
|
|
230
254
|
when it runs the query."""
|
|
@@ -383,6 +407,7 @@ DESCRIBED_VOCABULARIES: Final[dict[str, type]] = {
|
|
|
383
407
|
"chs_status": Status,
|
|
384
408
|
"chs_format": Format,
|
|
385
409
|
"transform_reason": Reason,
|
|
410
|
+
"merge_reason": MergeReason,
|
|
386
411
|
"value_src": Source,
|
|
387
412
|
"row_outcome": Outcome,
|
|
388
413
|
"batch_outcome": Outcome,
|
chtypes/_decode.py
CHANGED
|
@@ -28,9 +28,10 @@ import json
|
|
|
28
28
|
from collections.abc import Callable, Mapping
|
|
29
29
|
from typing import Any
|
|
30
30
|
|
|
31
|
-
from ._abi2._vocab import DefaultKind, FilterOutcome, Outcome, Reason, Source, Verdict
|
|
31
|
+
from ._abi2._vocab import DefaultKind, FilterOutcome, MergeReason, Outcome, Reason, Source, Verdict
|
|
32
32
|
from .errors import ArtifactCorruptError, internal
|
|
33
33
|
from .results import (
|
|
34
|
+
AtMergeEntry,
|
|
34
35
|
BatchResult,
|
|
35
36
|
BuildInfo,
|
|
36
37
|
Capabilities,
|
|
@@ -325,6 +326,29 @@ def _engine_cell(obj: dict[str, Any], where: str) -> EngineCell:
|
|
|
325
326
|
)
|
|
326
327
|
|
|
327
328
|
|
|
329
|
+
def _int_list(obj: Mapping[str, Any], key: str, where: str) -> tuple[int, ...] | None:
|
|
330
|
+
"""A list of non-negative integers: None when absent."""
|
|
331
|
+
if obj.get(key) is None:
|
|
332
|
+
return None
|
|
333
|
+
out = []
|
|
334
|
+
for item in _list(obj, key, where):
|
|
335
|
+
if isinstance(item, bool) or not isinstance(item, int) or item < 0:
|
|
336
|
+
raise _wrong(where, key, "an array of non-negative integers", item)
|
|
337
|
+
out.append(item)
|
|
338
|
+
return tuple(out)
|
|
339
|
+
|
|
340
|
+
|
|
341
|
+
def _at_merge(obj: dict[str, Any], where: str) -> AtMergeEntry:
|
|
342
|
+
# An unlisted reason is its unknown(n): kept, never a failure (rule r3).
|
|
343
|
+
return AtMergeEntry(
|
|
344
|
+
row=_int(obj, "row", where),
|
|
345
|
+
reason=MergeReason.of(_text(obj, "reason", where)),
|
|
346
|
+
column=_bytes_or_none(obj, "column", where),
|
|
347
|
+
stored=_bytes_or_none(obj, "stored", where),
|
|
348
|
+
input_rows=_int_list(obj, "input_rows", where),
|
|
349
|
+
)
|
|
350
|
+
|
|
351
|
+
|
|
328
352
|
def _engine_rows(obj: Mapping[str, Any], where: str) -> tuple[tuple[EngineCell, ...], ...]:
|
|
329
353
|
rows = []
|
|
330
354
|
for i, row in enumerate(_list(obj, "engine_rows", where)):
|
|
@@ -411,6 +435,8 @@ def decode_batch(raw: bytes, export: bytes | None) -> BatchResult:
|
|
|
411
435
|
framing=None
|
|
412
436
|
if framing is None
|
|
413
437
|
else _framing(_obj(framing, where, "framing"), f"{where}.framing"),
|
|
438
|
+
unsupported_settings=_name_list(obj, "unsupported_settings", where),
|
|
439
|
+
at_merge=_objects(obj, "at_merge", where, _at_merge),
|
|
414
440
|
)
|
|
415
441
|
|
|
416
442
|
|
chtypes/_ocifetch/_channel.py
CHANGED
|
@@ -18,6 +18,19 @@ five ways:
|
|
|
18
18
|
used through its subroot `<cache>/v2-dev`, never as a whole layout;
|
|
19
19
|
- a signed predicate must say abi 2.
|
|
20
20
|
|
|
21
|
+
It also resolves one tag the v1 contract never asks for: a version request
|
|
22
|
+
fetches `<tag>--fp-<its own fingerprint>` first, the newest dev build of the ABI
|
|
23
|
+
this binding speaks, and falls back to `<tag>` only when no base has that alias
|
|
24
|
+
(docs/guides/fetch-v1.md §3, "The dev channel's alias step"), so a dev build of a
|
|
25
|
+
newer fingerprint never strands this one. Its cache lookups (`resolve_installed`,
|
|
26
|
+
and `ensure`'s offline answer and its monotonic rule) see only the records whose
|
|
27
|
+
signed `abi_fingerprint` is its own: a build of another fingerprint in a shared
|
|
28
|
+
cache is never returned and never kept over this one's own, and it is never
|
|
29
|
+
removed or rewritten either. It is automatic, with no override; the trust checks
|
|
30
|
+
are unchanged, and a listing never shows an alias. The fingerprint
|
|
31
|
+
is the generated `CHS_ABI_FINGERPRINT` of the ABI layer (`chtypes._abi2._decls`),
|
|
32
|
+
never a hand-written copy.
|
|
33
|
+
|
|
21
34
|
The v1 contract itself stays in this package, unchanged, because the fetch-v1
|
|
22
35
|
conformance cases (tests/fixtures/fetch-v1) are its specification and the dev
|
|
23
36
|
channel shares every other rule with it. Only a test run reaches it:
|
|
@@ -29,16 +42,19 @@ override a user can reach.
|
|
|
29
42
|
from __future__ import annotations
|
|
30
43
|
|
|
31
44
|
import os
|
|
45
|
+
import re
|
|
32
46
|
import sys
|
|
33
47
|
import threading
|
|
34
48
|
from collections.abc import Callable, Sequence
|
|
35
49
|
from dataclasses import dataclass, replace
|
|
36
50
|
from typing import Final, Protocol
|
|
37
51
|
|
|
52
|
+
from chtypes._abi2._decls import CHS_ABI_FINGERPRINT
|
|
38
53
|
from chtypes._ocifetch import _constants as C
|
|
39
54
|
from chtypes._ocifetch._dsse import TrustedKey
|
|
40
55
|
|
|
41
56
|
__all__ = [
|
|
57
|
+
"ALIAS_SEPARATOR",
|
|
42
58
|
"DEV_ABI_GENERATION",
|
|
43
59
|
"DEV_CACHE_DIR",
|
|
44
60
|
"DEV_CHANNEL_BASE",
|
|
@@ -50,12 +66,15 @@ __all__ = [
|
|
|
50
66
|
"PinningRefusedError",
|
|
51
67
|
"abi",
|
|
52
68
|
"active",
|
|
69
|
+
"alias_tag",
|
|
53
70
|
"allow_overrides_for_tests",
|
|
54
71
|
"channel_name",
|
|
55
72
|
"enforce",
|
|
56
73
|
"ignored_settings_warned",
|
|
57
74
|
"pinning_requested",
|
|
58
75
|
"trusted_keys",
|
|
76
|
+
"use_own_fingerprint_for_tests",
|
|
77
|
+
"visible",
|
|
59
78
|
"use_dev_channel_for_tests",
|
|
60
79
|
"use_fetch_v1_for_tests",
|
|
61
80
|
]
|
|
@@ -79,6 +98,10 @@ DEV_RECORD_SCHEMA: Final = 2
|
|
|
79
98
|
# The abi a dev predicate must carry.
|
|
80
99
|
DEV_ABI_GENERATION: Final = 2
|
|
81
100
|
|
|
101
|
+
# Joins a tag and a fingerprint into the dev channel's alias tag:
|
|
102
|
+
# <tag>--fp-<64 lowercase hex> (docs/guides/fetch-v1.md §3).
|
|
103
|
+
ALIAS_SEPARATOR: Final = "--fp-"
|
|
104
|
+
|
|
82
105
|
# What every lock, frozen or update request gets from a 2.0.0-dev SDK, before
|
|
83
106
|
# any network call (rule r6).
|
|
84
107
|
PINNING_REFUSED: Final = (
|
|
@@ -102,6 +125,11 @@ class Channel:
|
|
|
102
125
|
keys: tuple[dict[str, str], ...] # {"keyid", "ed25519_hex"}: the default trust
|
|
103
126
|
overridable: bool # the base, trust and unsigned overrides are honored
|
|
104
127
|
pinnable: bool # lock, frozen and update are honored
|
|
128
|
+
# The fingerprint, 64 lowercase hex, this contract's SDK speaks: a version
|
|
129
|
+
# request resolves its alias tag before the tag itself, and the cache
|
|
130
|
+
# lookups see only records signed with it. "" does neither (the v1 contract
|
|
131
|
+
# and every production channel).
|
|
132
|
+
own_fingerprint: str = ""
|
|
105
133
|
|
|
106
134
|
|
|
107
135
|
# What a 2.0.0-dev SDK speaks, and what every process speaks unless a test
|
|
@@ -117,6 +145,7 @@ DEV_CHANNEL: Final = Channel(
|
|
|
117
145
|
keys=({"keyid": DEV_KEY_ID, "ed25519_hex": DEV_KEY_HEX},),
|
|
118
146
|
overridable=False,
|
|
119
147
|
pinnable=False,
|
|
148
|
+
own_fingerprint=CHS_ABI_FINGERPRINT.removeprefix("sha256:"),
|
|
120
149
|
)
|
|
121
150
|
|
|
122
151
|
# The v1 contract the fetch-v1 conformance cases specify, from the generated
|
|
@@ -202,6 +231,49 @@ def allow_overrides_for_tests() -> Callable[[], None]:
|
|
|
202
231
|
return _use(replace(DEV_CHANNEL, overridable=True))
|
|
203
232
|
|
|
204
233
|
|
|
234
|
+
_FINGERPRINT_HEX = re.compile(r"[0-9a-f]{64}")
|
|
235
|
+
|
|
236
|
+
|
|
237
|
+
def use_own_fingerprint_for_tests(fingerprint: str) -> Callable[[], None]:
|
|
238
|
+
"""Make this TEST run's active contract speak `fingerprint` (64 lowercase
|
|
239
|
+
hex) as the dev channel speaks its own: a version request resolves that
|
|
240
|
+
fingerprint's alias tag first, and the cache lookups see only records
|
|
241
|
+
signed with it. The fetch-v1 conformance cases that carry
|
|
242
|
+
`request.own_fingerprint` run under it, against fixtures that name a
|
|
243
|
+
fixture fingerprint. Everything else stays the active contract's. Returns
|
|
244
|
+
the restore function; raises outside a test run or on a fingerprint that is
|
|
245
|
+
not 64 lowercase hex."""
|
|
246
|
+
_test_only("use_own_fingerprint_for_tests")
|
|
247
|
+
if not _FINGERPRINT_HEX.fullmatch(fingerprint):
|
|
248
|
+
raise ValueError(f"use_own_fingerprint_for_tests: {fingerprint!r} is not 64 lowercase hex")
|
|
249
|
+
return _use(replace(active(), own_fingerprint=fingerprint))
|
|
250
|
+
|
|
251
|
+
|
|
252
|
+
def alias_tag(tag: str, channel: Channel | None = None) -> str | None:
|
|
253
|
+
"""The alias a version request for `tag` resolves first under a contract
|
|
254
|
+
with an own fingerprint, or None when it resolves `tag` alone: under the
|
|
255
|
+
v1 contract and every production channel, and for a request that is not a
|
|
256
|
+
version spelling (an arbitrary tag has no alias)."""
|
|
257
|
+
channel = channel or active()
|
|
258
|
+
if not channel.own_fingerprint or not re.fullmatch(C.SPELLING_REGEX, tag):
|
|
259
|
+
return None
|
|
260
|
+
return f"{tag}{ALIAS_SEPARATOR}{channel.own_fingerprint}"
|
|
261
|
+
|
|
262
|
+
|
|
263
|
+
def visible(predicate: object, channel: Channel | None = None) -> bool:
|
|
264
|
+
"""Whether a cache record (or a pre-seeded entry) whose SIGNED predicate is
|
|
265
|
+
`predicate` may answer a request under the active contract: under one with
|
|
266
|
+
an own fingerprint (the dev channel), only when the predicate's
|
|
267
|
+
`abi_fingerprint` is that fingerprint; under every other, always. A record
|
|
268
|
+
it cannot see is never returned and never kept by the monotonic rule, and
|
|
269
|
+
nothing removes or rewrites it: another SDK of another fingerprint owns it."""
|
|
270
|
+
channel = channel or active()
|
|
271
|
+
if not channel.own_fingerprint:
|
|
272
|
+
return True
|
|
273
|
+
fingerprint = predicate.get("abi_fingerprint") if isinstance(predicate, dict) else None
|
|
274
|
+
return fingerprint == f"sha256:{channel.own_fingerprint}"
|
|
275
|
+
|
|
276
|
+
|
|
205
277
|
def use_dev_channel_for_tests() -> Callable[[], None]:
|
|
206
278
|
"""Make this TEST run speak the dev channel exactly as a user's process does
|
|
207
279
|
(undoing either function above, for a test of the dev channel itself).
|
chtypes/_ocifetch/_ensure.py
CHANGED
|
@@ -355,6 +355,10 @@ def _newer_installed(
|
|
|
355
355
|
(`26.3.4.1`) keeps only a newer BUILD of that exact version. A request
|
|
356
356
|
that is not a version spelling (an arbitrary tag) names no range, so
|
|
357
357
|
nothing is kept for it.
|
|
358
|
+
|
|
359
|
+
Under the dev channel only an install of its own fingerprint counts
|
|
360
|
+
(`_channel.visible`): a newer build of another fingerprint, installed by
|
|
361
|
+
another dev SDK, is never kept over this one's own.
|
|
358
362
|
"""
|
|
359
363
|
if not re.match(C.SPELLING_REGEX, request_spelling):
|
|
360
364
|
return None
|
|
@@ -368,6 +372,8 @@ def _newer_installed(
|
|
|
368
372
|
for dir_, record in list_verified_records(roots):
|
|
369
373
|
if record.platform != platform_key:
|
|
370
374
|
continue
|
|
375
|
+
if not _channel.visible(record.predicate):
|
|
376
|
+
continue # another fingerprint's build (_channel.visible): never kept over this one's
|
|
371
377
|
if not version_within_request(record.version, request_spelling):
|
|
372
378
|
continue
|
|
373
379
|
version_key = _version_key(record.version)
|
|
@@ -693,7 +699,11 @@ def _ensure_floating(
|
|
|
693
699
|
scratch_root = _scratch_root(cache_root_path)
|
|
694
700
|
|
|
695
701
|
index_doc, index_bytes, index_digest = fetch_manifest_by_tag(
|
|
696
|
-
bases,
|
|
702
|
+
bases,
|
|
703
|
+
request.spelling,
|
|
704
|
+
policy=policy,
|
|
705
|
+
retry=retry,
|
|
706
|
+
alias=_channel.alias_tag(request.spelling),
|
|
697
707
|
)
|
|
698
708
|
platform_desc = resolve_platform_manifest(index_doc, platform_key)
|
|
699
709
|
manifest_hex = parse_digest(platform_desc.digest)
|
|
@@ -1050,6 +1060,7 @@ def _verify_and_install_from_local_blobs(
|
|
|
1050
1060
|
manifest_digest: str,
|
|
1051
1061
|
cache_write_root: Path,
|
|
1052
1062
|
trusted_keys: tuple[TrustedKey, ...],
|
|
1063
|
+
visible_only: bool = False,
|
|
1053
1064
|
) -> VerifiedRecord | None:
|
|
1054
1065
|
"""docs/guides/fetch-v1.md §1/§6: "A pre-seeded layout has entries in
|
|
1055
1066
|
`index.json` with no corresponding `unpacked/` directory yet; the first
|
|
@@ -1103,6 +1114,9 @@ def _verify_and_install_from_local_blobs(
|
|
|
1103
1114
|
return None
|
|
1104
1115
|
|
|
1105
1116
|
predicate = verified.statement.predicate
|
|
1117
|
+
if visible_only and not _channel.visible(predicate):
|
|
1118
|
+
# A lookup never installs another fingerprint's pre-seeded build.
|
|
1119
|
+
return None
|
|
1106
1120
|
layer_hex = parse_digest(layer_desc.digest)
|
|
1107
1121
|
if layer_hex not in verified.statement.subject_sha256:
|
|
1108
1122
|
return None
|
|
@@ -1180,6 +1194,7 @@ def _verify_preseeded_entries(roots: Sequence[Path], trusted_keys: tuple[Trusted
|
|
|
1180
1194
|
manifest_digest=digest,
|
|
1181
1195
|
cache_write_root=cache_write_root,
|
|
1182
1196
|
trusted_keys=trusted_keys,
|
|
1197
|
+
visible_only=True,
|
|
1183
1198
|
)
|
|
1184
1199
|
if record is not None:
|
|
1185
1200
|
already_verified.add(digest)
|
|
@@ -1224,7 +1239,10 @@ def resolve_installed(request: Request, platform: str, options: Options) -> Reso
|
|
|
1224
1239
|
candidates = [
|
|
1225
1240
|
(dir_path, record)
|
|
1226
1241
|
for dir_path, record in list_verified_records(roots)
|
|
1227
|
-
if record.platform == platform
|
|
1242
|
+
if record.platform == platform
|
|
1243
|
+
and version_within_request(record.version, request.spelling)
|
|
1244
|
+
# Under the dev channel, only its own fingerprint's builds (_channel.visible).
|
|
1245
|
+
and _channel.visible(record.predicate)
|
|
1228
1246
|
]
|
|
1229
1247
|
if not candidates:
|
|
1230
1248
|
return None
|
chtypes/_ocifetch/_http.py
CHANGED
|
@@ -88,6 +88,12 @@ class NotFoundHttpError(TransportError):
|
|
|
88
88
|
"""A 404 (or, for a `file://` base, a missing path)."""
|
|
89
89
|
|
|
90
90
|
|
|
91
|
+
class AliasNotFoundError(TransportError):
|
|
92
|
+
"""`mode="alias"`: every configured base answered the alias tag 404 (or,
|
|
93
|
+
for a `file://` base, has no such path). The one alias outcome the caller
|
|
94
|
+
answers with the tag itself (docs/guides/fetch-v1.md §3)."""
|
|
95
|
+
|
|
96
|
+
|
|
91
97
|
class UnauthorizedHttpError(TransportError):
|
|
92
98
|
"""A 401 that is not (or is no longer) resolvable by the anonymous token flow."""
|
|
93
99
|
|
|
@@ -506,6 +512,12 @@ def fetch_from_bases(
|
|
|
506
512
|
where it is retried within the shared budget before giving up — a
|
|
507
513
|
listed digest that 404s is a host fault, never "unpublished", so the
|
|
508
514
|
final exception for this mode is always `UnreachableHttpError`.
|
|
515
|
+
- `alias`: the dev channel's alias tag (docs/guides/fetch-v1.md §3). A 404
|
|
516
|
+
moves to the next base, as for a tag, but only a 404 on EVERY base is
|
|
517
|
+
"not found" (`AliasNotFoundError`, which the caller answers with the tag
|
|
518
|
+
itself). Any other failure on any base is raised as that failure, as the
|
|
519
|
+
tag would get it, so a transient error can never route a request to the
|
|
520
|
+
tag, which may name a build of another fingerprint.
|
|
509
521
|
|
|
510
522
|
`decided-here` (the spec does not state it): when bases disagree — some
|
|
511
523
|
404, some unreachable — the outcome follows the LAST base tried, since
|
|
@@ -516,6 +528,7 @@ def fetch_from_bases(
|
|
|
516
528
|
raise UnreachableHttpError("no base URLs configured")
|
|
517
529
|
last_index = len(bases) - 1
|
|
518
530
|
last_error: TransportError | None = None
|
|
531
|
+
alias_failure: TransportError | None = None # mode="alias": a base's non-404 failure
|
|
519
532
|
for i, base in enumerate(bases):
|
|
520
533
|
is_last = i == last_index
|
|
521
534
|
scheme = urlsplit(base).scheme
|
|
@@ -538,8 +551,13 @@ def fetch_from_bases(
|
|
|
538
551
|
continue
|
|
539
552
|
except UnreachableHttpError as e:
|
|
540
553
|
last_error = e
|
|
554
|
+
alias_failure = e
|
|
541
555
|
continue
|
|
542
556
|
assert last_error is not None
|
|
557
|
+
if mode == "alias":
|
|
558
|
+
if alias_failure is None:
|
|
559
|
+
raise AliasNotFoundError(f"{path}: not found on any configured base")
|
|
560
|
+
last_error = alias_failure
|
|
543
561
|
if mode == "tag" and isinstance(last_error, NotFoundHttpError):
|
|
544
562
|
raise last_error
|
|
545
563
|
if isinstance(last_error, (UnauthorizedHttpError, ForbiddenHttpError)):
|
chtypes/_ocifetch/_oci.py
CHANGED
|
@@ -16,7 +16,13 @@ from chtypes._ocifetch._errors import (
|
|
|
16
16
|
ArtifactUnpublishedError,
|
|
17
17
|
SourceIncompatibleError,
|
|
18
18
|
)
|
|
19
|
-
from chtypes._ocifetch._http import
|
|
19
|
+
from chtypes._ocifetch._http import (
|
|
20
|
+
AliasNotFoundError,
|
|
21
|
+
FetchPolicy,
|
|
22
|
+
RetryPolicy,
|
|
23
|
+
fetch_from_bases,
|
|
24
|
+
get_json,
|
|
25
|
+
)
|
|
20
26
|
|
|
21
27
|
__all__ = [
|
|
22
28
|
"Descriptor",
|
|
@@ -242,9 +248,31 @@ def fetch_manifest_by_tag(
|
|
|
242
248
|
policy: FetchPolicy,
|
|
243
249
|
retry: RetryPolicy,
|
|
244
250
|
max_bytes: int = C.MANIFEST_MAX_BYTES,
|
|
251
|
+
alias: str | None = None,
|
|
245
252
|
) -> tuple[dict, bytes, str]:
|
|
246
|
-
"""GET manifests/<tag>; returns (doc, raw_bytes, resolved_digest).
|
|
253
|
+
"""GET manifests/<tag>; returns (doc, raw_bytes, resolved_digest).
|
|
254
|
+
|
|
255
|
+
With `alias` (the dev channel's `<tag>--fp-<fingerprint>`, from
|
|
256
|
+
`_channel.alias_tag`), GET manifests/<alias> first, and the tag only when
|
|
257
|
+
every base answered the alias 404 (docs/guides/fetch-v1.md §3); any other
|
|
258
|
+
failure of the alias request is raised as it is. What is returned is then
|
|
259
|
+
trusted exactly as the tag's own answer would be."""
|
|
247
260
|
accept = (C.MEDIA_TYPE_INDEX, C.MEDIA_TYPE_MANIFEST)
|
|
261
|
+
if alias is not None:
|
|
262
|
+
try:
|
|
263
|
+
doc, resp = get_json(
|
|
264
|
+
bases,
|
|
265
|
+
f"/manifests/{alias}",
|
|
266
|
+
mode="alias",
|
|
267
|
+
accept=accept,
|
|
268
|
+
max_bytes=max_bytes,
|
|
269
|
+
policy=policy,
|
|
270
|
+
retry=retry,
|
|
271
|
+
)
|
|
272
|
+
except AliasNotFoundError:
|
|
273
|
+
pass
|
|
274
|
+
else:
|
|
275
|
+
return doc, resp.body, f"sha256:{hashlib.sha256(resp.body).hexdigest()}"
|
|
248
276
|
doc, resp = get_json(
|
|
249
277
|
bases,
|
|
250
278
|
f"/manifests/{tag}",
|
chtypes/results.py
CHANGED
|
@@ -14,9 +14,10 @@ from __future__ import annotations
|
|
|
14
14
|
from collections.abc import Mapping
|
|
15
15
|
from dataclasses import dataclass
|
|
16
16
|
|
|
17
|
-
from ._abi2._vocab import DefaultKind, FilterOutcome, Outcome, Verdict
|
|
17
|
+
from ._abi2._vocab import DefaultKind, FilterOutcome, MergeReason, Outcome, Verdict
|
|
18
18
|
|
|
19
19
|
__all__ = [
|
|
20
|
+
"AtMergeEntry",
|
|
20
21
|
"BatchResult",
|
|
21
22
|
"BuildInfo",
|
|
22
23
|
"Capabilities",
|
|
@@ -108,6 +109,31 @@ class EngineCell:
|
|
|
108
109
|
value: bytes | None = None
|
|
109
110
|
|
|
110
111
|
|
|
112
|
+
@dataclass(frozen=True, slots=True)
|
|
113
|
+
class AtMergeEntry:
|
|
114
|
+
"""One thing an `OPTIMIZE TABLE ... FINAL` of the part the INSERT writes would do
|
|
115
|
+
to one of its rows, at the call's clock instant: an entry of the batch's
|
|
116
|
+
`at_merge` list (ABI v2). `engine_rows` stays exactly what the INSERT writer
|
|
117
|
+
produces, so the two never contradict each other.
|
|
118
|
+
|
|
119
|
+
`row` indexes `engine_rows`: the part's row, not the input body's. `reason` is
|
|
120
|
+
a `MergeReason`; one the description does not list is its unknown(n) member
|
|
121
|
+
(rule r3). `column` is the column a `ttl_column_reset` resets (from `column`
|
|
122
|
+
or `column_b64`), None when the entry names none. `stored` is the column's
|
|
123
|
+
value after the reset (from `stored` or `stored_b64`), None when the document
|
|
124
|
+
carries none, which it omits when the column's DEFAULT reads the clock or a
|
|
125
|
+
generator and the value is decided at the merge. `input_rows` is the input
|
|
126
|
+
rows, each by its index in the body, that formed the part's row, None when
|
|
127
|
+
the document carries none.
|
|
128
|
+
"""
|
|
129
|
+
|
|
130
|
+
row: int
|
|
131
|
+
reason: MergeReason
|
|
132
|
+
column: bytes | None = None
|
|
133
|
+
stored: bytes | None = None
|
|
134
|
+
input_rows: tuple[int, ...] | None = None
|
|
135
|
+
|
|
136
|
+
|
|
111
137
|
@dataclass(frozen=True, slots=True)
|
|
112
138
|
class RowResult:
|
|
113
139
|
"""The `row` document. `columns` is every `cols` entry in document order;
|
|
@@ -160,6 +186,12 @@ class BatchResult:
|
|
|
160
186
|
can be fooled too. A caller that needs every record accounted for declines
|
|
161
187
|
a body with any skipped row or any `unconsumed` range
|
|
162
188
|
(docs/guides/batches.md).
|
|
189
|
+
|
|
190
|
+
`unsupported_settings` is the call's own settings the library declined, from
|
|
191
|
+
the batch's top-level list (ABI v2). `at_merge` is what an `OPTIMIZE TABLE ...
|
|
192
|
+
FINAL` of the part would do to its rows, at least: a later background merge
|
|
193
|
+
can remove more as more rows expire, and a row both removed and reset lists
|
|
194
|
+
only `ttl_delete`. Each is empty when the document carries none.
|
|
163
195
|
"""
|
|
164
196
|
|
|
165
197
|
outcome: Outcome
|
|
@@ -178,6 +210,8 @@ class BatchResult:
|
|
|
178
210
|
partition_count: int | None
|
|
179
211
|
unconsumed: tuple[Span, ...]
|
|
180
212
|
framing: Framing | None
|
|
213
|
+
unsupported_settings: tuple[bytes, ...] = ()
|
|
214
|
+
at_merge: tuple[AtMergeEntry, ...] = ()
|
|
181
215
|
|
|
182
216
|
|
|
183
217
|
@dataclass(frozen=True, slots=True)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: chtypes
|
|
3
|
-
Version: 2.0.0.
|
|
3
|
+
Version: 2.0.0.dev3
|
|
4
4
|
Summary: Python binding for the chtypes artifacts: ClickHouse's own type system, per version, behind the frozen chs_* C ABI
|
|
5
5
|
Project-URL: Homepage, https://github.com/wave-rf/chtypes
|
|
6
6
|
Project-URL: Repository, https://github.com/wave-rf/chtypes
|
|
@@ -19,7 +19,7 @@ Description-Content-Type: text/markdown
|
|
|
19
19
|
|
|
20
20
|
# chtypes — Python SDK
|
|
21
21
|
|
|
22
|
-
> **2.0.0-dev: UNSTABLE, staging only, not for production.** This is the ABI v2 development binding (public issue #511). It speaks ABI v2's unstable description, pins its dev fingerprint and refuses a library with any other ("update your dev SDK"). It fetches only from the staging dev channel (`https://registry-staging.wavehouse.dev/chtypes/v2-dev`) and trusts only the staging key; `CHTYPES_ARTIFACTS_URL`, `CHTYPES_TRUSTED_KEYS` and `CHTYPES_ALLOW_UNSIGNED`, and the `bases`, `trusted_keys` and `allow_unsigned` fetch options, are ignored, each with one warning; `--lock`, `--frozen` and `--update`, and the `frozen`, `lock_path`, `lock_write` and `update` options, are refused, because a dev build is replaceable and a superseded one expires after 14 days. Its cache is `${XDG_CACHE_HOME:-~/.cache}/chtypes/v2-dev`, or `<CHTYPES_CACHE>/v2-dev` under an explicit cache, which no 1.x SDK reads. Every vocabulary keeps a value it does not list as its `unknown(n)` member (`Outcome("x")`, whose `known` is `False`) instead of failing the document. The rules are r1 to r6 of [`docs/reference/abi-v2.md`](../docs/reference/abi-v2.md). For production, use the 1.x package: `pip install "chtypes<2"`.
|
|
22
|
+
> **2.0.0-dev: UNSTABLE, staging only, not for production.** This is the ABI v2 development binding (public issue #511). It speaks ABI v2's unstable description, pins its dev fingerprint and refuses a library with any other ("update your dev SDK"). It fetches only from the staging dev channel (`https://registry-staging.wavehouse.dev/chtypes/v2-dev`) and trusts only the staging key; `CHTYPES_ARTIFACTS_URL`, `CHTYPES_TRUSTED_KEYS` and `CHTYPES_ALLOW_UNSIGNED`, and the `bases`, `trusted_keys` and `allow_unsigned` fetch options, are ignored, each with one warning; `--lock`, `--frozen` and `--update`, and the `frozen`, `lock_path`, `lock_write` and `update` options, are refused, because a dev build is replaceable and a superseded one expires after 14 days. Its cache is `${XDG_CACHE_HOME:-~/.cache}/chtypes/v2-dev`, or `<CHTYPES_CACHE>/v2-dev` under an explicit cache, which no 1.x SDK reads. A dev SDK prefers the newest build of its own fingerprint: it resolves `<tag>--fp-<its fingerprint>` first and the tag only when no such build exists, and its cache lookups ignore another dev SDK's builds in a shared cache, so a newer dev fingerprint never strands it (§3 of [`docs/guides/fetch-v1.md`](../docs/guides/fetch-v1.md)). Every vocabulary keeps a value it does not list as its `unknown(n)` member (`Outcome("x")`, whose `known` is `False`) instead of failing the document. The rules are r1 to r6 of [`docs/reference/abi-v2.md`](../docs/reference/abi-v2.md). For production, use the 1.x package: `pip install "chtypes<2"`.
|
|
23
23
|
>
|
|
24
24
|
> An explicit cache directory gets the `v2-dev` subroot whether it comes from `CHTYPES_CACHE` or from `FetchOptions(cache_dir=...)`: the layout is under `<cache_dir>/v2-dev`, never in the directory itself, so code that inspects or pre-populates a cache looks there; `chtypes.cache_root` and `chtypes.search_dirs` return the resolved paths. `CHTYPES_OFFLINE=1` is the environment twin of `FetchOptions(offline=True)` and `--offline`: the cache only, no request.
|
|
25
25
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
chtypes/__init__.py,sha256=
|
|
1
|
+
chtypes/__init__.py,sha256=zKL5Y9XuiwZb-Ewfgvhr9IFxdpqpBS7Ub2ca68ZOxJc,5993
|
|
2
2
|
chtypes/__main__.py,sha256=XYGNTpTLXI1AUp7srvuNVv2SZAcfwLaXpoXRVInyhF4,14030
|
|
3
|
-
chtypes/_decode.py,sha256=
|
|
3
|
+
chtypes/_decode.py,sha256=5f-OamDm8Qx17FAWOdHO7ng7r16ZRtYy6QKn1LlnVrc,22207
|
|
4
4
|
chtypes/_ed25519.py,sha256=uwaAKWvgoc936e1ZfgLrtR47W87WWCL541kFaqBXrB0,4141
|
|
5
5
|
chtypes/_guard.py,sha256=zTtwprCS0XvGWY7GDozo-IP6GwjMAWl-o-AjShdCSVI,1985
|
|
6
6
|
chtypes/_input.py,sha256=i4OUmgmHxx618g0DVFYy8LmHhggfBT03NyMxH3ptZBI,6020
|
|
@@ -9,29 +9,29 @@ chtypes/errors.py,sha256=9qv94rxbe7hm7lSVFG33Qri82FwDtCY-EnJiwnMfkp8,9501
|
|
|
9
9
|
chtypes/library.py,sha256=G6DJFiRuv8--JARxfiSNEqeFLkafaMs_n9nG-IwoC74,17878
|
|
10
10
|
chtypes/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
11
11
|
chtypes/registry.py,sha256=HTrzS_3HjOkH4rU90AVMNfFLsLoaut0jgNlt_BJ28QY,13727
|
|
12
|
-
chtypes/results.py,sha256=
|
|
12
|
+
chtypes/results.py,sha256=6uvfkt8dLd-F9aXyxdzWU5_kDVRVirhwB3fcH5NhJDA,11037
|
|
13
13
|
chtypes/_abi2/__init__.py,sha256=d3i7_iUDMFT1jg49vRQz1SMH6oseYZHUR0oPt6aSTiY,1042
|
|
14
|
-
chtypes/_abi2/_decls.py,sha256=
|
|
15
|
-
chtypes/_abi2/_errmap.py,sha256=
|
|
14
|
+
chtypes/_abi2/_decls.py,sha256=ZFJUVXYIlyuKVRJyVh8KVxkT4GZjsY-CeCcohK4sWMQ,38454
|
|
15
|
+
chtypes/_abi2/_errmap.py,sha256=uQZ_rMaUU-tk1rSJ5wq_EjOkjesGeF-BKDJOs00KK3I,2886
|
|
16
16
|
chtypes/_abi2/_errors.py,sha256=wRE9M0oYtJz_9izQqpEBh5571M_1QzGWBoJ3pJTykWw,761
|
|
17
17
|
chtypes/_abi2/_loader.py,sha256=VMWrbWMp9Uq3K-7a219lfqAmSmBlD2hcB6WDJgG8XG0,12514
|
|
18
|
-
chtypes/_abi2/_vocab.py,sha256=
|
|
18
|
+
chtypes/_abi2/_vocab.py,sha256=kH58IzFHpK93ihNBo8UKMtJM6-umxbCaCZlFcqCk7Ro,15543
|
|
19
19
|
chtypes/_ocifetch/__init__.py,sha256=enfPOKkqP4kqAKqckBGWXN1OQsiYo9ngDP-wzG0zn2I,1484
|
|
20
|
-
chtypes/_ocifetch/_channel.py,sha256=
|
|
20
|
+
chtypes/_ocifetch/_channel.py,sha256=yqnb35GGxdviipspR3B10t6ZgI8RJMFvW7AnB7zYYfs,15981
|
|
21
21
|
chtypes/_ocifetch/_constants.py,sha256=UdOGIShDXaDP13NGbuftKZRE4u7VK8JfZmoeChw4GMU,5467
|
|
22
22
|
chtypes/_ocifetch/_dsse.py,sha256=0RaFJaG1jhXr1uPfuHxm7yAn6viNzYhfCRUf-sW5SvQ,9333
|
|
23
|
-
chtypes/_ocifetch/_ensure.py,sha256=
|
|
23
|
+
chtypes/_ocifetch/_ensure.py,sha256=gbUSpWvdXP9f6dlFWPk67Cl_bPJ02ehTRLkDrSVNnHI,61565
|
|
24
24
|
chtypes/_ocifetch/_errors.py,sha256=u0eIdrr3g00ugy8ZZoAXYIgUuZgEs6eMue2QkcWXC6w,6595
|
|
25
25
|
chtypes/_ocifetch/_faults.py,sha256=HpoB9tJW7aLRiJdCFnspzkwhpFATZ6f6hGi49Mbfnhs,7609
|
|
26
26
|
chtypes/_ocifetch/_goldens.py,sha256=8MicgeJSRd4WZkaEsdujRnOa7twQvbT-wfSMPZaD-ms,4191
|
|
27
|
-
chtypes/_ocifetch/_http.py,sha256=
|
|
27
|
+
chtypes/_ocifetch/_http.py,sha256=DGUW6ym9mrxOgn-9FcdqzuvVGcG9WJGlYyGHnkUvGRA,23817
|
|
28
28
|
chtypes/_ocifetch/_layout.py,sha256=ezEoVrsSMhvDhQ4U04hsGdu1-_ISZQ4V6zg4gGIChTQ,21300
|
|
29
29
|
chtypes/_ocifetch/_lock.py,sha256=14_uex9qXdOht-5FSrSvFvd99-gB7DmI3bDuxXs4YU8,5530
|
|
30
|
-
chtypes/_ocifetch/_oci.py,sha256=
|
|
30
|
+
chtypes/_ocifetch/_oci.py,sha256=YJi8y2iKIRh1_YI8lodWZXRxQfb_KrD5BhM-TQA1SCo,13595
|
|
31
31
|
chtypes/_ocifetch/_referrers.py,sha256=J2b4gN6vryz_IzYHxrd2Tp-tQO9Cz766rZtKgCVcKqA,6008
|
|
32
32
|
chtypes/_ocifetch/_unpack.py,sha256=Cn4-up63KL63L8pnkS5zwJ6VbbHPSigpCnmOdcHJ4RI,5609
|
|
33
|
-
chtypes-2.0.0.
|
|
34
|
-
chtypes-2.0.0.
|
|
35
|
-
chtypes-2.0.0.
|
|
36
|
-
chtypes-2.0.0.
|
|
37
|
-
chtypes-2.0.0.
|
|
33
|
+
chtypes-2.0.0.dev3.dist-info/METADATA,sha256=pCbL5T3EMtLWBTmEaHMGlNSjnUUQOJwYEWF5k7ycUk0,10966
|
|
34
|
+
chtypes-2.0.0.dev3.dist-info/WHEEL,sha256=W3fkpkm7-wf9vBI5Z-7s0eWkeM-spu78I8Neb98DeEg,87
|
|
35
|
+
chtypes-2.0.0.dev3.dist-info/entry_points.txt,sha256=TdNcluWBR0VOt5mVFROsp-NiVPAdR3EHo0UsHlMC0r0,50
|
|
36
|
+
chtypes-2.0.0.dev3.dist-info/licenses/LICENSE,sha256=z8d0m5b2O9McPEK1xHG_dWgUBT6EfBDz6wA0F7xSPTA,11358
|
|
37
|
+
chtypes-2.0.0.dev3.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|