chtypes 2.0.0.dev5__py3-none-any.whl → 2.0.0.dev6__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.
@@ -79,6 +79,7 @@ __all__ = [
79
79
  "visible",
80
80
  "use_dev_channel_for_tests",
81
81
  "use_fetch_v1_for_tests",
82
+ "use_prod_v2_for_tests",
82
83
  ]
83
84
 
84
85
  # The dev channel's registry and key (spec/abi-v2/docs.md, rule r6).
@@ -165,6 +166,25 @@ FETCH_V1_CHANNEL: Final = Channel(
165
166
  pinnable=True,
166
167
  )
167
168
 
169
+ # The production generation-2 channel a 2.0.0 SDK speaks after the lock: the v1
170
+ # contract with v2 values, from the generated constants (docs/guides/fetch-v1.md,
171
+ # "Generation 2 after the lock"). It trusts the release key, honors every
172
+ # override and pin, and has no alias step. NOT the default: only
173
+ # use_prod_v2_for_tests selects it, until the lock change makes it active()'s
174
+ # fallback.
175
+ PROD_V2_CHANNEL: Final = Channel(
176
+ name=C.PROD_V2_NAME,
177
+ abi=C.PROD_V2_ABI_GENERATION,
178
+ record_schema=C.PROD_V2_RECORD_SCHEMA,
179
+ root_leaf=C.PROD_V2_CACHE_DIR,
180
+ subroot=C.PROD_V2_CACHE_DIR,
181
+ system_dirs=C.PROD_V2_SYSTEM_DIRS,
182
+ bases=C.PROD_V2_BASES,
183
+ keys=C.RELEASE_KEYS,
184
+ overridable=True,
185
+ pinnable=True,
186
+ )
187
+
168
188
  _lock = threading.Lock()
169
189
  _current: Channel | None = None
170
190
 
@@ -224,6 +244,14 @@ def use_fetch_v1_for_tests() -> Callable[[], None]:
224
244
  return _use(FETCH_V1_CHANNEL)
225
245
 
226
246
 
247
+ def use_prod_v2_for_tests() -> Callable[[], None]:
248
+ """Make this TEST run speak the production generation-2 channel
249
+ (`PROD_V2_CHANNEL`): the v1 contract with abi 2, schema-2 records and the
250
+ `v2` subroot. Returns the restore function; raises outside a test run."""
251
+ _test_only("use_prod_v2_for_tests")
252
+ return _use(PROD_V2_CHANNEL)
253
+
254
+
227
255
  def allow_overrides_for_tests() -> Callable[[], None]:
228
256
  """Make this TEST run's dev channel honor the base, trust and unsigned
229
257
  overrides, so a test can reach a fixture registry signed with the test key;
@@ -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: 4554956faeb154fc324a6120b7b35976222c6bc8ecbce7fd0147502e3073611d
3
+ # Source sha256: 82afa8d6cba96c10b5945c6f7c1c8360a504cc8ac6231e74017cc6c20b908df6
4
4
  """The v1 fetch constants, generated: see docs/guides/fetch-v1.md."""
5
5
 
6
6
  from __future__ import annotations
@@ -101,6 +101,12 @@ RETIRED_MESSAGE_MAX_CODE_POINTS: Final[int] = 256
101
101
 
102
102
  CACHE_ROOT_TEMPLATE: Final[str] = "${XDG_CACHE_HOME:-$HOME/.cache}/chtypes/v1"
103
103
  SYSTEM_CACHE_DIRS: Final[tuple[str, ...]] = ("/usr/local/share/chtypes/v1", "/opt/chtypes/v1")
104
+ PROD_V2_NAME: Final[str] = "v2"
105
+ PROD_V2_ABI_GENERATION: Final[int] = 2
106
+ PROD_V2_RECORD_SCHEMA: Final[int] = 2
107
+ PROD_V2_CACHE_DIR: Final[str] = "v2"
108
+ PROD_V2_BASES: Final[tuple[str, ...]] = ("https://registry.wavehouse.dev/chtypes/v2",)
109
+ PROD_V2_SYSTEM_DIRS: Final[tuple[str, ...]] = ("/usr/local/share/chtypes/v2", "/opt/chtypes/v2")
104
110
  CACHE_UNPACKED_DIR: Final[str] = "unpacked/sha256"
105
111
  CACHE_VERIFIED_RECORD: Final[str] = "verified.json"
106
112
  CACHE_ANNOTATION_PREFIX: Final[str] = "dev.wavehouse.chtypes."
chtypes/registry.py CHANGED
@@ -15,13 +15,19 @@ The sequence of `for_version(request)`:
15
15
  Construction opens nothing. The binding orders and matches no versions itself:
16
16
  which installed build a floating request means, and the spelling rules, are the
17
17
  fetch layer's.
18
+
19
+ Opens of different requests never wait for each other's fetch (public issue
20
+ #491): the registry's lock guards its memo and the attempts in progress, and is
21
+ never held across steps 2 to 5. Concurrent opens of one request share one
22
+ attempt, and so one fetch, and each gets that attempt's `Library` or its error;
23
+ a failed attempt is never remembered, so the next open starts a new one.
18
24
  """
