chtypes 0.1.2__tar.gz → 0.2.1__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-0.2.1/CHANGELOG.md +63 -0
- {chtypes-0.1.2 → chtypes-0.2.1}/PKG-INFO +1 -1
- {chtypes-0.1.2 → chtypes-0.2.1}/pyproject.toml +3 -3
- {chtypes-0.1.2 → chtypes-0.2.1}/src/chtypes/__init__.py +2 -0
- {chtypes-0.1.2 → chtypes-0.2.1}/src/chtypes/__main__.py +3 -1
- {chtypes-0.1.2 → chtypes-0.2.1}/src/chtypes/_manifest.py +4 -1
- {chtypes-0.1.2 → chtypes-0.2.1}/src/chtypes/_native.py +2 -1
- {chtypes-0.1.2 → chtypes-0.2.1}/src/chtypes/errors.py +7 -1
- {chtypes-0.1.2 → chtypes-0.2.1}/src/chtypes/fetch.py +3 -2
- {chtypes-0.1.2 → chtypes-0.2.1}/src/chtypes/registry.py +3 -2
- {chtypes-0.1.2 → chtypes-0.2.1}/tests/conftest.py +1 -1
- chtypes-0.2.1/tests/test_abi_revision.py +139 -0
- {chtypes-0.1.2 → chtypes-0.2.1}/tests/test_cli.py +4 -1
- {chtypes-0.1.2 → chtypes-0.2.1}/tests/test_fetch.py +3 -3
- {chtypes-0.1.2 → chtypes-0.2.1}/tests/test_parity.py +22 -12
- {chtypes-0.1.2 → chtypes-0.2.1}/tests/test_registry.py +23 -0
- chtypes-0.2.1/uv.lock +108 -0
- chtypes-0.1.2/CHANGELOG.md +0 -35
- chtypes-0.1.2/uv.lock +0 -108
- {chtypes-0.1.2 → chtypes-0.2.1}/.gitignore +0 -0
- {chtypes-0.1.2 → chtypes-0.2.1}/LICENSE +0 -0
- {chtypes-0.1.2 → chtypes-0.2.1}/README.md +0 -0
- {chtypes-0.1.2 → chtypes-0.2.1}/src/chtypes/_document.py +0 -0
- {chtypes-0.1.2 → chtypes-0.2.1}/src/chtypes/_ed25519.py +0 -0
- {chtypes-0.1.2 → chtypes-0.2.1}/src/chtypes/_rawjson.py +0 -0
- {chtypes-0.1.2 → chtypes-0.2.1}/src/chtypes/discover.py +0 -0
- {chtypes-0.1.2 → chtypes-0.2.1}/src/chtypes/py.typed +0 -0
- {chtypes-0.1.2 → chtypes-0.2.1}/src/chtypes/results.py +0 -0
- {chtypes-0.1.2 → chtypes-0.2.1}/src/chtypes/transform.py +0 -0
- {chtypes-0.1.2 → chtypes-0.2.1}/tests/test_ed25519.py +0 -0
- {chtypes-0.1.2 → chtypes-0.2.1}/tests/test_errors.py +0 -0
- {chtypes-0.1.2 → chtypes-0.2.1}/tests/test_golden.py +0 -0
- {chtypes-0.1.2 → chtypes-0.2.1}/tests/test_rawjson.py +0 -0
- {chtypes-0.1.2 → chtypes-0.2.1}/tests/test_transform.py +0 -0
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
# Changelog — `chtypes`
|
|
2
|
+
|
|
3
|
+
All notable changes to the python binding. The format is [Keep a Changelog](https://keepachangelog.com/en/1.1.0/); this package follows [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
4
|
+
|
|
5
|
+
The four bindings in this repository are released together and give one answer, so an entry here has a counterpart in the other three.
|
|
6
|
+
|
|
7
|
+
## [0.2.1] — 2026-09-15
|
|
8
|
+
|
|
9
|
+
Released in step with the Rust crate, which gains `Registry::open` and `Error::LibraryRead`; this binding's public API is unchanged.
|
|
10
|
+
|
|
11
|
+
### Notes
|
|
12
|
+
|
|
13
|
+
- Speaks **ABI revision 4**, unchanged since 0.1.0, so no artifact needs relinking. The golden set this release was tested against is the one core serves, generated on the ClickHouse lines 24.8, 25.3, 25.8, 25.10, 26.2, 26.3, 26.4, 26.5, 26.6, 26.7 and 26.8.
|
|
14
|
+
- **The ABI-revision refusal is now run, not assumed.** This binding's suite points a registry at a generated artifact answering the wrong revision and asserts the load is refused naming both numbers, beside a matching-revision control that must load; CI fails the build if either case did not run (#36).
|
|
15
|
+
|
|
16
|
+
## [0.2.0] — 2026-09-15
|
|
17
|
+
|
|
18
|
+
### Changed — BREAKING
|
|
19
|
+
|
|
20
|
+
- **The TypeScript filter verdicts are the wire characters.** `Verdict.True` / `False` / `Error` / `Decline` are now `'t'` / `'f'` / `'e'` / `'d'`, which Go, Python and Rust have always rendered. TypeScript alone spelled them `'true'` / `'false'` / `'error'` / `'decline'`, so two SDKs could not share a log line, a fixture or a test. The four states and the fail-closed rule are unchanged: `'e'` and `'d'` are NOT answers, and a caller enforcing visibility must hide the row or fail the request on both — collapsing either into `false` inverts fail-closed into fail-open, the measured leak class. Only the rendered characters moved. **The parity suites now ENFORCE the shared value in all four bindings**; until this release two of the four merely declared it.
|
|
21
|
+
|
|
22
|
+
- **TypeScript `FetchError` now extends `ArtifactError` (and so `RegistryError`), where it previously extended `ChtypesError` directly.** A `try`/`catch` chain that tests `RegistryError` before `FetchError` now takes the `RegistryError` arm for the five fetch verdicts, which it did not before. Ordering a catch chain most-specific-first is unaffected.
|
|
23
|
+
|
|
24
|
+
### Added
|
|
25
|
+
|
|
26
|
+
- **Load-time checksum verification in Go and Rust**, matching what Python and TypeScript have always offered: re-hash the library against `manifest.json`'s `library_sha256` **before `dlopen`**, and refuse the load on a mismatch. Off by default in all four. Whether an artifact was re-hashed before being mapped used to depend on which binding a consumer picked, which is a security posture and not an API spelling. A manifest carrying no `library_sha256` is refused by Go, TypeScript and Rust — verification asked for and not possible is not verification — while Python's returns silently, which is what remains of issue #13's item A2.
|
|
27
|
+
- **One catchable artifact error in TypeScript.** `ArtifactError` is a new exported base: all six artifact conditions now extend it, where `ArtifactMissingError` previously sat under `RegistryError` and the other five under `FetchError`, so a caller had to catch two unrelated types. Every existing `code` value and message is unchanged, and `ArtifactMissingError instanceof RegistryError` still holds.
|
|
28
|
+
- **Eight constants that three bindings exported and the fourth did not**, so a caller no longer retypes a literal the other SDKs hand them: `Registry.SearchPath()` (go), `parse_signature_file` and `FETCH_COMMAND` (python), `RELEASE_KEY_ID` and `DEFAULT_LOCK_FILE` (ts), `fetch::LOCK_SCHEMA` (rust), and `CHTYPES_REGISTRY` / `CHTYPES_AUTOFETCH` as `EnvRegistry` / `EnvAutoFetch` (go) and `ENV_REGISTRY` / `ENV_AUTOFETCH` (ts). Each new name replaces the inline literal in its own binding's code path, so there is one definition rather than two.
|
|
29
|
+
|
|
30
|
+
### Notes
|
|
31
|
+
|
|
32
|
+
- Speaks **ABI revision 4**, unchanged since 0.1.0, so no artifact needs relinking. The golden set this release was tested against is the one core serves, generated on the ClickHouse lines 24.8, 25.3, 25.8, 25.10, 26.2, 26.3, 26.4, 26.5, 26.6, 26.7 and 26.8.
|
|
33
|
+
- `Library` still has no scope-based release in Python or TypeScript, while `Registry`, `Schema`, `Filter` and `Block` do. That asymmetry is deliberate — a `with library:` or `using library` closes at the end of a block, which is the mid-lifecycle teardown measured to segfault on the next open — and it is now recorded in the parity manifest so no future audit reopens it as a gap.
|
|
34
|
+
|
|
35
|
+
## [0.1.2] — 2026-09-11
|
|
36
|
+
|
|
37
|
+
### Changed — BREAKING
|
|
38
|
+
|
|
39
|
+
- **The transform reason for a materialized DEFAULT is now `default_materialized`** — previously the same word spelled with an `s` — and the exported constant naming it is now `Reason.DEFAULT_MATERIALIZED`. ClickHouse's own keyword is `MATERIALIZED`; its parser rejects the `s` spelling outright with a syntax error, so the reason naming that concept now matches the system it describes. Code comparing against the old string or the old constant name must be updated.
|
|
40
|
+
- This is an SDK-only change and **the ABI is untouched: it remains revision 4, and no artifact needs relinking.** The reason is derived in the binding, not received from the artifact — the library emits `default_substituted`, which each binding translates. That wire value is unchanged.
|
|
41
|
+
|
|
42
|
+
### Changed
|
|
43
|
+
|
|
44
|
+
- **The per-language README is now an orientation page, not a manual.** It carries install, a quickstart that runs, the three outcomes and the error model, and then points at `docs/` for the rest — roughly 450 lines of reference prose moved to `docs/guides/` and `docs/reference/` rather than being repeated four times and drifting four ways.
|
|
45
|
+
- **The published packages no longer cite a repository you cannot open.** 148 source comments — in every binding and in `include/chtypes.h`, all of which ship inside the crate, the sdist and the module — attributed the contract they implement to a private repository's specification. They now cite **the C ABI contract**, with `include/chtypes.h` as the public authority for it. Alongside those, 14 dead paths into that repository, the examples' instruction to build an artifact there, and two descriptions of the signing infrastructure are gone. What remains is every place where the two-repository split is itself the subject — describing it in prose is honest and stays legal; naming something a reader cannot open is not, and `scripts/lint-public.sh` is what enforces the difference. (An earlier draft of this entry enumerated three files. That count was never checked and was wrong by eighteen, which is the same mistake the entry above is about: the rule is checkable, a file list is not.)
|
|
46
|
+
- `scripts/lint-public.sh` is new and blocking in CI, because the sweep this replaces reported itself complete while 148 of its targets survived in a different shape. It refuses both the private repository's name and that exact prose pointer, and its `--selftest` proves each rule fires rather than assuming it.
|
|
47
|
+
|
|
48
|
+
## [0.1.1] — 2026-09-11
|
|
49
|
+
|
|
50
|
+
- **The golden set is served, not tracked.** `goldens/cases.json` no longer exists in this repository. Core publishes `sdk-goldens.json` in the rolling release as a row in the signed `SHA256SUMS`; `scripts/fetch.sh` installs it at `<registry>/sdk-goldens.json` and the golden test reads it offline from there (`CHTYPES_GOLDENS` overrides). A case runs only against the exact ClickHouse version `generated.exact` names for its line and skips loudly otherwise, so the set shrinks as lines are added rather than recording an answer twice.
|
|
51
|
+
- **Artifact identity gained a wrapper build.** Assets are `chtypes-<version>-<os>-<arch>-b<N>.tar.gz`, where `<N>` is the core commit's UNIX timestamp; an absent suffix means build 0 and stays valid. Line resolution ranks by `(version, build)`, so a rebuild of a line supersedes the older build.
|
|
52
|
+
- Fetching now retries the publish window, so a fetch that races a release no longer fails on sums naming a file the host has not finished serving.
|
|
53
|
+
- README install instructions name the published package rather than a path dependency.
|
|
54
|
+
- Two errors in the published README, both found by running the snippets rather than reading them: Python's `substituted` is on `RowResult`, not `BatchResult`, and TypeScript's `using schema = …` is a syntax error on Node 22, this package's own `engines` floor (`schema.close()` is portable).
|
|
55
|
+
- `docs/support.md` is new: which language versions, platforms and ClickHouse lines are supported, generated from the manifests and the release's own index.
|
|
56
|
+
|
|
57
|
+
## [0.1.0] — 2026-09-10
|
|
58
|
+
|
|
59
|
+
First release, published to PyPI from the tag `python/v0.1.0`.
|
|
60
|
+
|
|
61
|
+
- Speaks **ABI revision 4** of the frozen `chs_*` C ABI (`include/chtypes.h`, 28 functions). The binding `dlopen`s a per-version artifact and reimplements no ClickHouse semantics; `Transformed` is the one derived result.
|
|
62
|
+
- Verified against the public golden set (`goldens/cases.json`, schema 1, 31 cases), whose expectations were generated on ClickHouse 24.8, 25.3, 25.8, 25.10, 26.5, 26.6 and 26.7 — every case answered identically on all seven lines, and any case that did not was refused by the generator.
|
|
63
|
+
- Pre-1.0: the artifact name `libchtypes`, the `chs_` prefix and the `enum chs_format` numbers are frozen. Function signatures freeze at this tag.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: chtypes
|
|
3
|
-
Version: 0.1
|
|
3
|
+
Version: 0.2.1
|
|
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 = "0.1
|
|
3
|
+
version = "0.2.1"
|
|
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"
|
|
@@ -54,7 +54,7 @@ src = ["src", "tests"]
|
|
|
54
54
|
# the classes that have actually cost time here — unused imports and names (F),
|
|
55
55
|
# style errors that hide real ones (E), import order drift across 20 files (I),
|
|
56
56
|
# syntax this project's floor already allows (UP), and the mutable-default /
|
|
57
|
-
# loop-variable-capture family (B). `just lint` and
|
|
58
|
-
# both run this, so a finding is a gate failure, not advice.
|
|
57
|
+
# loop-variable-capture family (B). `just lint` and the core repository's
|
|
58
|
+
# static checks both run this, so a finding is a gate failure, not advice.
|
|
59
59
|
[tool.ruff.lint]
|
|
60
60
|
select = ["E", "F", "I", "UP", "B"]
|
|
@@ -64,6 +64,7 @@ from .errors import (
|
|
|
64
64
|
CODE_ARTIFACT_UNTRUSTED,
|
|
65
65
|
CODE_SOURCE_UNREACHABLE,
|
|
66
66
|
CODE_UNSUPPORTED,
|
|
67
|
+
FETCH_COMMAND,
|
|
67
68
|
ArtifactCorruptError,
|
|
68
69
|
ArtifactError,
|
|
69
70
|
ArtifactMissingError,
|
|
@@ -142,6 +143,7 @@ __all__ = [
|
|
|
142
143
|
"DOC_VALUES",
|
|
143
144
|
"ENV_AUTOFETCH",
|
|
144
145
|
"ENV_REGISTRY",
|
|
146
|
+
"FETCH_COMMAND",
|
|
145
147
|
"default_registry_dir",
|
|
146
148
|
"EXPORT_NONE",
|
|
147
149
|
"RELEASE_KEY_ID",
|
|
@@ -76,7 +76,9 @@ def build_parser() -> argparse.ArgumentParser:
|
|
|
76
76
|
fetch = sub.add_parser(
|
|
77
77
|
"fetch",
|
|
78
78
|
help="install one or more ClickHouse lines, verified",
|
|
79
|
-
description=
|
|
79
|
+
description=(
|
|
80
|
+
"Install ClickHouse lines through the verification chain (docs/guides/fetch.md §3)."
|
|
81
|
+
),
|
|
80
82
|
)
|
|
81
83
|
fetch.add_argument(
|
|
82
84
|
"lines",
|
|
@@ -111,7 +111,10 @@ def verify_library(version_dir: str | os.PathLike[str]) -> None:
|
|
|
111
111
|
with path.open("rb") as handle:
|
|
112
112
|
for chunk in iter(lambda: handle.read(1 << 20), b""):
|
|
113
113
|
digest.update(chunk)
|
|
114
|
-
|
|
114
|
+
# Case-insensitive: a hex digest is the same digest in either case, and Go
|
|
115
|
+
# and Rust already lower-case before comparing. Comparing raw here made an
|
|
116
|
+
# uppercase manifest digest pass in two bindings and fail in two.
|
|
117
|
+
if digest.hexdigest() != manifest.library_sha256.lower():
|
|
115
118
|
raise RegistryError(
|
|
116
119
|
f"chtypes: {path} sha256 {digest.hexdigest()} != manifest {manifest.library_sha256}"
|
|
117
120
|
)
|
|
@@ -179,7 +179,8 @@ class _RWLock:
|
|
|
179
179
|
|
|
180
180
|
**Readers** are every call that reaches `chs_row`, `chs_rows`,
|
|
181
181
|
`chs_schema_compile`, `chs_schema_engine`, `chs_schema_ttl`,
|
|
182
|
-
`chs_validate_type` and the column accessors. The C ABI contract
|
|
182
|
+
`chs_validate_type` and the column accessors. The C ABI contract
|
|
183
|
+
§Thread-safety declares those safe together **on distinct handles**, so
|
|
183
184
|
per-handle serialization is `Schema`'s own lock and not this one.
|
|
184
185
|
|
|
185
186
|
**The writer** is the pair that mutates per-library PROCESS state —
|
|
@@ -14,6 +14,7 @@ __all__ = [
|
|
|
14
14
|
"CODE_ARTIFACT_UNTRUSTED",
|
|
15
15
|
"CODE_SOURCE_UNREACHABLE",
|
|
16
16
|
"CODE_UNSUPPORTED",
|
|
17
|
+
"FETCH_COMMAND",
|
|
17
18
|
"ArtifactCorruptError",
|
|
18
19
|
"ArtifactError",
|
|
19
20
|
"ArtifactMissingError",
|
|
@@ -70,6 +71,11 @@ CODE_ARTIFACT_PINNED: Final = "CHTYPES_ARTIFACT_PINNED"
|
|
|
70
71
|
CODE_ARTIFACT_UNPUBLISHED: Final = "CHTYPES_ARTIFACT_UNPUBLISHED"
|
|
71
72
|
CODE_SOURCE_UNREACHABLE: Final = "CHTYPES_SOURCE_UNREACHABLE"
|
|
72
73
|
|
|
74
|
+
# This SDK's own fetch command, as the docs/guides/fetch.md §7 "Install it" message
|
|
75
|
+
# spells it. The VALUE is deliberately per-language — it is the command a
|
|
76
|
+
# caller of THIS SDK runs — go/ts/rust each carry their own equivalent.
|
|
77
|
+
FETCH_COMMAND: Final = "python -m chtypes fetch"
|
|
78
|
+
|
|
73
79
|
|
|
74
80
|
class ArtifactError(RegistryError):
|
|
75
81
|
"""An artifact is missing, could not be obtained, or failed verification.
|
|
@@ -123,7 +129,7 @@ class ArtifactMissingError(ArtifactError):
|
|
|
123
129
|
super().__init__(
|
|
124
130
|
f"chtypes: no artifact for ClickHouse {line} ({platform}). "
|
|
125
131
|
f"Looked in: {', '.join(self.looked_in)}.\n"
|
|
126
|
-
f"Install it:
|
|
132
|
+
f"Install it: {FETCH_COMMAND} {line}\n"
|
|
127
133
|
f"or set CHTYPES_AUTOFETCH=1 to fetch on first use."
|
|
128
134
|
)
|
|
129
135
|
|
|
@@ -79,6 +79,7 @@ __all__ = [
|
|
|
79
79
|
"fetch_lines",
|
|
80
80
|
"fetch_destination",
|
|
81
81
|
"installed_lines",
|
|
82
|
+
"parse_signature_file",
|
|
82
83
|
"read_lock",
|
|
83
84
|
"registry_search_path",
|
|
84
85
|
"trusted_keys",
|
|
@@ -258,7 +259,7 @@ def key_id(raw_public_key: bytes) -> str:
|
|
|
258
259
|
return hashlib.sha256(raw_public_key).hexdigest()[:16]
|
|
259
260
|
|
|
260
261
|
|
|
261
|
-
def
|
|
262
|
+
def parse_signature_file(sig: bytes) -> tuple[str, bytes]:
|
|
262
263
|
"""``SHA256SUMS.sig`` -> (the comment line, the 64 signature bytes)."""
|
|
263
264
|
comment = ""
|
|
264
265
|
body: list[str] = []
|
|
@@ -285,7 +286,7 @@ def verify_sums_signature(sums: bytes, sig: bytes, keys: Sequence[bytes], source
|
|
|
285
286
|
"""Step 0 of the chain: the signature over the EXACT bytes of ``SHA256SUMS``
|
|
286
287
|
with one of the trusted keys. Returns the id of the key that verified;
|
|
287
288
|
raises `ArtifactUntrustedError` otherwise."""
|
|
288
|
-
comment, signature =
|
|
289
|
+
comment, signature = parse_signature_file(sig)
|
|
289
290
|
for raw in keys:
|
|
290
291
|
if _ed25519_verify(raw, sums, signature):
|
|
291
292
|
return key_id(raw)
|
|
@@ -370,8 +370,9 @@ class Schema:
|
|
|
370
370
|
`BatchResult.rows` as `Outcome.SKIPPED`, carrying the server's own
|
|
371
371
|
caught error verbatim (2026-08-27).
|
|
372
372
|
|
|
373
|
-
**The revision-3 export channel** (the C ABI contract §Rows
|
|
374
|
-
|
|
373
|
+
**The revision-3 export channel** (the C ABI contract §Rows, for
|
|
374
|
+
which `include/chtypes.h` is the public authority) — still ONE
|
|
375
|
+
`chs_rows` call, never a
|
|
375
376
|
second, never re-parsing:
|
|
376
377
|
|
|
377
378
|
`export` is None (no bytes — today's path, byte-identical documents)
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
not run at all — it skips, loudly, by name. The fetch, CLI and pure-Python
|
|
3
3
|
tests need none and always run; that is what this repository's CI proves on
|
|
4
4
|
hosted runners, and the artifact-backed proof is the core repository's
|
|
5
|
-
|
|
5
|
+
server-truth suites against this same tree.
|
|
6
6
|
|
|
7
7
|
The registry comes from the search path (docs/guides/fetch.md §1): `$CHTYPES_REGISTRY`,
|
|
8
8
|
else the per-user artifact cache for this host (`chtypes.default_registry_dir()`:
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
"""The ABI-revision handshake, run against a wrong-revision fixture (#36).
|
|
2
|
+
|
|
3
|
+
`docs/reference/artifact.md` step 5 is normative: an artifact whose
|
|
4
|
+
`chs_abi_revision()` answers a value that is neither 0 nor the revision this
|
|
5
|
+
binding was written against MUST be refused, **naming both numbers**
|
|
6
|
+
(`chtypes/_native.py`, `NativeLibrary.__init__`: "refusing to call through
|
|
7
|
+
mismatched declarations"). This suite is the first thing in this repository
|
|
8
|
+
that actually RUNS that rule instead of assuming it.
|
|
9
|
+
|
|
10
|
+
The fixture is a pair of stub shared libraries generated from `include/chtypes.h`
|
|
11
|
+
itself, by the served ABI-revision fixture generator (`abi-revision/gen.py`):
|
|
12
|
+
one registry whose artifact answers `abi_revision + 1` (`wrong-revision/`), one
|
|
13
|
+
answering `abi_revision`
|
|
14
|
+
exactly (`at-revision/`), both on ClickHouse minor line "0.0". **Both halves
|
|
15
|
+
matter** — without the `at-revision` control, a binding whose own
|
|
16
|
+
`ABI_REVISION` had drifted would refuse everything and pass the refusal case
|
|
17
|
+
for entirely the wrong reason.
|
|
18
|
+
|
|
19
|
+
Every expected number comes from `fixture.json` — never from
|
|
20
|
+
`chtypes.ABI_REVISION` — because a case that read the constant under test
|
|
21
|
+
could not catch that constant being wrong.
|
|
22
|
+
|
|
23
|
+
`isolated_search_path` (`tests/conftest.py`) is used to keep the per-user
|
|
24
|
+
artifact cache and any ambient `$CHTYPES_REGISTRY`/system roots out of the
|
|
25
|
+
search path: `Registry(root)` puts `root` first regardless, but this removes
|
|
26
|
+
any chance that the fixture's odd "0.0" minor line is ever resolved from
|
|
27
|
+
somewhere other than the fixture itself.
|
|
28
|
+
|
|
29
|
+
Reopening a library after `close()` in the same process is measured to
|
|
30
|
+
segfault (`docs/reference/bindings.md` §Teardown), so nothing here closes a
|
|
31
|
+
loaded library: the refusal case never gets far enough to open one, the
|
|
32
|
+
control case leaves its `Library` for process teardown (`chs_shutdown` runs
|
|
33
|
+
via `atexit`), and neither path is ever loaded a second time.
|
|
34
|
+
"""
|
|
35
|
+
|
|
36
|
+
from __future__ import annotations
|
|
37
|
+
|
|
38
|
+
import json
|
|
39
|
+
import os
|
|
40
|
+
import re
|
|
41
|
+
from pathlib import Path
|
|
42
|
+
from typing import Final
|
|
43
|
+
|
|
44
|
+
import pytest
|
|
45
|
+
|
|
46
|
+
import chtypes
|
|
47
|
+
|
|
48
|
+
#: Where the fixture root lives. Built by the served abi-revision generator
|
|
49
|
+
#: (`abi-revision/gen.py build`, core repo); this suite never builds one itself.
|
|
50
|
+
ENV_ABI_FIXTURES: Final = "CHTYPES_ABI_FIXTURES"
|
|
51
|
+
|
|
52
|
+
#: The one ClickHouse minor line every fixture artifact answers to.
|
|
53
|
+
FIXTURE_MINOR: Final = "0.0"
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
@pytest.fixture(scope="session")
|
|
57
|
+
def abi_fixture() -> dict:
|
|
58
|
+
"""Parse and validate `fixture.json`. Missing `$CHTYPES_ABI_FIXTURES` is a
|
|
59
|
+
SKIP (no fallback build); a fixture that exists but is malformed is a
|
|
60
|
+
FAILURE naming the path — a bad fixture must never read as "nothing to
|
|
61
|
+
test here"."""
|
|
62
|
+
env = os.environ.get(ENV_ABI_FIXTURES)
|
|
63
|
+
if not env:
|
|
64
|
+
pytest.skip(
|
|
65
|
+
f"no ABI revision fixture: ${ENV_ABI_FIXTURES} is unset (build one with "
|
|
66
|
+
"abi-revision/gen.py build --out DIR --header include/chtypes.h)"
|
|
67
|
+
)
|
|
68
|
+
root = Path(env)
|
|
69
|
+
fixture_path = root / "fixture.json"
|
|
70
|
+
try:
|
|
71
|
+
raw = fixture_path.read_text()
|
|
72
|
+
except OSError as exc:
|
|
73
|
+
pytest.fail(f"{fixture_path}: cannot read the ABI revision fixture: {exc}")
|
|
74
|
+
try:
|
|
75
|
+
doc = json.loads(raw)
|
|
76
|
+
except json.JSONDecodeError as exc:
|
|
77
|
+
pytest.fail(f"{fixture_path}: not valid JSON: {exc}")
|
|
78
|
+
abi_revision = doc.get("abi_revision")
|
|
79
|
+
valid = isinstance(abi_revision, int) and not isinstance(abi_revision, bool)
|
|
80
|
+
if not valid or abi_revision <= 0:
|
|
81
|
+
pytest.fail(
|
|
82
|
+
f"{fixture_path}: abi_revision must be a positive integer, got {abi_revision!r}"
|
|
83
|
+
)
|
|
84
|
+
mismatch_revision = doc.get("mismatch_revision")
|
|
85
|
+
if mismatch_revision != abi_revision + 1:
|
|
86
|
+
pytest.fail(
|
|
87
|
+
f"{fixture_path}: mismatch_revision is {mismatch_revision!r}, expected "
|
|
88
|
+
f"abi_revision ({abi_revision}) + 1"
|
|
89
|
+
)
|
|
90
|
+
doc["root"] = str(root)
|
|
91
|
+
return doc
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
def _names_number(message: str, n: int) -> bool:
|
|
95
|
+
""" "The message says N", not "the message contains the digits of N": a
|
|
96
|
+
refusal naming revision 4 must not be satisfied by the 4 in a path like
|
|
97
|
+
`.../24.8/`, and one naming 5 must not be satisfied by 15."""
|
|
98
|
+
return re.search(rf"(^|[^0-9]){n}([^0-9]|$)", message) is not None
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
def _without_fixture_path(message: str, root: Path) -> str:
|
|
102
|
+
"""Strip every occurrence of the fixture root from a message before
|
|
103
|
+
`_names_number` checks it. The refusal message embeds the artifact's
|
|
104
|
+
PATH, and a path segment can itself hold a standalone digit (a scratchpad
|
|
105
|
+
session id such as `4d9fd784-...`), which would let the path alone
|
|
106
|
+
satisfy "the message names 4" for reasons that have nothing to do with
|
|
107
|
+
the refusal text itself. Both the given root and its resolved form are
|
|
108
|
+
stripped (macOS's `/tmp` vs `/private/tmp` differ), longest first so one
|
|
109
|
+
is never a partial match of the other.
|
|
110
|
+
"""
|
|
111
|
+
candidates = sorted({str(root), os.path.realpath(root)}, key=len, reverse=True)
|
|
112
|
+
for candidate in candidates:
|
|
113
|
+
message = message.replace(candidate, "<fixture>")
|
|
114
|
+
return message
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
def test_wrong_revision_is_refused(
|
|
118
|
+
abi_fixture: dict,
|
|
119
|
+
isolated_search_path: Path,
|
|
120
|
+
) -> None:
|
|
121
|
+
root = Path(abi_fixture["root"], "wrong-revision")
|
|
122
|
+
registry = chtypes.Registry(root)
|
|
123
|
+
with pytest.raises(chtypes.RegistryError) as excinfo:
|
|
124
|
+
registry.for_version(FIXTURE_MINOR)
|
|
125
|
+
message = str(excinfo.value)
|
|
126
|
+
stripped = _without_fixture_path(message, root)
|
|
127
|
+
for n in (abi_fixture["mismatch_revision"], abi_fixture["abi_revision"]):
|
|
128
|
+
assert _names_number(stripped, n), f"the refusal does not name {n}: {message!r}"
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
def test_matching_revision_loads(
|
|
132
|
+
abi_fixture: dict,
|
|
133
|
+
isolated_search_path: Path,
|
|
134
|
+
) -> None:
|
|
135
|
+
root = Path(abi_fixture["root"], "at-revision")
|
|
136
|
+
registry = chtypes.Registry(root)
|
|
137
|
+
library = registry.for_version(FIXTURE_MINOR)
|
|
138
|
+
assert library.abi_revision == abi_fixture["abi_revision"]
|
|
139
|
+
assert library.version == abi_fixture["clickhouse_version"]
|
|
@@ -23,7 +23,10 @@ PLATFORM = "linux-arm64"
|
|
|
23
23
|
|
|
24
24
|
needs_fixtures = pytest.mark.skipif(
|
|
25
25
|
not EXPECTED_FILE.is_file(),
|
|
26
|
-
reason=
|
|
26
|
+
reason=(
|
|
27
|
+
f"no fetch fixtures at {FIXTURES} "
|
|
28
|
+
"(generated in the core repository, docs/guides/fetch.md §9)"
|
|
29
|
+
),
|
|
27
30
|
)
|
|
28
31
|
|
|
29
32
|
|
|
@@ -504,7 +504,7 @@ def test_trusted_keys_policy(monkeypatch: pytest.MonkeyPatch) -> None:
|
|
|
504
504
|
|
|
505
505
|
def test_signature_file_shape() -> None:
|
|
506
506
|
sig = (FIXTURES / "signed" / "SHA256SUMS.sig").read_bytes()
|
|
507
|
-
comment, raw = fetch_module.
|
|
507
|
+
comment, raw = fetch_module.parse_signature_file(sig)
|
|
508
508
|
assert comment == f"chtypes artifacts, ed25519 key {_expected()['key_id']}"
|
|
509
509
|
assert len(raw) == 64
|
|
510
510
|
sums = (FIXTURES / "signed" / "SHA256SUMS").read_bytes()
|
|
@@ -517,9 +517,9 @@ def test_signature_file_shape() -> None:
|
|
|
517
517
|
sums + b"\n", sig, fetch_module.trusted_keys(_test_keys()), "x"
|
|
518
518
|
)
|
|
519
519
|
with pytest.raises(chtypes.ArtifactUntrustedError, match="base64"):
|
|
520
|
-
fetch_module.
|
|
520
|
+
fetch_module.parse_signature_file(b"untrusted comment: x\n!!!\n")
|
|
521
521
|
with pytest.raises(chtypes.ArtifactUntrustedError, match="64"):
|
|
522
|
-
fetch_module.
|
|
522
|
+
fetch_module.parse_signature_file(b"untrusted comment: x\nAAAA\n")
|
|
523
523
|
|
|
524
524
|
|
|
525
525
|
# ------------------------------------------------------------- tar safety
|
|
@@ -127,7 +127,9 @@ def test_every_capability_is_fully_declared() -> None:
|
|
|
127
127
|
problems.append(f"{cid}: duplicate id")
|
|
128
128
|
seen.add(cid)
|
|
129
129
|
if not cap.get("what", "").strip():
|
|
130
|
-
problems.append(
|
|
130
|
+
problems.append(
|
|
131
|
+
f"{cid}: no `what` — a capability with no description is a name, not a contract"
|
|
132
|
+
)
|
|
131
133
|
if cap.get("group") not in MANIFEST_DOC["groups"]:
|
|
132
134
|
problems.append(f"{cid}: unknown group {cap.get('group')!r}")
|
|
133
135
|
|
|
@@ -135,7 +137,9 @@ def test_every_capability_is_fully_declared() -> None:
|
|
|
135
137
|
for lang in MANIFEST_DOC["languages"]:
|
|
136
138
|
col = column(cap, lang)
|
|
137
139
|
if not col:
|
|
138
|
-
problems.append(
|
|
140
|
+
problems.append(
|
|
141
|
+
f"{cid}: no {lang} column at all — declare a spelling or an absence"
|
|
142
|
+
)
|
|
139
143
|
continue
|
|
140
144
|
if "absent" in col:
|
|
141
145
|
absent += 1
|
|
@@ -148,7 +152,9 @@ def test_every_capability_is_fully_declared() -> None:
|
|
|
148
152
|
problems.append(f"{cid}: {lang} column has neither `symbol` nor `absent`")
|
|
149
153
|
if absent == len(MANIFEST_DOC["languages"]):
|
|
150
154
|
problems.append(f"{cid}: absent in every binding — that is a note, not a contract")
|
|
151
|
-
assert not problems, "the parity manifest is not internally consistent:\n " + "\n ".join(
|
|
155
|
+
assert not problems, "the parity manifest is not internally consistent:\n " + "\n ".join(
|
|
156
|
+
problems
|
|
157
|
+
)
|
|
152
158
|
|
|
153
159
|
|
|
154
160
|
def test_the_go_copy_of_the_manifest_is_byte_identical() -> None:
|
|
@@ -162,7 +168,8 @@ def test_the_go_copy_of_the_manifest_is_byte_identical() -> None:
|
|
|
162
168
|
if GO_COPY.read_bytes() != MANIFEST.read_bytes():
|
|
163
169
|
pytest.fail(
|
|
164
170
|
f"{GO_COPY} has drifted from {MANIFEST}. The manifest is one file; this is its "
|
|
165
|
-
f"embedded copy. Re-sync it:\n\n
|
|
171
|
+
f"embedded copy. Re-sync it:\n\n "
|
|
172
|
+
f"cp tests/parity/manifest.json go/chtypes/testdata/parity.json\n"
|
|
166
173
|
)
|
|
167
174
|
|
|
168
175
|
|
|
@@ -285,7 +292,8 @@ def test_the_cli_offers_every_contract_subcommand() -> None:
|
|
|
285
292
|
"""`chtypes fetch|verify|list|where` is a surface too, and it drifted once."""
|
|
286
293
|
source = (SRC / "__main__.py").read_text(encoding="utf-8")
|
|
287
294
|
missing = [
|
|
288
|
-
f"{cap['id']}: the python CLI has no `{cap['value']}` subcommand,
|
|
295
|
+
f"{cap['id']}: the python CLI has no `{cap['value']}` subcommand, "
|
|
296
|
+
f"which {also_in(cap)} offer"
|
|
289
297
|
for cap in CAPABILITIES
|
|
290
298
|
if cap["kind"] == "cli" and spelling(cap, LANG) and f'"{cap["value"]}"' not in source
|
|
291
299
|
]
|
|
@@ -334,8 +342,8 @@ def test_no_public_name_escapes_the_contract() -> None:
|
|
|
334
342
|
assert not undeclared, (
|
|
335
343
|
f"python exports {len(undeclared)} public name(s) the parity contract has never heard "
|
|
336
344
|
f"of:\n " + "\n ".join(undeclared) + "\n\nEither give each one a capability in "
|
|
337
|
-
|
|
338
|
-
|
|
345
|
+
"tests/parity/manifest.json (if the other bindings should have it too) or list it under "
|
|
346
|
+
"`unlisted.python` with a reason (if it is deliberately ours alone)."
|
|
339
347
|
)
|
|
340
348
|
|
|
341
349
|
|
|
@@ -397,8 +405,9 @@ def test_the_manifest_and_the_spec_table_agree() -> None:
|
|
|
397
405
|
# assertion is about which COLUMNS exist, not about their alignment.
|
|
398
406
|
squashed = re.sub(r"[ \t]+", " ", full)
|
|
399
407
|
assert "| Concept | Go | Python | TypeScript | Rust |" in squashed, (
|
|
400
|
-
"docs/reference/bindings.md's object-model table has lost a language
|
|
401
|
-
"SDK over the same C ABI and the
|
|
408
|
+
"docs/reference/bindings.md's object-model table has lost a language "
|
|
409
|
+
"column. Every language is a peer SDK over the same C ABI and the "
|
|
410
|
+
"document says so in its own second paragraph; a table with "
|
|
402
411
|
"three of the four columns is how Rust came to be described only in a Notes cell."
|
|
403
412
|
)
|
|
404
413
|
|
|
@@ -420,10 +429,11 @@ def test_the_manifest_and_the_spec_table_agree() -> None:
|
|
|
420
429
|
undocumented.append(f"{cap['id']}: {lang} `{spelled}` — {cap['what']}")
|
|
421
430
|
assert checked > 0, "no object-model spelling was compared against docs/reference/bindings.md"
|
|
422
431
|
assert not undocumented, (
|
|
423
|
-
f"the parity manifest requires {len(undocumented)} spelling(s) that
|
|
432
|
+
f"the parity manifest requires {len(undocumented)} spelling(s) that "
|
|
433
|
+
f"docs/reference/bindings.md never "
|
|
424
434
|
f"names:\n " + "\n ".join(undocumented) + "\n\nEither document them there, or — if the "
|
|
425
|
-
|
|
426
|
-
|
|
435
|
+
"capability should not be required at all — remove it from the manifest. The two are one "
|
|
436
|
+
"contract stated twice."
|
|
427
437
|
)
|
|
428
438
|
|
|
429
439
|
|
|
@@ -178,6 +178,29 @@ def test_manifest_ignores_unknown_fields(tmp_path: Path) -> None:
|
|
|
178
178
|
assert manifest.library == "libchtypes.so"
|
|
179
179
|
|
|
180
180
|
|
|
181
|
+
def test_verify_library_accepts_an_uppercase_digest(tmp_path: Path) -> None:
|
|
182
|
+
"""A hex digest is the same digest in either case.
|
|
183
|
+
|
|
184
|
+
Go lower-cases (`strings.ToLower`) and Rust lower-cases
|
|
185
|
+
(`.to_ascii_lowercase()`) before comparing; Python and TypeScript once
|
|
186
|
+
compared raw, so an uppercase `library_sha256` was accepted by two
|
|
187
|
+
bindings and refused by two. docs/reference/artifact.md asserts all four
|
|
188
|
+
behave alike, so this pins it.
|
|
189
|
+
"""
|
|
190
|
+
payload = b"not really a library"
|
|
191
|
+
(tmp_path / "fake.so").write_bytes(payload)
|
|
192
|
+
(tmp_path / "manifest.json").write_text(
|
|
193
|
+
json.dumps(
|
|
194
|
+
{
|
|
195
|
+
"library": "fake.so",
|
|
196
|
+
"library_bytes": len(payload),
|
|
197
|
+
"library_sha256": hashlib.sha256(payload).hexdigest().upper(),
|
|
198
|
+
}
|
|
199
|
+
)
|
|
200
|
+
)
|
|
201
|
+
chtypes.verify_library(tmp_path) # must NOT raise
|
|
202
|
+
|
|
203
|
+
|
|
181
204
|
def test_verify_library_checks_the_bytes(tmp_path: Path) -> None:
|
|
182
205
|
payload = b"not really a library"
|
|
183
206
|
(tmp_path / "fake.so").write_bytes(payload)
|
chtypes-0.2.1/uv.lock
ADDED
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
version = 1
|
|
2
|
+
revision = 3
|
|
3
|
+
requires-python = ">=3.11"
|
|
4
|
+
|
|
5
|
+
[[package]]
|
|
6
|
+
name = "chtypes"
|
|
7
|
+
version = "0.2.1"
|
|
8
|
+
source = { editable = "." }
|
|
9
|
+
|
|
10
|
+
[package.dev-dependencies]
|
|
11
|
+
dev = [
|
|
12
|
+
{ name = "pytest" },
|
|
13
|
+
{ name = "ruff" },
|
|
14
|
+
]
|
|
15
|
+
|
|
16
|
+
[package.metadata]
|
|
17
|
+
|
|
18
|
+
[package.metadata.requires-dev]
|
|
19
|
+
dev = [
|
|
20
|
+
{ name = "pytest", specifier = ">=8.0" },
|
|
21
|
+
{ name = "ruff", specifier = ">=0.14" },
|
|
22
|
+
]
|
|
23
|
+
|
|
24
|
+
[[package]]
|
|
25
|
+
name = "colorama"
|
|
26
|
+
version = "0.4.6"
|
|
27
|
+
source = { registry = "https://pypi.org/simple" }
|
|
28
|
+
sdist = { url = "https://files.pythonhosted.org/packages/d8/53/6f443c9a4a8358a93a6792e2acffb9d9d5cb0a5cfd8802644b7b1c9a02e4/colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44", size = 27697, upload-time = "2022-10-25T02:36:22.414Z" }
|
|
29
|
+
wheels = [
|
|
30
|
+
{ url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335, upload-time = "2022-10-25T02:36:20.889Z" },
|
|
31
|
+
]
|
|
32
|
+
|
|
33
|
+
[[package]]
|
|
34
|
+
name = "iniconfig"
|
|
35
|
+
version = "2.3.0"
|
|
36
|
+
source = { registry = "https://pypi.org/simple" }
|
|
37
|
+
sdist = { url = "https://files.pythonhosted.org/packages/72/34/14ca021ce8e5dfedc35312d08ba8bf51fdd999c576889fc2c24cb97f4f10/iniconfig-2.3.0.tar.gz", hash = "sha256:c76315c77db068650d49c5b56314774a7804df16fee4402c1f19d6d15d8c4730", size = 20503, upload-time = "2025-10-18T21:55:43.219Z" }
|
|
38
|
+
wheels = [
|
|
39
|
+
{ url = "https://files.pythonhosted.org/packages/cb/b1/3846dd7f199d53cb17f49cba7e651e9ce294d8497c8c150530ed11865bb8/iniconfig-2.3.0-py3-none-any.whl", hash = "sha256:f631c04d2c48c52b84d0d0549c99ff3859c98df65b3101406327ecc7d53fbf12", size = 7484, upload-time = "2025-10-18T21:55:41.639Z" },
|
|
40
|
+
]
|
|
41
|
+
|
|
42
|
+
[[package]]
|
|
43
|
+
name = "packaging"
|
|
44
|
+
version = "26.3"
|
|
45
|
+
source = { registry = "https://pypi.org/simple" }
|
|
46
|
+
sdist = { url = "https://files.pythonhosted.org/packages/7d/fa/3944b40b07da9ce895c0e6303a5ab7d53da063554f534556b134a54d6093/packaging-26.3.tar.gz", hash = "sha256:94edc256424af38762eb31306eed28beb9f0efc50a8837492c9d6fd6004aed79", size = 313412, upload-time = "2026-08-04T18:15:28.737Z" }
|
|
47
|
+
wheels = [
|
|
48
|
+
{ url = "https://files.pythonhosted.org/packages/63/34/ba1c580383c9eada3711951fef0795c80b829a078d72188184bcab9dd527/packaging-26.3-py3-none-any.whl", hash = "sha256:d7193f7c8e4e93f444fde0262bf90af30e16fa0ad0ad44cb553c87339b23cd1c", size = 129956, upload-time = "2026-08-04T18:15:27.159Z" },
|
|
49
|
+
]
|
|
50
|
+
|
|
51
|
+
[[package]]
|
|
52
|
+
name = "pluggy"
|
|
53
|
+
version = "1.6.0"
|
|
54
|
+
source = { registry = "https://pypi.org/simple" }
|
|
55
|
+
sdist = { url = "https://files.pythonhosted.org/packages/f9/e2/3e91f31a7d2b083fe6ef3fa267035b518369d9511ffab804f839851d2779/pluggy-1.6.0.tar.gz", hash = "sha256:7dcc130b76258d33b90f61b658791dede3486c3e6bfb003ee5c9bfb396dd22f3", size = 69412, upload-time = "2025-05-15T12:30:07.975Z" }
|
|
56
|
+
wheels = [
|
|
57
|
+
{ url = "https://files.pythonhosted.org/packages/54/20/4d324d65cc6d9205fabedc306948156824eb9f0ee1633355a8f7ec5c66bf/pluggy-1.6.0-py3-none-any.whl", hash = "sha256:e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746", size = 20538, upload-time = "2025-05-15T12:30:06.134Z" },
|
|
58
|
+
]
|
|
59
|
+
|
|
60
|
+
[[package]]
|
|
61
|
+
name = "pygments"
|
|
62
|
+
version = "2.21.0"
|
|
63
|
+
source = { registry = "https://pypi.org/simple" }
|
|
64
|
+
sdist = { url = "https://files.pythonhosted.org/packages/49/2e/ced460408999b33da6b31b0021b0f37d329e202d4169aeb164493778f25b/pygments-2.21.0.tar.gz", hash = "sha256:610ca751c9bc2492b38eb9a38a7fbc93edbbb2d7182edaf34e66ae493dee5c8c", size = 5005329, upload-time = "2026-08-17T08:02:48.824Z" }
|
|
65
|
+
wheels = [
|
|
66
|
+
{ url = "https://files.pythonhosted.org/packages/71/46/17f022dd3e953bf20a04a028a21ec746d942f8d2af30fa0f124fa0e6a684/pygments-2.21.0-py3-none-any.whl", hash = "sha256:2363c69b61c4a97c838da3b130dcd6468f4848992b21a82f2a63ec34377137d9", size = 1250147, upload-time = "2026-08-17T08:02:44.912Z" },
|
|
67
|
+
]
|
|
68
|
+
|
|
69
|
+
[[package]]
|
|
70
|
+
name = "pytest"
|
|
71
|
+
version = "9.1.1"
|
|
72
|
+
source = { registry = "https://pypi.org/simple" }
|
|
73
|
+
dependencies = [
|
|
74
|
+
{ name = "colorama", marker = "sys_platform == 'win32'" },
|
|
75
|
+
{ name = "iniconfig" },
|
|
76
|
+
{ name = "packaging" },
|
|
77
|
+
{ name = "pluggy" },
|
|
78
|
+
{ name = "pygments" },
|
|
79
|
+
]
|
|
80
|
+
sdist = { url = "https://files.pythonhosted.org/packages/e4/47/b9efed96c114afcfa3c9d3fe98a76a1d14c74a9e266d397cf6eb64be5e01/pytest-9.1.1.tar.gz", hash = "sha256:1088fbde8f2b49d95a549a195707afa7a76a3ce9bcadc26b6d71f0ffda5fe313", size = 1636369, upload-time = "2026-06-19T10:58:32.857Z" }
|
|
81
|
+
wheels = [
|
|
82
|
+
{ url = "https://files.pythonhosted.org/packages/24/25/1de2678b631f5a49215c6c96fff41ba892b0a34df68d6d80292b1b48aa7f/pytest-9.1.1-py3-none-any.whl", hash = "sha256:37a86b45efb9a47a61a36449063e8e18d0cab3161329fc099eb21783169c4f0c", size = 386536, upload-time = "2026-06-19T10:58:31.347Z" },
|
|
83
|
+
]
|
|
84
|
+
|
|
85
|
+
[[package]]
|
|
86
|
+
name = "ruff"
|
|
87
|
+
version = "0.16.7"
|
|
88
|
+
source = { registry = "https://pypi.org/simple" }
|
|
89
|
+
sdist = { url = "https://files.pythonhosted.org/packages/82/bb/5a449b9162e49b139d72f61672bd3ac1d790221f796d3304e2241fff4c58/ruff-0.16.7.tar.gz", hash = "sha256:5f71d004ac1263b22fa39462ac5ae618a4b77d58981af2cc79bf79a29c12b1a6", size = 4924184, upload-time = "2026-09-10T18:04:06.336Z" }
|
|
90
|
+
wheels = [
|
|
91
|
+
{ url = "https://files.pythonhosted.org/packages/e3/b2/c80aeeb7f9e469c0d63a85d2f1ab6e1ebfbe10ea7a8d2438b7e09e3ff09e/ruff-0.16.7-py3-none-linux_armv6l.whl", hash = "sha256:727307773e7c7f9181d3ed3a2484186e56c1fa1874255911c74585eb2c7c19f9", size = 10048917, upload-time = "2026-09-10T18:03:30.28Z" },
|
|
92
|
+
{ url = "https://files.pythonhosted.org/packages/7b/96/20bb7bcae008004df52afcb7ac83432d4a467f2c17b672fe46d26be231c5/ruff-0.16.7-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:9d61c258deabf58f34c67bd4bb4d939c7f2e6b5f0e59c1cdd1cf771b11cde929", size = 10242929, upload-time = "2026-09-10T18:03:32.706Z" },
|
|
93
|
+
{ url = "https://files.pythonhosted.org/packages/90/b2/f184b0d5abec02db69cfd7e49b688ae0237554528ca777136c613bf36bee/ruff-0.16.7-py3-none-macosx_11_0_arm64.whl", hash = "sha256:7ab81118df8945e0193d0240712aa4496573595b75185c3636ed825592a0f728", size = 9847245, upload-time = "2026-09-10T18:03:34.509Z" },
|
|
94
|
+
{ url = "https://files.pythonhosted.org/packages/eb/2d/db1633a641866ed801e34cc6b60ef236c5e16f9b2124ab1d49cc24a5fe4f/ruff-0.16.7-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4c196c968874fc8019da8e7163de7a1a370f111e2309b4b7dfea0fce950198d0", size = 9961780, upload-time = "2026-09-10T18:03:36.618Z" },
|
|
95
|
+
{ url = "https://files.pythonhosted.org/packages/4d/98/edea21e1a3e38dbbc3bf6bb068b863b3b06184cf8533a4c7dbbe208a89d5/ruff-0.16.7-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ac8c3bd0a7e10ad31e6ce51e7a99f3cb772e69aecdd6b9ea7e99b362f62a62c0", size = 9866337, upload-time = "2026-09-10T18:03:38.805Z" },
|
|
96
|
+
{ url = "https://files.pythonhosted.org/packages/0b/11/a15e60d4c87b214646f116ca9d204475bf993ee1047459bc9a360fd4d6d1/ruff-0.16.7-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:398d3988edde000b5c75dc1b3f584708da9bc990de069c18909142580fec1af9", size = 10562512, upload-time = "2026-09-10T18:03:40.71Z" },
|
|
97
|
+
{ url = "https://files.pythonhosted.org/packages/29/42/eaff4c9b6d0c7cdf56df313a17e89ae854f5bbc0b0c8f9cce19be0ab7a8f/ruff-0.16.7-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ce05b62b770a8217c4646a9c4139fca00efe8fe5d71f87df2b243ff20d4584d1", size = 11302938, upload-time = "2026-09-10T18:03:42.607Z" },
|
|
98
|
+
{ url = "https://files.pythonhosted.org/packages/5d/43/c75aa59a4ec181fe2ec06cab30e198c1c6d107229a9f008ae3a7c16cabd8/ruff-0.16.7-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:af1b576fddb9d9ef2ececfb5fadcd6a624b25070ed85e3cfcfe449fc3ff6a7b9", size = 10840857, upload-time = "2026-09-10T18:03:44.604Z" },
|
|
99
|
+
{ url = "https://files.pythonhosted.org/packages/21/33/81f3da371942ea031105ba679d8d6e28ec1660ccd690a45f42d381161356/ruff-0.16.7-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9ce7f8f22df67c93ed96c717f9128eadb797144ac2bad475cf536f31d6100c55", size = 10370001, upload-time = "2026-09-10T18:03:46.706Z" },
|
|
100
|
+
{ url = "https://files.pythonhosted.org/packages/fa/0b/6345fb4dbf6dd0ed1cfe5d18391dc9c3f59cc81622a7b0a65b84b3e730ba/ruff-0.16.7-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:06d0e93d04f392996435ebd600c153f65b47d73fbec2415aa99c5ee5756b3a5f", size = 10548735, upload-time = "2026-09-10T18:03:48.658Z" },
|
|
101
|
+
{ url = "https://files.pythonhosted.org/packages/3f/4d/c5576adf511f92a328e5569dda190ecdd430da51f1a649f3a4a2fd73e21e/ruff-0.16.7-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:142151a5e7b93c1b11111337142f89dd2fbfee92161225c99a97222f22e32656", size = 10108496, upload-time = "2026-09-10T18:03:50.563Z" },
|
|
102
|
+
{ url = "https://files.pythonhosted.org/packages/ff/8c/667d83c16199a17a56adc6b0bd4c3beb5b767a2babcd16a56f76f9be7fd6/ruff-0.16.7-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:e6651f97a342d8b35d54d8991544ca22169b86dc54111cb604666940c431b750", size = 9860136, upload-time = "2026-09-10T18:03:52.621Z" },
|
|
103
|
+
{ url = "https://files.pythonhosted.org/packages/99/75/78d401106731999a1dd20cc5a6961e37e1eb9397a3b589f73f3a5ce146a3/ruff-0.16.7-py3-none-musllinux_1_2_i686.whl", hash = "sha256:ef140c6eb935fa9a84c9c607dfb2cb1b85843c192e79265b0c54f35f557ea8e5", size = 10286290, upload-time = "2026-09-10T18:03:55.207Z" },
|
|
104
|
+
{ url = "https://files.pythonhosted.org/packages/68/49/56f9c3a8b755df93a0ad318b2147bf4ef5dae9a7e5ec61c460109c67957f/ruff-0.16.7-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:53e39506a730fadeee0d998ed5946f30671f0db240c6c7c73bdabbe33604bb6f", size = 10745048, upload-time = "2026-09-10T18:03:57.299Z" },
|
|
105
|
+
{ url = "https://files.pythonhosted.org/packages/5f/ea/7f9b938a63ece4bec677ad7f9f7fa02df3383db1949ed93a382441c09a87/ruff-0.16.7-py3-none-win32.whl", hash = "sha256:2ea3470fcebcbc5df2fb0c6f3b90333fa9084c534e0111c038fa4a6ab9f1c4b7", size = 10059082, upload-time = "2026-09-10T18:03:59.632Z" },
|
|
106
|
+
{ url = "https://files.pythonhosted.org/packages/39/11/480a6973a927aa653e1cead6a6416008640e03a99d05b34c0434b8c6c366/ruff-0.16.7-py3-none-win_amd64.whl", hash = "sha256:7ac26aca826e9e21d0f1cb25b54ac660760a9fdd094d3e4df9848232be98cfc6", size = 10593368, upload-time = "2026-09-10T18:04:01.999Z" },
|
|
107
|
+
{ url = "https://files.pythonhosted.org/packages/8b/4b/51327018d056f0dad2c2238f26d1fb0f53707a9d91b75dea6d1b3039f136/ruff-0.16.7-py3-none-win_arm64.whl", hash = "sha256:aab7f39e2c9df6c596216070f98eef1207b94f8516cca20c808826974971855b", size = 10412401, upload-time = "2026-09-10T18:04:04.098Z" },
|
|
108
|
+
]
|
chtypes-0.1.2/CHANGELOG.md
DELETED
|
@@ -1,35 +0,0 @@
|
|
|
1
|
-
# Changelog — `chtypes`
|
|
2
|
-
|
|
3
|
-
All notable changes to the python binding. The format is [Keep a Changelog](https://keepachangelog.com/en/1.1.0/); this package follows [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
4
|
-
|
|
5
|
-
The four bindings in this repository are released together and give one answer, so an entry here has a counterpart in the other three.
|
|
6
|
-
|
|
7
|
-
## [0.1.2] — 2026-09-11
|
|
8
|
-
|
|
9
|
-
### Changed — BREAKING
|
|
10
|
-
|
|
11
|
-
- **The transform reason for a materialized DEFAULT is now `default_materialized`** — previously the same word spelled with an `s` — and the exported constant naming it is now `Reason.DEFAULT_MATERIALIZED`. ClickHouse's own keyword is `MATERIALIZED`; its parser rejects the `s` spelling outright with a syntax error, so the reason naming that concept now matches the system it describes. Code comparing against the old string or the old constant name must be updated.
|
|
12
|
-
- This is an SDK-only change and **the ABI is untouched: it remains revision 4, and no artifact needs relinking.** The reason is derived in the binding, not received from the artifact — the library emits `default_substituted`, which each binding translates. That wire value is unchanged.
|
|
13
|
-
|
|
14
|
-
### Changed
|
|
15
|
-
|
|
16
|
-
- **The per-language README is now an orientation page, not a manual.** It carries install, a quickstart that runs, the three outcomes and the error model, and then points at `docs/` for the rest — roughly 450 lines of reference prose moved to `docs/guides/` and `docs/reference/` rather than being repeated four times and drifting four ways.
|
|
17
|
-
- **The published packages no longer cite a repository you cannot open.** 148 source comments — in every binding and in `include/chtypes.h`, all of which ship inside the crate, the sdist and the module — attributed the contract they implement to a private repository's specification. They now cite **the C ABI contract**, with `include/chtypes.h` as the public authority for it. Alongside those, 14 dead paths into that repository, the examples' instruction to build an artifact there, and two descriptions of the signing infrastructure are gone. What remains naming it is the handful of places where the two-repository split is the actual subject — the README, `SECURITY.md`, and `docs/reference/README.md`, which says plainly that the full normative specification is not public.
|
|
18
|
-
- `scripts/lint-public.sh` is new and blocking in CI, because the sweep this replaces reported itself complete while 148 of its targets survived in a different shape. It refuses both the private repository's name and that exact prose pointer, and its `--selftest` proves each rule fires rather than assuming it.
|
|
19
|
-
|
|
20
|
-
## [0.1.1] — 2026-09-11
|
|
21
|
-
|
|
22
|
-
- **The golden set is served, not tracked.** `goldens/cases.json` no longer exists in this repository. Core publishes `sdk-goldens.json` in the rolling release as a row in the signed `SHA256SUMS`; `scripts/fetch.sh` installs it at `<registry>/sdk-goldens.json` and the golden test reads it offline from there (`CHTYPES_GOLDENS` overrides). A case runs only against the exact ClickHouse version `generated.exact` names for its line and skips loudly otherwise, so the set shrinks as lines are added rather than recording an answer twice.
|
|
23
|
-
- **Artifact identity gained a wrapper build.** Assets are `chtypes-<version>-<os>-<arch>-b<N>.tar.gz`, where `<N>` is the core commit's UNIX timestamp; an absent suffix means build 0 and stays valid. Line resolution ranks by `(version, build)`, so a rebuild of a line supersedes the older build.
|
|
24
|
-
- Fetching now retries the publish window, so a fetch that races a release no longer fails on sums naming a file the host has not finished serving.
|
|
25
|
-
- README install instructions name the published package rather than a path dependency.
|
|
26
|
-
- Two errors in the published README, both found by running the snippets rather than reading them: Python's `substituted` is on `RowResult`, not `BatchResult`, and TypeScript's `using schema = …` is a syntax error on Node 22, this package's own `engines` floor (`schema.close()` is portable).
|
|
27
|
-
- `docs/support.md` is new: which language versions, platforms and ClickHouse lines are supported, generated from the manifests and the release's own index.
|
|
28
|
-
|
|
29
|
-
## [0.1.0] — 2026-09-10
|
|
30
|
-
|
|
31
|
-
First release, published to PyPI from the tag `python/v0.1.0`.
|
|
32
|
-
|
|
33
|
-
- Speaks **ABI revision 4** of the frozen `chs_*` C ABI (`include/chtypes.h`, 28 functions). The binding `dlopen`s a per-version artifact and reimplements no ClickHouse semantics; `Transformed` is the one derived result.
|
|
34
|
-
- Verified against the public golden set (`goldens/cases.json`, schema 1, 31 cases), whose expectations were generated on ClickHouse 24.8, 25.3, 25.8, 25.10, 26.5, 26.6 and 26.7 — every case answered identically on all seven lines, and any case that did not was refused by the generator.
|
|
35
|
-
- Pre-1.0: the artifact name `libchtypes`, the `chs_` prefix and the `enum chs_format` numbers are frozen. Function signatures freeze at this tag.
|
chtypes-0.1.2/uv.lock
DELETED
|
@@ -1,108 +0,0 @@
|
|
|
1
|
-
version = 1
|
|
2
|
-
revision = 3
|
|
3
|
-
requires-python = ">=3.11"
|
|
4
|
-
|
|
5
|
-
[[package]]
|
|
6
|
-
name = "chtypes"
|
|
7
|
-
version = "0.1.2"
|
|
8
|
-
source = { editable = "." }
|
|
9
|
-
|
|
10
|
-
[package.dev-dependencies]
|
|
11
|
-
dev = [
|
|
12
|
-
{ name = "pytest" },
|
|
13
|
-
{ name = "ruff" },
|
|
14
|
-
]
|
|
15
|
-
|
|
16
|
-
[package.metadata]
|
|
17
|
-
|
|
18
|
-
[package.metadata.requires-dev]
|
|
19
|
-
dev = [
|
|
20
|
-
{ name = "pytest", specifier = ">=8.0" },
|
|
21
|
-
{ name = "ruff", specifier = ">=0.14" },
|
|
22
|
-
]
|
|
23
|
-
|
|
24
|
-
[[package]]
|
|
25
|
-
name = "colorama"
|
|
26
|
-
version = "0.4.6"
|
|
27
|
-
source = { registry = "https://pypi.org/simple" }
|
|
28
|
-
sdist = { url = "https://files.pythonhosted.org/packages/d8/53/6f443c9a4a8358a93a6792e2acffb9d9d5cb0a5cfd8802644b7b1c9a02e4/colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44", size = 27697, upload-time = "2022-10-25T02:36:22.414Z" }
|
|
29
|
-
wheels = [
|
|
30
|
-
{ url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335, upload-time = "2022-10-25T02:36:20.889Z" },
|
|
31
|
-
]
|
|
32
|
-
|
|
33
|
-
[[package]]
|
|
34
|
-
name = "iniconfig"
|
|
35
|
-
version = "2.3.0"
|
|
36
|
-
source = { registry = "https://pypi.org/simple" }
|
|
37
|
-
sdist = { url = "https://files.pythonhosted.org/packages/72/34/14ca021ce8e5dfedc35312d08ba8bf51fdd999c576889fc2c24cb97f4f10/iniconfig-2.3.0.tar.gz", hash = "sha256:c76315c77db068650d49c5b56314774a7804df16fee4402c1f19d6d15d8c4730", size = 20503, upload-time = "2025-10-18T21:55:43.219Z" }
|
|
38
|
-
wheels = [
|
|
39
|
-
{ url = "https://files.pythonhosted.org/packages/cb/b1/3846dd7f199d53cb17f49cba7e651e9ce294d8497c8c150530ed11865bb8/iniconfig-2.3.0-py3-none-any.whl", hash = "sha256:f631c04d2c48c52b84d0d0549c99ff3859c98df65b3101406327ecc7d53fbf12", size = 7484, upload-time = "2025-10-18T21:55:41.639Z" },
|
|
40
|
-
]
|
|
41
|
-
|
|
42
|
-
[[package]]
|
|
43
|
-
name = "packaging"
|
|
44
|
-
version = "26.3"
|
|
45
|
-
source = { registry = "https://pypi.org/simple" }
|
|
46
|
-
sdist = { url = "https://files.pythonhosted.org/packages/7d/fa/3944b40b07da9ce895c0e6303a5ab7d53da063554f534556b134a54d6093/packaging-26.3.tar.gz", hash = "sha256:94edc256424af38762eb31306eed28beb9f0efc50a8837492c9d6fd6004aed79", size = 313412, upload-time = "2026-08-04T18:15:28.737Z" }
|
|
47
|
-
wheels = [
|
|
48
|
-
{ url = "https://files.pythonhosted.org/packages/63/34/ba1c580383c9eada3711951fef0795c80b829a078d72188184bcab9dd527/packaging-26.3-py3-none-any.whl", hash = "sha256:d7193f7c8e4e93f444fde0262bf90af30e16fa0ad0ad44cb553c87339b23cd1c", size = 129956, upload-time = "2026-08-04T18:15:27.159Z" },
|
|
49
|
-
]
|
|
50
|
-
|
|
51
|
-
[[package]]
|
|
52
|
-
name = "pluggy"
|
|
53
|
-
version = "1.6.0"
|
|
54
|
-
source = { registry = "https://pypi.org/simple" }
|
|
55
|
-
sdist = { url = "https://files.pythonhosted.org/packages/f9/e2/3e91f31a7d2b083fe6ef3fa267035b518369d9511ffab804f839851d2779/pluggy-1.6.0.tar.gz", hash = "sha256:7dcc130b76258d33b90f61b658791dede3486c3e6bfb003ee5c9bfb396dd22f3", size = 69412, upload-time = "2025-05-15T12:30:07.975Z" }
|
|
56
|
-
wheels = [
|
|
57
|
-
{ url = "https://files.pythonhosted.org/packages/54/20/4d324d65cc6d9205fabedc306948156824eb9f0ee1633355a8f7ec5c66bf/pluggy-1.6.0-py3-none-any.whl", hash = "sha256:e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746", size = 20538, upload-time = "2025-05-15T12:30:06.134Z" },
|
|
58
|
-
]
|
|
59
|
-
|
|
60
|
-
[[package]]
|
|
61
|
-
name = "pygments"
|
|
62
|
-
version = "2.21.0"
|
|
63
|
-
source = { registry = "https://pypi.org/simple" }
|
|
64
|
-
sdist = { url = "https://files.pythonhosted.org/packages/49/2e/ced460408999b33da6b31b0021b0f37d329e202d4169aeb164493778f25b/pygments-2.21.0.tar.gz", hash = "sha256:610ca751c9bc2492b38eb9a38a7fbc93edbbb2d7182edaf34e66ae493dee5c8c", size = 5005329, upload-time = "2026-08-17T08:02:48.824Z" }
|
|
65
|
-
wheels = [
|
|
66
|
-
{ url = "https://files.pythonhosted.org/packages/71/46/17f022dd3e953bf20a04a028a21ec746d942f8d2af30fa0f124fa0e6a684/pygments-2.21.0-py3-none-any.whl", hash = "sha256:2363c69b61c4a97c838da3b130dcd6468f4848992b21a82f2a63ec34377137d9", size = 1250147, upload-time = "2026-08-17T08:02:44.912Z" },
|
|
67
|
-
]
|
|
68
|
-
|
|
69
|
-
[[package]]
|
|
70
|
-
name = "pytest"
|
|
71
|
-
version = "9.1.1"
|
|
72
|
-
source = { registry = "https://pypi.org/simple" }
|
|
73
|
-
dependencies = [
|
|
74
|
-
{ name = "colorama", marker = "sys_platform == 'win32'" },
|
|
75
|
-
{ name = "iniconfig" },
|
|
76
|
-
{ name = "packaging" },
|
|
77
|
-
{ name = "pluggy" },
|
|
78
|
-
{ name = "pygments" },
|
|
79
|
-
]
|
|
80
|
-
sdist = { url = "https://files.pythonhosted.org/packages/e4/47/b9efed96c114afcfa3c9d3fe98a76a1d14c74a9e266d397cf6eb64be5e01/pytest-9.1.1.tar.gz", hash = "sha256:1088fbde8f2b49d95a549a195707afa7a76a3ce9bcadc26b6d71f0ffda5fe313", size = 1636369, upload-time = "2026-06-19T10:58:32.857Z" }
|
|
81
|
-
wheels = [
|
|
82
|
-
{ url = "https://files.pythonhosted.org/packages/24/25/1de2678b631f5a49215c6c96fff41ba892b0a34df68d6d80292b1b48aa7f/pytest-9.1.1-py3-none-any.whl", hash = "sha256:37a86b45efb9a47a61a36449063e8e18d0cab3161329fc099eb21783169c4f0c", size = 386536, upload-time = "2026-06-19T10:58:31.347Z" },
|
|
83
|
-
]
|
|
84
|
-
|
|
85
|
-
[[package]]
|
|
86
|
-
name = "ruff"
|
|
87
|
-
version = "0.16.6"
|
|
88
|
-
source = { registry = "https://pypi.org/simple" }
|
|
89
|
-
sdist = { url = "https://files.pythonhosted.org/packages/a4/7c/6adb35d70e7c027e308274557901c7e00fb3407750faf3620c184ae058cb/ruff-0.16.6.tar.gz", hash = "sha256:dcf8a73d2ff77e99dde91244b4da16feba7f14e6beeb4015dee7c5a909e99050", size = 4921251, upload-time = "2026-09-03T16:57:29.037Z" }
|
|
90
|
-
wheels = [
|
|
91
|
-
{ url = "https://files.pythonhosted.org/packages/a4/28/9cc1b79639e284ec103f43c88c644db4eb58cbd0ea1ca11f1193435369ac/ruff-0.16.6-py3-none-linux_armv6l.whl", hash = "sha256:61c368c26bf8e973e5ab14a2772de587bc068ea3f9a277f673380749b4898fb8", size = 10015638, upload-time = "2026-09-03T16:56:40.986Z" },
|
|
92
|
-
{ url = "https://files.pythonhosted.org/packages/71/11/627d342ef727ea7794edf74fe23d60a074b02c3acc2e9436684e782286ca/ruff-0.16.6-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:ecf4f068e2e123e43a26e9db4e19524cc56563912404e83bbfca375757e45a32", size = 10220762, upload-time = "2026-09-03T16:56:44.681Z" },
|
|
93
|
-
{ url = "https://files.pythonhosted.org/packages/43/d9/b75668ce41e4c8d073d18d6d08672ba6906ce45d5c06ea4fdb2e84ce3853/ruff-0.16.6-py3-none-macosx_11_0_arm64.whl", hash = "sha256:99b62ea33baf130f50368798d841f0d95527b6d817bf31817b65dd058f1d314c", size = 9835082, upload-time = "2026-09-03T16:56:47.142Z" },
|
|
94
|
-
{ url = "https://files.pythonhosted.org/packages/99/97/123ab10b05cde889c107c20f5a9774955104b5552796a2a8584b089ae8eb/ruff-0.16.6-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7fbf89013f2bb3f6835a6038ff658dc8a1b38c98dc8e724b964168ad4e881876", size = 9949304, upload-time = "2026-09-03T16:56:49.813Z" },
|
|
95
|
-
{ url = "https://files.pythonhosted.org/packages/3e/58/a4a2c59dd2e5b85929c912d9cac3056eb9ee8c7e75e9b9fe3e109174966b/ruff-0.16.6-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:56a67065e22efa6bc4d498299d3bb06c0c90aace8fac2068b5a12f9dc4d8d51d", size = 9840612, upload-time = "2026-09-03T16:56:52.368Z" },
|
|
96
|
-
{ url = "https://files.pythonhosted.org/packages/61/6a/ff8c8626a786c4f49d48ced4a752dadbca65f5263005f9c2416578194694/ruff-0.16.6-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e25cc89174874b176a157e4428d66761c2c0c006654419bf384f967f361ff1b1", size = 10543465, upload-time = "2026-09-03T16:56:55.089Z" },
|
|
97
|
-
{ url = "https://files.pythonhosted.org/packages/ad/bb/c47535923365f337b82e28192e4e9eef2176511007cfd99a62fc22df5dad/ruff-0.16.6-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:0700580ed5303723cb3c11c2f1d2a8913ce77b7ea86646dddb887f5417a9ba70", size = 11267576, upload-time = "2026-09-03T16:56:57.791Z" },
|
|
98
|
-
{ url = "https://files.pythonhosted.org/packages/ba/50/e5119a5212b5cd63b51e1f4b25e7bd636a6668fc069a3160b108ad7e3c16/ruff-0.16.6-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:15f1d0b6e165a6e56567befb6629f8209271311d990bae0f37e6d065035ef5f3", size = 10781993, upload-time = "2026-09-03T16:57:00.666Z" },
|
|
99
|
-
{ url = "https://files.pythonhosted.org/packages/8b/98/083d8b4ef3c51a0d19db84367791cbe9f44e4b53343d19dfa83556e1cd9a/ruff-0.16.6-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d72c591a96986ee4268860e2b7235082129ca5e4cb9cbba653a4b57c11893757", size = 10317748, upload-time = "2026-09-03T16:57:03.428Z" },
|
|
100
|
-
{ url = "https://files.pythonhosted.org/packages/9a/29/68f7ff2c5ad95f19f00627ac2de95644e25fe47371ea60b2db1fd952315e/ruff-0.16.6-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:65a006baa18f33324325814c864daef03541d51564b98c517610ea756ab7003e", size = 10540096, upload-time = "2026-09-03T16:57:06.182Z" },
|
|
101
|
-
{ url = "https://files.pythonhosted.org/packages/c4/f9/79a8f6de85968641d68a7863aeec577551924ef066a990a48ff93167beab/ruff-0.16.6-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:cd02a7bf1a21a8735228a3e8c95a9dc5cf86bd2a52194f4aaae2a5755b4de0f4", size = 10100494, upload-time = "2026-09-03T16:57:09.194Z" },
|
|
102
|
-
{ url = "https://files.pythonhosted.org/packages/d9/e8/b81a22d9b90c00b892ccf2fa2ac36fa95de4c13ab85aea3e73795cfe4651/ruff-0.16.6-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:31b36f1e5ad85e0737f09d2be4e512e2e283583c14015da3b9dc07359ac0fc88", size = 9843663, upload-time = "2026-09-03T16:57:12.168Z" },
|
|
103
|
-
{ url = "https://files.pythonhosted.org/packages/39/aa/54f516ec5e5a11c4afdceb1c454ebb054ffb96e4f4a1705580b4346abd35/ruff-0.16.6-py3-none-musllinux_1_2_i686.whl", hash = "sha256:61029b4ab4aa723fd3064fab96b1d814492596bf0c792679fffcbde1e1679953", size = 10282461, upload-time = "2026-09-03T16:57:15.077Z" },
|
|
104
|
-
{ url = "https://files.pythonhosted.org/packages/52/0b/38d0aa8aa32372b96dc44f97b22e576c4147808271aab7b2cb1e353d4445/ruff-0.16.6-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:9ac8998457832c2061709d900856b7ad271dace0cb41f346588d540162bfa718", size = 10728808, upload-time = "2026-09-03T16:57:17.797Z" },
|
|
105
|
-
{ url = "https://files.pythonhosted.org/packages/5e/e5/9e274e24eeb027640ffc7442f21239f16d17f47acec15ae34f32e03a5c79/ruff-0.16.6-py3-none-win32.whl", hash = "sha256:0b87d9d16fcb63e8018423ca1d50b7260f15cb2da33e30db4baad4183a948c25", size = 10049212, upload-time = "2026-09-03T16:57:20.55Z" },
|
|
106
|
-
{ url = "https://files.pythonhosted.org/packages/22/31/72472449414223ed1a2da236b992adbb1a2ae59e34794574810f60ce068e/ruff-0.16.6-py3-none-win_amd64.whl", hash = "sha256:10d21c51c3495d8eaea7b703a16592117ea6eb1d649e36335aa965ff1173eb39", size = 10556402, upload-time = "2026-09-03T16:57:23.501Z" },
|
|
107
|
-
{ url = "https://files.pythonhosted.org/packages/fc/07/d781f8f8e1ac24bef9f3269cf62ffb1407ca24c3a8f12e5e22874f90528c/ruff-0.16.6-py3-none-win_arm64.whl", hash = "sha256:7a976c79b958f94e50a022a19f0f8c87387448020935ec14fc74331bd0a7f2c5", size = 10412850, upload-time = "2026-09-03T16:57:26.416Z" },
|
|
108
|
-
]
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|