chtypes 1.0.4__tar.gz → 2.0.0.dev0__tar.gz
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-1.0.4 → chtypes-2.0.0.dev0}/CHANGELOG.md +15 -0
- {chtypes-1.0.4 → chtypes-2.0.0.dev0}/PKG-INFO +6 -2
- {chtypes-1.0.4 → chtypes-2.0.0.dev0}/README.md +5 -1
- {chtypes-1.0.4 → chtypes-2.0.0.dev0}/pyproject.toml +1 -1
- {chtypes-1.0.4 → chtypes-2.0.0.dev0}/src/chtypes/__init__.py +13 -1
- {chtypes-1.0.4 → chtypes-2.0.0.dev0}/src/chtypes/__main__.py +64 -9
- {chtypes-1.0.4/src/chtypes/_abi1 → chtypes-2.0.0.dev0/src/chtypes/_abi2}/__init__.py +5 -3
- {chtypes-1.0.4/src/chtypes/_abi1 → chtypes-2.0.0.dev0/src/chtypes/_abi2}/_decls.py +15 -9
- {chtypes-1.0.4/src/chtypes/_abi1 → chtypes-2.0.0.dev0/src/chtypes/_abi2}/_errmap.py +3 -3
- {chtypes-1.0.4/src/chtypes/_abi1 → chtypes-2.0.0.dev0/src/chtypes/_abi2}/_loader.py +24 -7
- chtypes-2.0.0.dev0/src/chtypes/_abi2/_vocab.py +416 -0
- {chtypes-1.0.4 → chtypes-2.0.0.dev0}/src/chtypes/_decode.py +13 -15
- {chtypes-1.0.4 → chtypes-2.0.0.dev0}/src/chtypes/_ocifetch/__init__.py +6 -0
- chtypes-2.0.0.dev0/src/chtypes/_ocifetch/_channel.py +325 -0
- {chtypes-1.0.4 → chtypes-2.0.0.dev0}/src/chtypes/_ocifetch/_constants.py +3 -1
- {chtypes-1.0.4 → chtypes-2.0.0.dev0}/src/chtypes/_ocifetch/_ensure.py +100 -11
- {chtypes-1.0.4 → chtypes-2.0.0.dev0}/src/chtypes/_ocifetch/_errors.py +28 -1
- chtypes-2.0.0.dev0/src/chtypes/_ocifetch/_faults.py +203 -0
- {chtypes-1.0.4 → chtypes-2.0.0.dev0}/src/chtypes/_ocifetch/_layout.py +120 -19
- {chtypes-1.0.4 → chtypes-2.0.0.dev0}/src/chtypes/_ocifetch/_lock.py +5 -3
- {chtypes-1.0.4 → chtypes-2.0.0.dev0}/src/chtypes/errors.py +25 -1
- {chtypes-1.0.4 → chtypes-2.0.0.dev0}/src/chtypes/library.py +2 -2
- {chtypes-1.0.4 → chtypes-2.0.0.dev0}/src/chtypes/registry.py +53 -9
- {chtypes-1.0.4 → chtypes-2.0.0.dev0}/src/chtypes/results.py +1 -1
- {chtypes-1.0.4/tests/abi1 → chtypes-2.0.0.dev0/tests/abi2}/__init__.py +1 -1
- {chtypes-1.0.4/tests/abi1 → chtypes-2.0.0.dev0/tests/abi2}/conftest.py +28 -11
- {chtypes-1.0.4/tests/abi1 → chtypes-2.0.0.dev0/tests/abi2}/test_conformance.py +6 -6
- chtypes-2.0.0.dev0/tests/abi2/test_dev_fingerprint.py +59 -0
- {chtypes-1.0.4/tests/abi1 → chtypes-2.0.0.dev0/tests/abi2}/test_goldens_runner_stub.py +22 -7
- {chtypes-1.0.4/tests/abi1 → chtypes-2.0.0.dev0/tests/abi2}/test_loader.py +3 -3
- {chtypes-1.0.4/tests/abi1 → chtypes-2.0.0.dev0/tests/abi2}/test_public_library.py +9 -4
- {chtypes-1.0.4/tests/abi1 → chtypes-2.0.0.dev0/tests/abi2}/test_public_open.py +1 -1
- {chtypes-1.0.4/tests/abi1 → chtypes-2.0.0.dev0/tests/abi2}/test_public_registry.py +16 -1
- {chtypes-1.0.4/tests/abi1 → chtypes-2.0.0.dev0/tests/abi2}/test_public_setup_cases.py +3 -3
- chtypes-2.0.0.dev0/tests/abi2/test_reader_rules.py +243 -0
- {chtypes-1.0.4 → chtypes-2.0.0.dev0}/tests/api/test_decode.py +37 -13
- chtypes-2.0.0.dev0/tests/api/test_vocab.py +124 -0
- chtypes-2.0.0.dev0/tests/cli/conftest.py +26 -0
- chtypes-2.0.0.dev0/tests/cli/test_devchannel.py +126 -0
- {chtypes-1.0.4 → chtypes-2.0.0.dev0}/tests/cli/test_main.py +76 -2
- {chtypes-1.0.4 → chtypes-2.0.0.dev0}/tests/conftest.py +1 -1
- {chtypes-1.0.4 → chtypes-2.0.0.dev0}/tests/goldens_v1/test_runner.py +25 -5
- chtypes-2.0.0.dev0/tests/ocifetch/conftest.py +28 -0
- chtypes-2.0.0.dev0/tests/ocifetch/test_cache_roots.py +188 -0
- chtypes-2.0.0.dev0/tests/ocifetch/test_cache_strict.py +242 -0
- {chtypes-1.0.4 → chtypes-2.0.0.dev0}/tests/ocifetch/test_conformance.py +12 -0
- chtypes-2.0.0.dev0/tests/ocifetch/test_devchannel.py +268 -0
- chtypes-2.0.0.dev0/tests/ocifetch/test_seam_gate.py +57 -0
- chtypes-2.0.0.dev0/tests/test_abi_identity.py +24 -0
- chtypes-2.0.0.dev0/tests/test_devchannel_registry.py +98 -0
- {chtypes-1.0.4 → chtypes-2.0.0.dev0}/tests/test_fetch_options.py +9 -4
- {chtypes-1.0.4 → chtypes-2.0.0.dev0}/uv.lock +1 -1
- chtypes-1.0.4/src/chtypes/_abi1/_vocab.py +0 -254
- chtypes-1.0.4/tests/api/test_vocab.py +0 -90
- {chtypes-1.0.4 → chtypes-2.0.0.dev0}/.gitignore +0 -0
- {chtypes-1.0.4 → chtypes-2.0.0.dev0}/LICENSE +0 -0
- {chtypes-1.0.4/src/chtypes/_abi1 → chtypes-2.0.0.dev0/src/chtypes/_abi2}/_errors.py +0 -0
- {chtypes-1.0.4 → chtypes-2.0.0.dev0}/src/chtypes/_ed25519.py +0 -0
- {chtypes-1.0.4 → chtypes-2.0.0.dev0}/src/chtypes/_guard.py +0 -0
- {chtypes-1.0.4 → chtypes-2.0.0.dev0}/src/chtypes/_input.py +0 -0
- {chtypes-1.0.4 → chtypes-2.0.0.dev0}/src/chtypes/_ocifetch/_dsse.py +0 -0
- {chtypes-1.0.4 → chtypes-2.0.0.dev0}/src/chtypes/_ocifetch/_goldens.py +0 -0
- {chtypes-1.0.4 → chtypes-2.0.0.dev0}/src/chtypes/_ocifetch/_http.py +0 -0
- {chtypes-1.0.4 → chtypes-2.0.0.dev0}/src/chtypes/_ocifetch/_oci.py +0 -0
- {chtypes-1.0.4 → chtypes-2.0.0.dev0}/src/chtypes/_ocifetch/_referrers.py +0 -0
- {chtypes-1.0.4 → chtypes-2.0.0.dev0}/src/chtypes/_ocifetch/_unpack.py +0 -0
- {chtypes-1.0.4 → chtypes-2.0.0.dev0}/src/chtypes/_setup.py +0 -0
- {chtypes-1.0.4 → chtypes-2.0.0.dev0}/src/chtypes/py.typed +0 -0
- {chtypes-1.0.4/tests/abi1 → chtypes-2.0.0.dev0/tests/abi2}/_stub_tree.py +0 -0
- {chtypes-1.0.4 → chtypes-2.0.0.dev0}/tests/api/__init__.py +0 -0
- {chtypes-1.0.4 → chtypes-2.0.0.dev0}/tests/api/test_input.py +0 -0
- {chtypes-1.0.4 → chtypes-2.0.0.dev0}/tests/cli/__init__.py +0 -0
- {chtypes-1.0.4 → chtypes-2.0.0.dev0}/tests/goldens_v1/__init__.py +0 -0
- {chtypes-1.0.4 → chtypes-2.0.0.dev0}/tests/goldens_v1/data/stub-goldens.json +0 -0
- {chtypes-1.0.4 → chtypes-2.0.0.dev0}/tests/ocifetch/__init__.py +0 -0
- {chtypes-1.0.4 → chtypes-2.0.0.dev0}/tests/ocifetch/_bundle_support.py +0 -0
- {chtypes-1.0.4 → chtypes-2.0.0.dev0}/tests/ocifetch/_registry_support.py +0 -0
- {chtypes-1.0.4 → chtypes-2.0.0.dev0}/tests/ocifetch/_sign_support.py +0 -0
- {chtypes-1.0.4 → chtypes-2.0.0.dev0}/tests/ocifetch/test_dsse.py +0 -0
- {chtypes-1.0.4 → chtypes-2.0.0.dev0}/tests/ocifetch/test_ensure.py +0 -0
- {chtypes-1.0.4 → chtypes-2.0.0.dev0}/tests/ocifetch/test_goldens.py +0 -0
- {chtypes-1.0.4 → chtypes-2.0.0.dev0}/tests/ocifetch/test_http.py +0 -0
- {chtypes-1.0.4 → chtypes-2.0.0.dev0}/tests/ocifetch/test_layout.py +0 -0
- {chtypes-1.0.4 → chtypes-2.0.0.dev0}/tests/ocifetch/test_lock.py +0 -0
- {chtypes-1.0.4 → chtypes-2.0.0.dev0}/tests/ocifetch/test_oci.py +0 -0
- {chtypes-1.0.4 → chtypes-2.0.0.dev0}/tests/ocifetch/test_unpack.py +0 -0
- {chtypes-1.0.4 → chtypes-2.0.0.dev0}/tests/test_ed25519.py +0 -0
- {chtypes-1.0.4 → chtypes-2.0.0.dev0}/tests/test_examples_tour.py +0 -0
|
@@ -6,6 +6,21 @@ The four bindings in this repository are released together and give one answer,
|
|
|
6
6
|
|
|
7
7
|
## [Unreleased]
|
|
8
8
|
|
|
9
|
+
## [1.1.0] — 2026-10-06
|
|
10
|
+
|
|
11
|
+
### Added
|
|
12
|
+
|
|
13
|
+
- Strict cache mode, and one error code for a cache that cannot be used (#486). The new code, `CHTYPES_CACHE_UNUSABLE` (exit status 9), names the path that failed, a reason (`unreadable_root`, `not_a_directory`, `unreadable_entry`, `unacceptable_record`, `layout_0x` or `unwritable`) and the errno name: Go `*ArtifactError` with `Code` `CodeCacheUnusable` and `Path`, `Reason` and `OSError` (`errors.Is(err, ErrCacheUnusable)`), Python `CacheUnusableError` (`path`, `reason`, `os_error`), TypeScript `CacheUnusableError` (`path`, `reason`, `osError`), Rust `Error::CacheUnusable(CacheFault)`. Strict mode is opt-in: Go `FetchOptions.StrictCache`, Python `FetchOptions(strict_cache=True)`, TypeScript `strictCache: true`, Rust `FetchOptions::strict_cache`, `CHTYPES_CACHE_STRICT=1`, or `--strict` on every CLI command. Under it, an unreadable cache root or entry, a record with nothing to re-verify from, or a 0.x layout is that error, never "not installed" and never a fall-through to a system directory; `chtypes where --strict` checks the root before printing it, and `chtypes verify --strict` that verified nothing exits 1. The default mode answers as before, and now warns once per path it could not read. The rules are `docs/guides/fetch-v1.md` section 1, "The cache faults", and the `v1-cache-interop` job holds all four bindings to one answer for every fault, in both modes.
|
|
14
|
+
|
|
15
|
+
### Changed
|
|
16
|
+
|
|
17
|
+
- A write the fetch layer needed under the cache that failed is now `CHTYPES_CACHE_UNUSABLE` with reason `unwritable`, in every mode (#486). Go raised a `*UsageError` (CLI exit 2), Python a raw `OSError` (CLI exit 1), TypeScript a `UsageError` (CLI exit 2) and Rust `CHTYPES_SOURCE_UNREACHABLE` (exit 3), which a retry loop would retry. A cache root that cannot be read is now "not installed" with a warning in every binding and on every Python: Python before 3.14 raised a raw `PermissionError` there, and Rust `CHTYPES_SOURCE_UNREACHABLE`; strict mode makes it the new error.
|
|
18
|
+
- Rust's default mode now treats an unreadable record, entry or `index.json` under the cache as absent, with a warning, where it used to report `CHTYPES_SOURCE_UNREACHABLE` (#486). Strict mode makes it `CHTYPES_CACHE_UNUSABLE` in all four.
|
|
19
|
+
|
|
20
|
+
### Fixed
|
|
21
|
+
|
|
22
|
+
- A cache one uid writes is readable by another, the cache and the system directories are one search, and a lookup writes nothing (#486). Go created every cache directory 0700 and every file 0600, and Python wrote `verified.json` and `index.json` 0600, so a cache fetched by one uid (a CI user) read as empty to another (a container's nonroot user). Now all four create every directory and file with mode 0777 or 0666 less the process umask, so at umask 022 another uid can read the cache, and readable is never writable. Lookups read the system directories differently: Go answered from the first root with a match, and TypeScript never read a system directory's installs at all. Now all four answer with the newest build across the cache and every system directory, a tie going to the cache, and `list` and `verify` read the same roots. Go, TypeScript and Rust created the layout (`oci-layout`, `blobs/`, `unpacked/`) on a lookup, `fetch --offline` included; now a lookup creates nothing, so a read-only mount reads cleanly. A `chtypes verify` that verified no build now says so on stderr, `verified 0 builds under <root>`, where it printed nothing and exited 0. A miss from a cache that is a 0.x registry directory names it and the v1 root, in the CLI and the registry alike. No name or signature changes. The rules are `docs/guides/fetch-v1.md` sections 1 and 6; every binding runs the `root-order-*` conformance cases, and the `v1-cache-interop` job reads every binding's cache as another uid.
|
|
23
|
+
|
|
9
24
|
## [1.0.4] — 2026-10-06
|
|
10
25
|
|
|
11
26
|
### Fixed
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: chtypes
|
|
3
|
-
Version:
|
|
3
|
+
Version: 2.0.0.dev0
|
|
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,6 +19,8 @@ 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"`.
|
|
23
|
+
|
|
22
24
|
**If this row were inserted into this table on this ClickHouse version, what would happen?** chtypes answers with ClickHouse's own code: the real C++ type machinery, vendored per release into a native library behind the frozen `chs_*` C ABI and reached here through stdlib `ctypes`. Nothing semantic is reimplemented, so _"what does ClickHouse do with `256` into a `UInt8`?"_ is answered by ClickHouse rather than by a model of it. One peer binding among `{go, python, ts, rust}` — no language is privileged, and all four give one answer.
|
|
23
25
|
|
|
24
26
|
Pure ctypes: no compiler, no build step. The only dependency is a zstd decompressor for the artifact layer, which is the standard library's own from Python 3.14 and the `backports.zstd` package before that.
|
|
@@ -28,9 +30,11 @@ Pure ctypes: no compiler, no build step. The only dependency is a zstd decompres
|
|
|
28
30
|
Two things: this package, and at least one **artifact** — the per-version native library it `dlopen`s at runtime. The registry fetches the artifact on first use when autofetch is on (`Registry(autofetch=True)` or `CHTYPES_AUTOFETCH=1`), after verifying its signature and every byte against the signed statement; with autofetch off it answers only from what is already installed.
|
|
29
31
|
|
|
30
32
|
```sh
|
|
31
|
-
uv add chtypes # or: pip install chtypes
|
|
33
|
+
uv add "chtypes>=2.0.0.dev0" # or: pip install "chtypes>=2.0.0.dev0"
|
|
32
34
|
```
|
|
33
35
|
|
|
36
|
+
`uv add chtypes` and `pip install chtypes` never select a pre-release on their own: the specifier names a dev version, which is what allows one.
|
|
37
|
+
|
|
34
38
|
## Quickstart
|
|
35
39
|
|
|
36
40
|
```python
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
# chtypes — Python SDK
|
|
2
2
|
|
|
3
|
+
> **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"`.
|
|
4
|
+
|
|
3
5
|
**If this row were inserted into this table on this ClickHouse version, what would happen?** chtypes answers with ClickHouse's own code: the real C++ type machinery, vendored per release into a native library behind the frozen `chs_*` C ABI and reached here through stdlib `ctypes`. Nothing semantic is reimplemented, so _"what does ClickHouse do with `256` into a `UInt8`?"_ is answered by ClickHouse rather than by a model of it. One peer binding among `{go, python, ts, rust}` — no language is privileged, and all four give one answer.
|
|
4
6
|
|
|
5
7
|
Pure ctypes: no compiler, no build step. The only dependency is a zstd decompressor for the artifact layer, which is the standard library's own from Python 3.14 and the `backports.zstd` package before that.
|
|
@@ -9,9 +11,11 @@ Pure ctypes: no compiler, no build step. The only dependency is a zstd decompres
|
|
|
9
11
|
Two things: this package, and at least one **artifact** — the per-version native library it `dlopen`s at runtime. The registry fetches the artifact on first use when autofetch is on (`Registry(autofetch=True)` or `CHTYPES_AUTOFETCH=1`), after verifying its signature and every byte against the signed statement; with autofetch off it answers only from what is already installed.
|
|
10
12
|
|
|
11
13
|
```sh
|
|
12
|
-
uv add chtypes # or: pip install chtypes
|
|
14
|
+
uv add "chtypes>=2.0.0.dev0" # or: pip install "chtypes>=2.0.0.dev0"
|
|
13
15
|
```
|
|
14
16
|
|
|
17
|
+
`uv add chtypes` and `pip install chtypes` never select a pre-release on their own: the specifier names a dev version, which is what allows one.
|
|
18
|
+
|
|
15
19
|
## Quickstart
|
|
16
20
|
|
|
17
21
|
```python
|
|
@@ -1,5 +1,11 @@
|
|
|
1
1
|
"""chtypes: ClickHouse's own type system, per version, from Python.
|
|
2
2
|
|
|
3
|
+
2.0.0-dev: UNSTABLE, staging only, not for production. This pre-release speaks
|
|
4
|
+
the ABI v2 dev description (spec/abi-v2/docs.md): it fetches only from the
|
|
5
|
+
staging dev channel and trusts only its key, refuses locks and frozen fetches,
|
|
6
|
+
caches under its own `v2-dev` root, and refuses a library built from any other
|
|
7
|
+
dev fingerprint (rules r5 and r6).
|
|
8
|
+
|
|
3
9
|
One question, exactly: *if this row were inserted into this ClickHouse table on
|
|
4
10
|
this ClickHouse version, what would happen?* The answer comes from ClickHouse's
|
|
5
11
|
real C++ machinery, vendored per release into a shared library behind the frozen
|
|
@@ -40,9 +46,10 @@ from __future__ import annotations
|
|
|
40
46
|
from importlib.metadata import PackageNotFoundError as _PackageNotFound
|
|
41
47
|
from importlib.metadata import version as _package_version
|
|
42
48
|
|
|
43
|
-
from .
|
|
49
|
+
from ._abi2._vocab import (
|
|
44
50
|
EXPORT_NONE,
|
|
45
51
|
DefaultKind,
|
|
52
|
+
DiscoverQueryParam,
|
|
46
53
|
DocFlags,
|
|
47
54
|
FilterOutcome,
|
|
48
55
|
Format,
|
|
@@ -61,6 +68,7 @@ from .errors import (
|
|
|
61
68
|
CODE_ARTIFACT_PINNED,
|
|
62
69
|
CODE_ARTIFACT_UNPUBLISHED,
|
|
63
70
|
CODE_ARTIFACT_UNTRUSTED,
|
|
71
|
+
CODE_CACHE_UNUSABLE,
|
|
64
72
|
CODE_SOURCE_FORBIDDEN,
|
|
65
73
|
CODE_SOURCE_INCOMPATIBLE,
|
|
66
74
|
CODE_SOURCE_UNAUTHORIZED,
|
|
@@ -72,6 +80,7 @@ from .errors import (
|
|
|
72
80
|
ArtifactPinnedError,
|
|
73
81
|
ArtifactUnpublishedError,
|
|
74
82
|
ArtifactUntrustedError,
|
|
83
|
+
CacheUnusableError,
|
|
75
84
|
CallError,
|
|
76
85
|
ChtypesError,
|
|
77
86
|
InternalError,
|
|
@@ -114,6 +123,7 @@ __all__ = [
|
|
|
114
123
|
"CODE_ARTIFACT_PINNED",
|
|
115
124
|
"CODE_ARTIFACT_UNPUBLISHED",
|
|
116
125
|
"CODE_ARTIFACT_UNTRUSTED",
|
|
126
|
+
"CODE_CACHE_UNUSABLE",
|
|
117
127
|
"CODE_SOURCE_FORBIDDEN",
|
|
118
128
|
"CODE_SOURCE_INCOMPATIBLE",
|
|
119
129
|
"CODE_SOURCE_UNAUTHORIZED",
|
|
@@ -130,12 +140,14 @@ __all__ = [
|
|
|
130
140
|
"Block",
|
|
131
141
|
"BuildInfo",
|
|
132
142
|
"BytesIn",
|
|
143
|
+
"CacheUnusableError",
|
|
133
144
|
"CallError",
|
|
134
145
|
"Capabilities",
|
|
135
146
|
"ChtypesError",
|
|
136
147
|
"Column",
|
|
137
148
|
"Computed",
|
|
138
149
|
"DefaultKind",
|
|
150
|
+
"DiscoverQueryParam",
|
|
139
151
|
"DiscoveredColumn",
|
|
140
152
|
"Discovery",
|
|
141
153
|
"EngineCell",
|
|
@@ -2,16 +2,25 @@
|
|
|
2
2
|
(docs/guides/fetch-v1.md).
|
|
3
3
|
|
|
4
4
|
chtypes fetch <spelling>... | --all [--platform <os-arch>] [--cache <dir>] [--lock <file>]
|
|
5
|
-
[--frozen] [--offline] [--update]
|
|
6
|
-
chtypes verify [--cache <dir>]
|
|
7
|
-
chtypes list [--cache <dir>] [--offline]
|
|
8
|
-
chtypes where [--cache <dir>]
|
|
5
|
+
[--frozen] [--offline] [--update] [--strict]
|
|
6
|
+
chtypes verify [--cache <dir>] [--strict] re-verify every installed build
|
|
7
|
+
chtypes list [--cache <dir>] [--offline] [--strict]
|
|
8
|
+
chtypes where [--cache <dir>] [--strict] the cache root
|
|
9
9
|
|
|
10
10
|
A spelling is `26.8`, `26.8.15` or `26.8.15.10`. Exit statuses come from the
|
|
11
11
|
`errors` table of spec/fetch-v1/constants.json (generated into the fetch layer
|
|
12
12
|
as `ERROR_EXIT_CODES`); a usage error exits 2 and success exits 0. Progress and
|
|
13
13
|
diagnostics go to stderr; `fetch` prints one installed library path per line on
|
|
14
14
|
stdout.
|
|
15
|
+
|
|
16
|
+
2.0.0-dev: UNSTABLE, staging only, not for production. This CLI speaks the ABI
|
|
17
|
+
v2 dev channel (spec/abi-v2/docs.md, rules r5 and r6): it fetches only from
|
|
18
|
+
https://registry-staging.wavehouse.dev/chtypes/v2-dev and trusts only the
|
|
19
|
+
staging key; CHTYPES_ARTIFACTS_URL, CHTYPES_TRUSTED_KEYS and
|
|
20
|
+
CHTYPES_ALLOW_UNSIGNED are ignored, each with one warning; --lock, --frozen and
|
|
21
|
+
--update are refused before any network call; its default cache is
|
|
22
|
+
${XDG_CACHE_HOME:-~/.cache}/chtypes/v2-dev, and an explicit one (--cache,
|
|
23
|
+
CHTYPES_CACHE) is used through its v2-dev subroot.
|
|
15
24
|
"""
|
|
16
25
|
|
|
17
26
|
from __future__ import annotations
|
|
@@ -24,11 +33,13 @@ from collections.abc import Sequence
|
|
|
24
33
|
|
|
25
34
|
from . import __version__ as chtypes_version
|
|
26
35
|
from . import _ocifetch as fetch_layer
|
|
36
|
+
from ._ocifetch import _channel
|
|
27
37
|
from ._ocifetch import _constants as C
|
|
28
|
-
from ._ocifetch._ensure import _translate_transport_error, detect_host_platform
|
|
29
|
-
from ._ocifetch._errors import ArtifactCorruptError, FetchError
|
|
38
|
+
from ._ocifetch._ensure import _translate_transport_error, detect_host_platform, missing_notes
|
|
39
|
+
from ._ocifetch._errors import ArtifactCorruptError, ArtifactMissingError, FetchError
|
|
40
|
+
from ._ocifetch._faults import probe_roots
|
|
30
41
|
from ._ocifetch._http import FetchPolicy, RetryPolicy, TransportError, fetch_from_bases
|
|
31
|
-
from ._ocifetch._layout import resolve_cache_root
|
|
42
|
+
from ._ocifetch._layout import resolve_cache_root, search_roots
|
|
32
43
|
from ._ocifetch._lock import load_lock
|
|
33
44
|
from .errors import ChtypesError
|
|
34
45
|
from .registry import FetchOptions
|
|
@@ -66,6 +77,12 @@ def build_parser() -> argparse.ArgumentParser:
|
|
|
66
77
|
parser = _Parser(
|
|
67
78
|
prog="chtypes",
|
|
68
79
|
description="Fetch, verify and locate chtypes artifacts (docs/guides/fetch-v1.md).",
|
|
80
|
+
epilog=(
|
|
81
|
+
"2.0.0-dev: UNSTABLE, staging only, not for production. Fetches only from the staging "
|
|
82
|
+
"dev channel and trusts only its key; --lock, --frozen and --update are refused; "
|
|
83
|
+
"CHTYPES_ARTIFACTS_URL, CHTYPES_TRUSTED_KEYS and CHTYPES_ALLOW_UNSIGNED are ignored "
|
|
84
|
+
"(spec/abi-v2/docs.md, rule r6)."
|
|
85
|
+
),
|
|
69
86
|
)
|
|
70
87
|
parser.add_argument("--version", action="version", version=f"chtypes {_display_version()}")
|
|
71
88
|
sub = parser.add_subparsers(dest="command", metavar="<command>")
|
|
@@ -75,6 +92,12 @@ def build_parser() -> argparse.ArgumentParser:
|
|
|
75
92
|
p.add_argument(
|
|
76
93
|
"--cache", metavar="<dir>", help="the cache directory (default: CHTYPES_CACHE)"
|
|
77
94
|
)
|
|
95
|
+
p.add_argument(
|
|
96
|
+
"--strict",
|
|
97
|
+
action="store_true",
|
|
98
|
+
help="a cache that cannot be read is CHTYPES_CACHE_UNUSABLE, never not-installed "
|
|
99
|
+
"(default: CHTYPES_CACHE_STRICT=1)",
|
|
100
|
+
)
|
|
78
101
|
|
|
79
102
|
fetch = sub.add_parser(
|
|
80
103
|
"fetch",
|
|
@@ -151,6 +174,7 @@ def _options(args: argparse.Namespace, *, platform: str | None = None):
|
|
|
151
174
|
lock_write = True
|
|
152
175
|
fetch = FetchOptions(
|
|
153
176
|
cache_dir=getattr(args, "cache", None),
|
|
177
|
+
strict_cache=True if getattr(args, "strict", False) else None,
|
|
154
178
|
offline=getattr(args, "offline", False),
|
|
155
179
|
frozen=getattr(args, "frozen", False),
|
|
156
180
|
lock_path=lock_path,
|
|
@@ -188,6 +212,10 @@ def _published_tags(options) -> list[str]:
|
|
|
188
212
|
|
|
189
213
|
|
|
190
214
|
def _cmd_fetch(args: argparse.Namespace) -> int:
|
|
215
|
+
# A dev SDK pins nothing: the refusal comes before anything else, the
|
|
216
|
+
# network above all (rule r6).
|
|
217
|
+
if (args.lock or args.frozen or args.update) and not _channel.active().pinnable:
|
|
218
|
+
raise ValueError(f"chtypes: {_channel.PINNING_REFUSED}")
|
|
191
219
|
if args.all and args.spellings:
|
|
192
220
|
raise ValueError("chtypes: give spellings or --all, not both")
|
|
193
221
|
if not args.all and not args.spellings and not args.update:
|
|
@@ -225,8 +253,29 @@ def _cmd_fetch(args: argparse.Namespace) -> int:
|
|
|
225
253
|
return EXIT_OK
|
|
226
254
|
|
|
227
255
|
|
|
256
|
+
def _say_notes(options) -> None:
|
|
257
|
+
"""What the cache says about itself in the default mode: a warning per root
|
|
258
|
+
or entry it could not read, and the 0.x hint."""
|
|
259
|
+
for note in missing_notes(options):
|
|
260
|
+
_say(f"chtypes: {note}")
|
|
261
|
+
|
|
262
|
+
|
|
228
263
|
def _cmd_verify(args: argparse.Namespace) -> int:
|
|
229
|
-
|
|
264
|
+
options = _options(args)
|
|
265
|
+
results = fetch_layer.verify_installed(options)
|
|
266
|
+
if not results:
|
|
267
|
+
# An empty pass must never look like a good one (public issue #486):
|
|
268
|
+
# say that nothing was verified, and why when the cache says why. In
|
|
269
|
+
# strict mode it is a failure, so a mounted cache can be health-checked.
|
|
270
|
+
root = resolve_cache_root(args.cache)
|
|
271
|
+
_say(f"chtypes: verified 0 builds under {root}")
|
|
272
|
+
_say_notes(options)
|
|
273
|
+
if options.resolved_strict():
|
|
274
|
+
raise ArtifactMissingError(
|
|
275
|
+
f"chtypes: no build is installed under {root}, and strict mode needs one"
|
|
276
|
+
)
|
|
277
|
+
return EXIT_OK
|
|
278
|
+
_say_notes(options)
|
|
230
279
|
failed = 0
|
|
231
280
|
for r in results:
|
|
232
281
|
if not r.ok:
|
|
@@ -239,10 +288,12 @@ def _cmd_verify(args: argparse.Namespace) -> int:
|
|
|
239
288
|
|
|
240
289
|
|
|
241
290
|
def _cmd_list(args: argparse.Namespace) -> int:
|
|
242
|
-
options =
|
|
291
|
+
options = _options(args)
|
|
243
292
|
installed = fetch_layer.list_installed(options)
|
|
244
293
|
for r in sorted(installed, key=lambda r: (r.version, r.platform)):
|
|
245
294
|
sys.stdout.write(f"installed {r.version} {r.platform} {r.dir}\n")
|
|
295
|
+
sys.stdout.flush()
|
|
296
|
+
_say_notes(options)
|
|
246
297
|
if not args.offline:
|
|
247
298
|
for tag in _published_tags(options):
|
|
248
299
|
sys.stdout.write(f"published {tag} support unknown\n")
|
|
@@ -251,6 +302,10 @@ def _cmd_list(args: argparse.Namespace) -> int:
|
|
|
251
302
|
|
|
252
303
|
|
|
253
304
|
def _cmd_where(args: argparse.Namespace) -> int:
|
|
305
|
+
options = _options(args)
|
|
306
|
+
if options.resolved_strict():
|
|
307
|
+
# Strict mode checks the root before naming it.
|
|
308
|
+
probe_roots(search_roots(options.cache_dir, options.system_dirs), strict=True)
|
|
254
309
|
sys.stdout.write(f"{resolve_cache_root(args.cache)}\n")
|
|
255
310
|
return EXIT_OK
|
|
256
311
|
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
"""The ABI
|
|
1
|
+
"""The ABI v2 loader and generated declaration layer (plan
|
|
2
2
|
PLAN-sdk-v1-ffi-2026-10-01.md), private to this binding. Not part of `chtypes`'s
|
|
3
3
|
public surface: the public API (`chtypes.library`, `chtypes.registry`) is built
|
|
4
4
|
over it, and re-exports the vocabularies and errors it defines.
|
|
@@ -7,11 +7,13 @@ over it, and re-exports the vocabularies and errors it defines.
|
|
|
7
7
|
handle classes, the typed call wrappers on `Api`, and the test-only
|
|
8
8
|
`invoke_by_name()` dispatcher.
|
|
9
9
|
- `_vocab`: GENERATED -- `Format`, `Status`, `Outcome`, `Verdict`, `Reason`,
|
|
10
|
-
`Source`, `DefaultKind`, `DocFlags` and the facts the
|
|
10
|
+
`Source`, `DefaultKind`, `DiscoverQueryParam`, `DocFlags` and the facts the
|
|
11
|
+
description attaches, each vocabulary with its unknown(n) member (rule r3).
|
|
11
12
|
- `_errors`: the public `chtypes.errors` classes, re-exported under the names
|
|
12
13
|
`_errmap` uses.
|
|
13
14
|
- `_errmap`: GENERATED -- the sdk.json status/reason -> `_errors` class maps.
|
|
14
|
-
- `_loader`: hand-written -- `open()`/`open_unverified()`, the loader steps
|
|
15
|
+
- `_loader`: hand-written -- `open()`/`open_unverified()`, the loader steps,
|
|
16
|
+
and rule r6's exact refusal of another fingerprint.
|
|
15
17
|
"""
|
|
16
18
|
|
|
17
19
|
from __future__ import annotations
|
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
# GENERATED by scripts/abi-v1/gen.py from spec/abi-
|
|
2
|
-
"""The Python binding's generated ABI
|
|
1
|
+
# GENERATED by scripts/abi-v1/gen.py --major 2 from spec/abi-v2/abi.json (CHS_ABI_FINGERPRINT sha256:8ec0c6941f000aa07bfb9fbfef20205d3ee0a588833856bd12270ba42912bfb3) — DO NOT EDIT # noqa: E501
|
|
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
|
-
dispatcher the conformance suite drives from tests/fixtures/abi-
|
|
4
|
+
dispatcher the conformance suite drives from tests/fixtures/abi-v2/cases.json.
|
|
5
5
|
See scripts/abi-v1/emit/python.py's module docstring for the design this file
|
|
6
|
-
implements and why. This is the ONLY python/src/chtypes/
|
|
6
|
+
implements and why. This is the ONLY python/src/chtypes/_abi2 file that reads
|
|
7
7
|
a chs_* attribute (scripts/abi-v1/check-no-hand-decls.py exempts it by its
|
|
8
8
|
banner, above); every other file in this package calls the non-chs_-prefixed
|
|
9
9
|
names this module exposes instead.
|
|
@@ -18,8 +18,14 @@ from dataclasses import dataclass, field
|
|
|
18
18
|
|
|
19
19
|
from . import _errmap
|
|
20
20
|
|
|
21
|
-
CHS_ABI_VERSION =
|
|
22
|
-
CHS_ABI_FINGERPRINT = "sha256:
|
|
21
|
+
CHS_ABI_VERSION = 2
|
|
22
|
+
CHS_ABI_FINGERPRINT = "sha256:8ec0c6941f000aa07bfb9fbfef20205d3ee0a588833856bd12270ba42912bfb3"
|
|
23
|
+
|
|
24
|
+
# The description's stability: "unstable" while this generation is being
|
|
25
|
+
# designed, so CHS_ABI_FINGERPRINT moves with every change, and "locked" after
|
|
26
|
+
# (spec/abi-v2/docs.md, rule r6). A dev SDK refuses any other fingerprint with
|
|
27
|
+
# the dev message (_loader.py).
|
|
28
|
+
CHS_ABI_STABILITY = "unstable"
|
|
23
29
|
|
|
24
30
|
# sdk.json's loader.cross_check: (build_info field, predicate field, how to
|
|
25
31
|
# compare), read by _loader.py's step 5 without it ever touching sdk.json
|
|
@@ -516,7 +522,7 @@ class Api:
|
|
|
516
522
|
cls = _errmap.call_error_class(name)
|
|
517
523
|
message = info.message if info else b""
|
|
518
524
|
if name not in STATUS_BY_NAME:
|
|
519
|
-
message = f"status {status} is outside the closed
|
|
525
|
+
message = f"call status unknown({status}) is outside the closed set".encode() + (
|
|
520
526
|
b": " + message if message else b""
|
|
521
527
|
)
|
|
522
528
|
raise cls(
|
|
@@ -927,7 +933,7 @@ def resolve_all(lib: ctypes.CDLL) -> Api:
|
|
|
927
933
|
time step 6 runs, step 3 has already proven it exists). The first symbol
|
|
928
934
|
ctypes cannot dlsym raises MissingSymbol(name); _loader.py's step 6 turns
|
|
929
935
|
that into "missing_symbol:<name>", matching every missing-<sym> stub
|
|
930
|
-
variant (tests/fixtures/abi-
|
|
936
|
+
variant (tests/fixtures/abi-v2/cases.json), chs_build_info and
|
|
931
937
|
chs_clickhouse_version included -- loader step 4 needs chs_build_info
|
|
932
938
|
already resolved, which is exactly why this sweep must run before it."""
|
|
933
939
|
raw: dict = {}
|
|
@@ -1028,7 +1034,7 @@ def invoke_by_name(api: Api, name: str, args: Iterable[object] = ()) -> InvokeRe
|
|
|
1028
1034
|
(or None, where nullable) for handle. Output parameters are never
|
|
1029
1035
|
supplied by the caller: this dispatcher allocates them, and decodes them
|
|
1030
1036
|
into the returned InvokeResult. Test-only: every chs_ spelling below is
|
|
1031
|
-
why this file, alone in python/src/chtypes/
|
|
1037
|
+
why this file, alone in python/src/chtypes/_abi2, carries the generated
|
|
1032
1038
|
banner check-no-hand-decls.py exempts."""
|
|
1033
1039
|
spec = FUNCTIONS[name]
|
|
1034
1040
|
fn = api._raw[name]
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
# GENERATED by scripts/abi-v1/gen.py from spec/abi-
|
|
2
|
-
"""spec/abi-
|
|
1
|
+
# GENERATED by scripts/abi-v1/gen.py --major 2 from spec/abi-v2/abi.json (CHS_ABI_FINGERPRINT sha256:8ec0c6941f000aa07bfb9fbfef20205d3ee0a588833856bd12270ba42912bfb3) — DO NOT EDIT # noqa: E501
|
|
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
|
-
python/src/chtypes/
|
|
4
|
+
python/src/chtypes/_abi2/_errors.py). Only the MAPPING is generated: the
|
|
5
5
|
classes themselves are stable across a regeneration (plan section 3.4), so a
|
|
6
6
|
status renumbering or a new loader refusal reason changes this file, never
|
|
7
7
|
the classes.
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
"""The ABI
|
|
1
|
+
"""The ABI v2 loader, Python half (plan PLAN-sdk-v1-ffi section 3.2/3.3).
|
|
2
2
|
|
|
3
3
|
Decoupled from the fetch PRs on purpose (plan section 3.1): `open()` takes a
|
|
4
4
|
small, loader-owned `predicate` mapping rather than the fetch module's
|
|
@@ -8,13 +8,13 @@ wired up; `predicate` is passed through exactly as that seam promises.
|
|
|
8
8
|
|
|
9
9
|
Every `chs_*` spelling in this module is forbidden by
|
|
10
10
|
scripts/abi-v1/check-no-hand-decls.py (it is not a generated file), so this
|
|
11
|
-
calls ONLY python/src/chtypes/
|
|
11
|
+
calls ONLY python/src/chtypes/_abi2/_decls.py's non-`chs_`-prefixed surface:
|
|
12
12
|
`resolve_abi_version()`, `resolve_all()`, and the `Api` object they return.
|
|
13
13
|
|
|
14
14
|
Steps 1-6 run in the order plan section 3.2 gives (with step 6's full
|
|
15
15
|
resolve-all sweep run right after step 3, BEFORE step 4's
|
|
16
16
|
`chs_build_info()` call -- step 4 needs that symbol already resolved, and
|
|
17
|
-
`tests/fixtures/abi-
|
|
17
|
+
`tests/fixtures/abi-v2/cases.json`'s `missing-chs_build_info` and
|
|
18
18
|
`missing-chs_clickhouse_version` stub variants both expect the step 6
|
|
19
19
|
`missing_symbol:<name>` reason, not a step 4 one, which is only possible if
|
|
20
20
|
the full symbol sweep happens before chs_build_info is ever called). Step 7
|
|
@@ -54,6 +54,25 @@ def _refuse(reason: str, path: str, want: object = None, got: object = None) ->
|
|
|
54
54
|
raise cls(reason=reason, path=path, want=want, got=got)
|
|
55
55
|
|
|
56
56
|
|
|
57
|
+
def dev_fingerprint_message(sdk: str, library: str) -> str:
|
|
58
|
+
"""Rule r6's exact refusal of a library whose fingerprint is not this dev
|
|
59
|
+
SDK's (spec/abi-v2/docs.md): `sdk` is the SDK's own fingerprint and
|
|
60
|
+
`library` the library's, both full "sha256:" spellings."""
|
|
61
|
+
return f"this SDK speaks dev fingerprint {sdk}; the library has {library} — update your dev SDK"
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def _refuse_fingerprint(path: str, got: object) -> None:
|
|
65
|
+
"""Loader step 4's fingerprint refusal: CHTYPES_ARTIFACT_INCOMPATIBLE.
|
|
66
|
+
While this generation's description is unstable (a 2.0.0-dev SDK), its
|
|
67
|
+
message is exactly rule r6's; after the lock it is the ordinary refusal."""
|
|
68
|
+
want = _decls.CHS_ABI_FINGERPRINT
|
|
69
|
+
cls = _errmap.loader_error_class("fingerprint")
|
|
70
|
+
if _decls.CHS_ABI_STABILITY == "unstable":
|
|
71
|
+
message = dev_fingerprint_message(want, str(got))
|
|
72
|
+
raise cls(message, reason="fingerprint", path=path, want=want, got=got)
|
|
73
|
+
raise cls(reason="fingerprint", path=path, want=want, got=got)
|
|
74
|
+
|
|
75
|
+
|
|
57
76
|
def _glibc_version() -> tuple[int, int] | None:
|
|
58
77
|
"""Resolve `gnu_get_libc_version` dynamically from the running process:
|
|
59
78
|
`ctypes.CDLL(None)` is the process image's own global symbols (glibc's,
|
|
@@ -193,9 +212,7 @@ def _open(
|
|
|
193
212
|
info = _parse_build_info(raw, path)
|
|
194
213
|
|
|
195
214
|
if info["abi_fingerprint"] != _decls.CHS_ABI_FINGERPRINT:
|
|
196
|
-
|
|
197
|
-
"fingerprint", path, want=_decls.CHS_ABI_FINGERPRINT, got=info.get("abi_fingerprint")
|
|
198
|
-
)
|
|
215
|
+
_refuse_fingerprint(path, info.get("abi_fingerprint"))
|
|
199
216
|
|
|
200
217
|
if not skip_step5:
|
|
201
218
|
_cross_check(info, predicate, path)
|
|
@@ -231,7 +248,7 @@ def open(
|
|
|
231
248
|
timezone: bytes = b"",
|
|
232
249
|
defaults: bytes = b"",
|
|
233
250
|
) -> LoadResult:
|
|
234
|
-
"""Load and verify
|
|
251
|
+
"""Load and verify an ABI v2 artifact at `path` against its (already verified
|
|
235
252
|
elsewhere) signed `predicate`, then set the image zone (`timezone`, empty
|
|
236
253
|
= UTC) and the default settings (`defaults`, a JSON object of string
|
|
237
254
|
values, empty for none): step 7. Raises an artifact error naming the exact
|