chtypes 2.0.0.dev2__tar.gz → 2.0.0.dev4__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-2.0.0.dev2 → chtypes-2.0.0.dev4}/CHANGELOG.md +13 -0
- {chtypes-2.0.0.dev2 → chtypes-2.0.0.dev4}/PKG-INFO +4 -2
- {chtypes-2.0.0.dev2 → chtypes-2.0.0.dev4}/README.md +3 -1
- {chtypes-2.0.0.dev2 → chtypes-2.0.0.dev4}/pyproject.toml +1 -1
- {chtypes-2.0.0.dev2 → chtypes-2.0.0.dev4}/src/chtypes/__init__.py +12 -0
- {chtypes-2.0.0.dev2 → chtypes-2.0.0.dev4}/src/chtypes/_abi2/_decls.py +2 -2
- {chtypes-2.0.0.dev2 → chtypes-2.0.0.dev4}/src/chtypes/_abi2/_errmap.py +1 -1
- {chtypes-2.0.0.dev2 → chtypes-2.0.0.dev4}/src/chtypes/_abi2/_vocab.py +56 -5
- {chtypes-2.0.0.dev2 → chtypes-2.0.0.dev4}/src/chtypes/_decode.py +48 -1
- {chtypes-2.0.0.dev2 → chtypes-2.0.0.dev4}/src/chtypes/_ocifetch/_channel.py +111 -0
- {chtypes-2.0.0.dev2 → chtypes-2.0.0.dev4}/src/chtypes/_ocifetch/_constants.py +5 -1
- {chtypes-2.0.0.dev2 → chtypes-2.0.0.dev4}/src/chtypes/_ocifetch/_ensure.py +44 -6
- {chtypes-2.0.0.dev2 → chtypes-2.0.0.dev4}/src/chtypes/_ocifetch/_errors.py +15 -1
- {chtypes-2.0.0.dev2 → chtypes-2.0.0.dev4}/src/chtypes/_ocifetch/_http.py +46 -1
- {chtypes-2.0.0.dev2 → chtypes-2.0.0.dev4}/src/chtypes/_ocifetch/_oci.py +36 -4
- chtypes-2.0.0.dev4/src/chtypes/_ocifetch/_retired.py +77 -0
- {chtypes-2.0.0.dev2 → chtypes-2.0.0.dev4}/src/chtypes/errors.py +12 -0
- {chtypes-2.0.0.dev2 → chtypes-2.0.0.dev4}/src/chtypes/registry.py +1 -0
- {chtypes-2.0.0.dev2 → chtypes-2.0.0.dev4}/src/chtypes/results.py +59 -4
- {chtypes-2.0.0.dev2 → chtypes-2.0.0.dev4}/tests/abi2/test_public_server.py +64 -1
- {chtypes-2.0.0.dev2 → chtypes-2.0.0.dev4}/tests/abi2/test_public_setup_cases.py +28 -1
- {chtypes-2.0.0.dev2 → chtypes-2.0.0.dev4}/tests/abi2/test_reader_rules.py +13 -0
- {chtypes-2.0.0.dev2 → chtypes-2.0.0.dev4}/tests/api/test_decode.py +89 -1
- {chtypes-2.0.0.dev2 → chtypes-2.0.0.dev4}/tests/cli/test_main.py +40 -0
- {chtypes-2.0.0.dev2 → chtypes-2.0.0.dev4}/tests/ocifetch/test_conformance.py +82 -0
- {chtypes-2.0.0.dev2 → chtypes-2.0.0.dev4}/tests/ocifetch/test_devchannel.py +92 -1
- chtypes-2.0.0.dev4/tests/ocifetch/test_retired.py +181 -0
- {chtypes-2.0.0.dev2 → chtypes-2.0.0.dev4}/uv.lock +1 -1
- {chtypes-2.0.0.dev2 → chtypes-2.0.0.dev4}/.gitignore +0 -0
- {chtypes-2.0.0.dev2 → chtypes-2.0.0.dev4}/LICENSE +0 -0
- {chtypes-2.0.0.dev2 → chtypes-2.0.0.dev4}/src/chtypes/__main__.py +0 -0
- {chtypes-2.0.0.dev2 → chtypes-2.0.0.dev4}/src/chtypes/_abi2/__init__.py +0 -0
- {chtypes-2.0.0.dev2 → chtypes-2.0.0.dev4}/src/chtypes/_abi2/_errors.py +0 -0
- {chtypes-2.0.0.dev2 → chtypes-2.0.0.dev4}/src/chtypes/_abi2/_loader.py +0 -0
- {chtypes-2.0.0.dev2 → chtypes-2.0.0.dev4}/src/chtypes/_ed25519.py +0 -0
- {chtypes-2.0.0.dev2 → chtypes-2.0.0.dev4}/src/chtypes/_guard.py +0 -0
- {chtypes-2.0.0.dev2 → chtypes-2.0.0.dev4}/src/chtypes/_input.py +0 -0
- {chtypes-2.0.0.dev2 → chtypes-2.0.0.dev4}/src/chtypes/_ocifetch/__init__.py +0 -0
- {chtypes-2.0.0.dev2 → chtypes-2.0.0.dev4}/src/chtypes/_ocifetch/_dsse.py +0 -0
- {chtypes-2.0.0.dev2 → chtypes-2.0.0.dev4}/src/chtypes/_ocifetch/_faults.py +0 -0
- {chtypes-2.0.0.dev2 → chtypes-2.0.0.dev4}/src/chtypes/_ocifetch/_goldens.py +0 -0
- {chtypes-2.0.0.dev2 → chtypes-2.0.0.dev4}/src/chtypes/_ocifetch/_layout.py +0 -0
- {chtypes-2.0.0.dev2 → chtypes-2.0.0.dev4}/src/chtypes/_ocifetch/_lock.py +0 -0
- {chtypes-2.0.0.dev2 → chtypes-2.0.0.dev4}/src/chtypes/_ocifetch/_referrers.py +0 -0
- {chtypes-2.0.0.dev2 → chtypes-2.0.0.dev4}/src/chtypes/_ocifetch/_unpack.py +0 -0
- {chtypes-2.0.0.dev2 → chtypes-2.0.0.dev4}/src/chtypes/_setup.py +0 -0
- {chtypes-2.0.0.dev2 → chtypes-2.0.0.dev4}/src/chtypes/library.py +0 -0
- {chtypes-2.0.0.dev2 → chtypes-2.0.0.dev4}/src/chtypes/py.typed +0 -0
- {chtypes-2.0.0.dev2 → chtypes-2.0.0.dev4}/tests/abi2/__init__.py +0 -0
- {chtypes-2.0.0.dev2 → chtypes-2.0.0.dev4}/tests/abi2/_stub_tree.py +0 -0
- {chtypes-2.0.0.dev2 → chtypes-2.0.0.dev4}/tests/abi2/conftest.py +0 -0
- {chtypes-2.0.0.dev2 → chtypes-2.0.0.dev4}/tests/abi2/test_conformance.py +0 -0
- {chtypes-2.0.0.dev2 → chtypes-2.0.0.dev4}/tests/abi2/test_dev_fingerprint.py +0 -0
- {chtypes-2.0.0.dev2 → chtypes-2.0.0.dev4}/tests/abi2/test_goldens_runner_stub.py +0 -0
- {chtypes-2.0.0.dev2 → chtypes-2.0.0.dev4}/tests/abi2/test_loader.py +0 -0
- {chtypes-2.0.0.dev2 → chtypes-2.0.0.dev4}/tests/abi2/test_public_library.py +0 -0
- {chtypes-2.0.0.dev2 → chtypes-2.0.0.dev4}/tests/abi2/test_public_open.py +0 -0
- {chtypes-2.0.0.dev2 → chtypes-2.0.0.dev4}/tests/abi2/test_public_registry.py +0 -0
- {chtypes-2.0.0.dev2 → chtypes-2.0.0.dev4}/tests/api/__init__.py +0 -0
- {chtypes-2.0.0.dev2 → chtypes-2.0.0.dev4}/tests/api/test_input.py +0 -0
- {chtypes-2.0.0.dev2 → chtypes-2.0.0.dev4}/tests/api/test_vocab.py +0 -0
- {chtypes-2.0.0.dev2 → chtypes-2.0.0.dev4}/tests/cli/__init__.py +0 -0
- {chtypes-2.0.0.dev2 → chtypes-2.0.0.dev4}/tests/cli/conftest.py +0 -0
- {chtypes-2.0.0.dev2 → chtypes-2.0.0.dev4}/tests/cli/test_devchannel.py +0 -0
- {chtypes-2.0.0.dev2 → chtypes-2.0.0.dev4}/tests/conftest.py +0 -0
- {chtypes-2.0.0.dev2 → chtypes-2.0.0.dev4}/tests/goldens_v1/__init__.py +0 -0
- {chtypes-2.0.0.dev2 → chtypes-2.0.0.dev4}/tests/goldens_v1/data/stub-goldens.json +0 -0
- {chtypes-2.0.0.dev2 → chtypes-2.0.0.dev4}/tests/goldens_v1/test_runner.py +0 -0
- {chtypes-2.0.0.dev2 → chtypes-2.0.0.dev4}/tests/ocifetch/__init__.py +0 -0
- {chtypes-2.0.0.dev2 → chtypes-2.0.0.dev4}/tests/ocifetch/_bundle_support.py +0 -0
- {chtypes-2.0.0.dev2 → chtypes-2.0.0.dev4}/tests/ocifetch/_registry_support.py +0 -0
- {chtypes-2.0.0.dev2 → chtypes-2.0.0.dev4}/tests/ocifetch/_sign_support.py +0 -0
- {chtypes-2.0.0.dev2 → chtypes-2.0.0.dev4}/tests/ocifetch/conftest.py +0 -0
- {chtypes-2.0.0.dev2 → chtypes-2.0.0.dev4}/tests/ocifetch/test_cache_roots.py +0 -0
- {chtypes-2.0.0.dev2 → chtypes-2.0.0.dev4}/tests/ocifetch/test_cache_strict.py +0 -0
- {chtypes-2.0.0.dev2 → chtypes-2.0.0.dev4}/tests/ocifetch/test_dsse.py +0 -0
- {chtypes-2.0.0.dev2 → chtypes-2.0.0.dev4}/tests/ocifetch/test_ensure.py +0 -0
- {chtypes-2.0.0.dev2 → chtypes-2.0.0.dev4}/tests/ocifetch/test_goldens.py +0 -0
- {chtypes-2.0.0.dev2 → chtypes-2.0.0.dev4}/tests/ocifetch/test_http.py +0 -0
- {chtypes-2.0.0.dev2 → chtypes-2.0.0.dev4}/tests/ocifetch/test_layout.py +0 -0
- {chtypes-2.0.0.dev2 → chtypes-2.0.0.dev4}/tests/ocifetch/test_lock.py +0 -0
- {chtypes-2.0.0.dev2 → chtypes-2.0.0.dev4}/tests/ocifetch/test_oci.py +0 -0
- {chtypes-2.0.0.dev2 → chtypes-2.0.0.dev4}/tests/ocifetch/test_seam_gate.py +0 -0
- {chtypes-2.0.0.dev2 → chtypes-2.0.0.dev4}/tests/ocifetch/test_unpack.py +0 -0
- {chtypes-2.0.0.dev2 → chtypes-2.0.0.dev4}/tests/test_abi_identity.py +0 -0
- {chtypes-2.0.0.dev2 → chtypes-2.0.0.dev4}/tests/test_cache_roots_api.py +0 -0
- {chtypes-2.0.0.dev2 → chtypes-2.0.0.dev4}/tests/test_devchannel_registry.py +0 -0
- {chtypes-2.0.0.dev2 → chtypes-2.0.0.dev4}/tests/test_ed25519.py +0 -0
- {chtypes-2.0.0.dev2 → chtypes-2.0.0.dev4}/tests/test_examples_tour.py +0 -0
- {chtypes-2.0.0.dev2 → chtypes-2.0.0.dev4}/tests/test_fetch_options.py +0 -0
|
@@ -6,6 +6,19 @@ The four bindings in this repository are released together and give one answer,
|
|
|
6
6
|
|
|
7
7
|
## [Unreleased]
|
|
8
8
|
|
|
9
|
+
### Added
|
|
10
|
+
|
|
11
|
+
- The server's filter-declined settings (ABI v2; moves the dev fingerprint): `SchemaServer.filter_declined_settings`, a tuple of `DeclinedSetting` (`name`, `tier`), decoded from `schema_description`'s new required `server.filter_declined_settings`, with the new `DeclinedTier` vocabulary (`PREDICATE`, `PREDICATE_UNFLIPPED`, `RESULT_CONTENT`, and its unknown(n)). `compile_filter` on a schema whose server lists any is the library's `CHS_DECLINED`, an `UnsupportedError` carrying its message; a filter's own settings other than `session_timezone` are no longer declined at compile, and a declined one is named at evaluation instead.
|
|
12
|
+
- The v2 spec states that a filter verdict counts only when the evaluation's outcome is `ok`: over a body a server would not accept, every verdict is `d`.
|
|
13
|
+
- The v2 spec publishes the WHERE-affecting settings lists (`spec/abi-v2/where-settings/`) with the consumer rule.
|
|
14
|
+
- The v2 spec publishes the filter decline-reasons catalog (`spec/abi-v2/declines/`).
|
|
15
|
+
- A `410 Gone` is a retired repository, and permanent (#571). Any request the fetch layer makes (a tag, an alias, a digest, a blob, `tags/list`, the referrers API or its fallback tag, after a redirect too) that answers `410` is now the new code `CHTYPES_SOURCE_RETIRED` (exit status 10), never retried and never sent to the next base, nor from the dev channel's alias to its tag. It was `CHTYPES_SOURCE_UNREACHABLE` (exit 3) with the registry's message dropped, and Go, Python and Rust moved on to the next base, so a mirror's 404 could hide the retirement. The error names the URL that answered and carries the registry's own message, with control and bidirectional-formatting characters removed and cut to 256 code points: Go `CodeSourceRetired` (`errors.Is(err, ErrSourceRetired)`), Python and TypeScript `SourceRetiredError`, Rust `Error::SourceRetired`. The rule is `docs/guides/fetch-v1.md` section 2, "A retired repository", and the `retired-410-*` and `list-tags-retired-410` conformance cases and one shared message table (`tests/fixtures/retired-message/cases.json`) hold all four bindings to it.
|
|
16
|
+
- A 2.0.0-dev SDK prefers the newest build of its own fingerprint (#511). A version request resolves `<tag>--fp-<fingerprint>` first, the alias the staging dev registry serves beside every dev tag, with the binding's own generated ABI fingerprint, and the tag itself only when every base answers the alias 404; any other failure of the alias request is that failure, with no fallback, and an alias whose index lacks the platform is `CHTYPES_ARTIFACT_UNPUBLISHED`. The dev channel's cache lookups (the installed-build lookup a registry open makes first, the offline answer, and the monotonic rule of a fetch) now see only records whose signed `abi_fingerprint` is the SDK's own, so a newer build of another fingerprint, installed by another dev SDK in the shared cache, is never returned or kept over this one's own, and is never removed or rewritten. So a dev build of a newer fingerprint no longer strands an older dev SDK until its consumers upgrade. Trust is unchanged (the signed predicate and the load checks decide), a listing never shows an alias, nothing records one, `list` and `where` are unchanged, and there is no option: both steps are the dev channel's alone, and the v1 contract and every production channel never take them. The rule is `docs/guides/fetch-v1.md` section 3, and the `dev-alias-*`, `resolve-installed-dev-alias-*` and `list-tags-dev-alias` conformance cases hold all four bindings to it.
|
|
17
|
+
|
|
18
|
+
### Fixed
|
|
19
|
+
|
|
20
|
+
- A 2.0.0-dev SDK whose fingerprint no published build carries yet now fails closed with a precise message (#578). When every base answers its own alias 404 and the tag names a build signed for another fingerprint, `fetch` is `CHTYPES_ARTIFACT_UNPUBLISHED` (exit status 4): `no published build for this SDK's fingerprint <own fp>; newest published on this channel is <fp> (build <id>)`. It used to install that build, or report it already installed, and the lookup at open then answered `CHTYPES_ARTIFACT_MISSING` with advice to run `fetch`: a loop with no pointer to the cause. Nothing is downloaded or installed and the cache is unchanged. An alias that answers, a tag at the SDK's own fingerprint and the v1 contract are unchanged. All four bindings run the same fetch conformance cases, `fp-ahead-*`, and the rule is `docs/guides/fetch-v1.md` section 3.
|
|
21
|
+
|
|
9
22
|
## [1.1.0] — 2026-10-06
|
|
10
23
|
|
|
11
24
|
### Added
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: chtypes
|
|
3
|
-
Version: 2.0.0.
|
|
3
|
+
Version: 2.0.0.dev4
|
|
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,9 +19,11 @@ 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
|
+
>
|
|
26
|
+
> <!-- remove-at-v2-lock -->v2-dev builds before the one that carries this contract (its release notice names it) can answer `ok` with `t` verdicts after a refused row. On those builds, when a body or block holds more than one row, treat every verdict as `d` if `errors` is non-empty.<!-- /remove-at-v2-lock -->
|
|
25
27
|
|
|
26
28
|
**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.
|
|
27
29
|
|
|
@@ -1,8 +1,10 @@
|
|
|
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"`.
|
|
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. 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"`.
|
|
4
4
|
>
|
|
5
5
|
> 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.
|
|
6
|
+
>
|
|
7
|
+
> <!-- remove-at-v2-lock -->v2-dev builds before the one that carries this contract (its release notice names it) can answer `ok` with `t` verdicts after a refused row. On those builds, when a body or block holds more than one row, treat every verdict as `d` if `errors` is non-empty.<!-- /remove-at-v2-lock -->
|
|
6
8
|
|
|
7
9
|
**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.
|
|
8
10
|
|
|
@@ -48,11 +48,13 @@ from importlib.metadata import version as _package_version
|
|
|
48
48
|
|
|
49
49
|
from ._abi2._vocab import (
|
|
50
50
|
EXPORT_NONE,
|
|
51
|
+
DeclinedTier,
|
|
51
52
|
DefaultKind,
|
|
52
53
|
DiscoverQueryParam,
|
|
53
54
|
DocFlags,
|
|
54
55
|
FilterOutcome,
|
|
55
56
|
Format,
|
|
57
|
+
MergeReason,
|
|
56
58
|
Outcome,
|
|
57
59
|
Reason,
|
|
58
60
|
Source,
|
|
@@ -71,6 +73,7 @@ from .errors import (
|
|
|
71
73
|
CODE_CACHE_UNUSABLE,
|
|
72
74
|
CODE_SOURCE_FORBIDDEN,
|
|
73
75
|
CODE_SOURCE_INCOMPATIBLE,
|
|
76
|
+
CODE_SOURCE_RETIRED,
|
|
74
77
|
CODE_SOURCE_UNAUTHORIZED,
|
|
75
78
|
CODE_SOURCE_UNREACHABLE,
|
|
76
79
|
ArtifactCorruptError,
|
|
@@ -87,6 +90,7 @@ from .errors import (
|
|
|
87
90
|
SchemaError,
|
|
88
91
|
SourceForbiddenError,
|
|
89
92
|
SourceIncompatibleError,
|
|
93
|
+
SourceRetiredError,
|
|
90
94
|
SourceUnauthorizedError,
|
|
91
95
|
SourceUnreachableError,
|
|
92
96
|
UnsupportedError,
|
|
@@ -95,11 +99,13 @@ from .errors import (
|
|
|
95
99
|
from .library import Block, Filter, Library, Schema, Server, open_unverified
|
|
96
100
|
from .registry import FetchOptions, Registry, Resolved, TrustedKey, cache_root, search_dirs
|
|
97
101
|
from .results import (
|
|
102
|
+
AtMergeEntry,
|
|
98
103
|
BatchResult,
|
|
99
104
|
BuildInfo,
|
|
100
105
|
Capabilities,
|
|
101
106
|
Column,
|
|
102
107
|
Computed,
|
|
108
|
+
DeclinedSetting,
|
|
103
109
|
DiscoveredColumn,
|
|
104
110
|
Discovery,
|
|
105
111
|
EngineCell,
|
|
@@ -128,6 +134,7 @@ __all__ = [
|
|
|
128
134
|
"CODE_CACHE_UNUSABLE",
|
|
129
135
|
"CODE_SOURCE_FORBIDDEN",
|
|
130
136
|
"CODE_SOURCE_INCOMPATIBLE",
|
|
137
|
+
"CODE_SOURCE_RETIRED",
|
|
131
138
|
"CODE_SOURCE_UNAUTHORIZED",
|
|
132
139
|
"CODE_SOURCE_UNREACHABLE",
|
|
133
140
|
"EXPORT_NONE",
|
|
@@ -138,6 +145,7 @@ __all__ = [
|
|
|
138
145
|
"ArtifactPinnedError",
|
|
139
146
|
"ArtifactUnpublishedError",
|
|
140
147
|
"ArtifactUntrustedError",
|
|
148
|
+
"AtMergeEntry",
|
|
141
149
|
"BatchResult",
|
|
142
150
|
"Block",
|
|
143
151
|
"BuildInfo",
|
|
@@ -148,6 +156,8 @@ __all__ = [
|
|
|
148
156
|
"ChtypesError",
|
|
149
157
|
"Column",
|
|
150
158
|
"Computed",
|
|
159
|
+
"DeclinedSetting",
|
|
160
|
+
"DeclinedTier",
|
|
151
161
|
"DefaultKind",
|
|
152
162
|
"DiscoverQueryParam",
|
|
153
163
|
"DiscoveredColumn",
|
|
@@ -166,6 +176,7 @@ __all__ = [
|
|
|
166
176
|
"Header",
|
|
167
177
|
"InternalError",
|
|
168
178
|
"Library",
|
|
179
|
+
"MergeReason",
|
|
169
180
|
"Outcome",
|
|
170
181
|
"Reason",
|
|
171
182
|
"Registry",
|
|
@@ -182,6 +193,7 @@ __all__ = [
|
|
|
182
193
|
"Source",
|
|
183
194
|
"SourceForbiddenError",
|
|
184
195
|
"SourceIncompatibleError",
|
|
196
|
+
"SourceRetiredError",
|
|
185
197
|
"SourceUnauthorizedError",
|
|
186
198
|
"SourceUnreachableError",
|
|
187
199
|
"Span",
|
|
@@ -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:28c7445ce6b5c6d0ae33177fd60eb7b3b5654a966c842b8d812aea622824dc72) — 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:28c7445ce6b5c6d0ae33177fd60eb7b3b5654a966c842b8d812aea622824dc72"
|
|
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
|
|
@@ -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:28c7445ce6b5c6d0ae33177fd60eb7b3b5654a966c842b8d812aea622824dc72) — 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
|
|
@@ -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:28c7445ce6b5c6d0ae33177fd60eb7b3b5654a966c842b8d812aea622824dc72) — 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`, `
|
|
5
|
-
description attaches to each value
|
|
6
|
-
is_stored, `Verdict.answered`), and the
|
|
7
|
-
package keeps a copy of any of it.
|
|
4
|
+
`Verdict`, `DefaultKind`, `MergeReason`, `DeclinedTier`, `Reason`, `Source`,
|
|
5
|
+
`DiscoverQueryParam`) with the facts the description attaches to each value
|
|
6
|
+
(`Format.ch_name`, `Reason` lossy, `Source` is_stored, `Verdict.answered`), and the
|
|
7
|
+
`DocFlags` groups. No other file in 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,55 @@ 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
|
+
|
|
252
|
+
class DeclinedTier(StrEnum):
|
|
253
|
+
"""The WHERE-settings tier of a setting a server profile sets and this build's
|
|
254
|
+
filters do not honor in a WHERE, as a `DeclinedSetting` reports it."""
|
|
255
|
+
|
|
256
|
+
PREDICATE = "predicate"
|
|
257
|
+
PREDICATE_UNFLIPPED = "predicate-unflipped"
|
|
258
|
+
RESULT_CONTENT = "result-content"
|
|
259
|
+
|
|
260
|
+
@classmethod
|
|
261
|
+
def _missing_(cls, value: object) -> DeclinedTier | None:
|
|
262
|
+
return _unknown_str(cls, value)
|
|
263
|
+
|
|
264
|
+
@property
|
|
265
|
+
def known(self) -> bool:
|
|
266
|
+
"""Whether the description lists this value: False for exactly the unknown(n)
|
|
267
|
+
member an unlisted value reads as (rule r3)."""
|
|
268
|
+
return type(self)._value2member_map_.get(self._value_) is self
|
|
269
|
+
|
|
270
|
+
@classmethod
|
|
271
|
+
def of(cls, text: str) -> DeclinedTier:
|
|
272
|
+
"""Map a document's spelling. One the description does not list is kept as
|
|
273
|
+
this vocabulary's unknown(n) member, carrying it (rule r3)."""
|
|
274
|
+
return cls(text)
|
|
275
|
+
|
|
276
|
+
|
|
228
277
|
class DiscoverQueryParam(StrEnum):
|
|
229
278
|
"""A query parameter of the SQL `discover_query` returns, which the caller binds
|
|
230
279
|
when it runs the query."""
|
|
@@ -383,6 +432,7 @@ DESCRIBED_VOCABULARIES: Final[dict[str, type]] = {
|
|
|
383
432
|
"chs_status": Status,
|
|
384
433
|
"chs_format": Format,
|
|
385
434
|
"transform_reason": Reason,
|
|
435
|
+
"merge_reason": MergeReason,
|
|
386
436
|
"value_src": Source,
|
|
387
437
|
"row_outcome": Outcome,
|
|
388
438
|
"batch_outcome": Outcome,
|
|
@@ -390,6 +440,7 @@ DESCRIBED_VOCABULARIES: Final[dict[str, type]] = {
|
|
|
390
440
|
"filter_verdict": Verdict,
|
|
391
441
|
"discover_query_param": DiscoverQueryParam,
|
|
392
442
|
"default_kind": DefaultKind,
|
|
443
|
+
"declined_tier": DeclinedTier,
|
|
393
444
|
}
|
|
394
445
|
|
|
395
446
|
|
|
@@ -28,14 +28,25 @@ import json
|
|
|
28
28
|
from collections.abc import Callable, Mapping
|
|
29
29
|
from typing import Any
|
|
30
30
|
|
|
31
|
-
from ._abi2._vocab import
|
|
31
|
+
from ._abi2._vocab import (
|
|
32
|
+
DeclinedTier,
|
|
33
|
+
DefaultKind,
|
|
34
|
+
FilterOutcome,
|
|
35
|
+
MergeReason,
|
|
36
|
+
Outcome,
|
|
37
|
+
Reason,
|
|
38
|
+
Source,
|
|
39
|
+
Verdict,
|
|
40
|
+
)
|
|
32
41
|
from .errors import ArtifactCorruptError, internal
|
|
33
42
|
from .results import (
|
|
43
|
+
AtMergeEntry,
|
|
34
44
|
BatchResult,
|
|
35
45
|
BuildInfo,
|
|
36
46
|
Capabilities,
|
|
37
47
|
Column,
|
|
38
48
|
Computed,
|
|
49
|
+
DeclinedSetting,
|
|
39
50
|
DiscoveredColumn,
|
|
40
51
|
Discovery,
|
|
41
52
|
EngineCell,
|
|
@@ -325,6 +336,29 @@ def _engine_cell(obj: dict[str, Any], where: str) -> EngineCell:
|
|
|
325
336
|
)
|
|
326
337
|
|
|
327
338
|
|
|
339
|
+
def _int_list(obj: Mapping[str, Any], key: str, where: str) -> tuple[int, ...] | None:
|
|
340
|
+
"""A list of non-negative integers: None when absent."""
|
|
341
|
+
if obj.get(key) is None:
|
|
342
|
+
return None
|
|
343
|
+
out = []
|
|
344
|
+
for item in _list(obj, key, where):
|
|
345
|
+
if isinstance(item, bool) or not isinstance(item, int) or item < 0:
|
|
346
|
+
raise _wrong(where, key, "an array of non-negative integers", item)
|
|
347
|
+
out.append(item)
|
|
348
|
+
return tuple(out)
|
|
349
|
+
|
|
350
|
+
|
|
351
|
+
def _at_merge(obj: dict[str, Any], where: str) -> AtMergeEntry:
|
|
352
|
+
# An unlisted reason is its unknown(n): kept, never a failure (rule r3).
|
|
353
|
+
return AtMergeEntry(
|
|
354
|
+
row=_int(obj, "row", where),
|
|
355
|
+
reason=MergeReason.of(_text(obj, "reason", where)),
|
|
356
|
+
column=_bytes_or_none(obj, "column", where),
|
|
357
|
+
stored=_bytes_or_none(obj, "stored", where),
|
|
358
|
+
input_rows=_int_list(obj, "input_rows", where),
|
|
359
|
+
)
|
|
360
|
+
|
|
361
|
+
|
|
328
362
|
def _engine_rows(obj: Mapping[str, Any], where: str) -> tuple[tuple[EngineCell, ...], ...]:
|
|
329
363
|
rows = []
|
|
330
364
|
for i, row in enumerate(_list(obj, "engine_rows", where)):
|
|
@@ -411,6 +445,8 @@ def decode_batch(raw: bytes, export: bytes | None) -> BatchResult:
|
|
|
411
445
|
framing=None
|
|
412
446
|
if framing is None
|
|
413
447
|
else _framing(_obj(framing, where, "framing"), f"{where}.framing"),
|
|
448
|
+
unsupported_settings=_name_list(obj, "unsupported_settings", where),
|
|
449
|
+
at_merge=_objects(obj, "at_merge", where, _at_merge),
|
|
414
450
|
)
|
|
415
451
|
|
|
416
452
|
|
|
@@ -467,11 +503,22 @@ def _string_map(obj: Mapping[str, Any], key: str, where: str) -> dict[str, str]
|
|
|
467
503
|
return out
|
|
468
504
|
|
|
469
505
|
|
|
506
|
+
def _declined_setting(obj: dict[str, Any], where: str) -> DeclinedSetting:
|
|
507
|
+
# An unlisted tier is its unknown(n): kept, never a failure (rule r3).
|
|
508
|
+
return DeclinedSetting(
|
|
509
|
+
name=_name(obj, "name", where),
|
|
510
|
+
tier=DeclinedTier.of(_text(obj, "tier", where)),
|
|
511
|
+
)
|
|
512
|
+
|
|
513
|
+
|
|
470
514
|
def _server(obj: dict[str, Any], where: str) -> SchemaServer:
|
|
471
515
|
return SchemaServer(
|
|
472
516
|
timezone=_text(obj, "timezone", where),
|
|
473
517
|
settings=_string_map(obj, "settings", where) or {},
|
|
474
518
|
macros=_string_map(obj, "macros", where),
|
|
519
|
+
filter_declined_settings=_objects(
|
|
520
|
+
obj, "filter_declined_settings", where, _declined_setting
|
|
521
|
+
),
|
|
475
522
|
)
|
|
476
523
|
|
|
477
524
|
|
|
@@ -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,20 @@ 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
|
|
55
|
+
from chtypes._ocifetch._errors import ArtifactUnpublishedError
|
|
40
56
|
|
|
41
57
|
__all__ = [
|
|
58
|
+
"ALIAS_SEPARATOR",
|
|
42
59
|
"DEV_ABI_GENERATION",
|
|
43
60
|
"DEV_CACHE_DIR",
|
|
44
61
|
"DEV_CHANNEL_BASE",
|
|
@@ -50,12 +67,16 @@ __all__ = [
|
|
|
50
67
|
"PinningRefusedError",
|
|
51
68
|
"abi",
|
|
52
69
|
"active",
|
|
70
|
+
"ahead_of_registry",
|
|
71
|
+
"alias_tag",
|
|
53
72
|
"allow_overrides_for_tests",
|
|
54
73
|
"channel_name",
|
|
55
74
|
"enforce",
|
|
56
75
|
"ignored_settings_warned",
|
|
57
76
|
"pinning_requested",
|
|
58
77
|
"trusted_keys",
|
|
78
|
+
"use_own_fingerprint_for_tests",
|
|
79
|
+
"visible",
|
|
59
80
|
"use_dev_channel_for_tests",
|
|
60
81
|
"use_fetch_v1_for_tests",
|
|
61
82
|
]
|
|
@@ -79,6 +100,10 @@ DEV_RECORD_SCHEMA: Final = 2
|
|
|
79
100
|
# The abi a dev predicate must carry.
|
|
80
101
|
DEV_ABI_GENERATION: Final = 2
|
|
81
102
|
|
|
103
|
+
# Joins a tag and a fingerprint into the dev channel's alias tag:
|
|
104
|
+
# <tag>--fp-<64 lowercase hex> (docs/guides/fetch-v1.md §3).
|
|
105
|
+
ALIAS_SEPARATOR: Final = "--fp-"
|
|
106
|
+
|
|
82
107
|
# What every lock, frozen or update request gets from a 2.0.0-dev SDK, before
|
|
83
108
|
# any network call (rule r6).
|
|
84
109
|
PINNING_REFUSED: Final = (
|
|
@@ -102,6 +127,11 @@ class Channel:
|
|
|
102
127
|
keys: tuple[dict[str, str], ...] # {"keyid", "ed25519_hex"}: the default trust
|
|
103
128
|
overridable: bool # the base, trust and unsigned overrides are honored
|
|
104
129
|
pinnable: bool # lock, frozen and update are honored
|
|
130
|
+
# The fingerprint, 64 lowercase hex, this contract's SDK speaks: a version
|
|
131
|
+
# request resolves its alias tag before the tag itself, and the cache
|
|
132
|
+
# lookups see only records signed with it. "" does neither (the v1 contract
|
|
133
|
+
# and every production channel).
|
|
134
|
+
own_fingerprint: str = ""
|
|
105
135
|
|
|
106
136
|
|
|
107
137
|
# What a 2.0.0-dev SDK speaks, and what every process speaks unless a test
|
|
@@ -117,6 +147,7 @@ DEV_CHANNEL: Final = Channel(
|
|
|
117
147
|
keys=({"keyid": DEV_KEY_ID, "ed25519_hex": DEV_KEY_HEX},),
|
|
118
148
|
overridable=False,
|
|
119
149
|
pinnable=False,
|
|
150
|
+
own_fingerprint=CHS_ABI_FINGERPRINT.removeprefix("sha256:"),
|
|
120
151
|
)
|
|
121
152
|
|
|
122
153
|
# The v1 contract the fetch-v1 conformance cases specify, from the generated
|
|
@@ -202,6 +233,86 @@ def allow_overrides_for_tests() -> Callable[[], None]:
|
|
|
202
233
|
return _use(replace(DEV_CHANNEL, overridable=True))
|
|
203
234
|
|
|
204
235
|
|
|
236
|
+
_FINGERPRINT_HEX = re.compile(r"[0-9a-f]{64}")
|
|
237
|
+
|
|
238
|
+
|
|
239
|
+
def use_own_fingerprint_for_tests(fingerprint: str) -> Callable[[], None]:
|
|
240
|
+
"""Make this TEST run's active contract speak `fingerprint` (64 lowercase
|
|
241
|
+
hex) as the dev channel speaks its own: a version request resolves that
|
|
242
|
+
fingerprint's alias tag first, and the cache lookups see only records
|
|
243
|
+
signed with it. The fetch-v1 conformance cases that carry
|
|
244
|
+
`request.own_fingerprint` run under it, against fixtures that name a
|
|
245
|
+
fixture fingerprint. Everything else stays the active contract's. Returns
|
|
246
|
+
the restore function; raises outside a test run or on a fingerprint that is
|
|
247
|
+
not 64 lowercase hex."""
|
|
248
|
+
_test_only("use_own_fingerprint_for_tests")
|
|
249
|
+
if not _FINGERPRINT_HEX.fullmatch(fingerprint):
|
|
250
|
+
raise ValueError(f"use_own_fingerprint_for_tests: {fingerprint!r} is not 64 lowercase hex")
|
|
251
|
+
return _use(replace(active(), own_fingerprint=fingerprint))
|
|
252
|
+
|
|
253
|
+
|
|
254
|
+
def alias_tag(tag: str, channel: Channel | None = None) -> str | None:
|
|
255
|
+
"""The alias a version request for `tag` resolves first under a contract
|
|
256
|
+
with an own fingerprint, or None when it resolves `tag` alone: under the
|
|
257
|
+
v1 contract and every production channel, and for a request that is not a
|
|
258
|
+
version spelling (an arbitrary tag has no alias)."""
|
|
259
|
+
channel = channel or active()
|
|
260
|
+
if not channel.own_fingerprint or not re.fullmatch(C.SPELLING_REGEX, tag):
|
|
261
|
+
return None
|
|
262
|
+
return f"{tag}{ALIAS_SEPARATOR}{channel.own_fingerprint}"
|
|
263
|
+
|
|
264
|
+
|
|
265
|
+
def visible(predicate: object, channel: Channel | None = None) -> bool:
|
|
266
|
+
"""Whether a cache record (or a pre-seeded entry) whose SIGNED predicate is
|
|
267
|
+
`predicate` may answer a request under the active contract: under one with
|
|
268
|
+
an own fingerprint (the dev channel), only when the predicate's
|
|
269
|
+
`abi_fingerprint` is that fingerprint; under every other, always. A record
|
|
270
|
+
it cannot see is never returned and never kept by the monotonic rule, and
|
|
271
|
+
nothing removes or rewrites it: another SDK of another fingerprint owns it."""
|
|
272
|
+
channel = channel or active()
|
|
273
|
+
if not channel.own_fingerprint:
|
|
274
|
+
return True
|
|
275
|
+
fingerprint = predicate.get("abi_fingerprint") if isinstance(predicate, dict) else None
|
|
276
|
+
return fingerprint == f"sha256:{channel.own_fingerprint}"
|
|
277
|
+
|
|
278
|
+
|
|
279
|
+
def ahead_of_registry(
|
|
280
|
+
alias_absent: bool, predicate: object, channel: Channel | None = None
|
|
281
|
+
) -> None:
|
|
282
|
+
"""The dev channel's answer for an SDK whose fingerprint no published build
|
|
283
|
+
carries yet (docs/guides/fetch-v1.md §3; public issue #578): when every base
|
|
284
|
+
answered its own alias 404 (`alias_absent`) and the build the tag names is
|
|
285
|
+
signed for another fingerprint (`predicate`, the SIGNED predicate, the field
|
|
286
|
+
`visible` keys on), raise `ArtifactUnpublishedError`, the code a request no
|
|
287
|
+
build answers gets, naming both fingerprints. Nothing is installed and an
|
|
288
|
+
install of that build is never reported, because the lookup this SDK opens
|
|
289
|
+
through would refuse it. Returns in every other case: the alias answered,
|
|
290
|
+
the tag's build is this SDK's own, or the contract has no own fingerprint."""
|
|
291
|
+
channel = channel or active()
|
|
292
|
+
if not alias_absent or visible(predicate, channel):
|
|
293
|
+
return
|
|
294
|
+
raise ArtifactUnpublishedError(f"chtypes: {_ahead_message(channel.own_fingerprint, predicate)}")
|
|
295
|
+
|
|
296
|
+
|
|
297
|
+
def _ahead_message(own: str, predicate: object) -> str:
|
|
298
|
+
"""`ahead_of_registry`'s message: "no published build for this SDK's
|
|
299
|
+
fingerprint <own>; newest published on this channel is <fp> (build <id>)",
|
|
300
|
+
each fingerprint 64 lowercase hex without its `sha256:` prefix, the
|
|
301
|
+
parenthesis dropped when the predicate names no build, and "unnamed" for a
|
|
302
|
+
predicate that names no fingerprint."""
|
|
303
|
+
fields = predicate if isinstance(predicate, dict) else {}
|
|
304
|
+
theirs = fields.get("abi_fingerprint")
|
|
305
|
+
theirs = theirs.removeprefix("sha256:") if isinstance(theirs, str) else ""
|
|
306
|
+
message = (
|
|
307
|
+
f"no published build for this SDK's fingerprint {own}; "
|
|
308
|
+
f"newest published on this channel is {theirs or 'unnamed'}"
|
|
309
|
+
)
|
|
310
|
+
build = fields.get("build")
|
|
311
|
+
if isinstance(build, str) and build:
|
|
312
|
+
message += f" (build {build})"
|
|
313
|
+
return message
|
|
314
|
+
|
|
315
|
+
|
|
205
316
|
def use_dev_channel_for_tests() -> Callable[[], None]:
|
|
206
317
|
"""Make this TEST run speak the dev channel exactly as a user's process does
|
|
207
318
|
(undoing either function above, for a test of the dev channel itself).
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Code generated by scripts/fetch-v1/gen-constants.py from
|
|
2
2
|
# spec/fetch-v1/constants.json. DO NOT EDIT.
|
|
3
|
-
# Source sha256:
|
|
3
|
+
# Source sha256: 4554956faeb154fc324a6120b7b35976222c6bc8ecbce7fd0147502e3073611d
|
|
4
4
|
"""The v1 fetch constants, generated: see docs/guides/fetch-v1.md."""
|
|
5
5
|
|
|
6
6
|
from __future__ import annotations
|
|
@@ -83,6 +83,7 @@ RETRY_AFTER_STATUSES: Final[tuple[int, ...]] = (429, 503)
|
|
|
83
83
|
RETRY_AFTER_OVER_BUDGET: Final[str] = "refuse"
|
|
84
84
|
DIGEST_404_POLICY: Final[str] = "next_base_then_retry_on_last"
|
|
85
85
|
TAG_404_POLICY: Final[str] = "next_base_then_unpublished"
|
|
86
|
+
RETIRED_STATUSES: Final[tuple[int, ...]] = (410,)
|
|
86
87
|
|
|
87
88
|
CONNECT_TIMEOUT_S: Final[float] = 30.0
|
|
88
89
|
IDLE_READ_TIMEOUT_S: Final[float] = 60.0
|
|
@@ -95,6 +96,8 @@ MAX_REFERRERS: Final[int] = 16
|
|
|
95
96
|
MAX_REDIRECTS: Final[int] = 5
|
|
96
97
|
MAX_UNPACKED_BYTES: Final[int] = 4294967296
|
|
97
98
|
ZSTD_WINDOW_LOG_MAX: Final[int] = 27
|
|
99
|
+
RETIRED_BODY_MAX_BYTES: Final[int] = 65536
|
|
100
|
+
RETIRED_MESSAGE_MAX_CODE_POINTS: Final[int] = 256
|
|
98
101
|
|
|
99
102
|
CACHE_ROOT_TEMPLATE: Final[str] = "${XDG_CACHE_HOME:-$HOME/.cache}/chtypes/v1"
|
|
100
103
|
SYSTEM_CACHE_DIRS: Final[tuple[str, ...]] = ("/usr/local/share/chtypes/v1", "/opt/chtypes/v1")
|
|
@@ -122,4 +125,5 @@ ERROR_EXIT_CODES: Final[dict[str, int]] = {
|
|
|
122
125
|
"CHTYPES_SOURCE_INCOMPATIBLE": 7,
|
|
123
126
|
"CHTYPES_ARTIFACT_INCOMPATIBLE": 8,
|
|
124
127
|
"CHTYPES_CACHE_UNUSABLE": 9,
|
|
128
|
+
"CHTYPES_SOURCE_RETIRED": 10,
|
|
125
129
|
}
|