chtypes 2.0.0.dev11__py3-none-any.whl → 2.0.0.dev12__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 +3 -2
- chtypes/_ocifetch/_ensure.py +70 -16
- chtypes/library.py +9 -3
- {chtypes-2.0.0.dev11.dist-info → chtypes-2.0.0.dev12.dist-info}/METADATA +3 -3
- {chtypes-2.0.0.dev11.dist-info → chtypes-2.0.0.dev12.dist-info}/RECORD +8 -8
- {chtypes-2.0.0.dev11.dist-info → chtypes-2.0.0.dev12.dist-info}/WHEEL +1 -1
- {chtypes-2.0.0.dev11.dist-info → chtypes-2.0.0.dev12.dist-info}/entry_points.txt +0 -0
- {chtypes-2.0.0.dev11.dist-info → chtypes-2.0.0.dev12.dist-info}/licenses/LICENSE +0 -0
chtypes/__init__.py
CHANGED
|
@@ -13,14 +13,15 @@ C ABI. Nothing here reimplements a coercion rule, which is why the answers are
|
|
|
13
13
|
exact by construction rather than approximately right.
|
|
14
14
|
|
|
15
15
|
import chtypes
|
|
16
|
-
from chtypes import Format, Registry
|
|
16
|
+
from chtypes import DocFlags, Format, Registry
|
|
17
17
|
|
|
18
18
|
chtypes.setup(timezone="UTC") # once, before the first open
|
|
19
19
|
registry = Registry(autofetch=True)
|
|
20
20
|
library = registry.for_version("26.8") # a release line or an exact version
|
|
21
21
|
ddl = b"CREATE TABLE t (ts DateTime, seq UInt8) ENGINE = Memory"
|
|
22
22
|
with library.compile_table(ddl) as schema:
|
|
23
|
-
|
|
23
|
+
body = b'{"ts":"2026-01-15 10:30:00","seq":256}\\n'
|
|
24
|
+
batch = schema.rows(Format.JSON_EACH_ROW, body, doc_flags=DocFlags.ALL)
|
|
24
25
|
|
|
25
26
|
batch.outcome # Outcome.ACCEPTED: the insert would succeed
|
|
26
27
|
batch.transformed # the changes ClickHouse makes and reports as success
|
chtypes/_ocifetch/_ensure.py
CHANGED
|
@@ -1296,6 +1296,48 @@ def _ensure(
|
|
|
1296
1296
|
raise _translate_transport_error(e) from e
|
|
1297
1297
|
|
|
1298
1298
|
|
|
1299
|
+
def _local_bundle_referrers(
|
|
1300
|
+
blobs_dir: Path, skip: Collection[str], known: dict[str, bytes] | None = None
|
|
1301
|
+
) -> dict[str, list[tuple[str, dict]]]:
|
|
1302
|
+
"""Each subject manifest digest's signature referrer manifests among
|
|
1303
|
+
`blobs_dir`'s blobs, as (blob name, parsed manifest), in listing order.
|
|
1304
|
+
A referrer need not be listed in `index.json`, so every blob that could
|
|
1305
|
+
be one is read, once per lookup rather than once per entry. A blob named
|
|
1306
|
+
in `skip` (the caller's candidate layers) and a blob larger than
|
|
1307
|
+
`limits.manifest_bytes`, which cannot be a manifest, are never read, so
|
|
1308
|
+
no layer is read here, and a blob in `known` (bytes the caller already
|
|
1309
|
+
read, by name) is not read again (public issue #690)."""
|
|
1310
|
+
out: dict[str, list[tuple[str, dict]]] = {}
|
|
1311
|
+
try:
|
|
1312
|
+
blob_paths = list(blobs_dir.iterdir())
|
|
1313
|
+
except OSError:
|
|
1314
|
+
return out
|
|
1315
|
+
for blob_path in blob_paths:
|
|
1316
|
+
if blob_path.name in skip:
|
|
1317
|
+
continue
|
|
1318
|
+
try:
|
|
1319
|
+
body = known.get(blob_path.name) if known is not None else None
|
|
1320
|
+
if body is None:
|
|
1321
|
+
if blob_path.stat().st_size > C.MANIFEST_MAX_BYTES:
|
|
1322
|
+
continue
|
|
1323
|
+
body = blob_path.read_bytes()
|
|
1324
|
+
doc = json.loads(body)
|
|
1325
|
+
except (ValueError, OSError, UnicodeDecodeError):
|
|
1326
|
+
continue
|
|
1327
|
+
if not isinstance(doc, dict) or doc.get("mediaType") != C.MEDIA_TYPE_MANIFEST:
|
|
1328
|
+
continue
|
|
1329
|
+
subject = doc.get("subject") or {}
|
|
1330
|
+
subject_digest = subject.get("digest") if isinstance(subject, dict) else None
|
|
1331
|
+
if not isinstance(subject_digest, str):
|
|
1332
|
+
continue
|
|
1333
|
+
# A signature referrer only: a revocation of the manifest carries the same
|
|
1334
|
+
# bundle media type, and is not the signature of the artifact.
|
|
1335
|
+
if doc.get("artifactType") != C.MEDIA_TYPE_BUNDLE:
|
|
1336
|
+
continue
|
|
1337
|
+
out.setdefault(subject_digest, []).append((blob_path.name, doc))
|
|
1338
|
+
return out
|
|
1339
|
+
|
|
1340
|
+
|
|
1299
1341
|
def _verify_and_install_from_local_blobs(
|
|
1300
1342
|
*,
|
|
1301
1343
|
source_root: Path,
|
|
@@ -1303,6 +1345,7 @@ def _verify_and_install_from_local_blobs(
|
|
|
1303
1345
|
cache_write_root: Path,
|
|
1304
1346
|
trusted_keys: tuple[TrustedKey, ...],
|
|
1305
1347
|
visible_only: bool = False,
|
|
1348
|
+
referrers: dict[str, list[tuple[str, dict]]] | None = None,
|
|
1306
1349
|
) -> VerifiedRecord | None:
|
|
1307
1350
|
"""docs/guides/fetch-v1.md §1/§6: "A pre-seeded layout has entries in
|
|
1308
1351
|
`index.json` with no corresponding `unpacked/` directory yet; the first
|
|
@@ -1328,23 +1371,12 @@ def _verify_and_install_from_local_blobs(
|
|
|
1328
1371
|
except (ValueError, OSError, FetchError):
|
|
1329
1372
|
return None
|
|
1330
1373
|
|
|
1374
|
+
if referrers is None:
|
|
1375
|
+
referrers = _local_bundle_referrers(blobs_dir, {parse_digest(layer_desc.digest)})
|
|
1331
1376
|
verified = None
|
|
1332
1377
|
bundle_digest = None
|
|
1333
1378
|
bundle_manifest_digest = None
|
|
1334
|
-
for
|
|
1335
|
-
try:
|
|
1336
|
-
doc = json.loads(blob_path.read_bytes())
|
|
1337
|
-
except (ValueError, OSError, UnicodeDecodeError):
|
|
1338
|
-
continue
|
|
1339
|
-
if not isinstance(doc, dict) or doc.get("mediaType") != C.MEDIA_TYPE_MANIFEST:
|
|
1340
|
-
continue
|
|
1341
|
-
subject = doc.get("subject") or {}
|
|
1342
|
-
if subject.get("digest") != manifest_digest:
|
|
1343
|
-
continue
|
|
1344
|
-
# A signature referrer only: a revocation of the manifest carries the same
|
|
1345
|
-
# bundle media type, and is not the signature of the artifact.
|
|
1346
|
-
if doc.get("artifactType") != C.MEDIA_TYPE_BUNDLE:
|
|
1347
|
-
continue
|
|
1379
|
+
for referrer_name, doc in referrers.get(manifest_digest, ()):
|
|
1348
1380
|
try:
|
|
1349
1381
|
referrer_layer = manifest_single_layer(doc, expected_media_type=C.MEDIA_TYPE_BUNDLE)
|
|
1350
1382
|
bundle_bytes = (blobs_dir / parse_digest(referrer_layer.digest)).read_bytes()
|
|
@@ -1354,7 +1386,7 @@ def _verify_and_install_from_local_blobs(
|
|
|
1354
1386
|
if candidate is not None:
|
|
1355
1387
|
verified = candidate
|
|
1356
1388
|
bundle_digest = referrer_layer.digest
|
|
1357
|
-
bundle_manifest_digest = "sha256:" +
|
|
1389
|
+
bundle_manifest_digest = "sha256:" + referrer_name
|
|
1358
1390
|
break
|
|
1359
1391
|
if verified is None:
|
|
1360
1392
|
return None
|
|
@@ -1422,7 +1454,9 @@ def _verify_preseeded_entries(roots: Sequence[Path], trusted_keys: tuple[Trusted
|
|
|
1422
1454
|
anywhere yet, and verifies+unpacks it from that root's OWN local blobs,
|
|
1423
1455
|
writing the result into `roots[0]` (the writable cache) regardless of
|
|
1424
1456
|
which root — including a read-only system directory — the blobs came
|
|
1425
|
-
from.
|
|
1457
|
+
from. An entry that already has a record is passed over without reading
|
|
1458
|
+
a blob, and each root's signature referrers are read once, never by
|
|
1459
|
+
reading a candidate's layer (public issue #690)."""
|
|
1426
1460
|
cache_write_root = roots[0]
|
|
1427
1461
|
already_verified = {record.manifest for _dir, record in list_verified_records(roots)}
|
|
1428
1462
|
for root in roots:
|
|
@@ -1431,16 +1465,36 @@ def _verify_preseeded_entries(roots: Sequence[Path], trusted_keys: tuple[Trusted
|
|
|
1431
1465
|
index_doc = json.loads(index_path.read_text(encoding="utf-8"))
|
|
1432
1466
|
except (OSError, ValueError):
|
|
1433
1467
|
continue
|
|
1468
|
+
blobs_dir = root / "blobs" / "sha256"
|
|
1469
|
+
candidates: list[str] = []
|
|
1470
|
+
candidate_layers: set[str] = set()
|
|
1471
|
+
manifests_read: dict[str, bytes] = {}
|
|
1434
1472
|
for m in index_doc.get("manifests") or []:
|
|
1435
1473
|
digest = m.get("digest")
|
|
1436
1474
|
if not digest or digest in already_verified:
|
|
1437
1475
|
continue
|
|
1476
|
+
candidates.append(digest)
|
|
1477
|
+
try:
|
|
1478
|
+
manifest_hex = parse_digest(digest)
|
|
1479
|
+
manifest_bytes = (blobs_dir / manifest_hex).read_bytes()
|
|
1480
|
+
manifests_read[manifest_hex] = manifest_bytes
|
|
1481
|
+
layer = manifest_layer_descriptor(json.loads(manifest_bytes))
|
|
1482
|
+
candidate_layers.add(parse_digest(layer.digest))
|
|
1483
|
+
except (ValueError, OSError, FetchError):
|
|
1484
|
+
continue
|
|
1485
|
+
if not candidates:
|
|
1486
|
+
continue
|
|
1487
|
+
referrers = _local_bundle_referrers(blobs_dir, candidate_layers, manifests_read)
|
|
1488
|
+
for digest in candidates:
|
|
1489
|
+
if digest in already_verified:
|
|
1490
|
+
continue
|
|
1438
1491
|
record = _verify_and_install_from_local_blobs(
|
|
1439
1492
|
source_root=root,
|
|
1440
1493
|
manifest_digest=digest,
|
|
1441
1494
|
cache_write_root=cache_write_root,
|
|
1442
1495
|
trusted_keys=trusted_keys,
|
|
1443
1496
|
visible_only=True,
|
|
1497
|
+
referrers=referrers,
|
|
1444
1498
|
)
|
|
1445
1499
|
if record is not None:
|
|
1446
1500
|
already_verified.add(digest)
|
chtypes/library.py
CHANGED
|
@@ -54,6 +54,8 @@ __all__ = ["Block", "Filter", "Library", "Schema", "Server", "open_unverified"]
|
|
|
54
54
|
# One image per file, process-wide: keyed on the realpath's device and inode, so
|
|
55
55
|
# two registries, two spellings or a hardlink of one artifact share one image
|
|
56
56
|
# and one Library. Guarded by the setup lock, which an open holds throughout.
|
|
57
|
+
# The batch preview's default `doc_flags`: 0, the lean result (no per-value documents).
|
|
58
|
+
_LEAN_DOC_FLAGS = DocFlags(0)
|
|
57
59
|
_IMAGES: dict[tuple[int, int], Library] = {}
|
|
58
60
|
|
|
59
61
|
|
|
@@ -212,7 +214,8 @@ class Filter(_Handle):
|
|
|
212
214
|
insert_options_extra: Mapping[str, str] | None = None,
|
|
213
215
|
) -> FilterResult:
|
|
214
216
|
"""Evaluate over a body. `settings` and `session_timezone` are the body's
|
|
215
|
-
|
|
217
|
+
INSERT settings: they govern its parse and the expressions the INSERT
|
|
218
|
+
evaluates, never the WHERE, whose zone was fixed when the filter compiled.
|
|
216
219
|
`columns` is the INSERT column list and the keywords after it the
|
|
217
220
|
`insert_options` members, as for `Schema.rows`."""
|
|
218
221
|
payload = body_bytes(body)
|
|
@@ -304,11 +307,14 @@ class Schema(_Handle):
|
|
|
304
307
|
insert_options_extra: Mapping[str, str] | None = None,
|
|
305
308
|
row_filter: Filter | None = None,
|
|
306
309
|
export: Format | int | None = None,
|
|
307
|
-
doc_flags: DocFlags =
|
|
310
|
+
doc_flags: DocFlags = _LEAN_DOC_FLAGS,
|
|
308
311
|
) -> BatchResult:
|
|
309
312
|
"""A whole body. `export` asks for the accepted rows serialized in that
|
|
310
313
|
format (`payload`, and `spans`); to insert a body whose rows carry a
|
|
311
|
-
`default_generated` column, insert `payload`, never the original input.
|
|
314
|
+
`default_generated` column, insert `payload`, never the original input.
|
|
315
|
+
|
|
316
|
+
`doc_flags` chooses the document groups; absent means 0, the lean result
|
|
317
|
+
(no per-value documents). Pass `DocFlags.ALL` for every group."""
|
|
312
318
|
payload = body_bytes(body)
|
|
313
319
|
settings_bytes = settings_json(settings, session_timezone)
|
|
314
320
|
columns_bytes = columns_json(columns)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: chtypes
|
|
3
|
-
Version: 2.0.0.
|
|
3
|
+
Version: 2.0.0.dev12
|
|
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
|
|
@@ -43,7 +43,7 @@ uv add "chtypes>=2.0.0.dev0" # or: pip install "chtypes>=2.0.0.dev0"
|
|
|
43
43
|
|
|
44
44
|
```python
|
|
45
45
|
import chtypes
|
|
46
|
-
from chtypes import Format, Registry
|
|
46
|
+
from chtypes import DocFlags, Format, Registry
|
|
47
47
|
|
|
48
48
|
chtypes.setup(timezone="UTC") # once, before the first open (optional)
|
|
49
49
|
registry = Registry(autofetch=True)
|
|
@@ -51,7 +51,7 @@ library = registry.for_version("26.8") # a release line or an exact version
|
|
|
51
51
|
|
|
52
52
|
ddl = b"CREATE TABLE t (x UInt8, ts DateTime DEFAULT now()) ENGINE = Memory"
|
|
53
53
|
with library.compile_table(ddl) as schema:
|
|
54
|
-
batch = schema.rows(Format.JSON_EACH_ROW, b'{"x":256}\n')
|
|
54
|
+
batch = schema.rows(Format.JSON_EACH_ROW, b'{"x":256}\n', doc_flags=DocFlags.ALL)
|
|
55
55
|
|
|
56
56
|
row = batch.rows[0]
|
|
57
57
|
print(batch.outcome) # accepted
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
chtypes/__init__.py,sha256=
|
|
1
|
+
chtypes/__init__.py,sha256=8bwCCMfg7k233x9tK6Eg8lITeC8EDwSS5M8VCkxbuPY,6736
|
|
2
2
|
chtypes/__main__.py,sha256=6qal_3mJBVfNc9LNHsSB0d9Sfurxix6QbNWG5TEa4L4,19392
|
|
3
3
|
chtypes/_advisory.py,sha256=wTRNdwqLiOGsFKZQ7CYZHsfIhDrKc_arpaxEYZCUxV4,1212
|
|
4
4
|
chtypes/_codes.py,sha256=mRhjP-7fOU6j15le7r1jHwLJkiClMIIIEtfOkriR7uE,1301
|
|
@@ -8,7 +8,7 @@ chtypes/_guard.py,sha256=kVgvxRWu7H-eba3hTqPBmpqjxOCWdQGwiFAHvSnl1OU,1996
|
|
|
8
8
|
chtypes/_input.py,sha256=ZAUEs3r26_CsFiIvaXV9P-F_C9jlRRzMNKgG--G4HnQ,15157
|
|
9
9
|
chtypes/_setup.py,sha256=7mXvPxLCHS9h1FRWNr5uKfCUg-Gk8VjmC-5KNW5HogI,6125
|
|
10
10
|
chtypes/errors.py,sha256=-sCgdySXZn5HRL8Vkwbj9gSvhj0nvCCjwt3mFn6gKIc,10511
|
|
11
|
-
chtypes/library.py,sha256=
|
|
11
|
+
chtypes/library.py,sha256=MYlk7ksWiq3OP2nJ4YoqRhP2rqLc_xdRktdNQAnwjm0,22290
|
|
12
12
|
chtypes/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
13
13
|
chtypes/registry.py,sha256=CQHnHr9T-2LyqRsYl5wdWKIVWCOiX9Vx4BLcZL2yCB0,23702
|
|
14
14
|
chtypes/results.py,sha256=1HUdvhrjeEl9Fm1GOJUXVREtc7HN31s2yrQbm5_ZeKQ,15397
|
|
@@ -22,7 +22,7 @@ chtypes/_ocifetch/__init__.py,sha256=Nwmn1_JqkRSy-1q8182RT5m4HJfbdJgr54it7_TKvBg
|
|
|
22
22
|
chtypes/_ocifetch/_channel.py,sha256=1MuIj5O8N1d8OdTN4Rx4zf3kc2Nh1hjx4o5BIE6JgmA,20484
|
|
23
23
|
chtypes/_ocifetch/_constants.py,sha256=Gk-h6UkPg5dvNlhkr8z-UY4Y866unUPOSpE8ZwJvoJw,6287
|
|
24
24
|
chtypes/_ocifetch/_dsse.py,sha256=nYkjmRefmVTonH1KUADNNUl0J9sh2C3fg-0WEyURjcc,9838
|
|
25
|
-
chtypes/_ocifetch/_ensure.py,sha256=
|
|
25
|
+
chtypes/_ocifetch/_ensure.py,sha256=3d00ZmJY7qjBQAsyEL6g3nikiHnaMm7BzoKTxn-EGts,79685
|
|
26
26
|
chtypes/_ocifetch/_errors.py,sha256=SE0zcTUr1fSvSH7HiLGjAInYvD_im8_SyeZNf-VKzXI,7594
|
|
27
27
|
chtypes/_ocifetch/_faults.py,sha256=HpoB9tJW7aLRiJdCFnspzkwhpFATZ6f6hGi49Mbfnhs,7609
|
|
28
28
|
chtypes/_ocifetch/_goldens.py,sha256=8MicgeJSRd4WZkaEsdujRnOa7twQvbT-wfSMPZaD-ms,4191
|
|
@@ -37,8 +37,8 @@ chtypes/_ocifetch/_resolve.py,sha256=Qv8xFUdOR-Kb_JqmUeDiQ4_Xv2ETuNFpmoDckMp7eV8
|
|
|
37
37
|
chtypes/_ocifetch/_retired.py,sha256=mqnI9RA7UK0A5RthCeLlBeVAu5SHbtwKFwxsSZk5e6A,7018
|
|
38
38
|
chtypes/_ocifetch/_revocation.py,sha256=DdKRjFp1eFAxVD7DZHK1bj3_sWVLlaEkVEVx6JyV2Zs,23710
|
|
39
39
|
chtypes/_ocifetch/_unpack.py,sha256=Cn4-up63KL63L8pnkS5zwJ6VbbHPSigpCnmOdcHJ4RI,5609
|
|
40
|
-
chtypes-2.0.0.
|
|
41
|
-
chtypes-2.0.0.
|
|
42
|
-
chtypes-2.0.0.
|
|
43
|
-
chtypes-2.0.0.
|
|
44
|
-
chtypes-2.0.0.
|
|
40
|
+
chtypes-2.0.0.dev12.dist-info/METADATA,sha256=3hHZvSuwop0hmBO2CrCF4szXxFd26F5plQWz3Jkf5oI,11365
|
|
41
|
+
chtypes-2.0.0.dev12.dist-info/WHEEL,sha256=2iZrJkDIkeDh0k9SikKMqFuRP-ua6mb4Ir1AsER4BEw,87
|
|
42
|
+
chtypes-2.0.0.dev12.dist-info/entry_points.txt,sha256=TdNcluWBR0VOt5mVFROsp-NiVPAdR3EHo0UsHlMC0r0,50
|
|
43
|
+
chtypes-2.0.0.dev12.dist-info/licenses/LICENSE,sha256=z8d0m5b2O9McPEK1xHG_dWgUBT6EfBDz6wA0F7xSPTA,11358
|
|
44
|
+
chtypes-2.0.0.dev12.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|