chtypes 0.2.0__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.
Files changed (32) hide show
  1. {chtypes-0.2.0 → chtypes-0.2.1}/CHANGELOG.md +9 -0
  2. {chtypes-0.2.0 → chtypes-0.2.1}/PKG-INFO +1 -1
  3. {chtypes-0.2.0 → chtypes-0.2.1}/pyproject.toml +1 -1
  4. chtypes-0.2.1/tests/test_abi_revision.py +139 -0
  5. {chtypes-0.2.0 → chtypes-0.2.1}/uv.lock +1 -1
  6. {chtypes-0.2.0 → chtypes-0.2.1}/.gitignore +0 -0
  7. {chtypes-0.2.0 → chtypes-0.2.1}/LICENSE +0 -0
  8. {chtypes-0.2.0 → chtypes-0.2.1}/README.md +0 -0
  9. {chtypes-0.2.0 → chtypes-0.2.1}/src/chtypes/__init__.py +0 -0
  10. {chtypes-0.2.0 → chtypes-0.2.1}/src/chtypes/__main__.py +0 -0
  11. {chtypes-0.2.0 → chtypes-0.2.1}/src/chtypes/_document.py +0 -0
  12. {chtypes-0.2.0 → chtypes-0.2.1}/src/chtypes/_ed25519.py +0 -0
  13. {chtypes-0.2.0 → chtypes-0.2.1}/src/chtypes/_manifest.py +0 -0
  14. {chtypes-0.2.0 → chtypes-0.2.1}/src/chtypes/_native.py +0 -0
  15. {chtypes-0.2.0 → chtypes-0.2.1}/src/chtypes/_rawjson.py +0 -0
  16. {chtypes-0.2.0 → chtypes-0.2.1}/src/chtypes/discover.py +0 -0
  17. {chtypes-0.2.0 → chtypes-0.2.1}/src/chtypes/errors.py +0 -0
  18. {chtypes-0.2.0 → chtypes-0.2.1}/src/chtypes/fetch.py +0 -0
  19. {chtypes-0.2.0 → chtypes-0.2.1}/src/chtypes/py.typed +0 -0
  20. {chtypes-0.2.0 → chtypes-0.2.1}/src/chtypes/registry.py +0 -0
  21. {chtypes-0.2.0 → chtypes-0.2.1}/src/chtypes/results.py +0 -0
  22. {chtypes-0.2.0 → chtypes-0.2.1}/src/chtypes/transform.py +0 -0
  23. {chtypes-0.2.0 → chtypes-0.2.1}/tests/conftest.py +0 -0
  24. {chtypes-0.2.0 → chtypes-0.2.1}/tests/test_cli.py +0 -0
  25. {chtypes-0.2.0 → chtypes-0.2.1}/tests/test_ed25519.py +0 -0
  26. {chtypes-0.2.0 → chtypes-0.2.1}/tests/test_errors.py +0 -0
  27. {chtypes-0.2.0 → chtypes-0.2.1}/tests/test_fetch.py +0 -0
  28. {chtypes-0.2.0 → chtypes-0.2.1}/tests/test_golden.py +0 -0
  29. {chtypes-0.2.0 → chtypes-0.2.1}/tests/test_parity.py +0 -0
  30. {chtypes-0.2.0 → chtypes-0.2.1}/tests/test_rawjson.py +0 -0
  31. {chtypes-0.2.0 → chtypes-0.2.1}/tests/test_registry.py +0 -0
  32. {chtypes-0.2.0 → chtypes-0.2.1}/tests/test_transform.py +0 -0
@@ -4,6 +4,15 @@ 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.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
+
7
16
  ## [0.2.0] — 2026-09-15
8
17
 
9
18
  ### Changed — BREAKING
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: chtypes
3
- Version: 0.2.0
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.2.0"
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"
@@ -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"]
@@ -4,7 +4,7 @@ requires-python = ">=3.11"
4
4
 
5
5
  [[package]]
6
6
  name = "chtypes"
7
- version = "0.2.0"
7
+ version = "0.2.1"
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
File without changes