chtypes 2.0.0.dev4__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.
Files changed (90) hide show
  1. {chtypes-2.0.0.dev4 → chtypes-2.0.0.dev5}/CHANGELOG.md +24 -0
  2. {chtypes-2.0.0.dev4 → chtypes-2.0.0.dev5}/PKG-INFO +1 -1
  3. {chtypes-2.0.0.dev4 → chtypes-2.0.0.dev5}/pyproject.toml +1 -1
  4. {chtypes-2.0.0.dev4 → chtypes-2.0.0.dev5}/src/chtypes/__init__.py +2 -0
  5. {chtypes-2.0.0.dev4 → chtypes-2.0.0.dev5}/src/chtypes/_abi2/_decls.py +2 -2
  6. {chtypes-2.0.0.dev4 → chtypes-2.0.0.dev5}/src/chtypes/_abi2/_errmap.py +1 -1
  7. {chtypes-2.0.0.dev4 → chtypes-2.0.0.dev5}/src/chtypes/_abi2/_vocab.py +30 -4
  8. {chtypes-2.0.0.dev4 → chtypes-2.0.0.dev5}/src/chtypes/_decode.py +7 -4
  9. {chtypes-2.0.0.dev4 → chtypes-2.0.0.dev5}/src/chtypes/_ocifetch/_channel.py +29 -3
  10. {chtypes-2.0.0.dev4 → chtypes-2.0.0.dev5}/src/chtypes/_ocifetch/_ensure.py +29 -2
  11. {chtypes-2.0.0.dev4 → chtypes-2.0.0.dev5}/src/chtypes/results.py +30 -16
  12. {chtypes-2.0.0.dev4 → chtypes-2.0.0.dev5}/tests/abi2/test_public_server.py +67 -25
  13. {chtypes-2.0.0.dev4 → chtypes-2.0.0.dev5}/tests/abi2/test_public_setup_cases.py +11 -9
  14. {chtypes-2.0.0.dev4 → chtypes-2.0.0.dev5}/uv.lock +1 -1
  15. {chtypes-2.0.0.dev4 → chtypes-2.0.0.dev5}/.gitignore +0 -0
  16. {chtypes-2.0.0.dev4 → chtypes-2.0.0.dev5}/LICENSE +0 -0
  17. {chtypes-2.0.0.dev4 → chtypes-2.0.0.dev5}/README.md +0 -0
  18. {chtypes-2.0.0.dev4 → chtypes-2.0.0.dev5}/src/chtypes/__main__.py +0 -0
  19. {chtypes-2.0.0.dev4 → chtypes-2.0.0.dev5}/src/chtypes/_abi2/__init__.py +0 -0
  20. {chtypes-2.0.0.dev4 → chtypes-2.0.0.dev5}/src/chtypes/_abi2/_errors.py +0 -0
  21. {chtypes-2.0.0.dev4 → chtypes-2.0.0.dev5}/src/chtypes/_abi2/_loader.py +0 -0
  22. {chtypes-2.0.0.dev4 → chtypes-2.0.0.dev5}/src/chtypes/_ed25519.py +0 -0
  23. {chtypes-2.0.0.dev4 → chtypes-2.0.0.dev5}/src/chtypes/_guard.py +0 -0
  24. {chtypes-2.0.0.dev4 → chtypes-2.0.0.dev5}/src/chtypes/_input.py +0 -0
  25. {chtypes-2.0.0.dev4 → chtypes-2.0.0.dev5}/src/chtypes/_ocifetch/__init__.py +0 -0
  26. {chtypes-2.0.0.dev4 → chtypes-2.0.0.dev5}/src/chtypes/_ocifetch/_constants.py +0 -0
  27. {chtypes-2.0.0.dev4 → chtypes-2.0.0.dev5}/src/chtypes/_ocifetch/_dsse.py +0 -0
  28. {chtypes-2.0.0.dev4 → chtypes-2.0.0.dev5}/src/chtypes/_ocifetch/_errors.py +0 -0
  29. {chtypes-2.0.0.dev4 → chtypes-2.0.0.dev5}/src/chtypes/_ocifetch/_faults.py +0 -0
  30. {chtypes-2.0.0.dev4 → chtypes-2.0.0.dev5}/src/chtypes/_ocifetch/_goldens.py +0 -0
  31. {chtypes-2.0.0.dev4 → chtypes-2.0.0.dev5}/src/chtypes/_ocifetch/_http.py +0 -0
  32. {chtypes-2.0.0.dev4 → chtypes-2.0.0.dev5}/src/chtypes/_ocifetch/_layout.py +0 -0
  33. {chtypes-2.0.0.dev4 → chtypes-2.0.0.dev5}/src/chtypes/_ocifetch/_lock.py +0 -0
  34. {chtypes-2.0.0.dev4 → chtypes-2.0.0.dev5}/src/chtypes/_ocifetch/_oci.py +0 -0
  35. {chtypes-2.0.0.dev4 → chtypes-2.0.0.dev5}/src/chtypes/_ocifetch/_referrers.py +0 -0
  36. {chtypes-2.0.0.dev4 → chtypes-2.0.0.dev5}/src/chtypes/_ocifetch/_retired.py +0 -0
  37. {chtypes-2.0.0.dev4 → chtypes-2.0.0.dev5}/src/chtypes/_ocifetch/_unpack.py +0 -0
  38. {chtypes-2.0.0.dev4 → chtypes-2.0.0.dev5}/src/chtypes/_setup.py +0 -0
  39. {chtypes-2.0.0.dev4 → chtypes-2.0.0.dev5}/src/chtypes/errors.py +0 -0
  40. {chtypes-2.0.0.dev4 → chtypes-2.0.0.dev5}/src/chtypes/library.py +0 -0
  41. {chtypes-2.0.0.dev4 → chtypes-2.0.0.dev5}/src/chtypes/py.typed +0 -0
  42. {chtypes-2.0.0.dev4 → chtypes-2.0.0.dev5}/src/chtypes/registry.py +0 -0
  43. {chtypes-2.0.0.dev4 → chtypes-2.0.0.dev5}/tests/abi2/__init__.py +0 -0
  44. {chtypes-2.0.0.dev4 → chtypes-2.0.0.dev5}/tests/abi2/_stub_tree.py +0 -0
  45. {chtypes-2.0.0.dev4 → chtypes-2.0.0.dev5}/tests/abi2/conftest.py +0 -0
  46. {chtypes-2.0.0.dev4 → chtypes-2.0.0.dev5}/tests/abi2/test_conformance.py +0 -0
  47. {chtypes-2.0.0.dev4 → chtypes-2.0.0.dev5}/tests/abi2/test_dev_fingerprint.py +0 -0
  48. {chtypes-2.0.0.dev4 → chtypes-2.0.0.dev5}/tests/abi2/test_goldens_runner_stub.py +0 -0
  49. {chtypes-2.0.0.dev4 → chtypes-2.0.0.dev5}/tests/abi2/test_loader.py +0 -0
  50. {chtypes-2.0.0.dev4 → chtypes-2.0.0.dev5}/tests/abi2/test_public_library.py +0 -0
  51. {chtypes-2.0.0.dev4 → chtypes-2.0.0.dev5}/tests/abi2/test_public_open.py +0 -0
  52. {chtypes-2.0.0.dev4 → chtypes-2.0.0.dev5}/tests/abi2/test_public_registry.py +0 -0
  53. {chtypes-2.0.0.dev4 → chtypes-2.0.0.dev5}/tests/abi2/test_reader_rules.py +0 -0
  54. {chtypes-2.0.0.dev4 → chtypes-2.0.0.dev5}/tests/api/__init__.py +0 -0
  55. {chtypes-2.0.0.dev4 → chtypes-2.0.0.dev5}/tests/api/test_decode.py +0 -0
  56. {chtypes-2.0.0.dev4 → chtypes-2.0.0.dev5}/tests/api/test_input.py +0 -0
  57. {chtypes-2.0.0.dev4 → chtypes-2.0.0.dev5}/tests/api/test_vocab.py +0 -0
  58. {chtypes-2.0.0.dev4 → chtypes-2.0.0.dev5}/tests/cli/__init__.py +0 -0
  59. {chtypes-2.0.0.dev4 → chtypes-2.0.0.dev5}/tests/cli/conftest.py +0 -0
  60. {chtypes-2.0.0.dev4 → chtypes-2.0.0.dev5}/tests/cli/test_devchannel.py +0 -0
  61. {chtypes-2.0.0.dev4 → chtypes-2.0.0.dev5}/tests/cli/test_main.py +0 -0
  62. {chtypes-2.0.0.dev4 → chtypes-2.0.0.dev5}/tests/conftest.py +0 -0
  63. {chtypes-2.0.0.dev4 → chtypes-2.0.0.dev5}/tests/goldens_v1/__init__.py +0 -0
  64. {chtypes-2.0.0.dev4 → chtypes-2.0.0.dev5}/tests/goldens_v1/data/stub-goldens.json +0 -0
  65. {chtypes-2.0.0.dev4 → chtypes-2.0.0.dev5}/tests/goldens_v1/test_runner.py +0 -0
  66. {chtypes-2.0.0.dev4 → chtypes-2.0.0.dev5}/tests/ocifetch/__init__.py +0 -0
  67. {chtypes-2.0.0.dev4 → chtypes-2.0.0.dev5}/tests/ocifetch/_bundle_support.py +0 -0
  68. {chtypes-2.0.0.dev4 → chtypes-2.0.0.dev5}/tests/ocifetch/_registry_support.py +0 -0
  69. {chtypes-2.0.0.dev4 → chtypes-2.0.0.dev5}/tests/ocifetch/_sign_support.py +0 -0
  70. {chtypes-2.0.0.dev4 → chtypes-2.0.0.dev5}/tests/ocifetch/conftest.py +0 -0
  71. {chtypes-2.0.0.dev4 → chtypes-2.0.0.dev5}/tests/ocifetch/test_cache_roots.py +0 -0
  72. {chtypes-2.0.0.dev4 → chtypes-2.0.0.dev5}/tests/ocifetch/test_cache_strict.py +0 -0
  73. {chtypes-2.0.0.dev4 → chtypes-2.0.0.dev5}/tests/ocifetch/test_conformance.py +0 -0
  74. {chtypes-2.0.0.dev4 → chtypes-2.0.0.dev5}/tests/ocifetch/test_devchannel.py +0 -0
  75. {chtypes-2.0.0.dev4 → chtypes-2.0.0.dev5}/tests/ocifetch/test_dsse.py +0 -0
  76. {chtypes-2.0.0.dev4 → chtypes-2.0.0.dev5}/tests/ocifetch/test_ensure.py +0 -0
  77. {chtypes-2.0.0.dev4 → chtypes-2.0.0.dev5}/tests/ocifetch/test_goldens.py +0 -0
  78. {chtypes-2.0.0.dev4 → chtypes-2.0.0.dev5}/tests/ocifetch/test_http.py +0 -0
  79. {chtypes-2.0.0.dev4 → chtypes-2.0.0.dev5}/tests/ocifetch/test_layout.py +0 -0
  80. {chtypes-2.0.0.dev4 → chtypes-2.0.0.dev5}/tests/ocifetch/test_lock.py +0 -0
  81. {chtypes-2.0.0.dev4 → chtypes-2.0.0.dev5}/tests/ocifetch/test_oci.py +0 -0
  82. {chtypes-2.0.0.dev4 → chtypes-2.0.0.dev5}/tests/ocifetch/test_retired.py +0 -0
  83. {chtypes-2.0.0.dev4 → chtypes-2.0.0.dev5}/tests/ocifetch/test_seam_gate.py +0 -0
  84. {chtypes-2.0.0.dev4 → chtypes-2.0.0.dev5}/tests/ocifetch/test_unpack.py +0 -0
  85. {chtypes-2.0.0.dev4 → chtypes-2.0.0.dev5}/tests/test_abi_identity.py +0 -0
  86. {chtypes-2.0.0.dev4 → chtypes-2.0.0.dev5}/tests/test_cache_roots_api.py +0 -0
  87. {chtypes-2.0.0.dev4 → chtypes-2.0.0.dev5}/tests/test_devchannel_registry.py +0 -0
  88. {chtypes-2.0.0.dev4 → chtypes-2.0.0.dev5}/tests/test_ed25519.py +0 -0
  89. {chtypes-2.0.0.dev4 → chtypes-2.0.0.dev5}/tests/test_examples_tour.py +0 -0
  90. {chtypes-2.0.0.dev4 → chtypes-2.0.0.dev5}/tests/test_fetch_options.py +0 -0
