chtypes 2.0.0.dev3__py3-none-any.whl → 2.0.0.dev4__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
@@ -48,6 +48,7 @@ from importlib.metadata import version as _package_version
48
48
 
49
49
  from ._abi2._vocab import (
50
50
  EXPORT_NONE,
51
+ DeclinedTier,
51
52
  DefaultKind,
52
53
  DiscoverQueryParam,
53
54
  DocFlags,
@@ -72,6 +73,7 @@ from .errors import (
72
73
  CODE_CACHE_UNUSABLE,
73
74
  CODE_SOURCE_FORBIDDEN,
74
75
  CODE_SOURCE_INCOMPATIBLE,
76
+ CODE_SOURCE_RETIRED,
75
77
  CODE_SOURCE_UNAUTHORIZED,
76
78
  CODE_SOURCE_UNREACHABLE,
77
79
  ArtifactCorruptError,
@@ -88,6 +90,7 @@ from .errors import (
88
90
  SchemaError,
89
91
  SourceForbiddenError,
90
92
  SourceIncompatibleError,
93
+ SourceRetiredError,
91
94
  SourceUnauthorizedError,
92
95
  SourceUnreachableError,
93
96
  UnsupportedError,
@@ -102,6 +105,7 @@ from .results import (
102
105
  Capabilities,
103
106
  Column,
104
107
  Computed,
108
+ DeclinedSetting,
105
109
  DiscoveredColumn,
106
110
  Discovery,
107
111
  EngineCell,
@@ -130,6 +134,7 @@ __all__ = [
130
134
  "CODE_CACHE_UNUSABLE",
131
135
  "CODE_SOURCE_FORBIDDEN",
132
136
  "CODE_SOURCE_INCOMPATIBLE",
137
+ "CODE_SOURCE_RETIRED",
133
138
  "CODE_SOURCE_UNAUTHORIZED",
134
139
  "CODE_SOURCE_UNREACHABLE",
135
140
  "EXPORT_NONE",
@@ -151,6 +156,8 @@ __all__ = [
151
156
  "ChtypesError",
152
157
  "Column",
153
158
  "Computed",
159
+ "DeclinedSetting",
160
+ "DeclinedTier",
154
161
  "DefaultKind",
155
162
  "DiscoverQueryParam",
156
163
  "DiscoveredColumn",
@@ -186,6 +193,7 @@ __all__ = [
186
193
  "Source",
187
194
  "SourceForbiddenError",
188
195
  "SourceIncompatibleError",
196
+ "SourceRetiredError",
189
197
  "SourceUnauthorizedError",
190
198
  "SourceUnreachableError",
191
199
  "Span",
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:4129dccc3f4e698dedb0e99cae413f5240085f633c5a1244e2c55bf7a7bb6c3a) — 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:28c7445ce6b5c6d0ae33177fd60eb7b3b5654a966c842b8d812aea622824dc72) — 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:4129dccc3f4e698dedb0e99cae413f5240085f633c5a1244e2c55bf7a7bb6c3a"
22
+ CHS_ABI_FINGERPRINT = "sha256:28c7445ce6b5c6d0ae33177fd60eb7b3b5654a966c842b8d812aea622824dc72"
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
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:4129dccc3f4e698dedb0e99cae413f5240085f633c5a1244e2c55bf7a7bb6c3a) — 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:28c7445ce6b5c6d0ae33177fd60eb7b3b5654a966c842b8d812aea622824dc72) — 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/_vocab.py CHANGED
@@ -1,10 +1,10 @@
1
- # GENERATED by scripts/abi-v1/gen.py --major 2 from spec/abi-v2/abi.json (CHS_ABI_FINGERPRINT sha256:4129dccc3f4e698dedb0e99cae413f5240085f633c5a1244e2c55bf7a7bb6c3a) — 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:28c7445ce6b5c6d0ae33177fd60eb7b3b5654a966c842b8d812aea622824dc72) — 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
- `Verdict`, `DefaultKind`, `MergeReason`, `Reason`, `Source`, `DiscoverQueryParam`) with the
5
- facts the description attaches to each value (`Format.ch_name`, `Reason` lossy,
6
- `Source` is_stored, `Verdict.answered`), and the `DocFlags` groups. No other file in
7
- this package keeps a copy of any of it.
4
+ `Verdict`, `DefaultKind`, `MergeReason`, `DeclinedTier`, `Reason`, `Source`,
5
+ `DiscoverQueryParam`) with the facts the description attaches to each value
6
+ (`Format.ch_name`, `Reason` lossy, `Source` is_stored, `Verdict.answered`), and the
7
+ `DocFlags` groups. No other file in this package keeps a copy of any of it.
8
8
 
9
9
  Rule r3 (spec/abi-v2/docs.md): every vocabulary has an unknown(n) member, and a
10
10
  value the description does not list reads as it, carrying the raw value, for that
@@ -249,6 +249,31 @@ class MergeReason(StrEnum):
249
249
  return cls(text)
250
250
 
251
251
 
252
+ class DeclinedTier(StrEnum):
253
+ """The WHERE-settings tier of a setting a server profile sets and this build's
254
+ filters do not honor in a WHERE, as a `DeclinedSetting` reports it."""
255
+
256
+ PREDICATE = "predicate"
257
+ PREDICATE_UNFLIPPED = "predicate-unflipped"
258
+ RESULT_CONTENT = "result-content"
259
+
260
+ @classmethod
261
+ def _missing_(cls, value: object) -> DeclinedTier | None:
262
+ return _unknown_str(cls, value)
263
+
264
+ @property
265
+ def known(self) -> bool:
266
+ """Whether the description lists this value: False for exactly the unknown(n)
267
+ member an unlisted value reads as (rule r3)."""
268
+ return type(self)._value2member_map_.get(self._value_) is self
269
+
270
+ @classmethod
271
+ def of(cls, text: str) -> DeclinedTier:
272
+ """Map a document's spelling. One the description does not list is kept as
273
+ this vocabulary's unknown(n) member, carrying it (rule r3)."""
274
+ return cls(text)
275
+
276
+
252
277
  class DiscoverQueryParam(StrEnum):
253
278
  """A query parameter of the SQL `discover_query` returns, which the caller binds
254
279
  when it runs the query."""
@@ -415,6 +440,7 @@ DESCRIBED_VOCABULARIES: Final[dict[str, type]] = {
415
440
  "filter_verdict": Verdict,
416
441
  "discover_query_param": DiscoverQueryParam,
417
442
  "default_kind": DefaultKind,
443
+ "declined_tier": DeclinedTier,
418
444
  }
419
445
 
420
446
 
chtypes/_decode.py CHANGED
@@ -28,7 +28,16 @@ import json
28
28
  from collections.abc import Callable, Mapping
29
29
  from typing import Any
30
30
 
31
- from ._abi2._vocab import DefaultKind, FilterOutcome, MergeReason, Outcome, Reason, Source, Verdict
31
+ from ._abi2._vocab import (
32
+ DeclinedTier,
33
+ DefaultKind,
34
+ FilterOutcome,
35
+ MergeReason,
36
+ Outcome,
37
+ Reason,
38
+ Source,
39
+ Verdict,
40
+ )
32
41
  from .errors import ArtifactCorruptError, internal
33
42
  from .results import (
34
43
  AtMergeEntry,
@@ -37,6 +46,7 @@ from .results import (
37
46
  Capabilities,
38
47
  Column,
39
48
  Computed,
49
+ DeclinedSetting,
40
50
  DiscoveredColumn,
41
51
  Discovery,
42
52
  EngineCell,
@@ -493,11 +503,22 @@ def _string_map(obj: Mapping[str, Any], key: str, where: str) -> dict[str, str]
493
503
  return out
494
504
 
495
505
 
506
+ def _declined_setting(obj: dict[str, Any], where: str) -> DeclinedSetting:
507
+ # An unlisted tier is its unknown(n): kept, never a failure (rule r3).
508
+ return DeclinedSetting(
509
+ name=_name(obj, "name", where),
510
+ tier=DeclinedTier.of(_text(obj, "tier", where)),
511
+ )
512
+
513
+
496
514
  def _server(obj: dict[str, Any], where: str) -> SchemaServer:
497
515
  return SchemaServer(
498
516
  timezone=_text(obj, "timezone", where),
499
517
  settings=_string_map(obj, "settings", where) or {},
500
518
  macros=_string_map(obj, "macros", where),
519
+ filter_declined_settings=_objects(
520
+ obj, "filter_declined_settings", where, _declined_setting
521
+ ),
501
522
  )
502
523
 
503
524
 
@@ -52,6 +52,7 @@ from typing import Final, Protocol
52
52
  from chtypes._abi2._decls import CHS_ABI_FINGERPRINT
53
53
  from chtypes._ocifetch import _constants as C
54
54
  from chtypes._ocifetch._dsse import TrustedKey
55
+ from chtypes._ocifetch._errors import ArtifactUnpublishedError
55
56
 
56
57
  __all__ = [
57
58
  "ALIAS_SEPARATOR",
@@ -66,6 +67,7 @@ __all__ = [
66
67
  "PinningRefusedError",
67
68
  "abi",
68
69
  "active",
70
+ "ahead_of_registry",
69
71
  "alias_tag",
70
72
  "allow_overrides_for_tests",
71
73
  "channel_name",
@@ -274,6 +276,43 @@ def visible(predicate: object, channel: Channel | None = None) -> bool:
274
276
  return fingerprint == f"sha256:{channel.own_fingerprint}"
275
277
 
276
278
 
279
+ def ahead_of_registry(
280
+ alias_absent: bool, predicate: object, channel: Channel | None = None
281
+ ) -> None:
282
+ """The dev channel's answer for an SDK whose fingerprint no published build
283
+ carries yet (docs/guides/fetch-v1.md §3; public issue #578): when every base
284
+ answered its own alias 404 (`alias_absent`) and the build the tag names is
285
+ signed for another fingerprint (`predicate`, the SIGNED predicate, the field
286
+ `visible` keys on), raise `ArtifactUnpublishedError`, the code a request no
287
+ build answers gets, naming both fingerprints. Nothing is installed and an
288
+ install of that build is never reported, because the lookup this SDK opens
289
+ through would refuse it. Returns in every other case: the alias answered,
290
+ the tag's build is this SDK's own, or the contract has no own fingerprint."""
291
+ channel = channel or active()
292
+ if not alias_absent or visible(predicate, channel):
293
+ return
294
+ raise ArtifactUnpublishedError(f"chtypes: {_ahead_message(channel.own_fingerprint, predicate)}")
295
+
296
+
297
+ def _ahead_message(own: str, predicate: object) -> str:
298
+ """`ahead_of_registry`'s message: "no published build for this SDK's
299
+ fingerprint <own>; newest published on this channel is <fp> (build <id>)",
300
+ each fingerprint 64 lowercase hex without its `sha256:` prefix, the
301
+ parenthesis dropped when the predicate names no build, and "unnamed" for a
302
+ predicate that names no fingerprint."""
303
+ fields = predicate if isinstance(predicate, dict) else {}
304
+ theirs = fields.get("abi_fingerprint")
305
+ theirs = theirs.removeprefix("sha256:") if isinstance(theirs, str) else ""
306
+ message = (
307
+ f"no published build for this SDK's fingerprint {own}; "
308
+ f"newest published on this channel is {theirs or 'unnamed'}"
309
+ )
310
+ build = fields.get("build")
311
+ if isinstance(build, str) and build:
312
+ message += f" (build {build})"
313
+ return message
314
+
315
+
277
316
  def use_dev_channel_for_tests() -> Callable[[], None]:
278
317
  """Make this TEST run speak the dev channel exactly as a user's process does
279
318
  (undoing either function above, for a test of the dev channel itself).
@@ -1,6 +1,6 @@
1
1
  # Code generated by scripts/fetch-v1/gen-constants.py from
2
2
  # spec/fetch-v1/constants.json. DO NOT EDIT.
3
- # Source sha256: 163dbaa1e5173aa65de7d46f1eac27c02f862e96b5c8160a23fee8cfabd77a2a
3
+ # Source sha256: 4554956faeb154fc324a6120b7b35976222c6bc8ecbce7fd0147502e3073611d
4
4
  """The v1 fetch constants, generated: see docs/guides/fetch-v1.md."""
5
5
 
6
6
  from __future__ import annotations
@@ -83,6 +83,7 @@ RETRY_AFTER_STATUSES: Final[tuple[int, ...]] = (429, 503)
83
83
  RETRY_AFTER_OVER_BUDGET: Final[str] = "refuse"
84
84
  DIGEST_404_POLICY: Final[str] = "next_base_then_retry_on_last"
85
85
  TAG_404_POLICY: Final[str] = "next_base_then_unpublished"
86
+ RETIRED_STATUSES: Final[tuple[int, ...]] = (410,)
86
87
 
87
88
  CONNECT_TIMEOUT_S: Final[float] = 30.0
88
89
  IDLE_READ_TIMEOUT_S: Final[float] = 60.0
@@ -95,6 +96,8 @@ MAX_REFERRERS: Final[int] = 16
95
96
  MAX_REDIRECTS: Final[int] = 5
96
97
  MAX_UNPACKED_BYTES: Final[int] = 4294967296
97
98
  ZSTD_WINDOW_LOG_MAX: Final[int] = 27
99
+ RETIRED_BODY_MAX_BYTES: Final[int] = 65536
100
+ RETIRED_MESSAGE_MAX_CODE_POINTS: Final[int] = 256
98
101
 
99
102
  CACHE_ROOT_TEMPLATE: Final[str] = "${XDG_CACHE_HOME:-$HOME/.cache}/chtypes/v1"
100
103
  SYSTEM_CACHE_DIRS: Final[tuple[str, ...]] = ("/usr/local/share/chtypes/v1", "/opt/chtypes/v1")
@@ -122,4 +125,5 @@ ERROR_EXIT_CODES: Final[dict[str, int]] = {
122
125
  "CHTYPES_SOURCE_INCOMPATIBLE": 7,
123
126
  "CHTYPES_ARTIFACT_INCOMPATIBLE": 8,
124
127
  "CHTYPES_CACHE_UNUSABLE": 9,
128
+ "CHTYPES_SOURCE_RETIRED": 10,
125
129
  }
@@ -38,6 +38,7 @@ from chtypes._ocifetch._errors import (
38
38
  ArtifactUntrustedError,
39
39
  FetchError,
40
40
  SourceForbiddenError,
41
+ SourceRetiredError,
41
42
  SourceUnauthorizedError,
42
43
  SourceUnreachableError,
43
44
  )
@@ -49,6 +50,7 @@ from chtypes._ocifetch._http import (
49
50
  ForbiddenHttpError,
50
51
  NotFoundHttpError,
51
52
  OversizeHttpError,
53
+ RetiredHttpError,
52
54
  RetryPolicy,
53
55
  TransportError,
54
56
  UnauthorizedHttpError,
@@ -440,7 +442,7 @@ def _find_verified_signature(
440
442
  bases, ref.digest, policy=policy, retry=retry
441
443
  )
442
444
  blob_desc = manifest_single_layer(referrer_doc, expected_media_type=C.MEDIA_TYPE_BUNDLE)
443
- except ArtifactCorruptError:
445
+ except (ArtifactCorruptError, RetiredHttpError):
444
446
  raise
445
447
  except (TransportError, FetchError):
446
448
  continue
@@ -458,7 +460,7 @@ def _find_verified_signature(
458
460
  with open(bundle_path, "rb") as f:
459
461
  bundle_json = json.loads(f.read())
460
462
  verified = verify_bundle(bundle_json, trusted_keys)
461
- except ArtifactCorruptError:
463
+ except (ArtifactCorruptError, RetiredHttpError):
462
464
  raise
463
465
  except (TransportError, FetchError, json.JSONDecodeError):
464
466
  continue
@@ -698,7 +700,7 @@ def _ensure_floating(
698
700
  cache_root_path = roots[0]
699
701
  scratch_root = _scratch_root(cache_root_path)
700
702
 
701
- index_doc, index_bytes, index_digest = fetch_manifest_by_tag(
703
+ index_doc, index_bytes, index_digest, alias_absent = fetch_manifest_by_tag(
702
704
  bases,
703
705
  request.spelling,
704
706
  policy=policy,
@@ -711,6 +713,10 @@ def _ensure_floating(
711
713
  existing_dir = unpacked_dir_for(cache_root_path, manifest_hex)
712
714
  existing_record = read_verified_record(existing_dir)
713
715
  if existing_record is not None and existing_record.platform == platform_key:
716
+ # An install of another fingerprint's build, reached through the tag
717
+ # because the registry has none at this SDK's own, is never reported
718
+ # installed (public issue #578): refused before the cache is touched.
719
+ _channel.ahead_of_registry(alias_absent, existing_record.predicate)
714
720
  # A NEWER build within the request, installed beside it, is still the
715
721
  # answer ("monotonic-warning", docs/guides/fetch-v1.md §9).
716
722
  newer = _newer_installed(
@@ -801,6 +807,12 @@ def _ensure_floating(
801
807
  signed_by = verified.signed_by
802
808
 
803
809
  _check_predicate_matches_request(predicate, platform_key, request.spelling)
810
+ if found is not None:
811
+ # An SDK ahead of the registry (public issue #578): the alias for its
812
+ # own fingerprint answered 404 on every base, and the tag's build is
813
+ # signed for another. Refused from the signed statement, before the
814
+ # layer is requested, so nothing is downloaded or installed.
815
+ _channel.ahead_of_registry(alias_absent, predicate)
804
816
  newer = _newer_installed(roots, platform_key, predicate, request.spelling)
805
817
  if newer is not None:
806
818
  newer_dir, newer_record, warning = newer
@@ -983,6 +995,8 @@ def _translate_transport_error(e: TransportError) -> FetchError:
983
995
  return SourceForbiddenError(f"chtypes: {e}")
984
996
  if isinstance(e, OversizeHttpError):
985
997
  return ArtifactCorruptError(f"chtypes: {e}")
998
+ if isinstance(e, RetiredHttpError):
999
+ return SourceRetiredError(f"chtypes: {e}")
986
1000
  if isinstance(e, UnreachableHttpError):
987
1001
  return SourceUnreachableError(f"chtypes: {e}", retryable=getattr(e, "retryable", True))
988
1002
  return SourceUnreachableError(f"chtypes: {e}")
@@ -1368,7 +1382,9 @@ def _fetch_signed_impl(repository: str, ref: str, predicate_type: str, options:
1368
1382
  doc, _raw = fetch_manifest_by_digest(bases, ref, policy=policy, retry=retry)
1369
1383
  manifest_digest = ref
1370
1384
  else:
1371
- doc, _raw, manifest_digest = fetch_manifest_by_tag(bases, ref, policy=policy, retry=retry)
1385
+ doc, _raw, manifest_digest, _alias_absent = fetch_manifest_by_tag(
1386
+ bases, ref, policy=policy, retry=retry
1387
+ )
1372
1388
 
1373
1389
  layer_desc = manifest_single_layer(doc)
1374
1390
  found = _find_verified_signature(
@@ -1466,6 +1482,8 @@ def _fetch_goldens(
1466
1482
  retry=retry,
1467
1483
  max_bytes=C.MAX_UNPACKED_BYTES,
1468
1484
  )
1485
+ except RetiredHttpError:
1486
+ raise
1469
1487
  except (TransportError, FetchError) as e:
1470
1488
  first_failure = first_failure or e
1471
1489
  continue
@@ -1580,6 +1598,8 @@ def _verify_goldens_signature(
1580
1598
  except ArtifactCorruptError as e:
1581
1599
  failure = failure or e
1582
1600
  continue
1601
+ except RetiredHttpError:
1602
+ raise
1583
1603
  except (TransportError, FetchError, json.JSONDecodeError):
1584
1604
  continue
1585
1605
  finally:
@@ -5,7 +5,7 @@ Scoped to `chtypes._ocifetch`: the stable, public exception types live in
5
5
  imports from or edits. A future switch lane decides how (or whether) these
6
6
  become public types; until then every name here is private.
7
7
 
8
- Every exception carries a `.code` string from one of the eleven codes
8
+ Every exception carries a `.code` string from one of the twelve codes
9
9
  `spec/fetch-v1/constants.json`'s `errors` map assigns an exit status to
10
10
  (the generated `ERROR_EXIT_CODES` table), so a CLI added later needs no
11
11
  second mapping — it is the same lookup v0's `__main__.py` does today.
@@ -30,6 +30,7 @@ __all__ = [
30
30
  "CODE_CACHE_UNUSABLE",
31
31
  "CODE_SOURCE_FORBIDDEN",
32
32
  "CODE_SOURCE_INCOMPATIBLE",
33
+ "CODE_SOURCE_RETIRED",
33
34
  "CODE_SOURCE_UNAUTHORIZED",
34
35
  "CODE_SOURCE_UNREACHABLE",
35
36
  "ArtifactCorruptError",
@@ -41,6 +42,7 @@ __all__ = [
41
42
  "FetchError",
42
43
  "SourceForbiddenError",
43
44
  "SourceIncompatibleError",
45
+ "SourceRetiredError",
44
46
  "SourceUnauthorizedError",
45
47
  "SourceUnreachableError",
46
48
  ]
@@ -55,6 +57,7 @@ CODE_SOURCE_UNAUTHORIZED = "CHTYPES_SOURCE_UNAUTHORIZED"
55
57
  CODE_SOURCE_FORBIDDEN = "CHTYPES_SOURCE_FORBIDDEN"
56
58
  CODE_SOURCE_INCOMPATIBLE = "CHTYPES_SOURCE_INCOMPATIBLE"
57
59
  CODE_CACHE_UNUSABLE = "CHTYPES_CACHE_UNUSABLE"
60
+ CODE_SOURCE_RETIRED = "CHTYPES_SOURCE_RETIRED"
58
61
 
59
62
  _CODES_USED_HERE = (
60
63
  CODE_ARTIFACT_MISSING,
@@ -67,6 +70,7 @@ _CODES_USED_HERE = (
67
70
  CODE_SOURCE_FORBIDDEN,
68
71
  CODE_SOURCE_INCOMPATIBLE,
69
72
  CODE_CACHE_UNUSABLE,
73
+ CODE_SOURCE_RETIRED,
70
74
  )
71
75
 
72
76
  # A drift guard, cheaper than a test: every code this module raises must be
@@ -187,3 +191,13 @@ class CacheUnusableError(FetchError):
187
191
  self.path = path
188
192
  self.reason = reason
189
193
  self.os_error = os_error
194
+
195
+
196
+ class SourceRetiredError(FetchError):
197
+ """The source answered 410 Gone: a retired repository, which is permanent,
198
+ so the request was never retried and never sent to the next base
199
+ (docs/guides/fetch-v1.md §2, "A retired repository"; public issue #571).
200
+ The message names the URL that answered and carries the registry's own
201
+ message, made safe to print."""
202
+
203
+ code = CODE_SOURCE_RETIRED
@@ -29,6 +29,7 @@ from email.utils import parsedate_to_datetime
29
29
  from urllib.parse import urljoin, urlsplit, urlunsplit
30
30
 
31
31
  from chtypes._ocifetch import _constants as C
32
+ from chtypes._ocifetch._retired import retired_text
32
33
 
33
34
  __all__ = [
34
35
  "USER_AGENT",
@@ -38,6 +39,7 @@ __all__ = [
38
39
  "HttpResponse",
39
40
  "NotFoundHttpError",
40
41
  "OversizeHttpError",
42
+ "RetiredHttpError",
41
43
  "RetryPolicy",
42
44
  "TransportError",
43
45
  "UnauthorizedHttpError",
@@ -120,6 +122,13 @@ class OversizeHttpError(TransportError):
120
122
  """A response body exceeded the caller's declared size cap."""
121
123
 
122
124
 
125
+ class RetiredHttpError(TransportError):
126
+ """A retired repository (`RETIRED_STATUSES`, a 410; docs/guides/fetch-v1.md
127
+ §2): permanent, so never retried and never a reason to try the next base.
128
+ Its text names the URL that answered and carries the registry's message,
129
+ made safe to print (`_retired.retired_message`)."""
130
+
131
+
123
132
  class Clock:
124
133
  """The real wall clock. Tests inject a fake with the same interface so
125
134
  retry cases run instantly and sleeps are asserted exactly."""
@@ -228,7 +237,16 @@ def _single_request(
228
237
  )
229
238
  except urllib.error.HTTPError as e:
230
239
  try:
231
- body = _read_capped(e, max_bytes) if e.code not in _REDIRECT_CODES else b""
240
+ if e.code in C.RETIRED_STATUSES:
241
+ # Only the registry's message is wanted from a retired
242
+ # repository's answer: at most RETIRED_BODY_MAX_BYTES of it,
243
+ # and a body that fails part way is no body.
244
+ try:
245
+ body = e.read(C.RETIRED_BODY_MAX_BYTES)
246
+ except (OSError, ValueError, AttributeError):
247
+ body = b""
248
+ else:
249
+ body = _read_capped(e, max_bytes) if e.code not in _REDIRECT_CODES else b""
232
250
  finally:
233
251
  e.close()
234
252
  return HttpResponse(status=e.code, headers=dict(e.headers or {}), body=body, url=url)
@@ -380,6 +398,11 @@ def _http_get_once(
380
398
  headers.pop("Authorization", None)
381
399
  continue
382
400
 
401
+ if resp.status in C.RETIRED_STATUSES:
402
+ # A retired repository: permanent, never retried, and the
403
+ # exception ends fetch_from_bases's loop over bases.
404
+ raise RetiredHttpError(retired_text(current_url, resp.status, resp.body))
405
+
383
406
  if resp.status == 401:
384
407
  if not tried_anon_flow and not policy.token:
385
408
  challenge = resp.headers.get("WWW-Authenticate") or resp.headers.get(
@@ -523,6 +546,10 @@ def fetch_from_bases(
523
546
  404, some unreachable — the outcome follows the LAST base tried, since
524
547
  bases are an explicit preference order and the last one is the final
525
548
  word a caller configured.
549
+
550
+ In every mode a retired repository (`RetiredHttpError`, a 410) ends the
551
+ loop on the base that answered it: it is permanent, never a reason to try
552
+ the next base, and in `alias` mode never a reason to fall back to the tag.
526
553
  """
527
554
  if not bases:
528
555
  raise UnreachableHttpError("no base URLs configured")
chtypes/_ocifetch/_oci.py CHANGED
@@ -249,15 +249,19 @@ def fetch_manifest_by_tag(
249
249
  retry: RetryPolicy,
250
250
  max_bytes: int = C.MANIFEST_MAX_BYTES,
251
251
  alias: str | None = None,
252
- ) -> tuple[dict, bytes, str]:
253
- """GET manifests/<tag>; returns (doc, raw_bytes, resolved_digest).
252
+ ) -> tuple[dict, bytes, str, bool]:
253
+ """GET manifests/<tag>; returns (doc, raw_bytes, resolved_digest, alias_absent).
254
254
 
255
255
  With `alias` (the dev channel's `<tag>--fp-<fingerprint>`, from
256
256
  `_channel.alias_tag`), GET manifests/<alias> first, and the tag only when
257
257
  every base answered the alias 404 (docs/guides/fetch-v1.md §3); any other
258
258
  failure of the alias request is raised as it is. What is returned is then
259
- trusted exactly as the tag's own answer would be."""
259
+ trusted exactly as the tag's own answer would be. `alias_absent` reports
260
+ that fallback: the alias was asked for and every base answered it 404, so
261
+ the registry has no build at this SDK's fingerprint, and the build the tag
262
+ names is checked against it (`_channel.ahead_of_registry`)."""
260
263
  accept = (C.MEDIA_TYPE_INDEX, C.MEDIA_TYPE_MANIFEST)
264
+ alias_absent = False
261
265
  if alias is not None:
262
266
  try:
263
267
  doc, resp = get_json(
@@ -270,9 +274,9 @@ def fetch_manifest_by_tag(
270
274
  retry=retry,
271
275
  )
272
276
  except AliasNotFoundError:
273
- pass
277
+ alias_absent = True
274
278
  else:
275
- return doc, resp.body, f"sha256:{hashlib.sha256(resp.body).hexdigest()}"
279
+ return doc, resp.body, f"sha256:{hashlib.sha256(resp.body).hexdigest()}", False
276
280
  doc, resp = get_json(
277
281
  bases,
278
282
  f"/manifests/{tag}",
@@ -283,7 +287,7 @@ def fetch_manifest_by_tag(
283
287
  retry=retry,
284
288
  )
285
289
  digest = f"sha256:{hashlib.sha256(resp.body).hexdigest()}"
286
- return doc, resp.body, digest
290
+ return doc, resp.body, digest, alias_absent
287
291
 
288
292
 
289
293
  def fetch_blob_to_path(
@@ -0,0 +1,77 @@
1
+ """A retired repository (docs/guides/fetch-v1.md §2, "A retired repository";
2
+ public issue #571).
3
+
4
+ The registry's operator answers every route of a retired repository with 410
5
+ Gone (`RETIRED_STATUSES`) and a short error document. That is permanent: the
6
+ transport never retries it and never asks the next base, so the caller sees
7
+ `CHTYPES_SOURCE_RETIRED` with the URL that answered and the registry's own
8
+ message, made safe to print by `retired_message`, the one function every
9
+ binding has (tests/fixtures/retired-message/cases.json is the table all four
10
+ answer alike).
11
+ """
12
+
13
+ from __future__ import annotations
14
+
15
+ import json
16
+
17
+ from chtypes._ocifetch import _constants as C
18
+
19
+ __all__ = ["retired_message", "retired_text"]
20
+
21
+ _ELLIPSIS = "…"
22
+
23
+
24
+ def _removed(code_point: int) -> bool:
25
+ return (
26
+ code_point <= 0x1F
27
+ or 0x7F <= code_point <= 0x9F
28
+ or code_point in (0x200E, 0x200F)
29
+ or 0x202A <= code_point <= 0x202E
30
+ or 0x2066 <= code_point <= 0x2069
31
+ )
32
+
33
+
34
+ def _not_json(name: str) -> None:
35
+ # `json.loads` accepts NaN, Infinity and -Infinity, which are not JSON;
36
+ # the other three bindings' readers refuse them.
37
+ raise ValueError(f"{name} is not JSON")
38
+
39
+
40
+ def retired_message(body: bytes) -> str | None:
41
+ """The registry's own message in a retired repository's response body,
42
+ made safe to print, or None when it sent none.
43
+
44
+ `body` is what was read of it (at most `RETIRED_BODY_MAX_BYTES`). When it is
45
+ UTF-8 JSON whose `errors[0].message` is a string, that string is the
46
+ message: every code point in U+0000-U+001F, U+007F-U+009F, U+200E, U+200F,
47
+ U+202A-U+202E and U+2066-U+2069 is removed, and the rest is cut to
48
+ `RETIRED_MESSAGE_MAX_CODE_POINTS` code points, with U+2026 appended when it
49
+ was cut. Nothing else is changed, and an empty result is no message."""
50
+ try:
51
+ text = body.decode("utf-8")
52
+ doc = json.loads(text, parse_constant=_not_json)
53
+ except (UnicodeDecodeError, ValueError, RecursionError):
54
+ return None
55
+ if not isinstance(doc, dict):
56
+ return None
57
+ errors = doc.get("errors")
58
+ if not isinstance(errors, list) or not errors or not isinstance(errors[0], dict):
59
+ return None
60
+ message = errors[0].get("message")
61
+ # A lone surrogate escape is not a Unicode string (and could not be printed).
62
+ if not isinstance(message, str) or any("\ud800" <= ch <= "\udfff" for ch in message):
63
+ return None
64
+ kept = [ch for ch in message if not _removed(ord(ch))]
65
+ if len(kept) > C.RETIRED_MESSAGE_MAX_CODE_POINTS:
66
+ kept = [*kept[: C.RETIRED_MESSAGE_MAX_CODE_POINTS], _ELLIPSIS]
67
+ return "".join(kept) or None
68
+
69
+
70
+ def retired_text(url: str, status: int, body: bytes) -> str:
71
+ """The error text for a retired repository's answer: the URL and status
72
+ that answered, and the registry's message when it sent one."""
73
+ text = f"{url} answered {status} Gone: the repository is retired"
74
+ message = retired_message(body)
75
+ if message is not None:
76
+ text += f"; the registry says: {message}"
77
+ return text
chtypes/errors.py CHANGED
@@ -31,6 +31,7 @@ __all__ = [
31
31
  "CODE_CACHE_UNUSABLE",
32
32
  "CODE_SOURCE_FORBIDDEN",
33
33
  "CODE_SOURCE_INCOMPATIBLE",
34
+ "CODE_SOURCE_RETIRED",
34
35
  "CODE_SOURCE_UNAUTHORIZED",
35
36
  "CODE_SOURCE_UNREACHABLE",
36
37
  "ArtifactCorruptError",
@@ -47,6 +48,7 @@ __all__ = [
47
48
  "SchemaError",
48
49
  "SourceForbiddenError",
49
50
  "SourceIncompatibleError",
51
+ "SourceRetiredError",
50
52
  "SourceUnauthorizedError",
51
53
  "SourceUnreachableError",
52
54
  "UnsupportedError",
@@ -66,6 +68,7 @@ CODE_SOURCE_UNAUTHORIZED = "CHTYPES_SOURCE_UNAUTHORIZED"
66
68
  CODE_SOURCE_FORBIDDEN = "CHTYPES_SOURCE_FORBIDDEN"
67
69
  CODE_SOURCE_INCOMPATIBLE = "CHTYPES_SOURCE_INCOMPATIBLE"
68
70
  CODE_CACHE_UNUSABLE = "CHTYPES_CACHE_UNUSABLE"
71
+ CODE_SOURCE_RETIRED = "CHTYPES_SOURCE_RETIRED"
69
72
 
70
73
  # `chs_status` value of CHS_INVALID_ARGUMENT. The binding's own misuse errors
71
74
  # carry it so a handler sees one shape whichever side caught the misuse. It is
@@ -258,6 +261,15 @@ class SourceIncompatibleError(ArtifactError):
258
261
  code = CODE_SOURCE_INCOMPATIBLE
259
262
 
260
263
 
264
+ class SourceRetiredError(ArtifactError):
265
+ """The source answered 410 Gone: a retired repository, which is permanent,
266
+ so the request was never retried and never sent to the next base. The
267
+ message names the URL that answered and carries the registry's own
268
+ message, made safe to print."""
269
+
270
+ code = CODE_SOURCE_RETIRED
271
+
272
+
261
273
  class CacheUnusableError(ArtifactError):
262
274
  """A cache directory or entry the fetch layer could not read or write, or
263
275
  one strict mode refuses: never "not installed", never the network's
chtypes/registry.py CHANGED
@@ -51,6 +51,7 @@ _FETCH_CLASSES: dict[str, type[errors.ArtifactError]] = {
51
51
  errors.CODE_SOURCE_FORBIDDEN: errors.SourceForbiddenError,
52
52
  errors.CODE_SOURCE_INCOMPATIBLE: errors.SourceIncompatibleError,
53
53
  errors.CODE_CACHE_UNUSABLE: errors.CacheUnusableError,
54
+ errors.CODE_SOURCE_RETIRED: errors.SourceRetiredError,
54
55
  }
55
56
 
56
57
 
chtypes/results.py CHANGED
@@ -14,7 +14,7 @@ from __future__ import annotations
14
14
  from collections.abc import Mapping
15
15
  from dataclasses import dataclass
16
16
 
17
- from ._abi2._vocab import DefaultKind, FilterOutcome, MergeReason, Outcome, Verdict
17
+ from ._abi2._vocab import DeclinedTier, DefaultKind, FilterOutcome, MergeReason, Outcome, Verdict
18
18
 
19
19
  __all__ = [
20
20
  "AtMergeEntry",
@@ -23,6 +23,7 @@ __all__ = [
23
23
  "Capabilities",
24
24
  "Column",
25
25
  "Computed",
26
+ "DeclinedSetting",
26
27
  "DiscoveredColumn",
27
28
  "EngineCell",
28
29
  "Discovery",
@@ -223,8 +224,10 @@ class FilterRowError:
223
224
 
224
225
  @dataclass(frozen=True, slots=True)
225
226
  class FilterResult:
226
- """The `filter_result` document. `e` and `d` are never answers, and a caller
227
- enforcing visibility fails closed on both; `Verdict.answered` says which."""
227
+ """The `filter_result` document. A verdict counts only when `outcome` is `ok`;
228
+ otherwise treat every verdict as `d`, whatever the verdict string holds. Under
229
+ `ok`, `e` and `d` are never answers, and a caller enforcing visibility fails
230
+ closed on both; `Verdict.answered` says which."""
228
231
 
229
232
  outcome: FilterOutcome
230
233
  err_code: int
@@ -247,6 +250,18 @@ class Column:
247
250
  default_expr: bytes
248
251
 
249
252
 
253
+ @dataclass(frozen=True, slots=True)
254
+ class DeclinedSetting:
255
+ """One setting a server's profile sets that this build's filters do not honor
256
+ in a WHERE: an entry of the server's `filter_declined_settings` (ABI v2).
257
+ `name` is bytes (from `name` or `name_b64`). `tier` is the tier this build
258
+ line's WHERE-settings list gives it, a `DeclinedTier`; one the description
259
+ does not list is its unknown(n) member (rule r3)."""
260
+
261
+ name: bytes
262
+ tier: DeclinedTier
263
+
264
+
250
265
  @dataclass(frozen=True, slots=True)
251
266
  class SchemaServer:
252
267
  """The server a schema was compiled on, as the library holds it. Its strings
@@ -254,11 +269,17 @@ class SchemaServer:
254
269
  data-derived. `timezone` is the zone the schema's types bind: the profile's,
255
270
  or else the image zone. `settings` is the profile's settings (`{}` when it
256
271
  gave none). `macros` is None exactly when the profile carried no macros
257
- (unknown), and a mapping, even an empty one, when it did (the complete set)."""
272
+ (unknown), and a mapping, even an empty one, when it did (the complete set).
273
+ `filter_declined_settings` is the settings the profile sets that this
274
+ build's filters do not honor in a WHERE, in the profile's own order, empty
275
+ when it lists none: a schema on a server that lists any is one
276
+ `compile_filter` declines (`UnsupportedError`), and a consumer that streams
277
+ rows refuses such a tenant (spec/abi-v2/where-settings/README.md)."""
258
278
 
259
279
  timezone: str
260
280
  settings: Mapping[str, str]
261
281
  macros: Mapping[str, str] | None
282
+ filter_declined_settings: tuple[DeclinedSetting, ...] = ()
262
283
 
263
284
 
264
285
  @dataclass(frozen=True, slots=True)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: chtypes
3
- Version: 2.0.0.dev3
3
+ Version: 2.0.0.dev4
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
@@ -22,6 +22,8 @@ Description-Content-Type: text/markdown
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. A dev SDK prefers the newest build of its own fingerprint: it resolves `<tag>--fp-<its fingerprint>` first and the tag only when no such build exists, and its cache lookups ignore another dev SDK's builds in a shared cache, so a newer dev fingerprint never strands it (§3 of [`docs/guides/fetch-v1.md`](../docs/guides/fetch-v1.md)). 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
23
  >
24
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.
25
+ >
26
+ > <!-- remove-at-v2-lock -->v2-dev builds before the one that carries this contract (its release notice names it) can answer `ok` with `t` verdicts after a refused row. On those builds, when a body or block holds more than one row, treat every verdict as `d` if `errors` is non-empty.<!-- /remove-at-v2-lock -->
25
27
 
26
28
  **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.
27
29
 
@@ -1,37 +1,38 @@
1
- chtypes/__init__.py,sha256=zKL5Y9XuiwZb-Ewfgvhr9IFxdpqpBS7Ub2ca68ZOxJc,5993
1
+ chtypes/__init__.py,sha256=9xXEUgqnxJmAjUbz8X3coym4UyM01HShX7Lrds751VM,6177
2
2
  chtypes/__main__.py,sha256=XYGNTpTLXI1AUp7srvuNVv2SZAcfwLaXpoXRVInyhF4,14030
3
- chtypes/_decode.py,sha256=5f-OamDm8Qx17FAWOdHO7ng7r16ZRtYy6QKn1LlnVrc,22207
3
+ chtypes/_decode.py,sha256=v5pdwphwAldVzgHg6saWsxqUFjMHqcMO-XOuDyuRVZg,22686
4
4
  chtypes/_ed25519.py,sha256=uwaAKWvgoc936e1ZfgLrtR47W87WWCL541kFaqBXrB0,4141
5
5
  chtypes/_guard.py,sha256=zTtwprCS0XvGWY7GDozo-IP6GwjMAWl-o-AjShdCSVI,1985
6
6
  chtypes/_input.py,sha256=i4OUmgmHxx618g0DVFYy8LmHhggfBT03NyMxH3ptZBI,6020
7
7
  chtypes/_setup.py,sha256=n84T0hFmXu_fdnXQIFhk3dMVL0_-yhfdRZsqCXTLzzg,6114
8
- chtypes/errors.py,sha256=9qv94rxbe7hm7lSVFG33Qri82FwDtCY-EnJiwnMfkp8,9501
8
+ chtypes/errors.py,sha256=7gDi9Ij8yptrr9E0LZyA2XkeaFFVyZoQMVQjQj6mn0s,9936
9
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=HTrzS_3HjOkH4rU90AVMNfFLsLoaut0jgNlt_BJ28QY,13727
12
- chtypes/results.py,sha256=6uvfkt8dLd-F9aXyxdzWU5_kDVRVirhwB3fcH5NhJDA,11037
11
+ chtypes/registry.py,sha256=Q3LR7wBs7Ggy_5xG_-0bZrTRgvRWJO5OJbD92ecUlt4,13786
12
+ chtypes/results.py,sha256=OnxFUNyhlNeA2jwsMVAqYjKjmqguvKtWzrCwi7F6CFM,12123
13
13
  chtypes/_abi2/__init__.py,sha256=d3i7_iUDMFT1jg49vRQz1SMH6oseYZHUR0oPt6aSTiY,1042
14
- chtypes/_abi2/_decls.py,sha256=ZFJUVXYIlyuKVRJyVh8KVxkT4GZjsY-CeCcohK4sWMQ,38454
15
- chtypes/_abi2/_errmap.py,sha256=uQZ_rMaUU-tk1rSJ5wq_EjOkjesGeF-BKDJOs00KK3I,2886
14
+ chtypes/_abi2/_decls.py,sha256=X88Ylb33Sw5iJjikVTfVzdgCfjGvKpn4F3mT0qAnD7I,38454
15
+ chtypes/_abi2/_errmap.py,sha256=vNPgHFV742Px2aTyYqJez7I3qXRq80AOUcMqDvyPGFI,2886
16
16
  chtypes/_abi2/_errors.py,sha256=wRE9M0oYtJz_9izQqpEBh5571M_1QzGWBoJ3pJTykWw,761
17
17
  chtypes/_abi2/_loader.py,sha256=VMWrbWMp9Uq3K-7a219lfqAmSmBlD2hcB6WDJgG8XG0,12514
18
- chtypes/_abi2/_vocab.py,sha256=kH58IzFHpK93ihNBo8UKMtJM6-umxbCaCZlFcqCk7Ro,15543
18
+ chtypes/_abi2/_vocab.py,sha256=rzbenqXqDoWZkvpjRw0UUBEkgWoOtE462QUGyCE3cTw,16516
19
19
  chtypes/_ocifetch/__init__.py,sha256=enfPOKkqP4kqAKqckBGWXN1OQsiYo9ngDP-wzG0zn2I,1484
20
- chtypes/_ocifetch/_channel.py,sha256=yqnb35GGxdviipspR3B10t6ZgI8RJMFvW7AnB7zYYfs,15981
21
- chtypes/_ocifetch/_constants.py,sha256=UdOGIShDXaDP13NGbuftKZRE4u7VK8JfZmoeChw4GMU,5467
20
+ chtypes/_ocifetch/_channel.py,sha256=2L-sSetXv4iF0mpsIX4-iTAhNbu9eY1IsGnb4WTptwM,17980
21
+ chtypes/_ocifetch/_constants.py,sha256=IZtAK3oxnCELZysvrzRozJkntxKSQllGjxod5No-Kco,5644
22
22
  chtypes/_ocifetch/_dsse.py,sha256=0RaFJaG1jhXr1uPfuHxm7yAn6viNzYhfCRUf-sW5SvQ,9333
23
- chtypes/_ocifetch/_ensure.py,sha256=gbUSpWvdXP9f6dlFWPk67Cl_bPJ02ehTRLkDrSVNnHI,61565
24
- chtypes/_ocifetch/_errors.py,sha256=u0eIdrr3g00ugy8ZZoAXYIgUuZgEs6eMue2QkcWXC6w,6595
23
+ chtypes/_ocifetch/_ensure.py,sha256=GtuYJJXhL0TbUypx94OPr3BhnkrQoHuWjZdPYrvpLgw,62597
24
+ chtypes/_ocifetch/_errors.py,sha256=TTYpwhvZ7MyD9Ssg5js8_YB8Fl30rYBPlENyAetqEbQ,7129
25
25
  chtypes/_ocifetch/_faults.py,sha256=HpoB9tJW7aLRiJdCFnspzkwhpFATZ6f6hGi49Mbfnhs,7609
26
26
  chtypes/_ocifetch/_goldens.py,sha256=8MicgeJSRd4WZkaEsdujRnOa7twQvbT-wfSMPZaD-ms,4191
27
- chtypes/_ocifetch/_http.py,sha256=DGUW6ym9mrxOgn-9FcdqzuvVGcG9WJGlYyGHnkUvGRA,23817
27
+ chtypes/_ocifetch/_http.py,sha256=e1zAWu-2gvBe3FNE5bZ7jmz2OPn1bzImPfHWvA10O_4,25179
28
28
  chtypes/_ocifetch/_layout.py,sha256=ezEoVrsSMhvDhQ4U04hsGdu1-_ISZQ4V6zg4gGIChTQ,21300
29
29
  chtypes/_ocifetch/_lock.py,sha256=14_uex9qXdOht-5FSrSvFvd99-gB7DmI3bDuxXs4YU8,5530
30
- chtypes/_ocifetch/_oci.py,sha256=YJi8y2iKIRh1_YI8lodWZXRxQfb_KrD5BhM-TQA1SCo,13595
30
+ chtypes/_ocifetch/_oci.py,sha256=UdA8QGTh1O27DBYRf5UxsevjgI52IecQlEf9IO6Y0A4,13920
31
31
  chtypes/_ocifetch/_referrers.py,sha256=J2b4gN6vryz_IzYHxrd2Tp-tQO9Cz766rZtKgCVcKqA,6008
32
+ chtypes/_ocifetch/_retired.py,sha256=MQ6nbWLJw50IZYY1VfM6AiCZ4BySK0GVkGVOtAXaq3Y,3060
32
33
  chtypes/_ocifetch/_unpack.py,sha256=Cn4-up63KL63L8pnkS5zwJ6VbbHPSigpCnmOdcHJ4RI,5609
33
- chtypes-2.0.0.dev3.dist-info/METADATA,sha256=pCbL5T3EMtLWBTmEaHMGlNSjnUUQOJwYEWF5k7ycUk0,10966
34
- chtypes-2.0.0.dev3.dist-info/WHEEL,sha256=W3fkpkm7-wf9vBI5Z-7s0eWkeM-spu78I8Neb98DeEg,87
35
- chtypes-2.0.0.dev3.dist-info/entry_points.txt,sha256=TdNcluWBR0VOt5mVFROsp-NiVPAdR3EHo0UsHlMC0r0,50
36
- chtypes-2.0.0.dev3.dist-info/licenses/LICENSE,sha256=z8d0m5b2O9McPEK1xHG_dWgUBT6EfBDz6wA0F7xSPTA,11358
37
- chtypes-2.0.0.dev3.dist-info/RECORD,,
34
+ chtypes-2.0.0.dev4.dist-info/METADATA,sha256=xGAOsOpaNNPKh1cxhmBitwUrzSsCgiwBOqNltkBmWO0,11280
35
+ chtypes-2.0.0.dev4.dist-info/WHEEL,sha256=W3fkpkm7-wf9vBI5Z-7s0eWkeM-spu78I8Neb98DeEg,87
36
+ chtypes-2.0.0.dev4.dist-info/entry_points.txt,sha256=TdNcluWBR0VOt5mVFROsp-NiVPAdR3EHo0UsHlMC0r0,50
37
+ chtypes-2.0.0.dev4.dist-info/licenses/LICENSE,sha256=z8d0m5b2O9McPEK1xHG_dWgUBT6EfBDz6wA0F7xSPTA,11358
38
+ chtypes-2.0.0.dev4.dist-info/RECORD,,