chtypes 0.2.1__tar.gz → 0.2.2__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.2.1 → chtypes-0.2.2}/CHANGELOG.md +11 -0
  2. {chtypes-0.2.1 → chtypes-0.2.2}/PKG-INFO +1 -1
  3. {chtypes-0.2.1 → chtypes-0.2.2}/pyproject.toml +1 -1
  4. {chtypes-0.2.1 → chtypes-0.2.2}/src/chtypes/_manifest.py +3 -1
  5. {chtypes-0.2.1 → chtypes-0.2.2}/tests/test_registry.py +43 -0
  6. {chtypes-0.2.1 → chtypes-0.2.2}/uv.lock +1 -1
  7. {chtypes-0.2.1 → chtypes-0.2.2}/.gitignore +0 -0
  8. {chtypes-0.2.1 → chtypes-0.2.2}/LICENSE +0 -0
  9. {chtypes-0.2.1 → chtypes-0.2.2}/README.md +0 -0
  10. {chtypes-0.2.1 → chtypes-0.2.2}/src/chtypes/__init__.py +0 -0
  11. {chtypes-0.2.1 → chtypes-0.2.2}/src/chtypes/__main__.py +0 -0
  12. {chtypes-0.2.1 → chtypes-0.2.2}/src/chtypes/_document.py +0 -0
  13. {chtypes-0.2.1 → chtypes-0.2.2}/src/chtypes/_ed25519.py +0 -0
  14. {chtypes-0.2.1 → chtypes-0.2.2}/src/chtypes/_native.py +0 -0
  15. {chtypes-0.2.1 → chtypes-0.2.2}/src/chtypes/_rawjson.py +0 -0
  16. {chtypes-0.2.1 → chtypes-0.2.2}/src/chtypes/discover.py +0 -0
  17. {chtypes-0.2.1 → chtypes-0.2.2}/src/chtypes/errors.py +0 -0
  18. {chtypes-0.2.1 → chtypes-0.2.2}/src/chtypes/fetch.py +0 -0
  19. {chtypes-0.2.1 → chtypes-0.2.2}/src/chtypes/py.typed +0 -0
  20. {chtypes-0.2.1 → chtypes-0.2.2}/src/chtypes/registry.py +0 -0
  21. {chtypes-0.2.1 → chtypes-0.2.2}/src/chtypes/results.py +0 -0
  22. {chtypes-0.2.1 → chtypes-0.2.2}/src/chtypes/transform.py +0 -0
  23. {chtypes-0.2.1 → chtypes-0.2.2}/tests/conftest.py +0 -0
  24. {chtypes-0.2.1 → chtypes-0.2.2}/tests/test_abi_revision.py +0 -0
  25. {chtypes-0.2.1 → chtypes-0.2.2}/tests/test_cli.py +0 -0
  26. {chtypes-0.2.1 → chtypes-0.2.2}/tests/test_ed25519.py +0 -0
  27. {chtypes-0.2.1 → chtypes-0.2.2}/tests/test_errors.py +0 -0
  28. {chtypes-0.2.1 → chtypes-0.2.2}/tests/test_fetch.py +0 -0
  29. {chtypes-0.2.1 → chtypes-0.2.2}/tests/test_golden.py +0 -0
  30. {chtypes-0.2.1 → chtypes-0.2.2}/tests/test_parity.py +0 -0
  31. {chtypes-0.2.1 → chtypes-0.2.2}/tests/test_rawjson.py +0 -0
  32. {chtypes-0.2.1 → chtypes-0.2.2}/tests/test_transform.py +0 -0
@@ -4,6 +4,17 @@ 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.2] — 2026-09-17
8
+
9
+ ### Changed
10
+
11
+ - **`Registry(..., verify_hashes=True)` now refuses an artifact whose manifest carries no `library_sha256`, instead of loading it silently.** This is a behavior change in a security posture, not a silent fix: previously, asking for verification against a manifest with the field absent returned as if the bytes had been checked, when they never were — the caller believed the load was verified and it was not. `verify_library` now raises `RegistryError` naming the path and saying the manifest carries no `library_sha256`, the same shape its hash-mismatch path has always raised. `verify_hashes` off is unaffected: the field stays optional for a caller who did not ask. Go, TypeScript and Rust already refused this case; this was the one gap issue #13's item A2 left (#48).
12
+
13
+ ### Notes
14
+
15
+ - 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.
16
+ - Seven facts about the compiled library's behavior that a consumer previously had to discover by experiment are now written down: `CHECK`-constraint batch rejection, binding filter parameters as `String`, single-line compact JSON array loss under `allow_errors`, the JSONCompactEachRow export field separator, the value-injection pattern and its compile-time wrap trap, `Columns` as canonical and in declaration order, and the frozen-signatures promise reworded ahead of the next ABI revision (#56). That promise is now stated once, in `docs/support.md`, and linked from everywhere else rather than restated in nine places (#69).
17
+
7
18
  ## [0.2.1] — 2026-09-15
