chtypes 0.1.2__tar.gz → 0.2.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (32) hide show
  1. {chtypes-0.1.2 → chtypes-0.2.0}/CHANGELOG.md +20 -1
  2. {chtypes-0.1.2 → chtypes-0.2.0}/PKG-INFO +1 -1
  3. {chtypes-0.1.2 → chtypes-0.2.0}/pyproject.toml +3 -3
  4. {chtypes-0.1.2 → chtypes-0.2.0}/src/chtypes/__init__.py +2 -0
  5. {chtypes-0.1.2 → chtypes-0.2.0}/src/chtypes/__main__.py +3 -1
  6. {chtypes-0.1.2 → chtypes-0.2.0}/src/chtypes/_manifest.py +4 -1
  7. {chtypes-0.1.2 → chtypes-0.2.0}/src/chtypes/_native.py +2 -1
  8. {chtypes-0.1.2 → chtypes-0.2.0}/src/chtypes/errors.py +7 -1
  9. {chtypes-0.1.2 → chtypes-0.2.0}/src/chtypes/fetch.py +3 -2
  10. {chtypes-0.1.2 → chtypes-0.2.0}/src/chtypes/registry.py +3 -2
  11. {chtypes-0.1.2 → chtypes-0.2.0}/tests/conftest.py +1 -1
  12. {chtypes-0.1.2 → chtypes-0.2.0}/tests/test_cli.py +4 -1
  13. {chtypes-0.1.2 → chtypes-0.2.0}/tests/test_fetch.py +3 -3
  14. {chtypes-0.1.2 → chtypes-0.2.0}/tests/test_parity.py +22 -12
  15. {chtypes-0.1.2 → chtypes-0.2.0}/tests/test_registry.py +23 -0
  16. chtypes-0.2.0/uv.lock +108 -0
  17. chtypes-0.1.2/uv.lock +0 -108
  18. {chtypes-0.1.2 → chtypes-0.2.0}/.gitignore +0 -0
  19. {chtypes-0.1.2 → chtypes-0.2.0}/LICENSE +0 -0
  20. {chtypes-0.1.2 → chtypes-0.2.0}/README.md +0 -0
  21. {chtypes-0.1.2 → chtypes-0.2.0}/src/chtypes/_document.py +0 -0
  22. {chtypes-0.1.2 → chtypes-0.2.0}/src/chtypes/_ed25519.py +0 -0
  23. {chtypes-0.1.2 → chtypes-0.2.0}/src/chtypes/_rawjson.py +0 -0
  24. {chtypes-0.1.2 → chtypes-0.2.0}/src/chtypes/discover.py +0 -0
  25. {chtypes-0.1.2 → chtypes-0.2.0}/src/chtypes/py.typed +0 -0
  26. {chtypes-0.1.2 → chtypes-0.2.0}/src/chtypes/results.py +0 -0
  27. {chtypes-0.1.2 → chtypes-0.2.0}/src/chtypes/transform.py +0 -0
  28. {chtypes-0.1.2 → chtypes-0.2.0}/tests/test_ed25519.py +0 -0
  29. {chtypes-0.1.2 → chtypes-0.2.0}/tests/test_errors.py +0 -0
  30. {chtypes-0.1.2 → chtypes-0.2.0}/tests/test_golden.py +0 -0
  31. {chtypes-0.1.2 → chtypes-0.2.0}/tests/test_rawjson.py +0 -0
  32. {chtypes-0.1.2 → chtypes-0.2.0}/tests/test_transform.py +0 -0
@@ -4,6 +4,25 @@ All notable changes to the python binding. The format is [Keep a Changelog](http
4
4
 
5
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
6
 
7
+ ## [0.2.0] — 2026-09-15
8
+
9
+ ### Changed — BREAKING
10
+
11
+ - **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.
12
+
13
+ - **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.
14
+
15
+ ### Added
16
+
17
+ - **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.
18
+ - **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.
19
+ - **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.
20
+
21
+ ### Notes
22
+
23
+ - 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.
24
+ - `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.
25
+
7
26
  ## [0.1.2] — 2026-09-11
8
27
 
9
28
  ### Changed — BREAKING
@@ -14,7 +33,7 @@ The four bindings in this repository are released together and give one answer,
14
33
  ### Changed
15
34
 
16
35
  - **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.
36
+ - **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.)
18
37
  - `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
38
 
20
39
  ## [0.1.1] — 2026-09-11
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: chtypes
3
- Version: 0.1.2
3
+ Version: 0.2.0
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.2"
3
+ version = "0.2.0"
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 ci/steps/static-checks.sh
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="Install ClickHouse lines through the verification chain (docs/guides/fetch.md §3).",
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
- if digest.hexdigest() != manifest.library_sha256:
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 §Thread-safety declares those safe together **on distinct handles**, so
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: python -m chtypes fetch {line}\n"
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 _parse_signature(sig: bytes) -> tuple[str, bytes]:
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 = _parse_signature(sig)
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
- docs/proposals/rows-export.md) — still ONE `chs_rows` call, never a
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
- `certify` workflow against this same tree.
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()`:
@@ -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=f"no fetch fixtures at {FIXTURES} (generated in the core repository, docs/guides/fetch.md §9)",
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._parse_signature(sig)
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._parse_signature(b"untrusted comment: x\n!!!\n")
520
+ fetch_module.parse_signature_file(b"untrusted comment: x\n!!!\n")
521
521
  with pytest.raises(chtypes.ArtifactUntrustedError, match="64"):
522
- fetch_module._parse_signature(b"untrusted comment: x\nAAAA\n")
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(f"{cid}: no `what` — a capability with no description is a name, not a contract")
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(f"{cid}: no {lang} column at all — declare a spelling or an absence")
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(problems)
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 cp tests/parity/manifest.json go/chtypes/testdata/parity.json\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, which {also_in(cap)} offer"
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
- f"tests/parity/manifest.json (if the other bindings should have it too) or list it under "
338
- f'`unlisted.python` with a reason (if it is deliberately ours alone).'
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 column. Every language is a peer "
401
- "SDK over the same C ABI and the document says so in its own second paragraph; a table with "
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 docs/reference/bindings.md never "
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
- f"capability should not be required at all — remove it from the manifest. The two are one "
426
- f"contract stated twice."
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.0/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.0"
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/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