chtypes 2.0.0.dev3__tar.gz → 2.0.0.dev5__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.dev3 → chtypes-2.0.0.dev5}/CHANGELOG.md +33 -0
- {chtypes-2.0.0.dev3 → chtypes-2.0.0.dev5}/PKG-INFO +3 -1
- {chtypes-2.0.0.dev3 → chtypes-2.0.0.dev5}/README.md +2 -0
- {chtypes-2.0.0.dev3 → chtypes-2.0.0.dev5}/pyproject.toml +1 -1
- {chtypes-2.0.0.dev3 → chtypes-2.0.0.dev5}/src/chtypes/__init__.py +10 -0
- {chtypes-2.0.0.dev3 → chtypes-2.0.0.dev5}/src/chtypes/_abi2/_decls.py +2 -2
- {chtypes-2.0.0.dev3 → chtypes-2.0.0.dev5}/src/chtypes/_abi2/_errmap.py +1 -1
- {chtypes-2.0.0.dev3 → chtypes-2.0.0.dev5}/src/chtypes/_abi2/_vocab.py +57 -5
- {chtypes-2.0.0.dev3 → chtypes-2.0.0.dev5}/src/chtypes/_decode.py +25 -1
- {chtypes-2.0.0.dev3 → chtypes-2.0.0.dev5}/src/chtypes/_ocifetch/_channel.py +65 -0
- {chtypes-2.0.0.dev3 → chtypes-2.0.0.dev5}/src/chtypes/_ocifetch/_constants.py +5 -1
- {chtypes-2.0.0.dev3 → chtypes-2.0.0.dev5}/src/chtypes/_ocifetch/_ensure.py +51 -4
- {chtypes-2.0.0.dev3 → chtypes-2.0.0.dev5}/src/chtypes/_ocifetch/_errors.py +15 -1
- {chtypes-2.0.0.dev3 → chtypes-2.0.0.dev5}/src/chtypes/_ocifetch/_http.py +28 -1
- {chtypes-2.0.0.dev3 → chtypes-2.0.0.dev5}/src/chtypes/_ocifetch/_oci.py +10 -6
- chtypes-2.0.0.dev5/src/chtypes/_ocifetch/_retired.py +77 -0
- {chtypes-2.0.0.dev3 → chtypes-2.0.0.dev5}/src/chtypes/errors.py +12 -0
- {chtypes-2.0.0.dev3 → chtypes-2.0.0.dev5}/src/chtypes/registry.py +1 -0
- {chtypes-2.0.0.dev3 → chtypes-2.0.0.dev5}/src/chtypes/results.py +41 -6
- {chtypes-2.0.0.dev3 → chtypes-2.0.0.dev5}/tests/abi2/test_public_server.py +106 -1
- {chtypes-2.0.0.dev3 → chtypes-2.0.0.dev5}/tests/abi2/test_public_setup_cases.py +30 -1
- {chtypes-2.0.0.dev3 → chtypes-2.0.0.dev5}/tests/cli/test_main.py +40 -0
- {chtypes-2.0.0.dev3 → chtypes-2.0.0.dev5}/tests/ocifetch/test_conformance.py +8 -0
- chtypes-2.0.0.dev5/tests/ocifetch/test_retired.py +181 -0
- {chtypes-2.0.0.dev3 → chtypes-2.0.0.dev5}/uv.lock +1 -1
- {chtypes-2.0.0.dev3 → chtypes-2.0.0.dev5}/.gitignore +0 -0
- {chtypes-2.0.0.dev3 → chtypes-2.0.0.dev5}/LICENSE +0 -0
- {chtypes-2.0.0.dev3 → chtypes-2.0.0.dev5}/src/chtypes/__main__.py +0 -0
- {chtypes-2.0.0.dev3 → chtypes-2.0.0.dev5}/src/chtypes/_abi2/__init__.py +0 -0
- {chtypes-2.0.0.dev3 → chtypes-2.0.0.dev5}/src/chtypes/_abi2/_errors.py +0 -0
- {chtypes-2.0.0.dev3 → chtypes-2.0.0.dev5}/src/chtypes/_abi2/_loader.py +0 -0
- {chtypes-2.0.0.dev3 → chtypes-2.0.0.dev5}/src/chtypes/_ed25519.py +0 -0
- {chtypes-2.0.0.dev3 → chtypes-2.0.0.dev5}/src/chtypes/_guard.py +0 -0
- {chtypes-2.0.0.dev3 → chtypes-2.0.0.dev5}/src/chtypes/_input.py +0 -0
- {chtypes-2.0.0.dev3 → chtypes-2.0.0.dev5}/src/chtypes/_ocifetch/__init__.py +0 -0
- {chtypes-2.0.0.dev3 → chtypes-2.0.0.dev5}/src/chtypes/_ocifetch/_dsse.py +0 -0
- {chtypes-2.0.0.dev3 → chtypes-2.0.0.dev5}/src/chtypes/_ocifetch/_faults.py +0 -0
- {chtypes-2.0.0.dev3 → chtypes-2.0.0.dev5}/src/chtypes/_ocifetch/_goldens.py +0 -0
- {chtypes-2.0.0.dev3 → chtypes-2.0.0.dev5}/src/chtypes/_ocifetch/_layout.py +0 -0
- {chtypes-2.0.0.dev3 → chtypes-2.0.0.dev5}/src/chtypes/_ocifetch/_lock.py +0 -0
- {chtypes-2.0.0.dev3 → chtypes-2.0.0.dev5}/src/chtypes/_ocifetch/_referrers.py +0 -0
- {chtypes-2.0.0.dev3 → chtypes-2.0.0.dev5}/src/chtypes/_ocifetch/_unpack.py +0 -0
- {chtypes-2.0.0.dev3 → chtypes-2.0.0.dev5}/src/chtypes/_setup.py +0 -0
- {chtypes-2.0.0.dev3 → chtypes-2.0.0.dev5}/src/chtypes/library.py +0 -0
- {chtypes-2.0.0.dev3 → chtypes-2.0.0.dev5}/src/chtypes/py.typed +0 -0
- {chtypes-2.0.0.dev3 → chtypes-2.0.0.dev5}/tests/abi2/__init__.py +0 -0
- {chtypes-2.0.0.dev3 → chtypes-2.0.0.dev5}/tests/abi2/_stub_tree.py +0 -0
- {chtypes-2.0.0.dev3 → chtypes-2.0.0.dev5}/tests/abi2/conftest.py +0 -0
- {chtypes-2.0.0.dev3 → chtypes-2.0.0.dev5}/tests/abi2/test_conformance.py +0 -0
- {chtypes-2.0.0.dev3 → chtypes-2.0.0.dev5}/tests/abi2/test_dev_fingerprint.py +0 -0
- {chtypes-2.0.0.dev3 → chtypes-2.0.0.dev5}/tests/abi2/test_goldens_runner_stub.py +0 -0
- {chtypes-2.0.0.dev3 → chtypes-2.0.0.dev5}/tests/abi2/test_loader.py +0 -0
- {chtypes-2.0.0.dev3 → chtypes-2.0.0.dev5}/tests/abi2/test_public_library.py +0 -0
- {chtypes-2.0.0.dev3 → chtypes-2.0.0.dev5}/tests/abi2/test_public_open.py +0 -0
- {chtypes-2.0.0.dev3 → chtypes-2.0.0.dev5}/tests/abi2/test_public_registry.py +0 -0
- {chtypes-2.0.0.dev3 → chtypes-2.0.0.dev5}/tests/abi2/test_reader_rules.py +0 -0
- {chtypes-2.0.0.dev3 → chtypes-2.0.0.dev5}/tests/api/__init__.py +0 -0
- {chtypes-2.0.0.dev3 → chtypes-2.0.0.dev5}/tests/api/test_decode.py +0 -0
- {chtypes-2.0.0.dev3 → chtypes-2.0.0.dev5}/tests/api/test_input.py +0 -0
- {chtypes-2.0.0.dev3 → chtypes-2.0.0.dev5}/tests/api/test_vocab.py +0 -0
- {chtypes-2.0.0.dev3 → chtypes-2.0.0.dev5}/tests/cli/__init__.py +0 -0
- {chtypes-2.0.0.dev3 → chtypes-2.0.0.dev5}/tests/cli/conftest.py +0 -0
- {chtypes-2.0.0.dev3 → chtypes-2.0.0.dev5}/tests/cli/test_devchannel.py +0 -0
- {chtypes-2.0.0.dev3 → chtypes-2.0.0.dev5}/tests/conftest.py +0 -0
- {chtypes-2.0.0.dev3 → chtypes-2.0.0.dev5}/tests/goldens_v1/__init__.py +0 -0
- {chtypes-2.0.0.dev3 → chtypes-2.0.0.dev5}/tests/goldens_v1/data/stub-goldens.json +0 -0
- {chtypes-2.0.0.dev3 → chtypes-2.0.0.dev5}/tests/goldens_v1/test_runner.py +0 -0
- {chtypes-2.0.0.dev3 → chtypes-2.0.0.dev5}/tests/ocifetch/__init__.py +0 -0
- {chtypes-2.0.0.dev3 → chtypes-2.0.0.dev5}/tests/ocifetch/_bundle_support.py +0 -0
- {chtypes-2.0.0.dev3 → chtypes-2.0.0.dev5}/tests/ocifetch/_registry_support.py +0 -0
- {chtypes-2.0.0.dev3 → chtypes-2.0.0.dev5}/tests/ocifetch/_sign_support.py +0 -0
- {chtypes-2.0.0.dev3 → chtypes-2.0.0.dev5}/tests/ocifetch/conftest.py +0 -0
- {chtypes-2.0.0.dev3 → chtypes-2.0.0.dev5}/tests/ocifetch/test_cache_roots.py +0 -0
- {chtypes-2.0.0.dev3 → chtypes-2.0.0.dev5}/tests/ocifetch/test_cache_strict.py +0 -0
- {chtypes-2.0.0.dev3 → chtypes-2.0.0.dev5}/tests/ocifetch/test_devchannel.py +0 -0
- {chtypes-2.0.0.dev3 → chtypes-2.0.0.dev5}/tests/ocifetch/test_dsse.py +0 -0
- {chtypes-2.0.0.dev3 → chtypes-2.0.0.dev5}/tests/ocifetch/test_ensure.py +0 -0
- {chtypes-2.0.0.dev3 → chtypes-2.0.0.dev5}/tests/ocifetch/test_goldens.py +0 -0
- {chtypes-2.0.0.dev3 → chtypes-2.0.0.dev5}/tests/ocifetch/test_http.py +0 -0
- {chtypes-2.0.0.dev3 → chtypes-2.0.0.dev5}/tests/ocifetch/test_layout.py +0 -0
- {chtypes-2.0.0.dev3 → chtypes-2.0.0.dev5}/tests/ocifetch/test_lock.py +0 -0
- {chtypes-2.0.0.dev3 → chtypes-2.0.0.dev5}/tests/ocifetch/test_oci.py +0 -0
- {chtypes-2.0.0.dev3 → chtypes-2.0.0.dev5}/tests/ocifetch/test_seam_gate.py +0 -0
- {chtypes-2.0.0.dev3 → chtypes-2.0.0.dev5}/tests/ocifetch/test_unpack.py +0 -0
- {chtypes-2.0.0.dev3 → chtypes-2.0.0.dev5}/tests/test_abi_identity.py +0 -0
- {chtypes-2.0.0.dev3 → chtypes-2.0.0.dev5}/tests/test_cache_roots_api.py +0 -0
- {chtypes-2.0.0.dev3 → chtypes-2.0.0.dev5}/tests/test_devchannel_registry.py +0 -0
- {chtypes-2.0.0.dev3 → chtypes-2.0.0.dev5}/tests/test_ed25519.py +0 -0
- {chtypes-2.0.0.dev3 → chtypes-2.0.0.dev5}/tests/test_examples_tour.py +0 -0
- {chtypes-2.0.0.dev3 → chtypes-2.0.0.dev5}/tests/test_fetch_options.py +0 -0
|
@@ -8,8 +8,41 @@ The four bindings in this repository are released together and give one answer,
|
|
|
8
8
|
|
|
9
9
|
### Added
|
|
10
10
|
|
|
11
|
+
- Every settings layer's filter-declined settings, in one list (ABI v2; moves the dev fingerprint; #588): `SchemaDescription.filter_declined_settings`, a tuple of `DeclinedSetting`, decoded from `schema_description`'s new required top-level `filter_declined_settings`, over every layer known at schema compile (`setup`'s defaults as the compile captured them, the server profile, and the schema's own settings). `DeclinedSetting` gains `layer`, the highest layer that sets the name, from the new `DeclinedLayer` vocabulary (`DEFAULTS`, `SERVER`, `SCHEMA`, and its unknown(n)). One entry per name, ordered by layer and then by name bytes, as the library writes it; the binding sorts nothing.
|
|
12
|
+
- `Schema.describe` raises the library's `CHS_REJECTED` with ch_code 306 (TOO_DEEP_RECURSION), a `SchemaError`, for a deep input the server's stack check refuses (ABI v2: `chs_schema_describe` now lists `CHS_REJECTED`).
|
|
13
|
+
- 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.
|
|
14
|
+
- 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`.
|
|
15
|
+
- The v2 spec publishes the WHERE-affecting settings lists (`spec/abi-v2/where-settings/`) with the consumer rule.
|
|
16
|
+
- The v2 spec publishes the filter decline-reasons catalog (`spec/abi-v2/declines/`).
|
|
17
|
+
- The v2 spec's decline catalog has 49 entries, and it publishes the settings values a server refuses outright (`spec/abi-v2/where-settings/documented-pathological.tsv`).
|
|
18
|
+
- The v2 spec's decline catalog follows the producer's batch 11: its `server-profile-setting` entry now covers every settings layer the schema compiles under (the id is unchanged), the evaluation-time setting entries name only the filter's own settings, and its `stack-depth` entry lists `chs_schema_describe`.
|
|
19
|
+
- 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.
|
|
11
20
|
- 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.
|
|
12
21
|
|
|
22
|
+
### Changed
|
|
23
|
+
|
|
24
|
+
- `compile_filter` is the library's `CHS_DECLINED`, `UnsupportedError`, on a schema whose `filter_declined_settings` is not empty from any layer, and its message names each setting and its layer (ABI v2, #588). A declined setting from the defaults or the schema's own settings now fails the compile, where it was named at evaluation before; a filter's own settings and an evaluation's are still named at evaluation.
|
|
25
|
+
- **Enforcement gate lifted** for `26.3` on `linux-amd64`.
|
|
26
|
+
- **Enforcement gate lifted** for `26.7` on `linux-amd64`.
|
|
27
|
+
- **Enforcement gate lifted** for `26.8` on `linux-amd64`.
|
|
28
|
+
- **Enforcement gate lifted** for `26.9` on `linux-amd64`.
|
|
29
|
+
- **Enforcement gate lifted** for `26.3` on `linux-arm64`.
|
|
30
|
+
- **Enforcement gate lifted** for `26.7` on `linux-arm64`.
|
|
31
|
+
- **Enforcement gate lifted** for `26.8` on `linux-arm64`.
|
|
32
|
+
- **Enforcement gate lifted** for `26.9` on `linux-arm64`.
|
|
33
|
+
- **Enforcement gate lifted** for `26.7` on `darwin-arm64`.
|
|
34
|
+
- **Enforcement gate lifted** for `26.8` on `darwin-arm64`.
|
|
35
|
+
- **Enforcement gate lifted** for `26.9` on `darwin-arm64`.
|
|
36
|
+
|
|
37
|
+
### Removed
|
|
38
|
+
|
|
39
|
+
- `SchemaServer.filter_declined_settings` (a breaking change inside v2 dev, #588): the server member no longer carries the list. Read `SchemaDescription.filter_declined_settings`, whose `server`-layer entries are the profile's.
|
|
40
|
+
|
|
41
|
+
### Fixed
|
|
42
|
+
|
|
43
|
+
- A 2.0.0-dev SDK whose own-fingerprint alias has expired, with a cached build of that fingerprint for the request, now says so in the `CHTYPES_ARTIFACT_UNPUBLISHED` message of `fetch` (#581): `; a cached build for this fingerprint (build <id>) is still usable by open; upgrade the SDK to get newer builds` is appended. The code, the exit status (4) and the cache are unchanged, and all four bindings give the same message (`fp-ahead-cached-own-fp`).
|
|
44
|
+
- 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.
|
|
45
|
+
|
|
13
46
|
## [1.1.0] — 2026-10-06
|
|
14
47
|
|
|
15
48
|
### 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.dev5
|
|
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
|
|
@@ -22,6 +22,8 @@ Description-Content-Type: text/markdown
|
|
|
22
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
|
|
|
@@ -3,6 +3,8 @@
|
|
|
3
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,6 +48,8 @@ from importlib.metadata import version as _package_version
|
|
|
48
48
|
|
|
49
49
|
from ._abi2._vocab import (
|
|
50
50
|
EXPORT_NONE,
|
|
51
|
+
DeclinedLayer,
|
|
52
|
+
DeclinedTier,
|
|
51
53
|
DefaultKind,
|
|
52
54
|
DiscoverQueryParam,
|
|
53
55
|
DocFlags,
|
|
@@ -72,6 +74,7 @@ from .errors import (
|
|
|
72
74
|
CODE_CACHE_UNUSABLE,
|
|
73
75
|
CODE_SOURCE_FORBIDDEN,
|
|
74
76
|
CODE_SOURCE_INCOMPATIBLE,
|
|
77
|
+
CODE_SOURCE_RETIRED,
|
|
75
78
|
CODE_SOURCE_UNAUTHORIZED,
|
|
76
79
|
CODE_SOURCE_UNREACHABLE,
|
|
77
80
|
ArtifactCorruptError,
|
|
@@ -88,6 +91,7 @@ from .errors import (
|
|
|
88
91
|
SchemaError,
|
|
89
92
|
SourceForbiddenError,
|
|
90
93
|
SourceIncompatibleError,
|
|
94
|
+
SourceRetiredError,
|
|
91
95
|
SourceUnauthorizedError,
|
|
92
96
|
SourceUnreachableError,
|
|
93
97
|
UnsupportedError,
|
|
@@ -102,6 +106,7 @@ from .results import (
|
|
|
102
106
|
Capabilities,
|
|
103
107
|
Column,
|
|
104
108
|
Computed,
|
|
109
|
+
DeclinedSetting,
|
|
105
110
|
DiscoveredColumn,
|
|
106
111
|
Discovery,
|
|
107
112
|
EngineCell,
|
|
@@ -130,6 +135,7 @@ __all__ = [
|
|
|
130
135
|
"CODE_CACHE_UNUSABLE",
|
|
131
136
|
"CODE_SOURCE_FORBIDDEN",
|
|
132
137
|
"CODE_SOURCE_INCOMPATIBLE",
|
|
138
|
+
"CODE_SOURCE_RETIRED",
|
|
133
139
|
"CODE_SOURCE_UNAUTHORIZED",
|
|
134
140
|
"CODE_SOURCE_UNREACHABLE",
|
|
135
141
|
"EXPORT_NONE",
|
|
@@ -151,6 +157,9 @@ __all__ = [
|
|
|
151
157
|
"ChtypesError",
|
|
152
158
|
"Column",
|
|
153
159
|
"Computed",
|
|
160
|
+
"DeclinedLayer",
|
|
161
|
+
"DeclinedSetting",
|
|
162
|
+
"DeclinedTier",
|
|
154
163
|
"DefaultKind",
|
|
155
164
|
"DiscoverQueryParam",
|
|
156
165
|
"DiscoveredColumn",
|
|
@@ -186,6 +195,7 @@ __all__ = [
|
|
|
186
195
|
"Source",
|
|
187
196
|
"SourceForbiddenError",
|
|
188
197
|
"SourceIncompatibleError",
|
|
198
|
+
"SourceRetiredError",
|
|
189
199
|
"SourceUnauthorizedError",
|
|
190
200
|
"SourceUnreachableError",
|
|
191
201
|
"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:d60a681ea0e249ab9e2c83b50640ad9cef040261ddf61805106c93e2c68774e4) — 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:d60a681ea0e249ab9e2c83b50640ad9cef040261ddf61805106c93e2c68774e4"
|
|
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:d60a681ea0e249ab9e2c83b50640ad9cef040261ddf61805106c93e2c68774e4) — 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:d60a681ea0e249ab9e2c83b50640ad9cef040261ddf61805106c93e2c68774e4) — 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`, `MergeReason`, `
|
|
5
|
-
facts the description attaches to each value
|
|
6
|
-
`Source` is_stored, `Verdict.answered`), and the
|
|
7
|
-
this package keeps a copy of any of it.
|
|
4
|
+
`Verdict`, `DefaultKind`, `MergeReason`, `DeclinedTier`, `DeclinedLayer`, `Reason`,
|
|
5
|
+
`Source`, `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
|
|
@@ -249,6 +249,56 @@ class MergeReason(StrEnum):
|
|
|
249
249
|
return cls(text)
|
|
250
250
|
|
|
251
251
|
|
|
252
|
+
class DeclinedTier(StrEnum):
|
|
253
|
+
"""The WHERE-settings tier of a setting a settings layer 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
|
+
|
|
277
|
+
class DeclinedLayer(StrEnum):
|
|
278
|
+
"""The settings layer a `DeclinedSetting` comes from, the highest one that sets
|
|
279
|
+
the name: `defaults`, `server` or `schema`, lowest first."""
|
|
280
|
+
|
|
281
|
+
DEFAULTS = "defaults"
|
|
282
|
+
SERVER = "server"
|
|
283
|
+
SCHEMA = "schema"
|
|
284
|
+
|
|
285
|
+
@classmethod
|
|
286
|
+
def _missing_(cls, value: object) -> DeclinedLayer | None:
|
|
287
|
+
return _unknown_str(cls, value)
|
|
288
|
+
|
|
289
|
+
@property
|
|
290
|
+
def known(self) -> bool:
|
|
291
|
+
"""Whether the description lists this value: False for exactly the unknown(n)
|
|
292
|
+
member an unlisted value reads as (rule r3)."""
|
|
293
|
+
return type(self)._value2member_map_.get(self._value_) is self
|
|
294
|
+
|
|
295
|
+
@classmethod
|
|
296
|
+
def of(cls, text: str) -> DeclinedLayer:
|
|
297
|
+
"""Map a document's spelling. One the description does not list is kept as
|
|
298
|
+
this vocabulary's unknown(n) member, carrying it (rule r3)."""
|
|
299
|
+
return cls(text)
|
|
300
|
+
|
|
301
|
+
|
|
252
302
|
class DiscoverQueryParam(StrEnum):
|
|
253
303
|
"""A query parameter of the SQL `discover_query` returns, which the caller binds
|
|
254
304
|
when it runs the query."""
|
|
@@ -415,6 +465,8 @@ DESCRIBED_VOCABULARIES: Final[dict[str, type]] = {
|
|
|
415
465
|
"filter_verdict": Verdict,
|
|
416
466
|
"discover_query_param": DiscoverQueryParam,
|
|
417
467
|
"default_kind": DefaultKind,
|
|
468
|
+
"declined_tier": DeclinedTier,
|
|
469
|
+
"declined_layer": DeclinedLayer,
|
|
418
470
|
}
|
|
419
471
|
|
|
420
472
|
|
|
@@ -28,7 +28,17 @@ 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
|
+
DeclinedLayer,
|
|
33
|
+
DeclinedTier,
|
|
34
|
+
DefaultKind,
|
|
35
|
+
FilterOutcome,
|
|
36
|
+
MergeReason,
|
|
37
|
+
Outcome,
|
|
38
|
+
Reason,
|
|
39
|
+
Source,
|
|
40
|
+
Verdict,
|
|
41
|
+
)
|
|
32
42
|
from .errors import ArtifactCorruptError, internal
|
|
33
43
|
from .results import (
|
|
34
44
|
AtMergeEntry,
|
|
@@ -37,6 +47,7 @@ from .results import (
|
|
|
37
47
|
Capabilities,
|
|
38
48
|
Column,
|
|
39
49
|
Computed,
|
|
50
|
+
DeclinedSetting,
|
|
40
51
|
DiscoveredColumn,
|
|
41
52
|
Discovery,
|
|
42
53
|
EngineCell,
|
|
@@ -493,6 +504,15 @@ def _string_map(obj: Mapping[str, Any], key: str, where: str) -> dict[str, str]
|
|
|
493
504
|
return out
|
|
494
505
|
|
|
495
506
|
|
|
507
|
+
def _declined_setting(obj: dict[str, Any], where: str) -> DeclinedSetting:
|
|
508
|
+
# An unlisted tier or layer is its unknown(n): kept, never a failure (rule r3).
|
|
509
|
+
return DeclinedSetting(
|
|
510
|
+
name=_name(obj, "name", where),
|
|
511
|
+
tier=DeclinedTier.of(_text(obj, "tier", where)),
|
|
512
|
+
layer=DeclinedLayer.of(_text(obj, "layer", where)),
|
|
513
|
+
)
|
|
514
|
+
|
|
515
|
+
|
|
496
516
|
def _server(obj: dict[str, Any], where: str) -> SchemaServer:
|
|
497
517
|
return SchemaServer(
|
|
498
518
|
timezone=_text(obj, "timezone", where),
|
|
@@ -525,6 +545,10 @@ def decode_schema_description(raw: bytes) -> SchemaDescription:
|
|
|
525
545
|
if replicated is None
|
|
526
546
|
else _replicated(_obj(replicated, where, "replicated"), f"{where}.replicated")
|
|
527
547
|
),
|
|
548
|
+
# Over every layer known at schema compile, with or without a server.
|
|
549
|
+
filter_declined_settings=_objects(
|
|
550
|
+
obj, "filter_declined_settings", where, _declined_setting
|
|
551
|
+
),
|
|
528
552
|
)
|
|
529
553
|
|
|
530
554
|
|
|
@@ -52,6 +52,7 @@ from typing import Final, Protocol
|
|
|
52
52
|
from chtypes._abi2._decls import CHS_ABI_FINGERPRINT
|
|
53
53
|
from chtypes._ocifetch import _constants as C
|
|
54
54
|
from chtypes._ocifetch._dsse import TrustedKey
|
|
55
|
+
from chtypes._ocifetch._errors import ArtifactUnpublishedError
|
|
55
56
|
|
|
56
57
|
__all__ = [
|
|
57
58
|
"ALIAS_SEPARATOR",
|
|
@@ -66,6 +67,7 @@ __all__ = [
|
|
|
66
67
|
"PinningRefusedError",
|
|
67
68
|
"abi",
|
|
68
69
|
"active",
|
|
70
|
+
"ahead_of_registry",
|
|
69
71
|
"alias_tag",
|
|
70
72
|
"allow_overrides_for_tests",
|
|
71
73
|
"channel_name",
|
|
@@ -274,6 +276,69 @@ def visible(predicate: object, channel: Channel | None = None) -> bool:
|
|
|
274
276
|
return fingerprint == f"sha256:{channel.own_fingerprint}"
|
|
275
277
|
|
|
276
278
|
|
|
279
|
+
def ahead_of_registry(
|
|
280
|
+
alias_absent: bool,
|
|
281
|
+
predicate: object,
|
|
282
|
+
channel: Channel | None = None,
|
|
283
|
+
*,
|
|
284
|
+
cached_own_build: Callable[[], str | None] | None = None,
|
|
285
|
+
) -> None:
|
|
286
|
+
"""The dev channel's answer for an SDK whose fingerprint no published build
|
|
287
|
+
carries yet (docs/guides/fetch-v1.md §3; public issue #578): when every base
|
|
288
|
+
answered its own alias 404 (`alias_absent`) and the build the tag names is
|
|
289
|
+
signed for another fingerprint (`predicate`, the SIGNED predicate, the field
|
|
290
|
+
`visible` keys on), raise `ArtifactUnpublishedError`, the code a request no
|
|
291
|
+
build answers gets, naming both fingerprints. Nothing is installed and an
|
|
292
|
+
install of that build is never reported, because the lookup this SDK opens
|
|
293
|
+
through would refuse it. Returns in every other case: the alias answered,
|
|
294
|
+
the tag's build is this SDK's own, or the contract has no own fingerprint.
|
|
295
|
+
|
|
296
|
+
The expired alias with a usable cache (public issue #581): `cached_own_build`
|
|
297
|
+
looks up, through the filter open uses, the cache's verified record of this
|
|
298
|
+
SDK's OWN fingerprint for the request, and returns its build id ("" for a
|
|
299
|
+
record that names none) or None for no record. When there is one the
|
|
300
|
+
message grows by `_cached_own_suffix`; the code does not change."""
|
|
301
|
+
channel = channel or active()
|
|
302
|
+
if not alias_absent or visible(predicate, channel):
|
|
303
|
+
return
|
|
304
|
+
message = _ahead_message(channel.own_fingerprint, predicate)
|
|
305
|
+
if cached_own_build is not None:
|
|
306
|
+
build = cached_own_build()
|
|
307
|
+
if build is not None:
|
|
308
|
+
message += _cached_own_suffix(build)
|
|
309
|
+
raise ArtifactUnpublishedError(f"chtypes: {message}")
|
|
310
|
+
|
|
311
|
+
|
|
312
|
+
def _cached_own_suffix(build: str) -> str:
|
|
313
|
+
"""The clause appended when the cache holds an own-fingerprint build: "; a
|
|
314
|
+
cached build for this fingerprint (build <id>) is still usable by open;
|
|
315
|
+
upgrade the SDK to get newer builds", the parenthesis dropped when the
|
|
316
|
+
record names no build."""
|
|
317
|
+
suffix = "; a cached build for this fingerprint"
|
|
318
|
+
if build:
|
|
319
|
+
suffix += f" (build {build})"
|
|
320
|
+
return suffix + " is still usable by open; upgrade the SDK to get newer builds"
|
|
321
|
+
|
|
322
|
+
|
|
323
|
+
def _ahead_message(own: str, predicate: object) -> str:
|
|
324
|
+
"""`ahead_of_registry`'s message: "no published build for this SDK's
|
|
325
|
+
fingerprint <own>; newest published on this channel is <fp> (build <id>)",
|
|
326
|
+
each fingerprint 64 lowercase hex without its `sha256:` prefix, the
|
|
327
|
+
parenthesis dropped when the predicate names no build, and "unnamed" for a
|
|
328
|
+
predicate that names no fingerprint."""
|
|
329
|
+
fields = predicate if isinstance(predicate, dict) else {}
|
|
330
|
+
theirs = fields.get("abi_fingerprint")
|
|
331
|
+
theirs = theirs.removeprefix("sha256:") if isinstance(theirs, str) else ""
|
|
332
|
+
message = (
|
|
333
|
+
f"no published build for this SDK's fingerprint {own}; "
|
|
334
|
+
f"newest published on this channel is {theirs or 'unnamed'}"
|
|
335
|
+
)
|
|
336
|
+
build = fields.get("build")
|
|
337
|
+
if isinstance(build, str) and build:
|
|
338
|
+
message += f" (build {build})"
|
|
339
|
+
return message
|
|
340
|
+
|
|
341
|
+
|
|
277
342
|
def use_dev_channel_for_tests() -> Callable[[], None]:
|
|
278
343
|
"""Make this TEST run speak the dev channel exactly as a user's process does
|
|
279
344
|
(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
|
}
|
|
@@ -38,6 +38,7 @@ from chtypes._ocifetch._errors import (
|
|
|
38
38
|
ArtifactUntrustedError,
|
|
39
39
|
FetchError,
|
|
40
40
|
SourceForbiddenError,
|
|
41
|
+
SourceRetiredError,
|
|
41
42
|
SourceUnauthorizedError,
|
|
42
43
|
SourceUnreachableError,
|
|
43
44
|
)
|
|
@@ -49,6 +50,7 @@ from chtypes._ocifetch._http import (
|
|
|
49
50
|
ForbiddenHttpError,
|
|
50
51
|
NotFoundHttpError,
|
|
51
52
|
OversizeHttpError,
|
|
53
|
+
RetiredHttpError,
|
|
52
54
|
RetryPolicy,
|
|
53
55
|
TransportError,
|
|
54
56
|
UnauthorizedHttpError,
|
|
@@ -440,7 +442,7 @@ def _find_verified_signature(
|
|
|
440
442
|
bases, ref.digest, policy=policy, retry=retry
|
|
441
443
|
)
|
|
442
444
|
blob_desc = manifest_single_layer(referrer_doc, expected_media_type=C.MEDIA_TYPE_BUNDLE)
|
|
443
|
-
except ArtifactCorruptError:
|
|
445
|
+
except (ArtifactCorruptError, RetiredHttpError):
|
|
444
446
|
raise
|
|
445
447
|
except (TransportError, FetchError):
|
|
446
448
|
continue
|
|
@@ -458,7 +460,7 @@ def _find_verified_signature(
|
|
|
458
460
|
with open(bundle_path, "rb") as f:
|
|
459
461
|
bundle_json = json.loads(f.read())
|
|
460
462
|
verified = verify_bundle(bundle_json, trusted_keys)
|
|
461
|
-
except ArtifactCorruptError:
|
|
463
|
+
except (ArtifactCorruptError, RetiredHttpError):
|
|
462
464
|
raise
|
|
463
465
|
except (TransportError, FetchError, json.JSONDecodeError):
|
|
464
466
|
continue
|
|
@@ -698,7 +700,7 @@ def _ensure_floating(
|
|
|
698
700
|
cache_root_path = roots[0]
|
|
699
701
|
scratch_root = _scratch_root(cache_root_path)
|
|
700
702
|
|
|
701
|
-
index_doc, index_bytes, index_digest = fetch_manifest_by_tag(
|
|
703
|
+
index_doc, index_bytes, index_digest, alias_absent = fetch_manifest_by_tag(
|
|
702
704
|
bases,
|
|
703
705
|
request.spelling,
|
|
704
706
|
policy=policy,
|
|
@@ -711,6 +713,14 @@ def _ensure_floating(
|
|
|
711
713
|
existing_dir = unpacked_dir_for(cache_root_path, manifest_hex)
|
|
712
714
|
existing_record = read_verified_record(existing_dir)
|
|
713
715
|
if existing_record is not None and existing_record.platform == platform_key:
|
|
716
|
+
# An install of another fingerprint's build, reached through the tag
|
|
717
|
+
# because the registry has none at this SDK's own, is never reported
|
|
718
|
+
# installed (public issue #578): refused before the cache is touched.
|
|
719
|
+
_channel.ahead_of_registry(
|
|
720
|
+
alias_absent,
|
|
721
|
+
existing_record.predicate,
|
|
722
|
+
cached_own_build=lambda: _cached_own_build(roots, platform_key, request.spelling),
|
|
723
|
+
)
|
|
714
724
|
# A NEWER build within the request, installed beside it, is still the
|
|
715
725
|
# answer ("monotonic-warning", docs/guides/fetch-v1.md §9).
|
|
716
726
|
newer = _newer_installed(
|
|
@@ -801,6 +811,16 @@ def _ensure_floating(
|
|
|
801
811
|
signed_by = verified.signed_by
|
|
802
812
|
|
|
803
813
|
_check_predicate_matches_request(predicate, platform_key, request.spelling)
|
|
814
|
+
if found is not None:
|
|
815
|
+
# An SDK ahead of the registry (public issue #578): the alias for its
|
|
816
|
+
# own fingerprint answered 404 on every base, and the tag's build is
|
|
817
|
+
# signed for another. Refused from the signed statement, before the
|
|
818
|
+
# layer is requested, so nothing is downloaded or installed.
|
|
819
|
+
_channel.ahead_of_registry(
|
|
820
|
+
alias_absent,
|
|
821
|
+
predicate,
|
|
822
|
+
cached_own_build=lambda: _cached_own_build(roots, platform_key, request.spelling),
|
|
823
|
+
)
|
|
804
824
|
newer = _newer_installed(roots, platform_key, predicate, request.spelling)
|
|
805
825
|
if newer is not None:
|
|
806
826
|
newer_dir, newer_record, warning = newer
|
|
@@ -983,6 +1003,8 @@ def _translate_transport_error(e: TransportError) -> FetchError:
|
|
|
983
1003
|
return SourceForbiddenError(f"chtypes: {e}")
|
|
984
1004
|
if isinstance(e, OversizeHttpError):
|
|
985
1005
|
return ArtifactCorruptError(f"chtypes: {e}")
|
|
1006
|
+
if isinstance(e, RetiredHttpError):
|
|
1007
|
+
return SourceRetiredError(f"chtypes: {e}")
|
|
986
1008
|
if isinstance(e, UnreachableHttpError):
|
|
987
1009
|
return SourceUnreachableError(f"chtypes: {e}", retryable=getattr(e, "retryable", True))
|
|
988
1010
|
return SourceUnreachableError(f"chtypes: {e}")
|
|
@@ -1221,6 +1243,25 @@ def with_notes(message: str, notes: Sequence[str]) -> str:
|
|
|
1221
1243
|
return f"{message}. {' '.join(notes)}"
|
|
1222
1244
|
|
|
1223
1245
|
|
|
1246
|
+
def _cached_own_build(
|
|
1247
|
+
roots: Sequence[Path], platform_key: str, request_spelling: str
|
|
1248
|
+
) -> str | None:
|
|
1249
|
+
"""The build id of the newest cached record of this SDK's OWN fingerprint
|
|
1250
|
+
that answers the request (`resolve_installed`'s filter), "" when it names
|
|
1251
|
+
none, or None when the cache holds no such record (public issue #581)."""
|
|
1252
|
+
candidates = [
|
|
1253
|
+
record
|
|
1254
|
+
for _dir, record in list_verified_records(roots)
|
|
1255
|
+
if record.platform == platform_key
|
|
1256
|
+
and version_within_request(record.version, request_spelling)
|
|
1257
|
+
and _channel.visible(record.predicate)
|
|
1258
|
+
]
|
|
1259
|
+
if not candidates:
|
|
1260
|
+
return None
|
|
1261
|
+
best = max(candidates, key=lambda r: (spelling_components(r.version), r.build))
|
|
1262
|
+
return best.build or ""
|
|
1263
|
+
|
|
1264
|
+
|
|
1224
1265
|
def resolve_installed(request: Request, platform: str, options: Options) -> Resolved | None:
|
|
1225
1266
|
"""Cache-only: never touches the network (docs/guides/fetch-v1.md "The seam").
|
|
1226
1267
|
|
|
@@ -1368,7 +1409,9 @@ def _fetch_signed_impl(repository: str, ref: str, predicate_type: str, options:
|
|
|
1368
1409
|
doc, _raw = fetch_manifest_by_digest(bases, ref, policy=policy, retry=retry)
|
|
1369
1410
|
manifest_digest = ref
|
|
1370
1411
|
else:
|
|
1371
|
-
doc, _raw, manifest_digest = fetch_manifest_by_tag(
|
|
1412
|
+
doc, _raw, manifest_digest, _alias_absent = fetch_manifest_by_tag(
|
|
1413
|
+
bases, ref, policy=policy, retry=retry
|
|
1414
|
+
)
|
|
1372
1415
|
|
|
1373
1416
|
layer_desc = manifest_single_layer(doc)
|
|
1374
1417
|
found = _find_verified_signature(
|
|
@@ -1466,6 +1509,8 @@ def _fetch_goldens(
|
|
|
1466
1509
|
retry=retry,
|
|
1467
1510
|
max_bytes=C.MAX_UNPACKED_BYTES,
|
|
1468
1511
|
)
|
|
1512
|
+
except RetiredHttpError:
|
|
1513
|
+
raise
|
|
1469
1514
|
except (TransportError, FetchError) as e:
|
|
1470
1515
|
first_failure = first_failure or e
|
|
1471
1516
|
continue
|
|
@@ -1580,6 +1625,8 @@ def _verify_goldens_signature(
|
|
|
1580
1625
|
except ArtifactCorruptError as e:
|
|
1581
1626
|
failure = failure or e
|
|
1582
1627
|
continue
|
|
1628
|
+
except RetiredHttpError:
|
|
1629
|
+
raise
|
|
1583
1630
|
except (TransportError, FetchError, json.JSONDecodeError):
|
|
1584
1631
|
continue
|
|
1585
1632
|
finally:
|
|
@@ -5,7 +5,7 @@ Scoped to `chtypes._ocifetch`: the stable, public exception types live in
|
|
|
5
5
|
imports from or edits. A future switch lane decides how (or whether) these
|
|
6
6
|
become public types; until then every name here is private.
|
|
7
7
|
|
|
8
|
-
Every exception carries a `.code` string from one of the
|
|
8
|
+
Every exception carries a `.code` string from one of the twelve codes
|
|
9
9
|
`spec/fetch-v1/constants.json`'s `errors` map assigns an exit status to
|
|
10
10
|
(the generated `ERROR_EXIT_CODES` table), so a CLI added later needs no
|
|
11
11
|
second mapping — it is the same lookup v0's `__main__.py` does today.
|
|
@@ -30,6 +30,7 @@ __all__ = [
|
|
|
30
30
|
"CODE_CACHE_UNUSABLE",
|
|
31
31
|
"CODE_SOURCE_FORBIDDEN",
|
|
32
32
|
"CODE_SOURCE_INCOMPATIBLE",
|
|
33
|
+
"CODE_SOURCE_RETIRED",
|
|
33
34
|
"CODE_SOURCE_UNAUTHORIZED",
|
|
34
35
|
"CODE_SOURCE_UNREACHABLE",
|
|
35
36
|
"ArtifactCorruptError",
|
|
@@ -41,6 +42,7 @@ __all__ = [
|
|
|
41
42
|
"FetchError",
|
|
42
43
|
"SourceForbiddenError",
|
|
43
44
|
"SourceIncompatibleError",
|
|
45
|
+
"SourceRetiredError",
|
|
44
46
|
"SourceUnauthorizedError",
|
|
45
47
|
"SourceUnreachableError",
|
|
46
48
|
]
|
|
@@ -55,6 +57,7 @@ CODE_SOURCE_UNAUTHORIZED = "CHTYPES_SOURCE_UNAUTHORIZED"
|
|
|
55
57
|
CODE_SOURCE_FORBIDDEN = "CHTYPES_SOURCE_FORBIDDEN"
|
|
56
58
|
CODE_SOURCE_INCOMPATIBLE = "CHTYPES_SOURCE_INCOMPATIBLE"
|
|
57
59
|
CODE_CACHE_UNUSABLE = "CHTYPES_CACHE_UNUSABLE"
|
|
60
|
+
CODE_SOURCE_RETIRED = "CHTYPES_SOURCE_RETIRED"
|
|
58
61
|
|
|
59
62
|
_CODES_USED_HERE = (
|
|
60
63
|
CODE_ARTIFACT_MISSING,
|
|
@@ -67,6 +70,7 @@ _CODES_USED_HERE = (
|
|
|
67
70
|
CODE_SOURCE_FORBIDDEN,
|
|
68
71
|
CODE_SOURCE_INCOMPATIBLE,
|
|
69
72
|
CODE_CACHE_UNUSABLE,
|
|
73
|
+
CODE_SOURCE_RETIRED,
|
|
70
74
|
)
|
|
71
75
|
|
|
72
76
|
# A drift guard, cheaper than a test: every code this module raises must be
|
|
@@ -187,3 +191,13 @@ class CacheUnusableError(FetchError):
|
|
|
187
191
|
self.path = path
|
|
188
192
|
self.reason = reason
|
|
189
193
|
self.os_error = os_error
|
|
194
|
+
|
|
195
|
+
|
|
196
|
+
class SourceRetiredError(FetchError):
|
|
197
|
+
"""The source answered 410 Gone: a retired repository, which is permanent,
|
|
198
|
+
so the request was never retried and never sent to the next base
|
|
199
|
+
(docs/guides/fetch-v1.md §2, "A retired repository"; public issue #571).
|
|
200
|
+
The message names the URL that answered and carries the registry's own
|
|
201
|
+
message, made safe to print."""
|
|
202
|
+
|
|
203
|
+
code = CODE_SOURCE_RETIRED
|