8
19
 
9
20
  Released in step with the Rust crate, which gains `Registry::open` and `Error::LibraryRead`; this binding's public API is unchanged.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: chtypes
3
- Version: 0.2.1
3
+ Version: 0.2.2
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.2.1"
3
+ version = "0.2.2"
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"
@@ -106,7 +106,9 @@ def verify_library(version_dir: str | os.PathLike[str]) -> None:
106
106
  f"chtypes: {path} is {size} bytes, manifest says {manifest.library_bytes}"
107
107
  )
108
108
  if not manifest.library_sha256:
109
- return
109
+ raise RegistryError(
110
+ f"chtypes: {path} cannot be verified: manifest carries no library_sha256"
111
+ )
110
112
  digest = hashlib.sha256()
111
113
  with path.open("rb") as handle:
112
114
  for chunk in iter(lambda: handle.read(1 << 20), b""):
@@ -2,6 +2,7 @@
2
2
 
3
3
  from __future__ import annotations
4
4
 
5
+ import dataclasses
5
6
  import hashlib
6
7
  import json
7
8
  import os
@@ -221,6 +222,48 @@ def test_verify_library_checks_the_bytes(tmp_path: Path) -> None:
221
222
  chtypes.verify_library(tmp_path)
222
223
 
223
224
 
225
+ def test_verify_library_refuses_a_manifest_with_no_hash(tmp_path: Path) -> None:
226
+ """Verification asked for and not possible is not verification (#48): Go,
227
+ TypeScript and Rust all refuse an artifact whose manifest carries no
228
+ `library_sha256` when checksums are asked for. `verify_library` runs only
229
+ when a caller asked for it (`Registry(verify_hashes=True)`), so a manifest
230
+ missing the field must raise here too, not return quietly.
231
+ """
232
+ payload = b"not really a library"
233
+ (tmp_path / "fake.so").write_bytes(payload)
234
+ (tmp_path / "manifest.json").write_text(
235
+ json.dumps({"library": "fake.so", "library_bytes": len(payload)})
236
+ )
237
+ with pytest.raises(chtypes.RegistryError, match="library_sha256"):
238
+ chtypes.verify_library(tmp_path)
239
+
240
+
241
+ def test_registry_verify_hashes_refuses_a_manifest_with_no_hash(
242
+ registry: chtypes.Registry, tmp_path: Path
243
+ ) -> None:
244
+ """`Registry(..., verify_hashes=True)` must not load bytes it never
245
+ checked (#48): a manifest with the field simply absent is exactly the
246
+ shape a truncated or hand-assembled artifact directory has. With
247
+ `verify_hashes` off the field stays optional for a caller who did not
248
+ ask, and the same directory loads normally.
249
+ """
250
+ smallest = min(registry.libraries(), key=lambda lib: lib.manifest.library_bytes or 1 << 62)
251
+ fields = dataclasses.asdict(smallest.manifest)
252
+ del fields["library_sha256"]
253
+ line = tmp_path / smallest.minor
254
+ line.mkdir()
255
+ (line / smallest.manifest.library).symlink_to(Path(smallest.path).resolve())
256
+ (line / "manifest.json").write_text(json.dumps(fields))
257
+
258
+ strict = chtypes.Registry(tmp_path, verify_hashes=True)
259
+ with pytest.raises(chtypes.RegistryError, match="library_sha256"):
260
+ strict.libraries()
261
+
262
+ lenient = chtypes.Registry(tmp_path, verify_hashes=False)
263
+ loaded = lenient.for_version(smallest.minor) # must NOT raise
264
+ assert loaded.version == smallest.version
265
+
266
+
224
267
  def test_verify_a_real_artifact(registry: chtypes.Registry) -> None:
225
268
  # The one integrity check that means anything, run against the real bytes: a
226
269
  # move that reported success and truncated a 232 MB library looks identical
@@ -4,7 +4,7 @@ requires-python = ">=3.11"
4
4
 
5
5
  [[package]]
6
6
  name = "chtypes"
7
- version = "0.2.1"
7
+ version = "0.2.2"
8
8
  source = { editable = "." }
9
9
 
10
10
  [package.dev-dependencies]
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
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes