chtypes 2.0.0.dev0__tar.gz → 2.0.0.dev2__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.dev0 → chtypes-2.0.0.dev2}/PKG-INFO +8 -6
- {chtypes-2.0.0.dev0 → chtypes-2.0.0.dev2}/README.md +7 -5
- {chtypes-2.0.0.dev0 → chtypes-2.0.0.dev2}/pyproject.toml +1 -1
- {chtypes-2.0.0.dev0 → chtypes-2.0.0.dev2}/src/chtypes/__init__.py +11 -3
- {chtypes-2.0.0.dev0 → chtypes-2.0.0.dev2}/src/chtypes/__main__.py +23 -5
- {chtypes-2.0.0.dev0 → chtypes-2.0.0.dev2}/src/chtypes/_abi2/_decls.py +67 -14
- {chtypes-2.0.0.dev0 → chtypes-2.0.0.dev2}/src/chtypes/_abi2/_errmap.py +1 -1
- {chtypes-2.0.0.dev0 → chtypes-2.0.0.dev2}/src/chtypes/_abi2/_loader.py +28 -15
- {chtypes-2.0.0.dev0 → chtypes-2.0.0.dev2}/src/chtypes/_abi2/_vocab.py +1 -1
- {chtypes-2.0.0.dev0 → chtypes-2.0.0.dev2}/src/chtypes/_decode.py +48 -1
- {chtypes-2.0.0.dev0 → chtypes-2.0.0.dev2}/src/chtypes/_input.py +42 -1
- {chtypes-2.0.0.dev0 → chtypes-2.0.0.dev2}/src/chtypes/_ocifetch/_channel.py +4 -0
- {chtypes-2.0.0.dev0 → chtypes-2.0.0.dev2}/src/chtypes/_ocifetch/_ensure.py +6 -1
- {chtypes-2.0.0.dev0 → chtypes-2.0.0.dev2}/src/chtypes/_ocifetch/_layout.py +17 -7
- {chtypes-2.0.0.dev0 → chtypes-2.0.0.dev2}/src/chtypes/library.py +47 -3
- {chtypes-2.0.0.dev0 → chtypes-2.0.0.dev2}/src/chtypes/registry.py +25 -1
- {chtypes-2.0.0.dev0 → chtypes-2.0.0.dev2}/src/chtypes/results.py +32 -0
- {chtypes-2.0.0.dev0 → chtypes-2.0.0.dev2}/tests/abi2/test_dev_fingerprint.py +21 -0
- {chtypes-2.0.0.dev0 → chtypes-2.0.0.dev2}/tests/abi2/test_public_library.py +4 -2
- chtypes-2.0.0.dev2/tests/abi2/test_public_server.py +268 -0
- {chtypes-2.0.0.dev0 → chtypes-2.0.0.dev2}/tests/abi2/test_public_setup_cases.py +50 -1
- {chtypes-2.0.0.dev0 → chtypes-2.0.0.dev2}/tests/cli/test_devchannel.py +22 -0
- {chtypes-2.0.0.dev0 → chtypes-2.0.0.dev2}/tests/goldens_v1/test_runner.py +1 -1
- {chtypes-2.0.0.dev0 → chtypes-2.0.0.dev2}/tests/ocifetch/test_cache_roots.py +38 -1
- {chtypes-2.0.0.dev0 → chtypes-2.0.0.dev2}/tests/ocifetch/test_devchannel.py +60 -2
- chtypes-2.0.0.dev2/tests/test_cache_roots_api.py +61 -0
- {chtypes-2.0.0.dev0 → chtypes-2.0.0.dev2}/tests/test_examples_tour.py +1 -1
- {chtypes-2.0.0.dev0 → chtypes-2.0.0.dev2}/uv.lock +2 -2
- {chtypes-2.0.0.dev0 → chtypes-2.0.0.dev2}/.gitignore +0 -0
- {chtypes-2.0.0.dev0 → chtypes-2.0.0.dev2}/CHANGELOG.md +0 -0
- {chtypes-2.0.0.dev0 → chtypes-2.0.0.dev2}/LICENSE +0 -0
- {chtypes-2.0.0.dev0 → chtypes-2.0.0.dev2}/src/chtypes/_abi2/__init__.py +0 -0
- {chtypes-2.0.0.dev0 → chtypes-2.0.0.dev2}/src/chtypes/_abi2/_errors.py +0 -0
- {chtypes-2.0.0.dev0 → chtypes-2.0.0.dev2}/src/chtypes/_ed25519.py +0 -0
- {chtypes-2.0.0.dev0 → chtypes-2.0.0.dev2}/src/chtypes/_guard.py +0 -0
- {chtypes-2.0.0.dev0 → chtypes-2.0.0.dev2}/src/chtypes/_ocifetch/__init__.py +0 -0
- {chtypes-2.0.0.dev0 → chtypes-2.0.0.dev2}/src/chtypes/_ocifetch/_constants.py +0 -0
- {chtypes-2.0.0.dev0 → chtypes-2.0.0.dev2}/src/chtypes/_ocifetch/_dsse.py +0 -0
- {chtypes-2.0.0.dev0 → chtypes-2.0.0.dev2}/src/chtypes/_ocifetch/_errors.py +0 -0
- {chtypes-2.0.0.dev0 → chtypes-2.0.0.dev2}/src/chtypes/_ocifetch/_faults.py +0 -0
- {chtypes-2.0.0.dev0 → chtypes-2.0.0.dev2}/src/chtypes/_ocifetch/_goldens.py +0 -0
- {chtypes-2.0.0.dev0 → chtypes-2.0.0.dev2}/src/chtypes/_ocifetch/_http.py +0 -0
- {chtypes-2.0.0.dev0 → chtypes-2.0.0.dev2}/src/chtypes/_ocifetch/_lock.py +0 -0
- {chtypes-2.0.0.dev0 → chtypes-2.0.0.dev2}/src/chtypes/_ocifetch/_oci.py +0 -0
- {chtypes-2.0.0.dev0 → chtypes-2.0.0.dev2}/src/chtypes/_ocifetch/_referrers.py +0 -0
- {chtypes-2.0.0.dev0 → chtypes-2.0.0.dev2}/src/chtypes/_ocifetch/_unpack.py +0 -0
- {chtypes-2.0.0.dev0 → chtypes-2.0.0.dev2}/src/chtypes/_setup.py +0 -0
- {chtypes-2.0.0.dev0 → chtypes-2.0.0.dev2}/src/chtypes/errors.py +0 -0
- {chtypes-2.0.0.dev0 → chtypes-2.0.0.dev2}/src/chtypes/py.typed +0 -0
- {chtypes-2.0.0.dev0 → chtypes-2.0.0.dev2}/tests/abi2/__init__.py +0 -0
- {chtypes-2.0.0.dev0 → chtypes-2.0.0.dev2}/tests/abi2/_stub_tree.py +0 -0
- {chtypes-2.0.0.dev0 → chtypes-2.0.0.dev2}/tests/abi2/conftest.py +0 -0
- {chtypes-2.0.0.dev0 → chtypes-2.0.0.dev2}/tests/abi2/test_conformance.py +0 -0
- {chtypes-2.0.0.dev0 → chtypes-2.0.0.dev2}/tests/abi2/test_goldens_runner_stub.py +0 -0
- {chtypes-2.0.0.dev0 → chtypes-2.0.0.dev2}/tests/abi2/test_loader.py +0 -0
- {chtypes-2.0.0.dev0 → chtypes-2.0.0.dev2}/tests/abi2/test_public_open.py +0 -0
- {chtypes-2.0.0.dev0 → chtypes-2.0.0.dev2}/tests/abi2/test_public_registry.py +0 -0
- {chtypes-2.0.0.dev0 → chtypes-2.0.0.dev2}/tests/abi2/test_reader_rules.py +0 -0
- {chtypes-2.0.0.dev0 → chtypes-2.0.0.dev2}/tests/api/__init__.py +0 -0
- {chtypes-2.0.0.dev0 → chtypes-2.0.0.dev2}/tests/api/test_decode.py +0 -0
- {chtypes-2.0.0.dev0 → chtypes-2.0.0.dev2}/tests/api/test_input.py +0 -0
- {chtypes-2.0.0.dev0 → chtypes-2.0.0.dev2}/tests/api/test_vocab.py +0 -0
- {chtypes-2.0.0.dev0 → chtypes-2.0.0.dev2}/tests/cli/__init__.py +0 -0
- {chtypes-2.0.0.dev0 → chtypes-2.0.0.dev2}/tests/cli/conftest.py +0 -0
- {chtypes-2.0.0.dev0 → chtypes-2.0.0.dev2}/tests/cli/test_main.py +0 -0
- {chtypes-2.0.0.dev0 → chtypes-2.0.0.dev2}/tests/conftest.py +0 -0
- {chtypes-2.0.0.dev0 → chtypes-2.0.0.dev2}/tests/goldens_v1/__init__.py +0 -0
- {chtypes-2.0.0.dev0 → chtypes-2.0.0.dev2}/tests/goldens_v1/data/stub-goldens.json +0 -0
- {chtypes-2.0.0.dev0 → chtypes-2.0.0.dev2}/tests/ocifetch/__init__.py +0 -0
- {chtypes-2.0.0.dev0 → chtypes-2.0.0.dev2}/tests/ocifetch/_bundle_support.py +0 -0
- {chtypes-2.0.0.dev0 → chtypes-2.0.0.dev2}/tests/ocifetch/_registry_support.py +0 -0
- {chtypes-2.0.0.dev0 → chtypes-2.0.0.dev2}/tests/ocifetch/_sign_support.py +0 -0
- {chtypes-2.0.0.dev0 → chtypes-2.0.0.dev2}/tests/ocifetch/conftest.py +0 -0
- {chtypes-2.0.0.dev0 → chtypes-2.0.0.dev2}/tests/ocifetch/test_cache_strict.py +0 -0
- {chtypes-2.0.0.dev0 → chtypes-2.0.0.dev2}/tests/ocifetch/test_conformance.py +0 -0
- {chtypes-2.0.0.dev0 → chtypes-2.0.0.dev2}/tests/ocifetch/test_dsse.py +0 -0
- {chtypes-2.0.0.dev0 → chtypes-2.0.0.dev2}/tests/ocifetch/test_ensure.py +0 -0
- {chtypes-2.0.0.dev0 → chtypes-2.0.0.dev2}/tests/ocifetch/test_goldens.py +0 -0
- {chtypes-2.0.0.dev0 → chtypes-2.0.0.dev2}/tests/ocifetch/test_http.py +0 -0
- {chtypes-2.0.0.dev0 → chtypes-2.0.0.dev2}/tests/ocifetch/test_layout.py +0 -0
- {chtypes-2.0.0.dev0 → chtypes-2.0.0.dev2}/tests/ocifetch/test_lock.py +0 -0
- {chtypes-2.0.0.dev0 → chtypes-2.0.0.dev2}/tests/ocifetch/test_oci.py +0 -0
- {chtypes-2.0.0.dev0 → chtypes-2.0.0.dev2}/tests/ocifetch/test_seam_gate.py +0 -0
- {chtypes-2.0.0.dev0 → chtypes-2.0.0.dev2}/tests/ocifetch/test_unpack.py +0 -0
- {chtypes-2.0.0.dev0 → chtypes-2.0.0.dev2}/tests/test_abi_identity.py +0 -0
- {chtypes-2.0.0.dev0 → chtypes-2.0.0.dev2}/tests/test_devchannel_registry.py +0 -0
- {chtypes-2.0.0.dev0 → chtypes-2.0.0.dev2}/tests/test_ed25519.py +0 -0
- {chtypes-2.0.0.dev0 → chtypes-2.0.0.dev2}/tests/test_fetch_options.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: chtypes
|
|
3
|
-
Version: 2.0.0.
|
|
3
|
+
Version: 2.0.0.dev2
|
|
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
|
|
@@ -20,6 +20,8 @@ Description-Content-Type: text/markdown
|
|
|
20
20
|
# chtypes — Python SDK
|
|
21
21
|
|
|
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. 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
|
+
>
|
|
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.
|
|
23
25
|
|
|
24
26
|
**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.
|
|
25
27
|
|
|
@@ -41,18 +43,18 @@ uv add "chtypes>=2.0.0.dev0" # or: pip install "chtypes>=2.0.0.dev0"
|
|
|
41
43
|
import chtypes
|
|
42
44
|
from chtypes import Format, Registry
|
|
43
45
|
|
|
44
|
-
chtypes.setup(timezone="UTC")
|
|
46
|
+
chtypes.setup(timezone="UTC") # once, before the first open (optional)
|
|
45
47
|
registry = Registry(autofetch=True)
|
|
46
|
-
library = registry.for_version("26.8")
|
|
48
|
+
library = registry.for_version("26.8") # a release line or an exact version
|
|
47
49
|
|
|
48
50
|
ddl = b"CREATE TABLE t (x UInt8, ts DateTime DEFAULT now()) ENGINE = Memory"
|
|
49
51
|
with library.compile_table(ddl) as schema:
|
|
50
52
|
batch = schema.rows(Format.JSON_EACH_ROW, b'{"x":256}\n')
|
|
51
53
|
|
|
52
54
|
row = batch.rows[0]
|
|
53
|
-
print(batch.outcome)
|
|
54
|
-
print(row.values[0].text)
|
|
55
|
-
print(row.transformed[0].reason)
|
|
55
|
+
print(batch.outcome) # accepted
|
|
56
|
+
print(row.values[0].text) # b'0' what would actually be stored
|
|
57
|
+
print(row.transformed[0].reason) # overflow_wrap which is the product
|
|
56
58
|
```
|
|
57
59
|
|
|
58
60
|
The row is **accepted** and `256` is silently stored as `0`. That report — `transformed` — is why chtypes exists. Names, SQL, messages and renderings come back as `bytes`, never decoded for you: a column name that is not valid UTF-8 round-trips exactly.
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
# chtypes — Python SDK
|
|
2
2
|
|
|
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. 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
|
+
>
|
|
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.
|
|
4
6
|
|
|
5
7
|
**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.
|
|
6
8
|
|
|
@@ -22,18 +24,18 @@ uv add "chtypes>=2.0.0.dev0" # or: pip install "chtypes>=2.0.0.dev0"
|
|
|
22
24
|
import chtypes
|
|
23
25
|
from chtypes import Format, Registry
|
|
24
26
|
|
|
25
|
-
chtypes.setup(timezone="UTC")
|
|
27
|
+
chtypes.setup(timezone="UTC") # once, before the first open (optional)
|
|
26
28
|
registry = Registry(autofetch=True)
|
|
27
|
-
library = registry.for_version("26.8")
|
|
29
|
+
library = registry.for_version("26.8") # a release line or an exact version
|
|
28
30
|
|
|
29
31
|
ddl = b"CREATE TABLE t (x UInt8, ts DateTime DEFAULT now()) ENGINE = Memory"
|
|
30
32
|
with library.compile_table(ddl) as schema:
|
|
31
33
|
batch = schema.rows(Format.JSON_EACH_ROW, b'{"x":256}\n')
|
|
32
34
|
|
|
33
35
|
row = batch.rows[0]
|
|
34
|
-
print(batch.outcome)
|
|
35
|
-
print(row.values[0].text)
|
|
36
|
-
print(row.transformed[0].reason)
|
|
36
|
+
print(batch.outcome) # accepted
|
|
37
|
+
print(row.values[0].text) # b'0' what would actually be stored
|
|
38
|
+
print(row.transformed[0].reason) # overflow_wrap which is the product
|
|
37
39
|
```
|
|
38
40
|
|
|
39
41
|
The row is **accepted** and `256` is silently stored as `0`. That report — `transformed` — is why chtypes exists. Names, SQL, messages and renderings come back as `bytes`, never decoded for you: a column name that is not valid UTF-8 round-trips exactly.
|
|
@@ -59,7 +59,7 @@ from ._abi2._vocab import (
|
|
|
59
59
|
Status,
|
|
60
60
|
Verdict,
|
|
61
61
|
)
|
|
62
|
-
from ._input import BytesIn, Settings
|
|
62
|
+
from ._input import BytesIn, ServerProfile, Settings
|
|
63
63
|
from ._setup import setup
|
|
64
64
|
from .errors import (
|
|
65
65
|
CODE_ARTIFACT_CORRUPT,
|
|
@@ -92,8 +92,8 @@ from .errors import (
|
|
|
92
92
|
UnsupportedError,
|
|
93
93
|
UsageError,
|
|
94
94
|
)
|
|
95
|
-
from .library import Block, Filter, Library, Schema, open_unverified
|
|
96
|
-
from .registry import FetchOptions, Registry, Resolved, TrustedKey
|
|
95
|
+
from .library import Block, Filter, Library, Schema, Server, open_unverified
|
|
96
|
+
from .registry import FetchOptions, Registry, Resolved, TrustedKey, cache_root, search_dirs
|
|
97
97
|
from .results import (
|
|
98
98
|
BatchResult,
|
|
99
99
|
BuildInfo,
|
|
@@ -111,6 +111,8 @@ from .results import (
|
|
|
111
111
|
Header,
|
|
112
112
|
RowResult,
|
|
113
113
|
SchemaDescription,
|
|
114
|
+
SchemaReplicated,
|
|
115
|
+
SchemaServer,
|
|
114
116
|
Span,
|
|
115
117
|
Transform,
|
|
116
118
|
Value,
|
|
@@ -172,6 +174,10 @@ __all__ = [
|
|
|
172
174
|
"Schema",
|
|
173
175
|
"SchemaDescription",
|
|
174
176
|
"SchemaError",
|
|
177
|
+
"SchemaReplicated",
|
|
178
|
+
"SchemaServer",
|
|
179
|
+
"Server",
|
|
180
|
+
"ServerProfile",
|
|
175
181
|
"Settings",
|
|
176
182
|
"Source",
|
|
177
183
|
"SourceForbiddenError",
|
|
@@ -186,7 +192,9 @@ __all__ = [
|
|
|
186
192
|
"UsageError",
|
|
187
193
|
"Value",
|
|
188
194
|
"Verdict",
|
|
195
|
+
"cache_root",
|
|
189
196
|
"open_unverified",
|
|
197
|
+
"search_dirs",
|
|
190
198
|
"setup",
|
|
191
199
|
]
|
|
192
200
|
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
[--frozen] [--offline] [--update] [--strict]
|
|
6
6
|
chtypes verify [--cache <dir>] [--strict] re-verify every installed build
|
|
7
7
|
chtypes list [--cache <dir>] [--offline] [--strict]
|
|
8
|
-
chtypes where [--cache <dir>] [--strict] the cache root
|
|
8
|
+
chtypes where [--cache <dir>] [--strict] [--all] the cache root; --all: every search dir
|
|
9
9
|
|
|
10
10
|
A spelling is `26.8`, `26.8.15` or `26.8.15.10`. Exit statuses come from the
|
|
11
11
|
`errors` table of spec/fetch-v1/constants.json (generated into the fetch layer
|
|
@@ -137,7 +137,10 @@ def build_parser() -> argparse.ArgumentParser:
|
|
|
137
137
|
fetch.add_argument(
|
|
138
138
|
"--offline",
|
|
139
139
|
action="store_true",
|
|
140
|
-
help=
|
|
140
|
+
help=(
|
|
141
|
+
"never touch a source: installed and verified, or CHTYPES_ARTIFACT_MISSING "
|
|
142
|
+
"(or set CHTYPES_OFFLINE=1)"
|
|
143
|
+
),
|
|
141
144
|
)
|
|
142
145
|
|
|
143
146
|
verify = sub.add_parser(
|
|
@@ -154,11 +157,22 @@ def build_parser() -> argparse.ArgumentParser:
|
|
|
154
157
|
)
|
|
155
158
|
cache_option(listing)
|
|
156
159
|
listing.add_argument(
|
|
157
|
-
"--offline",
|
|
160
|
+
"--offline",
|
|
161
|
+
action="store_true",
|
|
162
|
+
help="list only what is installed; no network (or set CHTYPES_OFFLINE=1)",
|
|
158
163
|
)
|
|
159
164
|
|
|
160
|
-
where = sub.add_parser(
|
|
165
|
+
where = sub.add_parser(
|
|
166
|
+
"where",
|
|
167
|
+
help="the cache root",
|
|
168
|
+
description="Print the cache root; with --all, every directory searched.",
|
|
169
|
+
)
|
|
161
170
|
cache_option(where)
|
|
171
|
+
where.add_argument(
|
|
172
|
+
"--all",
|
|
173
|
+
action="store_true",
|
|
174
|
+
help="every directory searched, in order, the cache root first (one per line)",
|
|
175
|
+
)
|
|
162
176
|
return parser
|
|
163
177
|
|
|
164
178
|
|
|
@@ -294,7 +308,7 @@ def _cmd_list(args: argparse.Namespace) -> int:
|
|
|
294
308
|
sys.stdout.write(f"installed {r.version} {r.platform} {r.dir}\n")
|
|
295
309
|
sys.stdout.flush()
|
|
296
310
|
_say_notes(options)
|
|
297
|
-
if not
|
|
311
|
+
if not options.resolved_offline():
|
|
298
312
|
for tag in _published_tags(options):
|
|
299
313
|
sys.stdout.write(f"published {tag} support unknown\n")
|
|
300
314
|
sys.stdout.flush()
|
|
@@ -306,6 +320,10 @@ def _cmd_where(args: argparse.Namespace) -> int:
|
|
|
306
320
|
if options.resolved_strict():
|
|
307
321
|
# Strict mode checks the root before naming it.
|
|
308
322
|
probe_roots(search_roots(options.cache_dir, options.system_dirs), strict=True)
|
|
323
|
+
if args.all:
|
|
324
|
+
for directory in search_roots(options.cache_dir, options.system_dirs):
|
|
325
|
+
sys.stdout.write(f"{directory}\n")
|
|
326
|
+
return EXIT_OK
|
|
309
327
|
sys.stdout.write(f"{resolve_cache_root(args.cache)}\n")
|
|
310
328
|
return EXIT_OK
|
|
311
329
|
|
|
@@ -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:d87e91be2bbe75b985c38224268c2f7dc5d8236dab0a4b4d61357c6dfdc1a232) — 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:d87e91be2bbe75b985c38224268c2f7dc5d8236dab0a4b4d61357c6dfdc1a232"
|
|
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
|
|
@@ -61,6 +61,7 @@ _STATUS_OK = 0
|
|
|
61
61
|
HANDLE_FREE: dict[str, str] = {
|
|
62
62
|
"chs_buf": "chs_buf_free",
|
|
63
63
|
"chs_error": "chs_error_free",
|
|
64
|
+
"chs_server": "chs_server_free",
|
|
64
65
|
"chs_schema": "chs_schema_free",
|
|
65
66
|
"chs_filter": "chs_filter_free",
|
|
66
67
|
"chs_block": "chs_block_free",
|
|
@@ -102,6 +103,8 @@ ALL_SYMBOLS: tuple[str, ...] = (
|
|
|
102
103
|
"chs_schema_create",
|
|
103
104
|
"chs_schema_describe",
|
|
104
105
|
"chs_schema_free",
|
|
106
|
+
"chs_server_create",
|
|
107
|
+
"chs_server_free",
|
|
105
108
|
"chs_set_defaults",
|
|
106
109
|
"chs_shutdown",
|
|
107
110
|
"chs_type_validate",
|
|
@@ -174,6 +177,10 @@ class ErrorHandle(Handle):
|
|
|
174
177
|
"""A `chs_error` the library owns the free function for."""
|
|
175
178
|
|
|
176
179
|
|
|
180
|
+
class ServerHandle(Handle):
|
|
181
|
+
"""A `chs_server` the library owns the free function for."""
|
|
182
|
+
|
|
183
|
+
|
|
177
184
|
class SchemaHandle(Handle):
|
|
178
185
|
"""A `chs_schema` the library owns the free function for."""
|
|
179
186
|
|
|
@@ -189,6 +196,7 @@ class BlockHandle(Handle):
|
|
|
189
196
|
_HANDLE_CLASS: dict[str, type[Handle]] = {
|
|
190
197
|
"chs_buf": BufHandle,
|
|
191
198
|
"chs_error": ErrorHandle,
|
|
199
|
+
"chs_server": ServerHandle,
|
|
192
200
|
"chs_schema": SchemaHandle,
|
|
193
201
|
"chs_filter": FilterHandle,
|
|
194
202
|
"chs_block": BlockHandle,
|
|
@@ -324,12 +332,24 @@ _add(
|
|
|
324
332
|
_P("out", "out_handle", "chs_buf"),
|
|
325
333
|
_P("err", "out_error", "chs_error", nullable=True),
|
|
326
334
|
)
|
|
335
|
+
_add(
|
|
336
|
+
"chs_server_create",
|
|
337
|
+
"status",
|
|
338
|
+
"chs_status",
|
|
339
|
+
_P("profile", "bytes_in", None),
|
|
340
|
+
_P("options", "bytes_in", None),
|
|
341
|
+
_P("out", "out_handle", "chs_server"),
|
|
342
|
+
_P("err", "out_error", "chs_error", nullable=True),
|
|
343
|
+
)
|
|
344
|
+
_add("chs_server_free", "void", None, _P("server", "handle", "chs_server", nullable=True))
|
|
327
345
|
_add(
|
|
328
346
|
"chs_schema_create",
|
|
329
347
|
"status",
|
|
330
348
|
"chs_status",
|
|
349
|
+
_P("server", "handle", "chs_server", nullable=True),
|
|
331
350
|
_P("create_table", "bytes_in", None),
|
|
332
351
|
_P("settings", "bytes_in", None),
|
|
352
|
+
_P("options", "bytes_in", None),
|
|
333
353
|
_P("out", "out_handle", "chs_schema"),
|
|
334
354
|
_P("err", "out_error", "chs_error", nullable=True),
|
|
335
355
|
)
|
|
@@ -486,6 +506,18 @@ def resolve_abi_version(lib: ctypes.CDLL) -> Callable[..., object]:
|
|
|
486
506
|
return fn
|
|
487
507
|
|
|
488
508
|
|
|
509
|
+
def resolve_build_info(lib: ctypes.CDLL) -> Callable[..., object]:
|
|
510
|
+
"""Loader step 4's dlsym: `chs_build_info` ALONE, so the fingerprint can be
|
|
511
|
+
compared before step 6's full sweep (rule r6 beats a missing symbol,
|
|
512
|
+
public issue #537). Absent is MissingSymbol, the step 4 refusal."""
|
|
513
|
+
try:
|
|
514
|
+
fn = lib.chs_build_info
|
|
515
|
+
except AttributeError:
|
|
516
|
+
raise MissingSymbol("chs_build_info") from None
|
|
517
|
+
_set_signature(fn, FUNCTIONS["chs_build_info"])
|
|
518
|
+
return fn
|
|
519
|
+
|
|
520
|
+
|
|
489
521
|
class Api:
|
|
490
522
|
"""Every described symbol, resolved and signature-typed, step 6
|
|
491
523
|
(plan section 3.2). Constructed ONLY by resolve_all(): the constructor
|
|
@@ -703,19 +735,42 @@ class Api:
|
|
|
703
735
|
self._check(status, err)
|
|
704
736
|
return _read_buf(self, out.value)
|
|
705
737
|
|
|
738
|
+
def server_create(
|
|
739
|
+
self,
|
|
740
|
+
profile: bytes | None,
|
|
741
|
+
options: bytes | None,
|
|
742
|
+
) -> ServerHandle:
|
|
743
|
+
"""The generated call wrapper for chs_server_create (thread class any).
|
|
744
|
+
|
|
745
|
+
Raises the class the status maps to on a non-OK status."""
|
|
746
|
+
out = ctypes.c_void_p()
|
|
747
|
+
err = ctypes.c_void_p()
|
|
748
|
+
status = self._raw["chs_server_create"](
|
|
749
|
+
*_bytes_in(profile),
|
|
750
|
+
*_bytes_in(options),
|
|
751
|
+
ctypes.byref(out),
|
|
752
|
+
ctypes.byref(err),
|
|
753
|
+
)
|
|
754
|
+
self._check(status, err)
|
|
755
|
+
return ServerHandle(out.value, self._raw["chs_server_free"])
|
|
756
|
+
|
|
706
757
|
def schema_create(
|
|
707
758
|
self,
|
|
759
|
+
server: ServerHandle | None,
|
|
708
760
|
create_table: bytes | None,
|
|
709
761
|
settings: bytes | None,
|
|
762
|
+
options: bytes | None,
|
|
710
763
|
) -> SchemaHandle:
|
|
711
|
-
"""The generated call wrapper for chs_schema_create (thread class
|
|
764
|
+
"""The generated call wrapper for chs_schema_create (thread class shared).
|
|
712
765
|
|
|
713
766
|
Raises the class the status maps to on a non-OK status."""
|
|
714
767
|
out = ctypes.c_void_p()
|
|
715
768
|
err = ctypes.c_void_p()
|
|
716
769
|
status = self._raw["chs_schema_create"](
|
|
770
|
+
None if server is None else server.value,
|
|
717
771
|
*_bytes_in(create_table),
|
|
718
772
|
*_bytes_in(settings),
|
|
773
|
+
*_bytes_in(options),
|
|
719
774
|
ctypes.byref(out),
|
|
720
775
|
ctypes.byref(err),
|
|
721
776
|
)
|
|
@@ -925,17 +980,15 @@ _API_KEY = object()
|
|
|
925
980
|
|
|
926
981
|
|
|
927
982
|
def resolve_all(lib: ctypes.CDLL) -> Api:
|
|
928
|
-
"""Every described symbol, in sorted, deterministic order --
|
|
929
|
-
INCLUDED, even though _loader.py's
|
|
930
|
-
resolved
|
|
931
|
-
below needs every symbol reachable through ONE
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
chs_clickhouse_version included -- loader step 4 needs chs_build_info
|
|
938
|
-
already resolved, which is exactly why this sweep must run before it."""
|
|
983
|
+
"""Every described symbol, in sorted, deterministic order -- the handshake
|
|
984
|
+
symbols INCLUDED, even though _loader.py's steps 3 and 4 (resolve_abi_version,
|
|
985
|
+
resolve_build_info) already resolved theirs before this runs: the test-only
|
|
986
|
+
invoke_by_name() dispatcher below needs every symbol reachable through ONE
|
|
987
|
+
Api, and re-resolving an already-present symbol is harmless. The first
|
|
988
|
+
symbol ctypes cannot dlsym raises MissingSymbol(name); _loader.py's step 6
|
|
989
|
+
turns that into "missing_symbol:<name>", matching every missing-<sym> stub
|
|
990
|
+
variant. Step 6 runs AFTER step 4's fingerprint comparison, so a library
|
|
991
|
+
with another fingerprint is refused with rule r6's message first."""
|
|
939
992
|
raw: dict = {}
|
|
940
993
|
for name in ALL_SYMBOLS:
|
|
941
994
|
try:
|
|
@@ -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:d87e91be2bbe75b985c38224268c2f7dc5d8236dab0a4b4d61357c6dfdc1a232) — 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
|
|
@@ -9,15 +9,20 @@ wired up; `predicate` is passed through exactly as that seam promises.
|
|
|
9
9
|
Every `chs_*` spelling in this module is forbidden by
|
|
10
10
|
scripts/abi-v1/check-no-hand-decls.py (it is not a generated file), so this
|
|
11
11
|
calls ONLY python/src/chtypes/_abi2/_decls.py's non-`chs_`-prefixed surface:
|
|
12
|
-
`resolve_abi_version()`, `resolve_all()`, and the `Api` object they return.
|
|
13
|
-
|
|
14
|
-
Steps 1-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
12
|
+
`resolve_abi_version()`, `resolve_build_info()`, `resolve_all()`, and the `Api` object they return.
|
|
13
|
+
|
|
14
|
+
Steps 1-7 run in the documented order, the order Go, TypeScript and Rust
|
|
15
|
+
run: glibc (1), dlopen (2), `chs_abi_version` alone (3), `chs_build_info`
|
|
16
|
+
alone, parsed strictly and its fingerprint compared (4: rule r6's exact
|
|
17
|
+
message wins here), the signed-statement cross-check (5), and only then the
|
|
18
|
+
resolve-every-declared-symbol sweep (6). A library with another fingerprint
|
|
19
|
+
that also lacks a symbol this SDK declares (every fingerprint move that adds
|
|
20
|
+
an export meets one) is therefore refused with r6's message, never as
|
|
21
|
+
`missing_symbol:<name>` (public issue #537; the stub variant
|
|
22
|
+
`fingerprint-other-missing-symbol`). A symbol missing from step 4's own
|
|
23
|
+
`chs_build_info` is still `missing_symbol:chs_build_info`, and
|
|
24
|
+
`tests/fixtures/abi-v2/cases.json`'s other `missing-<sym>` variants still
|
|
25
|
+
expect `missing_symbol:<name>` from step 6. Step 7
|
|
21
26
|
calls the image's once-per-image zone setup (empty zone = UTC unless the
|
|
22
27
|
caller supplies one) and then its default settings.
|
|
23
28
|
"""
|
|
@@ -198,15 +203,15 @@ def _open(
|
|
|
198
203
|
if version != _decls.CHS_ABI_VERSION:
|
|
199
204
|
_refuse("abi_version", path, want=_decls.CHS_ABI_VERSION, got=version)
|
|
200
205
|
|
|
206
|
+
# Step 4: chs_build_info ALONE, parsed strictly, then the fingerprint
|
|
207
|
+
# compared. Rule r6 is decided here, before the step 6 sweep can name a
|
|
208
|
+
# missing symbol instead (#537).
|
|
201
209
|
try:
|
|
202
|
-
|
|
210
|
+
build_info_fn = _decls.resolve_build_info(lib)
|
|
203
211
|
except _decls.MissingSymbol as e:
|
|
204
212
|
_refuse(f"missing_symbol:{e.name}", path, want=e.name, got=None)
|
|
205
|
-
# _refuse always raises
|
|
206
|
-
|
|
207
|
-
raise
|
|
208
|
-
|
|
209
|
-
raw = api.build_info()
|
|
213
|
+
raise # never reached: _refuse always raises
|
|
214
|
+
raw = build_info_fn()
|
|
210
215
|
if raw is None:
|
|
211
216
|
_refuse("build_info_malformed", path, got="chs_build_info() returned NULL")
|
|
212
217
|
info = _parse_build_info(raw, path)
|
|
@@ -214,9 +219,17 @@ def _open(
|
|
|
214
219
|
if info["abi_fingerprint"] != _decls.CHS_ABI_FINGERPRINT:
|
|
215
220
|
_refuse_fingerprint(path, info.get("abi_fingerprint"))
|
|
216
221
|
|
|
222
|
+
# Step 5: the signed predicate against build_info.
|
|
217
223
|
if not skip_step5:
|
|
218
224
|
_cross_check(info, predicate, path)
|
|
219
225
|
|
|
226
|
+
# Step 6: resolve every declared symbol.
|
|
227
|
+
try:
|
|
228
|
+
api = _decls.resolve_all(lib)
|
|
229
|
+
except _decls.MissingSymbol as e:
|
|
230
|
+
_refuse(f"missing_symbol:{e.name}", path, want=e.name, got=None)
|
|
231
|
+
raise # never reached: _refuse always raises
|
|
232
|
+
|
|
220
233
|
# Step 7: the once-per-image setup (docs/reference/bindings-v1.md section
|
|
221
234
|
# 6): the image zone (empty = UTC), then the default settings when there
|
|
222
235
|
# are any. After every handshake check and before any other call. A
|
|
@@ -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:d87e91be2bbe75b985c38224268c2f7dc5d8236dab0a4b4d61357c6dfdc1a232) — 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
4
|
`Verdict`, `DefaultKind`, `Reason`, `Source`, `DiscoverQueryParam`) with the facts the
|
|
@@ -47,6 +47,8 @@ from .results import (
|
|
|
47
47
|
Header,
|
|
48
48
|
RowResult,
|
|
49
49
|
SchemaDescription,
|
|
50
|
+
SchemaReplicated,
|
|
51
|
+
SchemaServer,
|
|
50
52
|
Span,
|
|
51
53
|
Transform,
|
|
52
54
|
Value,
|
|
@@ -449,10 +451,55 @@ def _column(obj: dict[str, Any], where: str) -> Column:
|
|
|
449
451
|
)
|
|
450
452
|
|
|
451
453
|
|
|
454
|
+
def _string_map(obj: Mapping[str, Any], key: str, where: str) -> dict[str, str] | None:
|
|
455
|
+
"""An object of plain JSON strings (a server's settings or macros, the
|
|
456
|
+
caller's own values given back): None when absent, a dict, even an empty
|
|
457
|
+
one, when present, so absent and `{}` stay apart."""
|
|
458
|
+
value = obj.get(key)
|
|
459
|
+
if value is None:
|
|
460
|
+
return None
|
|
461
|
+
inner = _obj(value, where, key)
|
|
462
|
+
out: dict[str, str] = {}
|
|
463
|
+
for name, item in inner.items():
|
|
464
|
+
if not isinstance(item, str):
|
|
465
|
+
raise _wrong(f"{where}.{key}", name, "a string", item)
|
|
466
|
+
out[name] = item
|
|
467
|
+
return out
|
|
468
|
+
|
|
469
|
+
|
|
470
|
+
def _server(obj: dict[str, Any], where: str) -> SchemaServer:
|
|
471
|
+
return SchemaServer(
|
|
472
|
+
timezone=_text(obj, "timezone", where),
|
|
473
|
+
settings=_string_map(obj, "settings", where) or {},
|
|
474
|
+
macros=_string_map(obj, "macros", where),
|
|
475
|
+
)
|
|
476
|
+
|
|
477
|
+
|
|
478
|
+
def _replicated(obj: dict[str, Any], where: str) -> SchemaReplicated:
|
|
479
|
+
return SchemaReplicated(
|
|
480
|
+
zookeeper_path=_bytes(obj, "zookeeper_path", where),
|
|
481
|
+
replica_name=_bytes(obj, "replica_name", where),
|
|
482
|
+
)
|
|
483
|
+
|
|
484
|
+
|
|
452
485
|
def decode_schema_description(raw: bytes) -> SchemaDescription:
|
|
453
486
|
where = "schema_description"
|
|
454
487
|
obj = _obj(strict_loads(raw, where), where)
|
|
455
|
-
|
|
488
|
+
# Both are absent on a schema compiled without a server, so that document
|
|
489
|
+
# decodes exactly as it did before servers existed.
|
|
490
|
+
server = obj.get("server")
|
|
491
|
+
replicated = obj.get("replicated")
|
|
492
|
+
return SchemaDescription(
|
|
493
|
+
columns=_objects(obj, "columns", where, _column),
|
|
494
|
+
server=None
|
|
495
|
+
if server is None
|
|
496
|
+
else _server(_obj(server, where, "server"), f"{where}.server"),
|
|
497
|
+
replicated=(
|
|
498
|
+
None
|
|
499
|
+
if replicated is None
|
|
500
|
+
else _replicated(_obj(replicated, where, "replicated"), f"{where}.replicated")
|
|
501
|
+
),
|
|
502
|
+
)
|
|
456
503
|
|
|
457
504
|
|
|
458
505
|
def _discovered(obj: dict[str, Any], where: str) -> DiscoveredColumn:
|
|
@@ -12,10 +12,11 @@ from __future__ import annotations
|
|
|
12
12
|
import base64
|
|
13
13
|
import json
|
|
14
14
|
from collections.abc import Mapping, Sequence
|
|
15
|
+
from dataclasses import dataclass
|
|
15
16
|
|
|
16
17
|
from .errors import misuse
|
|
17
18
|
|
|
18
|
-
__all__ = ["BytesIn", "Settings"]
|
|
19
|
+
__all__ = ["BytesIn", "ServerProfile", "Settings"]
|
|
19
20
|
|
|
20
21
|
BytesIn = bytes | str
|
|
21
22
|
Settings = Mapping[str, str]
|
|
@@ -107,3 +108,43 @@ def columns_json(columns: Sequence[BytesIn] | None) -> bytes | None:
|
|
|
107
108
|
except UnicodeDecodeError:
|
|
108
109
|
names.append({"name_b64": base64.b64encode(raw).decode("ascii")})
|
|
109
110
|
return _dump(names)
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
@dataclass(frozen=True, slots=True)
|
|
114
|
+
class ServerProfile:
|
|
115
|
+
"""One ClickHouse server, as the library's `input:server_profile` describes
|
|
116
|
+
it. Every field is optional and passed through as given: the library
|
|
117
|
+
validates the zone with DateLUT, each setting with the server's own SET
|
|
118
|
+
check, and the macros with ClickHouse's own reader. The binding validates
|
|
119
|
+
nothing in it.
|
|
120
|
+
|
|
121
|
+
`timezone` is the server's zone, the one its `timezone()` returns; None or
|
|
122
|
+
"" leaves it undescribed (omitted) and the image zone applies. `settings` is
|
|
123
|
+
what the server's profile applies to every query, layered under a schema's
|
|
124
|
+
and a call's own; None omits it, and `{}` is sent as `{}`. `macros` is the
|
|
125
|
+
server's `<macros>`: None omits it, and the server's macros are UNKNOWN (a
|
|
126
|
+
schema whose engine reads one is declined); a mapping, even an empty one, is
|
|
127
|
+
sent (`{}` when empty) and is the server's COMPLETE set."""
|
|
128
|
+
|
|
129
|
+
timezone: str | None = None
|
|
130
|
+
settings: Settings | None = None
|
|
131
|
+
macros: Settings | None = None
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
def server_profile_json(profile: ServerProfile) -> bytes:
|
|
135
|
+
"""The profile document: an empty or absent zone and an absent map are
|
|
136
|
+
omitted; a present map is sent as given, `{}` when empty. Absent `macros` is
|
|
137
|
+
not empty `macros`."""
|
|
138
|
+
if not isinstance(profile, ServerProfile):
|
|
139
|
+
raise TypeError(f"profile must be a ServerProfile, not {type(profile).__name__}")
|
|
140
|
+
doc: dict[str, object] = {}
|
|
141
|
+
if profile.timezone is not None:
|
|
142
|
+
if not isinstance(profile.timezone, str):
|
|
143
|
+
raise TypeError(f"timezone must be str, not {type(profile.timezone).__name__}")
|
|
144
|
+
if profile.timezone != "":
|
|
145
|
+
doc["timezone"] = profile.timezone
|
|
146
|
+
if profile.settings is not None:
|
|
147
|
+
doc["settings"] = _string_map(profile.settings, "settings")
|
|
148
|
+
if profile.macros is not None:
|
|
149
|
+
doc["macros"] = _string_map(profile.macros, "macros")
|
|
150
|
+
return _dump(doc)
|
|
@@ -69,6 +69,10 @@ DEV_KEY_ID: Final = "824345f9bcf8e5bf"
|
|
|
69
69
|
DEV_KEY_HEX: Final = "5cd30c53c65a1ebc2d85836a41deb06661bb0ae7b658adb9eb116ec2db8e9b1c"
|
|
70
70
|
# The dev channel's cache directory name: the default root's last element and
|
|
71
71
|
# an explicit cache's subroot (rule r5).
|
|
72
|
+
# The environment twin of the offline option (public issue #528): CHTYPES_OFFLINE=1
|
|
73
|
+
# reads the cache only and makes no request. It names no source, so rule r6 holds.
|
|
74
|
+
# Not a generated constant: the generated set is the v1 fetch contract's.
|
|
75
|
+
ENV_OFFLINE_NAME: Final = "CHTYPES_OFFLINE"
|
|
72
76
|
DEV_CACHE_DIR: Final = "v2-dev"
|
|
73
77
|
# The verified.json schema the dev channel writes, and the only one it reads (rule r5).
|
|
74
78
|
DEV_RECORD_SCHEMA: Final = 2
|
|
@@ -137,6 +137,8 @@ class Options:
|
|
|
137
137
|
token: str | None = None
|
|
138
138
|
trusted_keys: tuple[TrustedKey, ...] | None = None
|
|
139
139
|
allow_unsigned: bool = False
|
|
140
|
+
# Offline mode is on when this is set OR CHTYPES_OFFLINE=1; neither turns
|
|
141
|
+
# the other off (public issue #528).
|
|
140
142
|
offline: bool = False
|
|
141
143
|
frozen: bool = False
|
|
142
144
|
lock_path: str | os.PathLike[str] | None = None
|
|
@@ -185,6 +187,9 @@ class Options:
|
|
|
185
187
|
def resolved_platform(self) -> str:
|
|
186
188
|
return self.platform or detect_host_platform()
|
|
187
189
|
|
|
190
|
+
def resolved_offline(self) -> bool:
|
|
191
|
+
return self.offline or os.environ.get(_channel.ENV_OFFLINE_NAME) == "1"
|
|
192
|
+
|
|
188
193
|
def resolved_strict(self) -> bool:
|
|
189
194
|
if self.strict_cache is not None:
|
|
190
195
|
return self.strict_cache
|
|
@@ -1012,7 +1017,7 @@ def _ensure(
|
|
|
1012
1017
|
request: Request, platform_key: str, options: Options, lock: Lock | None, roots: Sequence[Path]
|
|
1013
1018
|
) -> Resolved:
|
|
1014
1019
|
try:
|
|
1015
|
-
if options.
|
|
1020
|
+
if options.resolved_offline():
|
|
1016
1021
|
resolved = resolve_installed(request, platform_key, options)
|
|
1017
1022
|
if resolved is None:
|
|
1018
1023
|
raise ArtifactMissingError(
|