chtypes 2.0.0.dev0__py3-none-any.whl → 2.0.0.dev2__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 CHANGED
@@ -59,7 +59,7 @@ from ._abi2._vocab import (
59
59
  Status,
60
60
  Verdict,
61
61
  )
62
- from ._input import BytesIn, Settings
62
+ from ._input import BytesIn, ServerProfile, Settings
63
63
  from ._setup import setup
64
64
  from .errors import (
65
65
  CODE_ARTIFACT_CORRUPT,
@@ -92,8 +92,8 @@ from .errors import (
92
92
  UnsupportedError,
93
93
  UsageError,
94
94
  )
95
- from .library import Block, Filter, Library, Schema, open_unverified
96
- from .registry import FetchOptions, Registry, Resolved, TrustedKey
95
+ from .library import Block, Filter, Library, Schema, Server, open_unverified
96
+ from .registry import FetchOptions, Registry, Resolved, TrustedKey, cache_root, search_dirs
97
97
  from .results import (
98
98
  BatchResult,
99
99
  BuildInfo,
@@ -111,6 +111,8 @@ from .results import (
111
111
  Header,
112
112
  RowResult,
113
113
  SchemaDescription,
114
+ SchemaReplicated,
115
+ SchemaServer,
114
116
  Span,
115
117
  Transform,
116
118
  Value,
@@ -172,6 +174,10 @@ __all__ = [
172
174
  "Schema",
173
175
  "SchemaDescription",
174
176
  "SchemaError",
177
+ "SchemaReplicated",
178
+ "SchemaServer",
179
+ "Server",
180
+ "ServerProfile",
175
181
  "Settings",
176
182
  "Source",
177
183
  "SourceForbiddenError",
@@ -186,7 +192,9 @@ __all__ = [
186
192
  "UsageError",
187
193
  "Value",
188
194
  "Verdict",
195
+ "cache_root",
189
196
  "open_unverified",
197
+ "search_dirs",
190
198
  "setup",
191
199
  ]
192
200
 
chtypes/__main__.py CHANGED
@@ -5,7 +5,7 @@
5
5
  [--frozen] [--offline] [--update] [--strict]
6
6
  chtypes verify [--cache <dir>] [--strict] re-verify every installed build
7
7
  chtypes list [--cache <dir>] [--offline] [--strict]
8
- chtypes where [--cache <dir>] [--strict] the cache root
8
+ chtypes where [--cache <dir>] [--strict] [--all] the cache root; --all: every search dir
9
9
 
10
10
  A spelling is `26.8`, `26.8.15` or `26.8.15.10`. Exit statuses come from the
11
11
  `errors` table of spec/fetch-v1/constants.json (generated into the fetch layer
@@ -137,7 +137,10 @@ def build_parser() -> argparse.ArgumentParser:
137
137
  fetch.add_argument(
138
138
  "--offline",
139
139
  action="store_true",
140
- help="never touch a source: installed and verified, or CHTYPES_ARTIFACT_MISSING",
140
+ help=(
141
+ "never touch a source: installed and verified, or CHTYPES_ARTIFACT_MISSING "
142
+ "(or set CHTYPES_OFFLINE=1)"
143
+ ),
141
144
  )
142
145
 
143
146
  verify = sub.add_parser(
@@ -154,11 +157,22 @@ def build_parser() -> argparse.ArgumentParser:
154
157
  )
155
158
  cache_option(listing)
156
159
  listing.add_argument(
157
- "--offline", action="store_true", help="list only what is installed; no network"
160
+ "--offline",
161
+ action="store_true",
162
+ help="list only what is installed; no network (or set CHTYPES_OFFLINE=1)",
158
163
  )
159
164
 
160
- where = sub.add_parser("where", help="the cache root", description="Print the cache root.")
165
+ where = sub.add_parser(
166
+ "where",
167
+ help="the cache root",
168
+ description="Print the cache root; with --all, every directory searched.",
169
+ )
161
170
  cache_option(where)
171
+ where.add_argument(
172
+ "--all",
173
+ action="store_true",
174
+ help="every directory searched, in order, the cache root first (one per line)",
175
+ )
162
176
  return parser
163
177
 
164
178
 
@@ -294,7 +308,7 @@ def _cmd_list(args: argparse.Namespace) -> int:
294
308
  sys.stdout.write(f"installed {r.version} {r.platform} {r.dir}\n")
295
309
  sys.stdout.flush()
296
310
  _say_notes(options)
297
- if not args.offline:
311
+ if not options.resolved_offline():
298
312
  for tag in _published_tags(options):
299
313
  sys.stdout.write(f"published {tag} support unknown\n")
300
314
  sys.stdout.flush()
@@ -306,6 +320,10 @@ def _cmd_where(args: argparse.Namespace) -> int:
306
320
  if options.resolved_strict():
307
321
  # Strict mode checks the root before naming it.
308
322
  probe_roots(search_roots(options.cache_dir, options.system_dirs), strict=True)
323
+ if args.all:
324
+ for directory in search_roots(options.cache_dir, options.system_dirs):
325
+ sys.stdout.write(f"{directory}\n")
326
+ return EXIT_OK
309
327
  sys.stdout.write(f"{resolve_cache_root(args.cache)}\n")
310
328
  return EXIT_OK
311
329
 
chtypes/_abi2/_decls.py CHANGED
@@ -1,4 +1,4 @@
1
- # GENERATED by scripts/abi-v1/gen.py --major 2 from spec/abi-v2/abi.json (CHS_ABI_FINGERPRINT sha256:8ec0c6941f000aa07bfb9fbfef20205d3ee0a588833856bd12270ba42912bfb3) — DO NOT EDIT # noqa: E501
1
+ # GENERATED by scripts/abi-v1/gen.py --major 2 from spec/abi-v2/abi.json (CHS_ABI_FINGERPRINT sha256:d87e91be2bbe75b985c38224268c2f7dc5d8236dab0a4b4d61357c6dfdc1a232) — DO NOT EDIT # noqa: E501
2
2
  """The Python binding's generated ABI v2 layer: ctypes signatures, the handle
3
3
  classes, the two-phase resolve functions and invoke_by_name(), the test-only
4
4
  dispatcher the conformance suite drives from tests/fixtures/abi-v2/cases.json.
@@ -19,7 +19,7 @@ from dataclasses import dataclass, field
19
19
  from . import _errmap
20
20
 
21
21
  CHS_ABI_VERSION = 2
22
- CHS_ABI_FINGERPRINT = "sha256:8ec0c6941f000aa07bfb9fbfef20205d3ee0a588833856bd12270ba42912bfb3"
22
+ CHS_ABI_FINGERPRINT = "sha256:d87e91be2bbe75b985c38224268c2f7dc5d8236dab0a4b4d61357c6dfdc1a232"
23
23
 
24
24
  # The description's stability: "unstable" while this generation is being
25
25
  # designed, so CHS_ABI_FINGERPRINT moves with every change, and "locked" after
@@ -61,6 +61,7 @@ _STATUS_OK = 0
61
61
  HANDLE_FREE: dict[str, str] = {
62
62
  "chs_buf": "chs_buf_free",
63
63
  "chs_error": "chs_error_free",
64
+ "chs_server": "chs_server_free",
64
65
  "chs_schema": "chs_schema_free",
65
66
  "chs_filter": "chs_filter_free",
66
67
  "chs_block": "chs_block_free",
@@ -102,6 +103,8 @@ ALL_SYMBOLS: tuple[str, ...] = (
102
103
  "chs_schema_create",
103
104
  "chs_schema_describe",
104
105
  "chs_schema_free",
106
+ "chs_server_create",
107
+ "chs_server_free",
105
108
  "chs_set_defaults",
106
109
  "chs_shutdown",
107
110
  "chs_type_validate",
@@ -174,6 +177,10 @@ class ErrorHandle(Handle):
174
177
  """A `chs_error` the library owns the free function for."""
175
178
 
176
179
 
180
+ class ServerHandle(Handle):
181
+ """A `chs_server` the library owns the free function for."""
182
+
183
+
177
184
  class SchemaHandle(Handle):
178
185
  """A `chs_schema` the library owns the free function for."""
179
186
 
@@ -189,6 +196,7 @@ class BlockHandle(Handle):
189
196
  _HANDLE_CLASS: dict[str, type[Handle]] = {
190
197
  "chs_buf": BufHandle,
191
198
  "chs_error": ErrorHandle,
199
+ "chs_server": ServerHandle,
192
200
  "chs_schema": SchemaHandle,
193
201
  "chs_filter": FilterHandle,
194
202
  "chs_block": BlockHandle,
@@ -324,12 +332,24 @@ _add(
324
332
  _P("out", "out_handle", "chs_buf"),
325
333
  _P("err", "out_error", "chs_error", nullable=True),
326
334
  )
335
+ _add(
336
+ "chs_server_create",
337
+ "status",
338
+ "chs_status",
339
+ _P("profile", "bytes_in", None),
340
+ _P("options", "bytes_in", None),
341
+ _P("out", "out_handle", "chs_server"),
342
+ _P("err", "out_error", "chs_error", nullable=True),
343
+ )
344
+ _add("chs_server_free", "void", None, _P("server", "handle", "chs_server", nullable=True))
327
345
  _add(
328
346
  "chs_schema_create",
329
347
  "status",
330
348
  "chs_status",
349
+ _P("server", "handle", "chs_server", nullable=True),
331
350
  _P("create_table", "bytes_in", None),
332
351
  _P("settings", "bytes_in", None),
352
+ _P("options", "bytes_in", None),
333
353
  _P("out", "out_handle", "chs_schema"),
334
354
  _P("err", "out_error", "chs_error", nullable=True),
335
355
  )
@@ -486,6 +506,18 @@ def resolve_abi_version(lib: ctypes.CDLL) -> Callable[..., object]:
486
506
  return fn
487
507
 
488
508
 
509
+ def resolve_build_info(lib: ctypes.CDLL) -> Callable[..., object]:
510
+ """Loader step 4's dlsym: `chs_build_info` ALONE, so the fingerprint can be
511
+ compared before step 6's full sweep (rule r6 beats a missing symbol,
512
+ public issue #537). Absent is MissingSymbol, the step 4 refusal."""
513
+ try:
514
+ fn = lib.chs_build_info
515
+ except AttributeError:
516
+ raise MissingSymbol("chs_build_info") from None
517
+ _set_signature(fn, FUNCTIONS["chs_build_info"])
518
+ return fn
519
+
520
+
489
521
  class Api:
490
522
  """Every described symbol, resolved and signature-typed, step 6
491
523
  (plan section 3.2). Constructed ONLY by resolve_all(): the constructor
@@ -703,19 +735,42 @@ class Api:
703
735
  self._check(status, err)
704
736
  return _read_buf(self, out.value)
705
737
 
738
+ def server_create(
739
+ self,
740
+ profile: bytes | None,
741
+ options: bytes | None,
742
+ ) -> ServerHandle:
743
+ """The generated call wrapper for chs_server_create (thread class any).
744
+
745
+ Raises the class the status maps to on a non-OK status."""
746
+ out = ctypes.c_void_p()
747
+ err = ctypes.c_void_p()
748
+ status = self._raw["chs_server_create"](
749
+ *_bytes_in(profile),
750
+ *_bytes_in(options),
751
+ ctypes.byref(out),
752
+ ctypes.byref(err),
753
+ )
754
+ self._check(status, err)
755
+ return ServerHandle(out.value, self._raw["chs_server_free"])
756
+
706
757
  def schema_create(
707
758
  self,
759
+ server: ServerHandle | None,
708
760
  create_table: bytes | None,
709
761
  settings: bytes | None,
762
+ options: bytes | None,
710
763
  ) -> SchemaHandle:
711
- """The generated call wrapper for chs_schema_create (thread class any).
764
+ """The generated call wrapper for chs_schema_create (thread class shared).
712
765
 
713
766
  Raises the class the status maps to on a non-OK status."""
714
767
  out = ctypes.c_void_p()
715
768
  err = ctypes.c_void_p()
716
769
  status = self._raw["chs_schema_create"](
770
+ None if server is None else server.value,
717
771
  *_bytes_in(create_table),
718
772
  *_bytes_in(settings),
773
+ *_bytes_in(options),
719
774
  ctypes.byref(out),
720
775
  ctypes.byref(err),
721
776
  )
@@ -925,17 +980,15 @@ _API_KEY = object()
925
980
 
926
981
 
927
982
  def resolve_all(lib: ctypes.CDLL) -> Api:
928
- """Every described symbol, in sorted, deterministic order -- chs_abi_version
929
- INCLUDED, even though _loader.py's step 3 (resolve_abi_version) already
930
- resolved it before this runs: the test-only invoke_by_name() dispatcher
931
- below needs every symbol reachable through ONE Api, handshake class
932
- included, and re-resolving an already-present symbol is harmless (by the
933
- time step 6 runs, step 3 has already proven it exists). The first symbol
934
- ctypes cannot dlsym raises MissingSymbol(name); _loader.py's step 6 turns
935
- that into "missing_symbol:<name>", matching every missing-<sym> stub
936
- variant (tests/fixtures/abi-v2/cases.json), chs_build_info and
937
- chs_clickhouse_version included -- loader step 4 needs chs_build_info
938
- already resolved, which is exactly why this sweep must run before it."""
983
+ """Every described symbol, in sorted, deterministic order -- the handshake
984
+ symbols INCLUDED, even though _loader.py's steps 3 and 4 (resolve_abi_version,
985
+ resolve_build_info) already resolved theirs before this runs: the test-only
986
+ invoke_by_name() dispatcher below needs every symbol reachable through ONE
987
+ Api, and re-resolving an already-present symbol is harmless. The first
988
+ symbol ctypes cannot dlsym raises MissingSymbol(name); _loader.py's step 6
989
+ turns that into "missing_symbol:<name>", matching every missing-<sym> stub
990
+ variant. Step 6 runs AFTER step 4's fingerprint comparison, so a library
991
+ with another fingerprint is refused with rule r6's message first."""
939
992
  raw: dict = {}
940
993
  for name in ALL_SYMBOLS:
941
994
  try:
chtypes/_abi2/_errmap.py CHANGED
@@ -1,4 +1,4 @@
1
- # GENERATED by scripts/abi-v1/gen.py --major 2 from spec/abi-v2/abi.json (CHS_ABI_FINGERPRINT sha256:8ec0c6941f000aa07bfb9fbfef20205d3ee0a588833856bd12270ba42912bfb3) — DO NOT EDIT # noqa: E501
1
+ # GENERATED by scripts/abi-v1/gen.py --major 2 from spec/abi-v2/abi.json (CHS_ABI_FINGERPRINT sha256:d87e91be2bbe75b985c38224268c2f7dc5d8236dab0a4b4d61357c6dfdc1a232) — DO NOT EDIT # noqa: E501
2
2
  """spec/abi-v2/sdk.json's error-mapping tables, as references to the
3
3
  public classes (python/src/chtypes/errors.py, re-exported by
4
4
  python/src/chtypes/_abi2/_errors.py). Only the MAPPING is generated: the
chtypes/_abi2/_loader.py CHANGED
@@ -9,15 +9,20 @@ wired up; `predicate` is passed through exactly as that seam promises.
9
9
  Every `chs_*` spelling in this module is forbidden by
10
10
  scripts/abi-v1/check-no-hand-decls.py (it is not a generated file), so this
11
11
  calls ONLY python/src/chtypes/_abi2/_decls.py's non-`chs_`-prefixed surface:
12
- `resolve_abi_version()`, `resolve_all()`, and the `Api` object they return.
13
-
14
- Steps 1-6 run in the order plan section 3.2 gives (with step 6's full
15
- resolve-all sweep run right after step 3, BEFORE step 4's
16
- `chs_build_info()` call -- step 4 needs that symbol already resolved, and
17
- `tests/fixtures/abi-v2/cases.json`'s `missing-chs_build_info` and
18
- `missing-chs_clickhouse_version` stub variants both expect the step 6
19
- `missing_symbol:<name>` reason, not a step 4 one, which is only possible if
20
- the full symbol sweep happens before chs_build_info is ever called). Step 7
12
+ `resolve_abi_version()`, `resolve_build_info()`, `resolve_all()`, and the `Api` object they return.
13
+
14
+ Steps 1-7 run in the documented order, the order Go, TypeScript and Rust
15
+ run: glibc (1), dlopen (2), `chs_abi_version` alone (3), `chs_build_info`
16
+ alone, parsed strictly and its fingerprint compared (4: rule r6's exact
17
+ message wins here), the signed-statement cross-check (5), and only then the
18
+ resolve-every-declared-symbol sweep (6). A library with another fingerprint
19
+ that also lacks a symbol this SDK declares (every fingerprint move that adds
20
+ an export meets one) is therefore refused with r6's message, never as
21
+ `missing_symbol:<name>` (public issue #537; the stub variant
22
+ `fingerprint-other-missing-symbol`). A symbol missing from step 4's own
23
+ `chs_build_info` is still `missing_symbol:chs_build_info`, and
24
+ `tests/fixtures/abi-v2/cases.json`'s other `missing-<sym>` variants still
25
+ expect `missing_symbol:<name>` from step 6. Step 7
21
26
  calls the image's once-per-image zone setup (empty zone = UTC unless the
22
27
  caller supplies one) and then its default settings.
23
28
  """
@@ -198,15 +203,15 @@ def _open(
198
203
  if version != _decls.CHS_ABI_VERSION:
199
204
  _refuse("abi_version", path, want=_decls.CHS_ABI_VERSION, got=version)
200
205
 
206
+ # Step 4: chs_build_info ALONE, parsed strictly, then the fingerprint
207
+ # compared. Rule r6 is decided here, before the step 6 sweep can name a
208
+ # missing symbol instead (#537).
201
209
  try:
202
- api = _decls.resolve_all(lib)
210
+ build_info_fn = _decls.resolve_build_info(lib)
203
211
  except _decls.MissingSymbol as e:
204
212
  _refuse(f"missing_symbol:{e.name}", path, want=e.name, got=None)
205
- # _refuse always raises; the following line only satisfies a type
206
- # checker that cannot see that. (Never reached.)
207
- raise
208
-
209
- raw = api.build_info()
213
+ raise # never reached: _refuse always raises
214
+ raw = build_info_fn()
210
215
  if raw is None:
211
216
  _refuse("build_info_malformed", path, got="chs_build_info() returned NULL")
212
217
  info = _parse_build_info(raw, path)
@@ -214,9 +219,17 @@ def _open(
214
219
  if info["abi_fingerprint"] != _decls.CHS_ABI_FINGERPRINT:
215
220
  _refuse_fingerprint(path, info.get("abi_fingerprint"))
216
221
 
222
+ # Step 5: the signed predicate against build_info.
217
223
  if not skip_step5:
218
224
  _cross_check(info, predicate, path)
219
225
 
226
+ # Step 6: resolve every declared symbol.
227
+ try:
228
+ api = _decls.resolve_all(lib)
229
+ except _decls.MissingSymbol as e:
230
+ _refuse(f"missing_symbol:{e.name}", path, want=e.name, got=None)
231
+ raise # never reached: _refuse always raises
232
+
220
233
  # Step 7: the once-per-image setup (docs/reference/bindings-v1.md section
221
234
  # 6): the image zone (empty = UTC), then the default settings when there
222
235
  # are any. After every handshake check and before any other call. A
chtypes/_abi2/_vocab.py CHANGED
@@ -1,4 +1,4 @@
1
- # GENERATED by scripts/abi-v1/gen.py --major 2 from spec/abi-v2/abi.json (CHS_ABI_FINGERPRINT sha256:8ec0c6941f000aa07bfb9fbfef20205d3ee0a588833856bd12270ba42912bfb3) — DO NOT EDIT # noqa: E501
1
+ # GENERATED by scripts/abi-v1/gen.py --major 2 from spec/abi-v2/abi.json (CHS_ABI_FINGERPRINT sha256:d87e91be2bbe75b985c38224268c2f7dc5d8236dab0a4b4d61357c6dfdc1a232) — DO NOT EDIT # noqa: E501
2
2
  """The Python binding's generated vocabularies, from spec/abi-v2/abi.json: the C
3
3
  enums (`Format`, `Status`), the document vocabularies (`Outcome`, `FilterOutcome`,
4
4
  `Verdict`, `DefaultKind`, `Reason`, `Source`, `DiscoverQueryParam`) with the facts the
chtypes/_decode.py CHANGED
@@ -47,6 +47,8 @@ from .results import (
47
47
  Header,
48
48
  RowResult,
49
49
  SchemaDescription,
50
+ SchemaReplicated,
51
+ SchemaServer,
50
52
  Span,
51
53
  Transform,
52
54
  Value,
@@ -449,10 +451,55 @@ def _column(obj: dict[str, Any], where: str) -> Column:
449
451
  )
450
452
 
451
453
 
454
+ def _string_map(obj: Mapping[str, Any], key: str, where: str) -> dict[str, str] | None:
455
+ """An object of plain JSON strings (a server's settings or macros, the
456
+ caller's own values given back): None when absent, a dict, even an empty
457
+ one, when present, so absent and `{}` stay apart."""
458
+ value = obj.get(key)
459
+ if value is None:
460
+ return None
461
+ inner = _obj(value, where, key)
462
+ out: dict[str, str] = {}
463
+ for name, item in inner.items():
464
+ if not isinstance(item, str):
465
+ raise _wrong(f"{where}.{key}", name, "a string", item)
466
+ out[name] = item
467
+ return out
468
+
469
+
470
+ def _server(obj: dict[str, Any], where: str) -> SchemaServer:
471
+ return SchemaServer(
472
+ timezone=_text(obj, "timezone", where),
473
+ settings=_string_map(obj, "settings", where) or {},
474
+ macros=_string_map(obj, "macros", where),
475
+ )
476
+
477
+
478
+ def _replicated(obj: dict[str, Any], where: str) -> SchemaReplicated:
479
+ return SchemaReplicated(
480
+ zookeeper_path=_bytes(obj, "zookeeper_path", where),
481
+ replica_name=_bytes(obj, "replica_name", where),
482
+ )
483
+
484
+
452
485
  def decode_schema_description(raw: bytes) -> SchemaDescription:
453
486
  where = "schema_description"
454
487
  obj = _obj(strict_loads(raw, where), where)
455
- return SchemaDescription(columns=_objects(obj, "columns", where, _column))
488
+ # Both are absent on a schema compiled without a server, so that document
489
+ # decodes exactly as it did before servers existed.
490
+ server = obj.get("server")
491
+ replicated = obj.get("replicated")
492
+ return SchemaDescription(
493
+ columns=_objects(obj, "columns", where, _column),
494
+ server=None
495
+ if server is None
496
+ else _server(_obj(server, where, "server"), f"{where}.server"),
497
+ replicated=(
498
+ None
499
+ if replicated is None
500
+ else _replicated(_obj(replicated, where, "replicated"), f"{where}.replicated")
501
+ ),
502
+ )
456
503
 
457
504
 
458
505
  def _discovered(obj: dict[str, Any], where: str) -> DiscoveredColumn:
chtypes/_input.py CHANGED
@@ -12,10 +12,11 @@ from __future__ import annotations
12
12
  import base64
13
13
  import json
14
14
  from collections.abc import Mapping, Sequence
15
+ from dataclasses import dataclass
15
16
 
16
17
  from .errors import misuse
17
18
 
18
- __all__ = ["BytesIn", "Settings"]
19
+ __all__ = ["BytesIn", "ServerProfile", "Settings"]
19
20
 
20
21
  BytesIn = bytes | str
21
22
  Settings = Mapping[str, str]
@@ -107,3 +108,43 @@ def columns_json(columns: Sequence[BytesIn] | None) -> bytes | None:
107
108
  except UnicodeDecodeError:
108
109
  names.append({"name_b64": base64.b64encode(raw).decode("ascii")})
109
110
  return _dump(names)
111
+
112
+
113
+ @dataclass(frozen=True, slots=True)
114
+ class ServerProfile:
115
+ """One ClickHouse server, as the library's `input:server_profile` describes
116
+ it. Every field is optional and passed through as given: the library
117
+ validates the zone with DateLUT, each setting with the server's own SET
118
+ check, and the macros with ClickHouse's own reader. The binding validates
119
+ nothing in it.
120
+
121
+ `timezone` is the server's zone, the one its `timezone()` returns; None or
122
+ "" leaves it undescribed (omitted) and the image zone applies. `settings` is
123
+ what the server's profile applies to every query, layered under a schema's
124
+ and a call's own; None omits it, and `{}` is sent as `{}`. `macros` is the
125
+ server's `<macros>`: None omits it, and the server's macros are UNKNOWN (a
126
+ schema whose engine reads one is declined); a mapping, even an empty one, is
127
+ sent (`{}` when empty) and is the server's COMPLETE set."""
128
+
129
+ timezone: str | None = None
130
+ settings: Settings | None = None
131
+ macros: Settings | None = None
132
+
133
+
134
+ def server_profile_json(profile: ServerProfile) -> bytes:
135
+ """The profile document: an empty or absent zone and an absent map are
136
+ omitted; a present map is sent as given, `{}` when empty. Absent `macros` is
137
+ not empty `macros`."""
138
+ if not isinstance(profile, ServerProfile):
139
+ raise TypeError(f"profile must be a ServerProfile, not {type(profile).__name__}")
140
+ doc: dict[str, object] = {}
141
+ if profile.timezone is not None:
142
+ if not isinstance(profile.timezone, str):
143
+ raise TypeError(f"timezone must be str, not {type(profile.timezone).__name__}")
144
+ if profile.timezone != "":
145
+ doc["timezone"] = profile.timezone
146
+ if profile.settings is not None:
147
+ doc["settings"] = _string_map(profile.settings, "settings")
148
+ if profile.macros is not None:
149
+ doc["macros"] = _string_map(profile.macros, "macros")
150
+ return _dump(doc)
@@ -69,6 +69,10 @@ DEV_KEY_ID: Final = "824345f9bcf8e5bf"
69
69
  DEV_KEY_HEX: Final = "5cd30c53c65a1ebc2d85836a41deb06661bb0ae7b658adb9eb116ec2db8e9b1c"
70
70
  # The dev channel's cache directory name: the default root's last element and
71
71
  # an explicit cache's subroot (rule r5).
72
+ # The environment twin of the offline option (public issue #528): CHTYPES_OFFLINE=1
73
+ # reads the cache only and makes no request. It names no source, so rule r6 holds.
74
+ # Not a generated constant: the generated set is the v1 fetch contract's.
75
+ ENV_OFFLINE_NAME: Final = "CHTYPES_OFFLINE"
72
76
  DEV_CACHE_DIR: Final = "v2-dev"
73
77
  # The verified.json schema the dev channel writes, and the only one it reads (rule r5).
74
78
  DEV_RECORD_SCHEMA: Final = 2
@@ -137,6 +137,8 @@ class Options:
137
137
  token: str | None = None
138
138
  trusted_keys: tuple[TrustedKey, ...] | None = None
139
139
  allow_unsigned: bool = False
140
+ # Offline mode is on when this is set OR CHTYPES_OFFLINE=1; neither turns
141
+ # the other off (public issue #528).
140
142
  offline: bool = False
141
143
  frozen: bool = False
142
144
  lock_path: str | os.PathLike[str] | None = None
@@ -185,6 +187,9 @@ class Options:
185
187
  def resolved_platform(self) -> str:
186
188
  return self.platform or detect_host_platform()
187
189
 
190
+ def resolved_offline(self) -> bool:
191
+ return self.offline or os.environ.get(_channel.ENV_OFFLINE_NAME) == "1"
192
+
188
193
  def resolved_strict(self) -> bool:
189
194
  if self.strict_cache is not None:
190
195
  return self.strict_cache
@@ -1012,7 +1017,7 @@ def _ensure(
1012
1017
  request: Request, platform_key: str, options: Options, lock: Lock | None, roots: Sequence[Path]
1013
1018
  ) -> Resolved:
1014
1019
  try:
1015
- if options.offline:
1020
+ if options.resolved_offline():
1016
1021
  resolved = resolve_installed(request, platform_key, options)
1017
1022
  if resolved is None:
1018
1023
  raise ArtifactMissingError(
@@ -49,20 +49,30 @@ def _expand_template(template: str) -> str:
49
49
  return string.Template(out).safe_substitute(mapping)
50
50
 
51
51
 
52
+ def _absolute(p: str | os.PathLike[str]) -> Path:
53
+ """A relative cache directory is the process cwd's at this call, resolved here
54
+ once so every path derived from it is absolute (fetch-v1.md section 9, public
55
+ issue #541). Lexical, like Go's `filepath.Abs`: no symlink is followed."""
56
+ return Path(os.path.abspath(p))
57
+
58
+
52
59
  def resolve_cache_root(cache_dir: str | os.PathLike[str] | None = None) -> Path:
53
60
  """`CHTYPES_CACHE` (or an explicit override) names the cache directory
54
61
  ITSELF, not a parent `chtypes/` to append to (constants: `cache.root_template`).
55
62
 
56
63
  The active fetch contract decides the rest (_channel.py): under the dev
57
- channel an explicit cache is used through its subroot `<cache>/v2-dev`,
58
- never as a whole layout, which a 1.x binding uses (rule r5, a MUST), and
59
- the default root is `${XDG_CACHE_HOME:-~/.cache}/chtypes/v2-dev`."""
64
+ channel an explicit cache, from the environment or the option, is used
65
+ through its subroot `<cache>/v2-dev`, never as a whole layout, which a 1.x
66
+ binding uses (rule r5, a MUST), and the default root is
67
+ `${XDG_CACHE_HOME:-~/.cache}/chtypes/v2-dev`. The result is absolute (#541)."""
60
68
  channel = _channel.active()
61
- explicit = cache_dir if cache_dir is not None else (os.environ.get(C.ENV_CACHE_NAME) or None)
62
- if explicit is not None:
63
- root = Path(explicit)
69
+ explicit = os.fspath(cache_dir) if cache_dir is not None else ""
70
+ if not explicit:
71
+ explicit = os.environ.get(C.ENV_CACHE_NAME) or ""
72
+ if explicit:
73
+ root = _absolute(explicit)
64
74
  return root / channel.subroot if channel.subroot else root
65
- return Path(_expand_template(C.CACHE_ROOT_TEMPLATE)).parent / channel.root_leaf
75
+ return _absolute(Path(_expand_template(C.CACHE_ROOT_TEMPLATE)).parent / channel.root_leaf)
66
76
 
67
77
 
68
78
  def cache_root(cache_dir: str | os.PathLike[str] | None = None) -> Path:
chtypes/library.py CHANGED
@@ -23,9 +23,11 @@ from ._abi2._vocab import EXPORT_NONE, DocFlags, Format
23
23
  from ._guard import CloseGuard
24
24
  from ._input import (
25
25
  BytesIn,
26
+ ServerProfile,
26
27
  Settings,
27
28
  body_bytes,
28
29
  columns_json,
30
+ server_profile_json,
29
31
  settings_json,
30
32
  string_map_json,
31
33
  to_bytes,
@@ -40,7 +42,7 @@ from .results import (
40
42
  SchemaDescription,
41
43
  )
42
44
 
43
- __all__ = ["Block", "Filter", "Library", "Schema", "open_unverified"]
45
+ __all__ = ["Block", "Filter", "Library", "Schema", "Server", "open_unverified"]
44
46
 
45
47
  # One image per file, process-wide: keyed on the realpath's device and inode, so
46
48
  # two registries, two spellings or a hardlink of one artifact share one image
@@ -159,6 +161,19 @@ class _Handle:
159
161
  self.close()
160
162
 
161
163
 
164
+ class Server(_Handle):
165
+ """One ClickHouse server (`chs_server`), made by `Library.new_server` and
166
+ passed to `Library.compile_table(server=)`. Immutable once made, so safe to
167
+ share across threads: any number of threads may compile tables on one server
168
+ at once. `close` releases the caller's reference, after the compiles already
169
+ using the server have returned; a schema compiled on the server holds its own
170
+ counted reference inside the library, so a server and its schemas close in
171
+ any order."""
172
+
173
+ __slots__ = ()
174
+ _what = "Server"
175
+
176
+
162
177
  class Block(_Handle):
163
178
  """A body parsed once, for evaluating many filters over it. It holds a counted
164
179
  reference to its schema inside the library."""
@@ -388,16 +403,45 @@ class Library:
388
403
  """Live handle counts per kind in this image (a diagnostic)."""
389
404
  return _decode.decode_live_handles(self._api.live_handles())
390
405
 
406
+ def new_server(self, profile: ServerProfile) -> Server:
407
+ """Describe one ClickHouse server from a profile (`chs_server_create`). The
408
+ library validates the whole profile here, once: a zone DateLUT cannot
409
+ load, or a setting the server's SET check refuses, is the library's own
410
+ refusal (`SchemaError`); a malformed macro set is a `UsageError`; a build
411
+ that will not describe the profile declines it (`UnsupportedError`). The
412
+ binding checks none of it first. No server option is defined yet, so the
413
+ options document is empty."""
414
+ document = server_profile_json(profile)
415
+ return Server(self, self._api.server_create(document, None))
416
+
391
417
  def compile_table(
392
418
  self,
393
419
  create_table: BytesIn,
394
420
  *,
395
421
  settings: Settings | None = None,
396
422
  session_timezone: str | None = None,
423
+ server: Server | None = None,
397
424
  ) -> Schema:
398
425
  """Compile exactly one `CREATE TABLE` statement. `settings` is the profile;
399
- a compiled type always takes the image zone, never the profile's."""
426
+ a compiled type always takes the image zone, never the profile's.
427
+
428
+ `server` (from `new_server`) compiles the table on that server: the
429
+ schema's home zone is the server's timezone, the server's settings layer
430
+ under the schema's own, and a Replicated engine's ZooKeeper path and
431
+ replica name expand the server's macros. A closed server is a
432
+ `UsageError`, raised before any call; a server from another library is
433
+ that library's own refusal, also a `UsageError`. `None` is no server: the
434
+ schema is on the image's own server."""
400
435
  statement = to_bytes(create_table, "create_table")
401
436
  settings_bytes = settings_json(settings, session_timezone)
402
- handle = self._api.schema_create(statement, settings_bytes)
437
+ if server is not None and not isinstance(server, Server):
438
+ raise TypeError(f"server must be a Server, not {type(server).__name__}")
439
+ # No schema option is defined yet: none, which the library reads as `{}`.
440
+ # A closed server is refused here, before the call, because its freed
441
+ # handle would cross as NULL and the library would compile on the
442
+ # image's server instead of refusing.
443
+ with _hold(*(() if server is None else (server._guard,))):
444
+ handle = self._api.schema_create(
445
+ None if server is None else server._handle, statement, settings_bytes, None
446
+ )
403
447
  return Schema(self, handle)
chtypes/registry.py CHANGED
@@ -32,10 +32,11 @@ from ._ocifetch import _constants as _fetch_constants
32
32
  from ._ocifetch._dsse import TrustedKey
33
33
  from ._ocifetch._ensure import detect_host_platform, missing_notes, with_notes
34
34
  from ._ocifetch._errors import FetchError
35
+ from ._ocifetch._layout import resolve_cache_root, search_roots
35
36
  from ._ocifetch._oci import version_within_request
36
37
  from .library import Library, open_image
37
38
 
38
- __all__ = ["FetchOptions", "Registry", "Resolved", "TrustedKey"]
39
+ __all__ = ["FetchOptions", "Registry", "Resolved", "TrustedKey", "cache_root", "search_dirs"]
39
40
 
40
41
  _TRUTHY = ("1", "true", "yes", "on")
41
42
 
@@ -80,6 +81,8 @@ class FetchOptions:
80
81
  token: str | None = None
81
82
  trusted_keys: Sequence[TrustedKey] | None = None
82
83
  allow_unsigned: bool | None = None
84
+ # Offline reads the cache only and makes no request. It is on when this is
85
+ # set OR CHTYPES_OFFLINE=1; neither turns the other off (public issue #528).
83
86
  offline: bool = False
84
87
  frozen: bool = False
85
88
  lock_path: str | os.PathLike[str] | None = None
@@ -120,6 +123,27 @@ class FetchOptions:
120
123
  )
121
124
 
122
125
 
126
+ def cache_root(options: FetchOptions | None = None) -> Path:
127
+ """The cache root a fetch, list or `chtypes where` with `options` would use:
128
+ `options.cache_dir`, else `CHTYPES_CACHE` (each used through its `v2-dev`
129
+ subroot, spec/abi-v2/docs.md rule r5), else
130
+ `${XDG_CACHE_HOME:-~/.cache}/chtypes/v2-dev`.
131
+
132
+ It is the fetch layer's own resolution, `search_dirs(options)[0]`. It
133
+ creates nothing and reads no cache."""
134
+ return resolve_cache_root((options or FetchOptions()).cache_dir)
135
+
136
+
137
+ def search_dirs(options: FetchOptions | None = None) -> tuple[Path, ...]:
138
+ """Every directory a lookup reads for installed builds, in the order it reads
139
+ them: the cache root first, then each read-only system directory
140
+ (`options.system_dirs`; `None` is the built-in list, an empty sequence none).
141
+ The order is the fetch layer's own, and on a tie the earlier directory wins.
142
+ It creates nothing and touches no file."""
143
+ o = options or FetchOptions()
144
+ return search_roots(o.cache_dir, o.system_dirs)
145
+
146
+
123
147
  def _wrap(exc: FetchError) -> errors.ArtifactError:
124
148
  """The fetch layer's error as the public class of its code, one family with
125
149
  the loader's. The original stays as `__cause__`."""
chtypes/results.py CHANGED
@@ -11,6 +11,7 @@ vendored reader does not know a fact, the document says `null` and the field is
11
11
 
12
12
  from __future__ import annotations
13
13
 
14
+ from collections.abc import Mapping
14
15
  from dataclasses import dataclass
15
16
 
16
17
  from ._abi2._vocab import DefaultKind, FilterOutcome, Outcome, Verdict
@@ -32,6 +33,8 @@ __all__ = [
32
33
  "Header",
33
34
  "RowResult",
34
35
  "SchemaDescription",
36
+ "SchemaReplicated",
37
+ "SchemaServer",
35
38
  "Span",
36
39
  "Transform",
37
40
  "Value",
@@ -210,9 +213,38 @@ class Column:
210
213
  default_expr: bytes
211
214
 
212
215
 
216
+ @dataclass(frozen=True, slots=True)
217
+ class SchemaServer:
218
+ """The server a schema was compiled on, as the library holds it. Its strings
219
+ are the caller's own profile values given back, so they are plain text, not
220
+ data-derived. `timezone` is the zone the schema's types bind: the profile's,
221
+ or else the image zone. `settings` is the profile's settings (`{}` when it
222
+ gave none). `macros` is None exactly when the profile carried no macros
223
+ (unknown), and a mapping, even an empty one, when it did (the complete set)."""
224
+
225
+ timezone: str
226
+ settings: Mapping[str, str]
227
+ macros: Mapping[str, str] | None
228
+
229
+
230
+ @dataclass(frozen=True, slots=True)
231
+ class SchemaReplicated:
232
+ """What ClickHouse's own TableZnodeInfo resolved for a Replicated engine on a
233
+ server, fully expanded. Both expand DDL bytes, so both are `bytes`."""
234
+
235
+ zookeeper_path: bytes
236
+ replica_name: bytes
237
+
238
+
213
239
  @dataclass(frozen=True, slots=True)
214
240
  class SchemaDescription:
241
+ """A schema's columns, in declared order, and the server it was compiled on.
242
+ `server` is None exactly when it was compiled without one; `replicated` is
243
+ None when the document carries none."""
244
+
215
245
  columns: tuple[Column, ...]
246
+ server: SchemaServer | None = None
247
+ replicated: SchemaReplicated | None = None
216
248
 
217
249
 
218
250
  @dataclass(frozen=True, slots=True)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: chtypes
3
- Version: 2.0.0.dev0
3
+ Version: 2.0.0.dev2
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
@@ -20,6 +20,8 @@ Description-Content-Type: text/markdown
20
20
  # chtypes — Python SDK
21
21
 
22
22
  > **2.0.0-dev: UNSTABLE, staging only, not for production.** This is the ABI v2 development binding (public issue #511). It speaks ABI v2's unstable description, pins its dev fingerprint and refuses a library with any other ("update your dev SDK"). It fetches only from the staging dev channel (`https://registry-staging.wavehouse.dev/chtypes/v2-dev`) and trusts only the staging key; `CHTYPES_ARTIFACTS_URL`, `CHTYPES_TRUSTED_KEYS` and `CHTYPES_ALLOW_UNSIGNED`, and the `bases`, `trusted_keys` and `allow_unsigned` fetch options, are ignored, each with one warning; `--lock`, `--frozen` and `--update`, and the `frozen`, `lock_path`, `lock_write` and `update` options, are refused, because a dev build is replaceable and a superseded one expires after 14 days. Its cache is `${XDG_CACHE_HOME:-~/.cache}/chtypes/v2-dev`, or `<CHTYPES_CACHE>/v2-dev` under an explicit cache, which no 1.x SDK reads. Every vocabulary keeps a value it does not list as its `unknown(n)` member (`Outcome("x")`, whose `known` is `False`) instead of failing the document. The rules are r1 to r6 of [`docs/reference/abi-v2.md`](../docs/reference/abi-v2.md). For production, use the 1.x package: `pip install "chtypes<2"`.
23
+ >
24
+ > An explicit cache directory gets the `v2-dev` subroot whether it comes from `CHTYPES_CACHE` or from `FetchOptions(cache_dir=...)`: the layout is under `<cache_dir>/v2-dev`, never in the directory itself, so code that inspects or pre-populates a cache looks there; `chtypes.cache_root` and `chtypes.search_dirs` return the resolved paths. `CHTYPES_OFFLINE=1` is the environment twin of `FetchOptions(offline=True)` and `--offline`: the cache only, no request.
23
25
 
24
26
  **If this row were inserted into this table on this ClickHouse version, what would happen?** chtypes answers with ClickHouse's own code: the real C++ type machinery, vendored per release into a native library behind the frozen `chs_*` C ABI and reached here through stdlib `ctypes`. Nothing semantic is reimplemented, so _"what does ClickHouse do with `256` into a `UInt8`?"_ is answered by ClickHouse rather than by a model of it. One peer binding among `{go, python, ts, rust}` — no language is privileged, and all four give one answer.
25
27
 
@@ -41,18 +43,18 @@ uv add "chtypes>=2.0.0.dev0" # or: pip install "chtypes>=2.0.0.dev0"
41
43
  import chtypes
42
44
  from chtypes import Format, Registry
43
45
 
44
- chtypes.setup(timezone="UTC") # once, before the first open (optional)
46
+ chtypes.setup(timezone="UTC") # once, before the first open (optional)
45
47
  registry = Registry(autofetch=True)
46
- library = registry.for_version("26.8") # a release line or an exact version
48
+ library = registry.for_version("26.8") # a release line or an exact version
47
49
 
48
50
  ddl = b"CREATE TABLE t (x UInt8, ts DateTime DEFAULT now()) ENGINE = Memory"
49
51
  with library.compile_table(ddl) as schema:
50
52
  batch = schema.rows(Format.JSON_EACH_ROW, b'{"x":256}\n')
51
53
 
52
54
  row = batch.rows[0]
53
- print(batch.outcome) # accepted
54
- print(row.values[0].text) # b'0' what would actually be stored
55
- print(row.transformed[0].reason) # overflow_wrap which is the product
55
+ print(batch.outcome) # accepted
56
+ print(row.values[0].text) # b'0' what would actually be stored
57
+ print(row.transformed[0].reason) # overflow_wrap which is the product
56
58
  ```
57
59
 
58
60
  The row is **accepted** and `256` is silently stored as `0`. That report — `transformed` — is why chtypes exists. Names, SQL, messages and renderings come back as `bytes`, never decoded for you: a column name that is not valid UTF-8 round-trips exactly.
@@ -1,37 +1,37 @@
1
- chtypes/__init__.py,sha256=UkNZYe0u3tqvvKDIsp4D0fPHTMLeuKtp60hs_TOkn_k,5715
2
- chtypes/__main__.py,sha256=m75yDXRqTSyqNZJ-p3TTl_5knMkaKyurjxyd8gZ1OLw,13474
3
- chtypes/_decode.py,sha256=TQX9LyzqvVus5gHRUoFI9x9ELYOaf33hcan2jZtC6ko,19507
1
+ chtypes/__init__.py,sha256=aIVdrUX79p18amwUcV27FItM4SA1n-LlipJHLopgHgU,5919
2
+ chtypes/__main__.py,sha256=XYGNTpTLXI1AUp7srvuNVv2SZAcfwLaXpoXRVInyhF4,14030
3
+ chtypes/_decode.py,sha256=i_nHXx6QKCbXej0GJMnfq_utRE-Oq7SiqPNETOmhFZ4,21149
4
4
  chtypes/_ed25519.py,sha256=uwaAKWvgoc936e1ZfgLrtR47W87WWCL541kFaqBXrB0,4141
5
5
  chtypes/_guard.py,sha256=zTtwprCS0XvGWY7GDozo-IP6GwjMAWl-o-AjShdCSVI,1985
6
- chtypes/_input.py,sha256=GonZVBB7gOPXEoDBDjEExiZXMIW7gbTIFAopvcxqnTs,4039
6
+ chtypes/_input.py,sha256=i4OUmgmHxx618g0DVFYy8LmHhggfBT03NyMxH3ptZBI,6020
7
7
  chtypes/_setup.py,sha256=n84T0hFmXu_fdnXQIFhk3dMVL0_-yhfdRZsqCXTLzzg,6114
8
8
  chtypes/errors.py,sha256=9qv94rxbe7hm7lSVFG33Qri82FwDtCY-EnJiwnMfkp8,9501
9
- chtypes/library.py,sha256=gG4MN2zwfiLOFn0k-nZsZ2vAaBQBNe97DBbUbTtus7k,15426
9
+ chtypes/library.py,sha256=G6DJFiRuv8--JARxfiSNEqeFLkafaMs_n9nG-IwoC74,17878
10
10
  chtypes/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
11
- chtypes/registry.py,sha256=Wra-DzLYrWPHsyPlsTPOmPvuc1as5jPEfA7gxVYHPs4,12444
12
- chtypes/results.py,sha256=JXHgnd06jXGbNGfHToBO0tRxUPy1Q3urOsH4EXax3-0,8062
11
+ chtypes/registry.py,sha256=HTrzS_3HjOkH4rU90AVMNfFLsLoaut0jgNlt_BJ28QY,13727
12
+ chtypes/results.py,sha256=AI0QALKW-NcgM5qOUxUMesFXd4BcsujzuiaG8ejBelg,9330
13
13
  chtypes/_abi2/__init__.py,sha256=d3i7_iUDMFT1jg49vRQz1SMH6oseYZHUR0oPt6aSTiY,1042
14
- chtypes/_abi2/_decls.py,sha256=qlbkMEzXwjp7mZta5-NXVHU2DTEDqTd1Qzwc4rP0RKA,36677
15
- chtypes/_abi2/_errmap.py,sha256=sgx97MNwOP3zgnonkcH8OKFOHiClaecL2AMOc3BKBqc,2886
14
+ chtypes/_abi2/_decls.py,sha256=c2xuqG0rFgSthOO1FHEGzPhr4ldKQzoOdRxyXaEslq8,38454
15
+ chtypes/_abi2/_errmap.py,sha256=L6WmPSXqqEoNchX1jrfWsbogthm5crwF-8tQBly330E,2886
16
16
  chtypes/_abi2/_errors.py,sha256=wRE9M0oYtJz_9izQqpEBh5571M_1QzGWBoJ3pJTykWw,761
17
- chtypes/_abi2/_loader.py,sha256=RuhC9COv3lWA67SHYd7UAPdg7FsjIVkXgWNg2_Lr0HM,11717
18
- chtypes/_abi2/_vocab.py,sha256=X_3ebytcvCklP2iJfHMaLTrvwQVz3FmJP1dxp6CWTCM,14663
17
+ chtypes/_abi2/_loader.py,sha256=VMWrbWMp9Uq3K-7a219lfqAmSmBlD2hcB6WDJgG8XG0,12514
18
+ chtypes/_abi2/_vocab.py,sha256=6UyJwWDnp_R59YMDF7ZWuLchVU7Sq8asmQChH21QEDw,14663
19
19
  chtypes/_ocifetch/__init__.py,sha256=enfPOKkqP4kqAKqckBGWXN1OQsiYo9ngDP-wzG0zn2I,1484
20
- chtypes/_ocifetch/_channel.py,sha256=aumM_S8X7_H2GXhaDZwoSTcg-m9_k4QSu5XTqSWZ6EQ,11836
20
+ chtypes/_ocifetch/_channel.py,sha256=p6FbWbrCBTyXmYjdP4UOkkjj02aEkn8Em9EE6xuUZ1Y,12121
21
21
  chtypes/_ocifetch/_constants.py,sha256=UdOGIShDXaDP13NGbuftKZRE4u7VK8JfZmoeChw4GMU,5467
22
22
  chtypes/_ocifetch/_dsse.py,sha256=0RaFJaG1jhXr1uPfuHxm7yAn6viNzYhfCRUf-sW5SvQ,9333
23
- chtypes/_ocifetch/_ensure.py,sha256=lVD_Ysg-IRNjJ_EuIHRz3fnfxy_j5Ib7M_ouLifZCfw,60521
23
+ chtypes/_ocifetch/_ensure.py,sha256=NG_Fhg-UkafZZC5oHnG4f61W9-sWp9jE8gXOWEWJWrs,60772
24
24
  chtypes/_ocifetch/_errors.py,sha256=u0eIdrr3g00ugy8ZZoAXYIgUuZgEs6eMue2QkcWXC6w,6595
25
25
  chtypes/_ocifetch/_faults.py,sha256=HpoB9tJW7aLRiJdCFnspzkwhpFATZ6f6hGi49Mbfnhs,7609
26
26
  chtypes/_ocifetch/_goldens.py,sha256=8MicgeJSRd4WZkaEsdujRnOa7twQvbT-wfSMPZaD-ms,4191
27
27
  chtypes/_ocifetch/_http.py,sha256=vz3i0p5I8ch8SONpoEab0Vb5uX9MvPOHjfcaGU03bgo,22807
28
- chtypes/_ocifetch/_layout.py,sha256=SwaBgxwvYh5zGyHTZrP4-icAzhqenQSSUMIMKDlEn2A,20842
28
+ chtypes/_ocifetch/_layout.py,sha256=ezEoVrsSMhvDhQ4U04hsGdu1-_ISZQ4V6zg4gGIChTQ,21300
29
29
  chtypes/_ocifetch/_lock.py,sha256=14_uex9qXdOht-5FSrSvFvd99-gB7DmI3bDuxXs4YU8,5530
30
30
  chtypes/_ocifetch/_oci.py,sha256=lmrlykbyQev55uFDn8UBwXyMLthd3-pQq3xIyfXCUXY,12704
31
31
  chtypes/_ocifetch/_referrers.py,sha256=J2b4gN6vryz_IzYHxrd2Tp-tQO9Cz766rZtKgCVcKqA,6008
32
32
  chtypes/_ocifetch/_unpack.py,sha256=Cn4-up63KL63L8pnkS5zwJ6VbbHPSigpCnmOdcHJ4RI,5609
33
- chtypes-2.0.0.dev0.dist-info/METADATA,sha256=RlGXGLQU7lKwz8_nNkEyQQOx8gbef-84qlB_kNdwVgM,10213
34
- chtypes-2.0.0.dev0.dist-info/WHEEL,sha256=W3fkpkm7-wf9vBI5Z-7s0eWkeM-spu78I8Neb98DeEg,87
35
- chtypes-2.0.0.dev0.dist-info/entry_points.txt,sha256=TdNcluWBR0VOt5mVFROsp-NiVPAdR3EHo0UsHlMC0r0,50
36
- chtypes-2.0.0.dev0.dist-info/licenses/LICENSE,sha256=z8d0m5b2O9McPEK1xHG_dWgUBT6EfBDz6wA0F7xSPTA,11358
37
- chtypes-2.0.0.dev0.dist-info/RECORD,,
33
+ chtypes-2.0.0.dev2.dist-info/METADATA,sha256=ORRyWPTzbIQl_tI3z1ZSAWNYwcOesmInGA0xvW6Eb5o,10632
34
+ chtypes-2.0.0.dev2.dist-info/WHEEL,sha256=W3fkpkm7-wf9vBI5Z-7s0eWkeM-spu78I8Neb98DeEg,87
35
+ chtypes-2.0.0.dev2.dist-info/entry_points.txt,sha256=TdNcluWBR0VOt5mVFROsp-NiVPAdR3EHo0UsHlMC0r0,50
36
+ chtypes-2.0.0.dev2.dist-info/licenses/LICENSE,sha256=z8d0m5b2O9McPEK1xHG_dWgUBT6EfBDz6wA0F7xSPTA,11358
37
+ chtypes-2.0.0.dev2.dist-info/RECORD,,