chtypes 0.1.0__py3-none-any.whl

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/__init__.py ADDED
@@ -0,0 +1,202 @@
1
+ """chtypes — ClickHouse's own type system, per version, from Python.
2
+
3
+ One question, exactly: *if this row were inserted into this ClickHouse table on
4
+ this ClickHouse version, what would happen?* The answer comes from ClickHouse's
5
+ real C++ machinery (`DataTypeFactory`, `ISerialization`, `ReadHelpers`,
6
+ `evaluateMissingDefaults`, the TTL algorithms, `MergeTreeDataWriter::mergeBlock`)
7
+ vendored per release into a shared library behind the frozen `chs_*` C ABI.
8
+ Nothing here reimplements a coercion rule, which is why the answers are exact by
9
+ construction rather than approximately right.
10
+
11
+ from chtypes import Format, Registry
12
+
13
+ registry = Registry() # the search path: $CHTYPES_REGISTRY, the cache, …
14
+ library = registry.for_version("25.8") # a minor line or an exact patch
15
+ with library.compile_ddl("ts DateTime, seq UInt8") as schema:
16
+ batch = schema.rows(Format.JSON_EACH_ROW, b'{"ts":"2026-01-15 10:30:00","seq":256}\\n')
17
+
18
+ batch.outcome # Outcome.ACCEPTED — the insert would succeed
19
+ batch.rows[0].value("seq") # Value(text='0', ...) — and store 0
20
+ batch.transformed # Transform(column='seq', input='256', stored='0',
21
+ # reason='overflow_wrap', row=0)
22
+
23
+ Three things a caller must not skip, each of which cost this repository
24
+ something to learn:
25
+
26
+ * **`transformed` is the product.** ClickHouse returns success for every one of
27
+ those changes. Reading `outcome` alone tells a tenant their row was accepted
28
+ and nothing about the value the table will hold.
29
+ * **A row accepted per row may not be stored per batch.** `BatchResult.transformed`
30
+ folds in the storage layer's verdicts (`ttl_expired`, `ttl_column_expired`),
31
+ and `engine_rows` — when present — is the stored truth, not `rows`.
32
+ * **`Outcome.UNSUPPORTED` is not a rejection.** It means a real server might well
33
+ have accepted this and this build declines to guess. Treating it as either a
34
+ rejection or an acceptance manufactures a wrong answer the product never gave.
35
+
36
+ The specification in `spec/` is normative; `go/chtypes` (Go) is the reference
37
+ implementation. Where this binding and that package disagree, the package is
38
+ right.
39
+ """
40
+
41
+ from __future__ import annotations
42
+
43
+ from ._native import ABI_REVISION
44
+ from ._rawjson import RawNumber, quote_bare_denormals
45
+ from .discover import (
46
+ QUERY_CHANGED_SETTINGS,
47
+ QUERY_SERVER_VERSION,
48
+ QUERY_TABLE_COLUMNS,
49
+ DiscoveredColumn,
50
+ ServerProfile,
51
+ parse_changed_settings_result,
52
+ parse_columns_result,
53
+ parse_version_result,
54
+ reconstruct_ddl,
55
+ )
56
+ from .errors import (
57
+ CODE_ARTIFACT_CORRUPT,
58
+ CODE_ARTIFACT_MISSING,
59
+ CODE_ARTIFACT_PINNED,
60
+ CODE_ARTIFACT_UNPUBLISHED,
61
+ CODE_ARTIFACT_UNTRUSTED,
62
+ CODE_SOURCE_UNREACHABLE,
63
+ CODE_UNSUPPORTED,
64
+ ArtifactCorruptError,
65
+ ArtifactError,
66
+ ArtifactMissingError,
67
+ ArtifactPinnedError,
68
+ ArtifactUnpublishedError,
69
+ ArtifactUntrustedError,
70
+ ChtypesError,
71
+ RegistryError,
72
+ SchemaError,
73
+ SourceUnreachableError,
74
+ UnsignedArtifactWarning,
75
+ UnsupportedError,
76
+ )
77
+ from .fetch import (
78
+ ENV_AUTOFETCH,
79
+ RELEASE_KEY_ID,
80
+ RELEASE_PUBLIC_KEY,
81
+ ensure,
82
+ fetch_destination,
83
+ fetch_lines,
84
+ registry_search_path,
85
+ )
86
+ from .registry import (
87
+ ENV_REGISTRY,
88
+ Block,
89
+ Filter,
90
+ Library,
91
+ Manifest,
92
+ Registry,
93
+ Schema,
94
+ default_registry_dir,
95
+ host_platform,
96
+ minor_of,
97
+ read_manifest,
98
+ verify_library,
99
+ )
100
+ from .results import (
101
+ COMPILE_DECLARED,
102
+ DOC_ALL,
103
+ DOC_DEFAULTS,
104
+ DOC_TRANSFORMS,
105
+ DOC_VALUES,
106
+ EXPORT_NONE,
107
+ LOSSLESS_REASONS,
108
+ BatchResult,
109
+ Column,
110
+ Computed,
111
+ DefaultKind,
112
+ FilterOutcome,
113
+ FilterResult,
114
+ FilterRowError,
115
+ Format,
116
+ Outcome,
117
+ Reason,
118
+ RowResult,
119
+ Span,
120
+ Substitution,
121
+ Transform,
122
+ Value,
123
+ Verdict,
124
+ )
125
+
126
+ __all__ = [
127
+ "ABI_REVISION",
128
+ "CODE_ARTIFACT_CORRUPT",
129
+ "CODE_ARTIFACT_MISSING",
130
+ "CODE_ARTIFACT_PINNED",
131
+ "CODE_ARTIFACT_UNPUBLISHED",
132
+ "CODE_ARTIFACT_UNTRUSTED",
133
+ "CODE_SOURCE_UNREACHABLE",
134
+ "CODE_UNSUPPORTED",
135
+ "COMPILE_DECLARED",
136
+ "DOC_ALL",
137
+ "DOC_DEFAULTS",
138
+ "DOC_TRANSFORMS",
139
+ "DOC_VALUES",
140
+ "ENV_AUTOFETCH",
141
+ "ENV_REGISTRY",
142
+ "default_registry_dir",
143
+ "EXPORT_NONE",
144
+ "RELEASE_KEY_ID",
145
+ "RELEASE_PUBLIC_KEY",
146
+ "LOSSLESS_REASONS",
147
+ "QUERY_CHANGED_SETTINGS",
148
+ "QUERY_SERVER_VERSION",
149
+ "QUERY_TABLE_COLUMNS",
150
+ "ArtifactCorruptError",
151
+ "ArtifactError",
152
+ "ArtifactMissingError",
153
+ "ArtifactPinnedError",
154
+ "ArtifactUnpublishedError",
155
+ "ArtifactUntrustedError",
156
+ "BatchResult",
157
+ "Block",
158
+ "ChtypesError",
159
+ "Column",
160
+ "Computed",
161
+ "DefaultKind",
162
+ "DiscoveredColumn",
163
+ "Filter",
164
+ "FilterOutcome",
165
+ "FilterResult",
166
+ "FilterRowError",
167
+ "Format",
168
+ "Library",
169
+ "Manifest",
170
+ "Outcome",
171
+ "RawNumber",
172
+ "Reason",
173
+ "Registry",
174
+ "RegistryError",
175
+ "RowResult",
176
+ "Schema",
177
+ "SchemaError",
178
+ "ServerProfile",
179
+ "SourceUnreachableError",
180
+ "Span",
181
+ "Substitution",
182
+ "Transform",
183
+ "UnsignedArtifactWarning",
184
+ "UnsupportedError",
185
+ "Value",
186
+ "Verdict",
187
+ "ensure",
188
+ "fetch_destination",
189
+ "fetch_lines",
190
+ "host_platform",
191
+ "minor_of",
192
+ "parse_changed_settings_result",
193
+ "parse_columns_result",
194
+ "parse_version_result",
195
+ "quote_bare_denormals",
196
+ "read_manifest",
197
+ "reconstruct_ddl",
198
+ "registry_search_path",
199
+ "verify_library",
200
+ ]
201
+
202
+ __version__ = "0.1.0"
chtypes/__main__.py ADDED
@@ -0,0 +1,268 @@
1
+ """``python -m chtypes`` and the ``chtypes`` console script — docs/fetch.md §6.
2
+
3
+ chtypes fetch <line>... [--all] [--platform <os-arch>] [--dest <dir>]
4
+ [--tag <t> | --url <base>] [--lock <file>] [--frozen]
5
+ [--force] [--offline]
6
+ chtypes verify [--dest <dir>] re-hash every installed line against its manifest
7
+ chtypes list [--dest <dir>] what is installed, and what the release offers
8
+ chtypes where the registry directory fetch would write to
9
+
10
+ Exit codes: 0 ok · 1 verification failed · 2 usage · 3 source unreachable ·
11
+ 4 not published for this platform/line. Progress goes to stderr; ``fetch``
12
+ prints the installed directory alone on stdout, one per line.
13
+ """
14
+
15
+ from __future__ import annotations
16
+
17
+ import argparse
18
+ import sys
19
+ import warnings
20
+ from collections.abc import Sequence
21
+
22
+ from ._manifest import Manifest
23
+ from .errors import (
24
+ CODE_ARTIFACT_UNPUBLISHED,
25
+ CODE_SOURCE_UNREACHABLE,
26
+ ArtifactError,
27
+ ChtypesError,
28
+ UnsignedArtifactWarning,
29
+ )
30
+ from .fetch import DEFAULT_ARTIFACTS_URL, DEFAULT_TAG, PLATFORMS, ReleaseEntry
31
+
32
+ __all__ = ["main"]
33
+
34
+ EXIT_OK = 0
35
+ EXIT_VERIFY_FAILED = 1
36
+ EXIT_USAGE = 2
37
+ EXIT_UNREACHABLE = 3
38
+ EXIT_UNPUBLISHED = 4
39
+
40
+ _EXIT_FOR_CODE = {
41
+ CODE_SOURCE_UNREACHABLE: EXIT_UNREACHABLE,
42
+ CODE_ARTIFACT_UNPUBLISHED: EXIT_UNPUBLISHED,
43
+ }
44
+
45
+
46
+ def _say(line: str) -> None:
47
+ sys.stderr.write(line + "\n")
48
+ sys.stderr.flush()
49
+
50
+
51
+ class _Parser(argparse.ArgumentParser):
52
+ """argparse exits 2 on usage errors already; this keeps the message shape ours."""
53
+
54
+ def error(self, message: str) -> None: # type: ignore[override]
55
+ self.print_usage(sys.stderr)
56
+ _say(f"chtypes: {message}")
57
+ raise SystemExit(EXIT_USAGE)
58
+
59
+
60
+ def build_parser() -> argparse.ArgumentParser:
61
+ parser = _Parser(
62
+ prog="chtypes",
63
+ description="Fetch, verify and locate chtypes artifacts (docs/fetch.md).",
64
+ )
65
+ sub = parser.add_subparsers(dest="command", metavar="<command>")
66
+ sub.required = True
67
+
68
+ def dest_option(p: argparse.ArgumentParser) -> None:
69
+ p.add_argument(
70
+ "--dest",
71
+ metavar="<dir>",
72
+ help="the registry directory (default: $CHTYPES_REGISTRY, else the per-user cache)",
73
+ )
74
+
75
+ fetch = sub.add_parser(
76
+ "fetch",
77
+ help="install one or more ClickHouse lines, verified",
78
+ description="Install ClickHouse lines through the verification chain (docs/fetch.md §3).",
79
+ )
80
+ fetch.add_argument(
81
+ "lines",
82
+ nargs="*",
83
+ metavar="<line>",
84
+ help="a minor line (25.8) or an exact patch (25.8.28.1-lts, a hard requirement)",
85
+ )
86
+ fetch.add_argument("--all", action="store_true", help="every line the release publishes")
87
+ fetch.add_argument(
88
+ "--platform",
89
+ metavar="<os-arch>",
90
+ choices=PLATFORMS,
91
+ help=f"one of {', '.join(PLATFORMS)} (default: this host)",
92
+ )
93
+ dest_option(fetch)
94
+ where_from = fetch.add_mutually_exclusive_group()
95
+ where_from.add_argument(
96
+ "--tag",
97
+ metavar="<t>",
98
+ help=f"a release tag on the artifacts host (default: the rolling '{DEFAULT_TAG}')",
99
+ )
100
+ where_from.add_argument(
101
+ "--url",
102
+ metavar="<base>",
103
+ help=f"any base: an http(s) URL, a file:// URL or a directory "
104
+ f"(default: $CHTYPES_ARTIFACTS_URL or {DEFAULT_ARTIFACTS_URL}, plus the tag)",
105
+ )
106
+ fetch.add_argument(
107
+ "--lock", metavar="<file>", help="record what was installed in this lock file (§5)"
108
+ )
109
+ fetch.add_argument(
110
+ "--frozen",
111
+ action="store_true",
112
+ help="refuse anything the lock file does not pin (default lock: chtypes.lock)",
113
+ )
114
+ fetch.add_argument("--force", action="store_true", help="re-download an installed line")
115
+ fetch.add_argument(
116
+ "--offline",
117
+ action="store_true",
118
+ help="never touch the source: installed and verified, or CHTYPES_SOURCE_UNREACHABLE",
119
+ )
120
+
121
+ verify = sub.add_parser(
122
+ "verify",
123
+ help="re-hash every installed line against its manifest",
124
+ description="Re-hash every installed line's library against its own manifest.json.",
125
+ )
126
+ dest_option(verify)
127
+
128
+ listing = sub.add_parser(
129
+ "list",
130
+ help="what is installed, and what the release offers",
131
+ description="What is installed in the registry, and what the release offers for it.",
132
+ )
133
+ dest_option(listing)
134
+ listing.add_argument("--platform", metavar="<os-arch>", choices=PLATFORMS)
135
+ lfrom = listing.add_mutually_exclusive_group()
136
+ lfrom.add_argument("--tag", metavar="<t>")
137
+ lfrom.add_argument("--url", metavar="<base>")
138
+
139
+ where = sub.add_parser(
140
+ "where",
141
+ help="the registry directory fetch would write to",
142
+ description="Print the registry directory fetch would write to (docs/fetch.md §1).",
143
+ )
144
+ where.add_argument("--platform", metavar="<os-arch>", choices=PLATFORMS)
145
+ return parser
146
+
147
+
148
+ def _cmd_fetch(args: argparse.Namespace) -> int:
149
+ from .fetch import fetch_lines
150
+
151
+ installed = fetch_lines(
152
+ args.lines,
153
+ all_lines=args.all,
154
+ dest=args.dest,
155
+ platform=args.platform,
156
+ url=args.url,
157
+ tag=args.tag,
158
+ lock=args.lock,
159
+ frozen=args.frozen,
160
+ force=args.force,
161
+ offline=args.offline,
162
+ progress=_say,
163
+ )
164
+ for directory in installed:
165
+ sys.stdout.write(f"{directory}\n")
166
+ sys.stdout.flush()
167
+ return EXIT_OK
168
+
169
+
170
+ def _cmd_verify(args: argparse.Namespace) -> int:
171
+ from .fetch import fetch_destination, verify_registry
172
+
173
+ registry = fetch_destination(args.dest)
174
+ report = verify_registry(registry)
175
+ if not report:
176
+ _say(f"chtypes: nothing installed under {registry}")
177
+ return EXIT_OK
178
+ failed = 0
179
+ for minor, directory, error in report:
180
+ if error is None:
181
+ sys.stdout.write(f"{minor:<8} ok {directory}\n")
182
+ else:
183
+ failed += 1
184
+ sys.stdout.write(f"{minor:<8} FAILED {directory}\n")
185
+ _say(f" {error}")
186
+ sys.stdout.flush()
187
+ if failed:
188
+ _say(f"chtypes: {failed} of {len(report)} installed line(s) FAILED verification")
189
+ return EXIT_VERIFY_FAILED
190
+ _say(f"chtypes: {len(report)} installed line(s) verified under {registry}")
191
+ return EXIT_OK
192
+
193
+
194
+ def _cmd_list(args: argparse.Namespace) -> int:
195
+ from .fetch import Fetcher, fetch_destination, host_platform, installed_lines
196
+
197
+ platform = args.platform or host_platform()
198
+ registry = fetch_destination(args.dest, platform=platform)
199
+ have = installed_lines(registry)
200
+ sys.stdout.write(f"installed ({registry}):\n")
201
+ if not have:
202
+ sys.stdout.write(" (nothing)\n")
203
+ for minor, (directory, manifest) in have.items():
204
+ sys.stdout.write(f" {minor:<8} {manifest.clickhouse_version:<18} {directory}\n")
205
+ sys.stdout.flush()
206
+
207
+ fetcher = Fetcher(dest=registry, platform=platform, url=args.url, tag=args.tag, progress=_say)
208
+ release = fetcher.release()
209
+ offered = release.offered(platform)
210
+ signed = f"signed by key {release.signed_by}" if release.signed_by else "UNSIGNED"
211
+ sys.stdout.write(f"release ({release.source}, {signed}) offers for {platform}:\n")
212
+ if not offered:
213
+ sys.stdout.write(" (nothing)\n")
214
+ for entry in offered:
215
+ state = "installed" if _installed(have, entry) else "not installed"
216
+ sys.stdout.write(
217
+ f" {entry.minor:<8} {entry.clickhouse_version:<18} {entry.file} [{state}]\n"
218
+ )
219
+ sys.stdout.flush()
220
+ return EXIT_OK
221
+
222
+
223
+ def _installed(have: dict[str, tuple[object, Manifest]], entry: ReleaseEntry) -> bool:
224
+ got = have.get(entry.minor)
225
+ return got is not None and got[1].clickhouse_version == entry.clickhouse_version
226
+
227
+
228
+ def _cmd_where(args: argparse.Namespace) -> int:
229
+ from .fetch import fetch_destination
230
+
231
+ sys.stdout.write(f"{fetch_destination(platform=args.platform)}\n")
232
+ return EXIT_OK
233
+
234
+
235
+ _COMMANDS = {
236
+ "fetch": _cmd_fetch,
237
+ "verify": _cmd_verify,
238
+ "list": _cmd_list,
239
+ "where": _cmd_where,
240
+ }
241
+
242
+
243
+ def main(argv: Sequence[str] | None = None) -> int:
244
+ """Run the CLI; returns the exit code (docs/fetch.md §6)."""
245
+ parser = build_parser()
246
+ args = parser.parse_args(list(sys.argv[1:] if argv is None else argv))
247
+ # The library's loud warning reaches stderr through the progress channel
248
+ # here; the `warnings` copy would print it a second time.
249
+ warnings.simplefilter("ignore", UnsignedArtifactWarning)
250
+ try:
251
+ return _COMMANDS[args.command](args)
252
+ except ValueError as exc: # a bad option combination or spelling: usage
253
+ _say(str(exc))
254
+ return EXIT_USAGE
255
+ except ArtifactError as exc:
256
+ _say(str(exc))
257
+ _say(f"chtypes: {exc.code}")
258
+ return _EXIT_FOR_CODE.get(exc.code, EXIT_VERIFY_FAILED)
259
+ except ChtypesError as exc:
260
+ _say(str(exc))
261
+ return EXIT_VERIFY_FAILED
262
+ except KeyboardInterrupt:
263
+ _say("chtypes: interrupted")
264
+ return 130
265
+
266
+
267
+ if __name__ == "__main__":
268
+ sys.exit(main())