19
25
 
20
26
  from __future__ import annotations
21
27
 
22
28
  import os
23
29
  import threading
24
- from collections.abc import Sequence
30
+ from collections.abc import Callable, Sequence
25
31
  from dataclasses import dataclass
26
32
  from pathlib import Path
27
33
 
@@ -168,6 +174,19 @@ def _wrap(exc: FetchError) -> errors.ArtifactError:
168
174
  return wrapped
169
175
 
170
176
 
177
+ class _Flight:
178
+ """One attempt to open one request: the installed lookup, the fetch when
179
+ there is one, and the load. Every open of that request made while it is in
180
+ progress waits on it and gets its answer."""
181
+
182
+ __slots__ = ("done", "error", "library")
183
+
184
+ def __init__(self) -> None:
185
+ self.done = threading.Event()
186
+ self.library: Library | None = None
187
+ self.error: BaseException | None = None
188
+
189
+
171
190
  class Registry:
172
191
  """Opens versions. Safe across threads. `autofetch` defaults to
173
192
  `CHTYPES_AUTOFETCH`, and is off when that is unset.
@@ -175,6 +194,9 @@ class Registry:
175
194
  Construction opens nothing: only `for_version` and `preload` open an
176
195
  artifact. `preload` opens each listed request through the loader at
177
196
  construction, in list order, and never fetches, even with autofetch on.
197
+
198
+ An open never waits for another request's fetch, and concurrent opens of
199
+ one request share one fetch (public issue #491).
178
200
  """
179
201
 