@@ -8,15 +8,39 @@ 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`).
11
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.
12
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`.
13
15
  - The v2 spec publishes the WHERE-affecting settings lists (`spec/abi-v2/where-settings/`) with the consumer rule.
14
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`.
15
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.
16
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.
17
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
+
18
41
  ### Fixed
19
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`).
20
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.
21
45
 
22
46
  ## [1.1.0] — 2026-10-06
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: chtypes
3
- Version: 2.0.0.dev4
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
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "chtypes"
3
- version = "2.0.0.dev4"
3
+ version = "2.0.0.dev5"
4
4
  description = "Python binding for the chtypes artifacts: ClickHouse's own type system, per version, behind the frozen chs_* C ABI"
5
5
  readme = "README.md"
6
6
  license = "Apache-2.0"
@@ -48,6 +48,7 @@ from importlib.metadata import version as _package_version
48
48
 
49
49
  from ._abi2._vocab import (
50
50
  EXPORT_NONE,
51
+ DeclinedLayer,
51
52
  DeclinedTier,
52
53
  DefaultKind,
53
54
  DiscoverQueryParam,
@@ -156,6 +157,7 @@ __all__ = [
156
157
  "ChtypesError",
157
158
  "Column",
158
159
  "Computed",
160
+ "DeclinedLayer",
159
161
  "DeclinedSetting",
160
162
  "DeclinedTier",
161
163
  "DefaultKind",
@@ -1,4 +1,4 @@
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
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:28c7445ce6b5c6d0ae33177fd60eb7b3b5654a966c842b8d812aea622824dc72"
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:28c7445ce6b5c6d0ae33177fd60eb7b3b5654a966c842b8d812aea622824dc72) — DO NOT EDIT # noqa: E501
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,8 +1,8 @@
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
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`, `DeclinedTier`, `Reason`, `Source`,
5
- `DiscoverQueryParam`) with the facts the description attaches to each value
4
+ `Verdict`, `DefaultKind`, `MergeReason`, `DeclinedTier`, `DeclinedLayer`, `Reason`,
5
+ `Source`, `DiscoverQueryParam`) with the facts the description attaches to each value
6
6
  (`Format.ch_name`, `Reason` lossy, `Source` is_stored, `Verdict.answered`), and the
7
7
  `DocFlags` groups. No other file in this package keeps a copy of any of it.
8
8
 
@@ -250,7 +250,7 @@ class MergeReason(StrEnum):
250
250
 
251
251
 
252
252
  class DeclinedTier(StrEnum):
253
- """The WHERE-settings tier of a setting a server profile sets and this build's
253
+ """The WHERE-settings tier of a setting a settings layer sets and this build's
254
254
  filters do not honor in a WHERE, as a `DeclinedSetting` reports it."""
255
255
 
256
256
  PREDICATE = "predicate"
@@ -274,6 +274,31 @@ class DeclinedTier(StrEnum):
274
274
  return cls(text)
275
275
 
276
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
+
277
302
  class DiscoverQueryParam(StrEnum):
278
303
  """A query parameter of the SQL `discover_query` returns, which the caller binds
279
304
  when it runs the query."""
@@ -441,6 +466,7 @@ DESCRIBED_VOCABULARIES: Final[dict[str, type]] = {
441
466
  "discover_query_param": DiscoverQueryParam,
442
467
  "default_kind": DefaultKind,
443
468
  "declined_tier": DeclinedTier,
469
+ "declined_layer": DeclinedLayer,
444
470
  }
445
471
 
446
472
 
@@ -29,6 +29,7 @@ from collections.abc import Callable, Mapping
29
29
  from typing import Any
30
30
 
31
31
  from ._abi2._vocab import (
32
+ DeclinedLayer,
32
33
  DeclinedTier,
33
34
  DefaultKind,
34
35
  FilterOutcome,
@@ -504,10 +505,11 @@ def _string_map(obj: Mapping[str, Any], key: str, where: str) -> dict[str, str]
504
505
 
505
506
 
506
507
  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
+ # An unlisted tier or layer is its unknown(n): kept, never a failure (rule r3).
508
509
  return DeclinedSetting(
509
510
  name=_name(obj, "name", where),
510
511
  tier=DeclinedTier.of(_text(obj, "tier", where)),
512
+ layer=DeclinedLayer.of(_text(obj, "layer", where)),
511
513
  )
512
514
 
513
515
 
@@ -516,9 +518,6 @@ def _server(obj: dict[str, Any], where: str) -> SchemaServer:
516
518
  timezone=_text(obj, "timezone", where),
517
519
  settings=_string_map(obj, "settings", where) or {},
518
520
  macros=_string_map(obj, "macros", where),
519
- filter_declined_settings=_objects(
520
- obj, "filter_declined_settings", where, _declined_setting
521
- ),
522
521
  )
523
522
 
524
523
 
@@ -546,6 +545,10 @@ def decode_schema_description(raw: bytes) -> SchemaDescription:
546
545
  if replicated is None
547
546
  else _replicated(_obj(replicated, where, "replicated"), f"{where}.replicated")
548
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
+ ),
549
552
  )
550
553
 
551
554
 
@@ -277,7 +277,11 @@ def visible(predicate: object, channel: Channel | None = None) -> bool:
277
277
 
278
278
 
279
279
  def ahead_of_registry(
280
- alias_absent: bool, predicate: object, channel: Channel | None = None
280
+ alias_absent: bool,
281
+ predicate: object,
282
+ channel: Channel | None = None,
283
+ *,
284
+ cached_own_build: Callable[[], str | None] | None = None,
281
285
  ) -> None:
282
286
  """The dev channel's answer for an SDK whose fingerprint no published build
283
287
  carries yet (docs/guides/fetch-v1.md §3; public issue #578): when every base
@@ -287,11 +291,33 @@ def ahead_of_registry(
287
291
  build answers gets, naming both fingerprints. Nothing is installed and an
288
292
  install of that build is never reported, because the lookup this SDK opens
289
293
  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."""
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."""
291
301
  channel = channel or active()
292
302
  if not alias_absent or visible(predicate, channel):
293
303
  return
294
- raise ArtifactUnpublishedError(f"chtypes: {_ahead_message(channel.own_fingerprint, predicate)}")
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"
295
321
 
296
322
 
297
323
  def _ahead_message(own: str, predicate: object) -> str:
@@ -716,7 +716,11 @@ def _ensure_floating(
716
716
  # An install of another fingerprint's build, reached through the tag
717
717
  # because the registry has none at this SDK's own, is never reported
718
718
  # installed (public issue #578): refused before the cache is touched.
719
- _channel.ahead_of_registry(alias_absent, existing_record.predicate)
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
+ )
720
724
  # A NEWER build within the request, installed beside it, is still the
721
725
  # answer ("monotonic-warning", docs/guides/fetch-v1.md §9).
722
726
  newer = _newer_installed(
@@ -812,7 +816,11 @@ def _ensure_floating(
812
816
  # own fingerprint answered 404 on every base, and the tag's build is
813
817
  # signed for another. Refused from the signed statement, before the
814
818
  # layer is requested, so nothing is downloaded or installed.
815
- _channel.ahead_of_registry(alias_absent, predicate)
819
+ _channel.ahead_of_registry(
820
+ alias_absent,
821
+ predicate,
822
+ cached_own_build=lambda: _cached_own_build(roots, platform_key, request.spelling),
823
+ )
816
824
  newer = _newer_installed(roots, platform_key, predicate, request.spelling)
817
825
  if newer is not None:
818
826
  newer_dir, newer_record, warning = newer
@@ -1235,6 +1243,25 @@ def with_notes(message: str, notes: Sequence[str]) -> str:
1235
1243
  return f"{message}. {' '.join(notes)}"
1236
1244
 
1237
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
+
1238
1265
  def resolve_installed(request: Request, platform: str, options: Options) -> Resolved | None:
1239
1266
  """Cache-only: never touches the network (docs/guides/fetch-v1.md "The seam").
1240
1267
 
@@ -14,7 +14,15 @@ from __future__ import annotations
14
14
  from collections.abc import Mapping
15
15
  from dataclasses import dataclass
16
16
 
17
- from ._abi2._vocab import DeclinedTier, DefaultKind, FilterOutcome, MergeReason, Outcome, Verdict
17
+ from ._abi2._vocab import (
18
+ DeclinedLayer,
19
+ DeclinedTier,
20
+ DefaultKind,
21
+ FilterOutcome,
22
+ MergeReason,
23
+ Outcome,
24
+ Verdict,
25
+ )
18
26
 
19
27
  __all__ = [
20
28
  "AtMergeEntry",
@@ -252,14 +260,17 @@ class Column:
252
260
 
253
261
  @dataclass(frozen=True, slots=True)
254
262
  class DeclinedSetting:
255
- """One setting a server's profile sets that this build's filters do not honor
256
- in a WHERE: an entry of the server's `filter_declined_settings` (ABI v2).
257
- `name` is bytes (from `name` or `name_b64`). `tier` is the tier this build
258
- line's WHERE-settings list gives it, a `DeclinedTier`; one the description
259
- does not list is its unknown(n) member (rule r3)."""
263
+ """One setting the schema compiles under that this build's filters do not
264
+ honor in a WHERE: an entry of the schema description's
265
+ `filter_declined_settings` (ABI v2). `name` is bytes (from `name` or
266
+ `name_b64`). `tier` is the tier this build line's WHERE-settings list gives
267
+ it, a `DeclinedTier`, and `layer` the settings layer it comes from, the
268
+ highest one that sets the name, a `DeclinedLayer`; a value the description
269
+ does not list is its vocabulary's unknown(n) member (rule r3)."""
260
270
 
261
271
  name: bytes
262
272
  tier: DeclinedTier
273
+ layer: DeclinedLayer
263
274
 
264
275
 
265
276
  @dataclass(frozen=True, slots=True)
@@ -269,17 +280,11 @@ class SchemaServer:
269
280
  data-derived. `timezone` is the zone the schema's types bind: the profile's,
270
281
  or else the image zone. `settings` is the profile's settings (`{}` when it
271
282
  gave none). `macros` is None exactly when the profile carried no macros
272
- (unknown), and a mapping, even an empty one, when it did (the complete set).
273
- `filter_declined_settings` is the settings the profile sets that this
274
- build's filters do not honor in a WHERE, in the profile's own order, empty
275
- when it lists none: a schema on a server that lists any is one
276
- `compile_filter` declines (`UnsupportedError`), and a consumer that streams
277
- rows refuses such a tenant (spec/abi-v2/where-settings/README.md)."""
283
+ (unknown), and a mapping, even an empty one, when it did (the complete set)."""
278
284
 
279
285
  timezone: str
280
286
  settings: Mapping[str, str]
281
287
  macros: Mapping[str, str] | None
282
- filter_declined_settings: tuple[DeclinedSetting, ...] = ()
283
288
 
284
289
 
285
290
  @dataclass(frozen=True, slots=True)
@@ -293,13 +298,22 @@ class SchemaReplicated:
293
298
 
294
299
  @dataclass(frozen=True, slots=True)
295
300
  class SchemaDescription:
296
- """A schema's columns, in declared order, and the server it was compiled on.
297
- `server` is None exactly when it was compiled without one; `replicated` is
298
- None when the document carries none."""
301
+ """A schema's columns, in declared order, the server it was compiled on, and
302
+ the settings its filters decline. `server` is None exactly when it was
303
+ compiled without one; `replicated` is None when the document carries none.
304
+ `filter_declined_settings` is the settings the schema compiles under that
305
+ this build's filters do not honor in a WHERE, over every layer known at
306
+ schema compile (the defaults `setup` set, the server profile and the
307
+ schema's own settings), empty when it lists none: one entry per name, at
308
+ the highest layer that sets it, ordered by layer and then by name bytes, as
309
+ the library wrote it. A schema that lists any is one `compile_filter`
310
+ declines (`UnsupportedError`), and a consumer that streams rows refuses
311
+ such a tenant (spec/abi-v2/where-settings/README.md)."""
299
312
 
300
313
  columns: tuple[Column, ...]
301
314
  server: SchemaServer | None = None
302
315
  replicated: SchemaReplicated | None = None
316
+ filter_declined_settings: tuple[DeclinedSetting, ...] = ()
303
317
 
304
318
 
305
319
  @dataclass(frozen=True, slots=True)
@@ -14,6 +14,7 @@ import threading
14
14
  import pytest
15
15
 
16
16
  from chtypes import (
17
+ DeclinedLayer,
17
18
  DeclinedSetting,
18
19
  DeclinedTier,
19
20
  SchemaReplicated,
@@ -276,54 +277,95 @@ def test_a_malformed_server_or_replicated_member_is_an_internal_error(extra) ->
276
277
  _describe(**extra)
277
278
 
278
279
 
279
- _PROFILE = {
280
- "timezone": "UTC",
281
- "settings": {"final": "1", "aggregate_functions_null_for_empty": "1"},
282
- }
280
+ _SERVER = {"timezone": "UTC", "settings": {"final": "1"}}
283
281
 
284
282
 
285
283
  def _declined(entries) -> tuple:
286
- d = _describe(server={**_PROFILE, "filter_declined_settings": entries})
287
- return d.server.filter_declined_settings
284
+ return _describe(filter_declined_settings=entries, server=_SERVER).filter_declined_settings
288
285
 
289
286
 
290
287
  def test_filter_declined_settings_empty_and_absent() -> None:
291
288
  # [] is present and empty; absent (a document of no build at this
292
289
  # fingerprint) reads as empty too. Neither is a failure.
293
290
  assert _declined([]) == ()
294
- absent = _describe(server={"timezone": "UTC", "settings": {}})
295
- assert absent.server.filter_declined_settings == ()
291
+ assert _describe(server=_SERVER).filter_declined_settings == ()
296
292
 
297
293
 
298
- def test_filter_declined_settings_in_profile_order_b64_and_an_unknown_tier() -> None:
294
+ def test_filter_declined_settings_in_document_order_b64_and_unknown_values() -> None:
299
295
  got = _declined(
300
296
  [
301
- {"name": "final", "tier": "result-content", "x_future": 1},
302
- {"name": "aggregate_functions_null_for_empty", "tier": "predicate"},
303
- {"name_b64": "eP95", "tier": "predicate-unflipped"},
304
- {"name": "x_future_setting", "tier": "x_future_tier", "x_future_obj": {"a": [1]}},
297
+ {
298
+ "name": "aggregate_functions_null_for_empty",
299
+ "tier": "predicate",
300
+ "layer": "defaults",
301
+ "x_future": 1,
302
+ },
303
+ {"name": "final", "tier": "result-content", "layer": "server"},
304
+ {"name": "additional_result_filter", "tier": "result-content", "layer": "schema"},
305
+ {"name_b64": "eP95", "tier": "predicate-unflipped", "layer": "schema"},
306
+ {
307
+ "name": "x_future_setting",
308
+ "tier": "x_future_tier",
309
+ "layer": "x_future_layer",
310
+ "x_future_obj": {"a": [1]},
311
+ },
305
312
  ]
306
313
  )
314
+ # The document's own order, never re-sorted (here not name order).
307
315
  assert got == (
308
- DeclinedSetting(b"final", DeclinedTier.RESULT_CONTENT),
309
- DeclinedSetting(b"aggregate_functions_null_for_empty", DeclinedTier.PREDICATE),
310
- DeclinedSetting(b"x\xffy", DeclinedTier.PREDICATE_UNFLIPPED),
311
- DeclinedSetting(b"x_future_setting", DeclinedTier.of("x_future_tier")),
316
+ DeclinedSetting(
317
+ b"aggregate_functions_null_for_empty", DeclinedTier.PREDICATE, DeclinedLayer.DEFAULTS
318
+ ),
319
+ DeclinedSetting(b"final", DeclinedTier.RESULT_CONTENT, DeclinedLayer.SERVER),
320
+ DeclinedSetting(
321
+ b"additional_result_filter", DeclinedTier.RESULT_CONTENT, DeclinedLayer.SCHEMA
322
+ ),
323
+ DeclinedSetting(b"x\xffy", DeclinedTier.PREDICATE_UNFLIPPED, DeclinedLayer.SCHEMA),
324
+ DeclinedSetting(
325
+ b"x_future_setting",
326
+ DeclinedTier.of("x_future_tier"),
327
+ DeclinedLayer.of("x_future_layer"),
328
+ ),
329
+ )
330
+ # Rule r3: an unlisted tier or layer is kept as it came, as its unknown(n).
331
+ assert [e.tier.known for e in got] == [True, True, True, True, False]
332
+ assert [e.layer.known for e in got] == [True, True, True, True, False]
333
+ assert got[4].tier == "x_future_tier" and got[4].layer == "x_future_layer"
334
+
335
+
336
+ def test_filter_declined_settings_without_a_server_and_the_removed_server_list() -> None:
337
+ # A schema with no server lists its own layers all the same.
338
+ d = _describe(
339
+ filter_declined_settings=[{"name": "final", "tier": "result-content", "layer": "schema"}]
340
+ )
341
+ assert d.server is None
342
+ assert d.filter_declined_settings == (
343
+ DeclinedSetting(b"final", DeclinedTier.RESULT_CONTENT, DeclinedLayer.SCHEMA),
344
+ )
345
+ # The removed server-level list is an unknown member now (rule r2): the
346
+ # top-level list is the one decoded.
347
+ d = _describe(
348
+ filter_declined_settings=[],
349
+ server={
350
+ **_SERVER,
351
+ "filter_declined_settings": [{"name": "final", "tier": "result-content"}],
352
+ },
353
+ )
354
+ assert d.filter_declined_settings == () and d.server == SchemaServer(
355
+ "UTC", {"final": "1"}, None
312
356
  )
313
- # Rule r3: an unlisted tier is kept as it came, as its unknown(n).
314
- assert [e.tier.known for e in got] == [True, True, True, False]
315
- assert got[3].tier == "x_future_tier"
316
357
 
317
358
 
318
359
  @pytest.mark.parametrize(
319
360
  "entries",
320
361
  [
321
- {"name": "final", "tier": "predicate"},
362
+ {"name": "final", "tier": "predicate", "layer": "server"},
322
363
  ["final"],
323
- [{"name": "final", "name_b64": "ZmluYWw=", "tier": "predicate"}],
324
- [{"tier": "predicate"}],
325
- [{"name_b64": "*", "tier": "predicate"}],
326
- [{"name": "final", "tier": 1}],
364
+ [{"name": "final", "name_b64": "ZmluYWw=", "tier": "predicate", "layer": "server"}],
365
+ [{"tier": "predicate", "layer": "server"}],
366
+ [{"name_b64": "*", "tier": "predicate", "layer": "server"}],
367
+ [{"name": "final", "tier": 1, "layer": "server"}],
368
+ [{"name": "final", "tier": "predicate", "layer": 2}],
327
369
  ],
328
370
  )
329
371
  def test_a_malformed_filter_declined_settings_is_an_internal_error(entries) -> None:
@@ -75,19 +75,20 @@ def _outcome(want: dict, error: BaseException | None) -> str:
75
75
 
76
76
 
77
77
  def _declined(described, want: list[dict]) -> str:
78
- """What differs between a description's server `filter_declined_settings`
79
- and a describe step's expectation; "" when none."""
80
- if described.server is None:
81
- return "the description carries no server"
82
- got = described.server.filter_declined_settings
78
+ """What differs between a description's `filter_declined_settings` and a
79
+ describe step's expectation; "" when none."""
80
+ got = described.filter_declined_settings
83
81
  if len(got) != len(want):
84
82
  return f"filter_declined_settings has {len(got)} entries, want {len(want)}: {got!r}"
85
83
  for i, (g, w) in enumerate(zip(got, want, strict=True)):
86
84
  if g.name.hex() != w["name_hex"]:
87
85
  return f"entry {i}: name {g.name!r}, want hex {w['name_hex']}"
88
- if g.tier != w["tier"] or g.tier.known != w["known"]:
89
- want_tier = f"{w['tier']!r} (known {w['known']})"
86
+ if g.tier != w["tier"] or g.tier.known != w["tier_known"]:
87
+ want_tier = f"{w['tier']!r} (known {w['tier_known']})"
90
88
  return f"entry {i}: tier {g.tier!r} (known {g.tier.known}), want {want_tier}"
89
+ if g.layer != w["layer"] or g.layer.known != w["layer_known"]:
90
+ want_layer = f"{w['layer']!r} (known {w['layer_known']})"
91
+ return f"entry {i}: layer {g.layer!r} (known {g.layer.known}), want {want_layer}"
91
92
  return ""
92
93
 
93
94
 
@@ -195,9 +196,10 @@ def test_setup_case(
195
196
  if opened is not None:
196
197
  current = opened
197
198
  diff = _outcome(step["expect"], error)
198
- if not diff and step["op"] == "describe":
199
+ if not diff and step["op"] == "describe" and step["expect"].get("ok"):
199
200
  # The list as this binding decoded it from the document the stub
200
- # answered, never a value the case sets by hand.
201
+ # answered, never a value the case sets by hand. A describe the
202
+ # case expects to fail has no list.
201
203
  diff = _declined(described, step["declined"])
202
204
  if not diff and "rows_passed" in step and step["rows_passed"] != batch_rows_passed:
203
205
  diff = (
@@ -74,7 +74,7 @@ wheels = [
74
74
 
75
75
  [[package]]
76
76
  name = "chtypes"
77
- version = "2.0.0.dev4"
77
+ version = "2.0.0.dev5"
78
78
  source = { editable = "." }
79
79
  dependencies = [
80
80
  { name = "backports-zstd", marker = "python_full_version < '3.14'" },
File without changes
File without changes
File without changes