180
202
  def __init__(
@@ -189,8 +211,14 @@ class Registry:
189
211
  env = os.environ.get(_fetch_constants.ENV_AUTOFETCH_NAME, "")
190
212
  autofetch = env.strip().lower() in _TRUTHY
191
213
  self._autofetch = autofetch
192
- self._lock = threading.RLock()
214
+ # Guards the memo and the attempts in progress, never a lookup, a fetch
215
+ # or a load.
216
+ self._lock = threading.Lock()
193
217
  self._memo: dict[str, Library] = {}
218
+ self._flights: dict[str, _Flight] = {}
219
+ # A test hook, None outside tests: called each time an open starts
220
+ # waiting on an attempt, its own or another open's.
221
+ self._on_wait: Callable[[str], None] | None = None
194
222
  if isinstance(preload, str):
195
223
  raise TypeError("preload must be a sequence of requests, not a single string")
196
224
  for request in preload:
@@ -221,30 +249,63 @@ class Registry:
221
249
  return tuple(seen)
222
250
 
223
251
  def _open(self, request: str, *, allow_fetch: bool) -> Library:
252
+ with self._lock:
253
+ hit = self._memo.get(request)
254
+ if hit is not None:
255
+ return hit
256
+ # A refused version spelling is the caller's own misuse, refused before
257
+ # anything is attempted, and unlocks nothing.
258
+ try:
259
+ fetch_request = Request(request)
260
+ except ValueError as exc:
261
+ raise errors.misuse(str(exc)) from None
262
+ # So is a pinning request on the dev channel (rule r6).
263
+ _refuse_pinning(self._fetch._to_options())
224
264
  with self._lock:
225
265
  hit = self._memo.get(request)
226
266
  if hit is not None:
227
267
  return hit
228
- # A refused version spelling is the caller's own misuse, refused
229
- # before anything is attempted, and unlocks nothing. An open that
230
- # attempted a load and failed unlocks the setup record while no
231
- # image has completed load step 7, whatever failed: the resolve,
232
- # the fetch, the signature or any load step (bindings-v1.md
233
- # section 6, rule 4).
234
- try:
235
- fetch_request = Request(request)
236
- except ValueError as exc:
237
- raise errors.misuse(str(exc)) from None
238
- # So is a pinning request on the dev channel (rule r6).
239
- _refuse_pinning(self._fetch._to_options())
240
- began = _setup.generation()
241
- try:
242
- library = self._resolve_and_open(request, fetch_request, allow_fetch)
243
- except Exception:
244
- _setup.open_failed(began)
245
- raise
246
- self._memo[request] = library
247
- return library
268
+ flight = self._flights.get(request)
269
+ lead = flight is None
270
+ if flight is None:
271
+ flight = self._flights[request] = _Flight()
272
+ on_wait = self._on_wait
273
+ if on_wait is not None:
274
+ on_wait(request)
275
+ if lead:
276
+ self._attempt(flight, request, fetch_request, allow_fetch)
277
+ else:
278
+ flight.done.wait()
279
+ if flight.error is not None:
280
+ raise flight.error
281
+ assert flight.library is not None
282
+ return flight.library
283
+
284
+ def _attempt(
285
+ self, flight: _Flight, request: str, fetch_request: Request, allow_fetch: bool
286
+ ) -> None:
287
+ """Run an attempt on the leading open's thread, with no lock held, then
288
+ release every open waiting on it. An attempt that tried a load and
289
+ failed unlocks the setup record while no image has completed load step
290
+ 7, whatever failed: the resolve, the fetch, the signature or any load
291
+ step (bindings-v1.md section 6, rule 4). It is never remembered."""
292
+ began = _setup.generation()
293
+ library: Library | None = None
294
+ error: BaseException | None = None
295
+ try:
296
+ library = self._resolve_and_open(request, fetch_request, allow_fetch)
297
+ except Exception as exc:
298
+ _setup.open_failed(began)
299
+ error = exc
300
+ except BaseException as exc:
301
+ error = exc
302
+ with self._lock:
303
+ del self._flights[request]
304
+ if error is None:
305
+ assert library is not None
306
+ self._memo[request] = library
307
+ flight.library, flight.error = library, error
308
+ flight.done.set()
248
309
 
249
310
  def _resolve_and_open(self, request: str, fetch_request: Request, allow_fetch: bool) -> Library:
250
311
  try:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: chtypes
3
- Version: 2.0.0.dev5
3
+ Version: 2.0.0.dev6
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
@@ -8,7 +8,7 @@ chtypes/_setup.py,sha256=n84T0hFmXu_fdnXQIFhk3dMVL0_-yhfdRZsqCXTLzzg,6114
8
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=Q3LR7wBs7Ggy_5xG_-0bZrTRgvRWJO5OJbD92ecUlt4,13786
11
+ chtypes/registry.py,sha256=ApkHjOx4jBxeKJCZ7aTsPDJCWFnSd6rDRLfXam4ddQg,16275
12
12
  chtypes/results.py,sha256=gue7f4pqnpvXlSteYqhmqU1ZuAYCD7p4AgTiuYWpgYY,12591
13
13
  chtypes/_abi2/__init__.py,sha256=d3i7_iUDMFT1jg49vRQz1SMH6oseYZHUR0oPt6aSTiY,1042
14
14
  chtypes/_abi2/_decls.py,sha256=tGj_gOkMsw9fv8lWBXHUMn6dfYqpkta_nq1BhT6p4Dk,38454
@@ -17,8 +17,8 @@ chtypes/_abi2/_errors.py,sha256=wRE9M0oYtJz_9izQqpEBh5571M_1QzGWBoJ3pJTykWw,761
17
17
  chtypes/_abi2/_loader.py,sha256=VMWrbWMp9Uq3K-7a219lfqAmSmBlD2hcB6WDJgG8XG0,12514
18
18
  chtypes/_abi2/_vocab.py,sha256=6Ly15gQ1Vf3SP8hDSWCg1jnwVEURCBWSj9LZgv23xfA,17442
19
19
  chtypes/_ocifetch/__init__.py,sha256=enfPOKkqP4kqAKqckBGWXN1OQsiYo9ngDP-wzG0zn2I,1484
20
- chtypes/_ocifetch/_channel.py,sha256=8GqSNwuWaO3f4550N_DEsswqJu-0mWFAXbCB3ccFF9M,19108
21
- chtypes/_ocifetch/_constants.py,sha256=IZtAK3oxnCELZysvrzRozJkntxKSQllGjxod5No-Kco,5644
20
+ chtypes/_ocifetch/_channel.py,sha256=YEt6qlhr-AMFhSHefFvS5GQJ30-Qh4PAbGN6xnHZwxU,20217
21
+ chtypes/_ocifetch/_constants.py,sha256=Lt7hrSxFr5L2y87wsw33bbCekEwdq8wKn8N3i_r79o8,5974
22
22
  chtypes/_ocifetch/_dsse.py,sha256=0RaFJaG1jhXr1uPfuHxm7yAn6viNzYhfCRUf-sW5SvQ,9333
23
23
  chtypes/_ocifetch/_ensure.py,sha256=rI_xj0dccct7wMz0DwHoJTAB-yGVYFkLZ2Z0qRXuR30,63612
24
24
  chtypes/_ocifetch/_errors.py,sha256=TTYpwhvZ7MyD9Ssg5js8_YB8Fl30rYBPlENyAetqEbQ,7129
@@ -31,8 +31,8 @@ chtypes/_ocifetch/_oci.py,sha256=UdA8QGTh1O27DBYRf5UxsevjgI52IecQlEf9IO6Y0A4,139
31
31
  chtypes/_ocifetch/_referrers.py,sha256=J2b4gN6vryz_IzYHxrd2Tp-tQO9Cz766rZtKgCVcKqA,6008
32
32
  chtypes/_ocifetch/_retired.py,sha256=MQ6nbWLJw50IZYY1VfM6AiCZ4BySK0GVkGVOtAXaq3Y,3060
33
33
  chtypes/_ocifetch/_unpack.py,sha256=Cn4-up63KL63L8pnkS5zwJ6VbbHPSigpCnmOdcHJ4RI,5609
34
- chtypes-2.0.0.dev5.dist-info/METADATA,sha256=42l-skEiDCQQ3VA7jXzokD7woNnMpYzZm-qVMw2vivc,11280
35
- chtypes-2.0.0.dev5.dist-info/WHEEL,sha256=W3fkpkm7-wf9vBI5Z-7s0eWkeM-spu78I8Neb98DeEg,87
36
- chtypes-2.0.0.dev5.dist-info/entry_points.txt,sha256=TdNcluWBR0VOt5mVFROsp-NiVPAdR3EHo0UsHlMC0r0,50
37
- chtypes-2.0.0.dev5.dist-info/licenses/LICENSE,sha256=z8d0m5b2O9McPEK1xHG_dWgUBT6EfBDz6wA0F7xSPTA,11358
38
- chtypes-2.0.0.dev5.dist-info/RECORD,,
34
+ chtypes-2.0.0.dev6.dist-info/METADATA,sha256=T0boAAXpcSjeW7OqcCM0WihNHOX7ZRGU4ePoQnAlL-I,11280
35
+ chtypes-2.0.0.dev6.dist-info/WHEEL,sha256=W3fkpkm7-wf9vBI5Z-7s0eWkeM-spu78I8Neb98DeEg,87
36
+ chtypes-2.0.0.dev6.dist-info/entry_points.txt,sha256=TdNcluWBR0VOt5mVFROsp-NiVPAdR3EHo0UsHlMC0r0,50
37
+ chtypes-2.0.0.dev6.dist-info/licenses/LICENSE,sha256=z8d0m5b2O9McPEK1xHG_dWgUBT6EfBDz6wA0F7xSPTA,11358
38
+ chtypes-2.0.0.dev6.dist-info/RECORD,,