Skip to content

Commit 29f6363

Browse files
committed
fix: two runtime PEP 604 sites that FA102 structurally cannot catch
3.9 was the last red leg: 1 failed, 8 errors, 1712 passed. Same family as 17e7e6c, but a variant the linter cannot see - both files already carry `from __future__ import annotations`, which is exactly why FA102 stays quiet while the code still breaks at runtime. backend.py `cast(CUDABatchDecoder | None, ...)`. The future import stringifies annotations; an argument to `cast()` is an ordinary expression and is evaluated on import, so `type.__or__` is reached on 3.9 and the package fails to import. Quote the target - `cast` never evaluates its first argument, and type checkers read string forward refs fine. rest_api.py `n_qubits: int | None` on a pydantic BaseModel. Here the future import actively hurts: the annotation arrives at pydantic as the string "int | None", which it eval()s to resolve the model, and on 3.9 that raises. It surfaced as six errors recommending `eval_type_backport`, plus two cascading 'NoneType' has no attribute '_rate_lock' errors once the module failed to load. Use `Optional[int]`. The neighbouring `list[list[int]]` is left alone - PEP 585 builtin subscripting landed in 3.9; only PEP 604 unions did not. Swept for the rest of this shape rather than fixing only what failed: these are the only two `cast()` sites and the only pydantic field affected; no isinstance/issubclass union arguments exist. The real guard is the 3.9 leg itself, which now gets far enough to import every module - it could not before 17e7e6c.
1 parent b9425e0 commit 29f6363

2 files changed

Lines changed: 19 additions & 4 deletions

File tree

python/qector_decoder_v3/backend.py

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -295,11 +295,19 @@ def _get_cpu_single(self) -> FastUnionFindDecoder:
295295
def _get_cpu_rayon(self) -> BatchDecoder:
296296
return cast(BatchDecoder, self._get_decoder(Backend.CPU_RAYON))
297297

298+
# The cast targets are quoted deliberately. `from __future__ import
299+
# annotations` stringifies *annotations*, but an argument to `cast()` is an
300+
# ordinary runtime expression, so a bare `X | None` here is evaluated on
301+
# import and raises TypeError on 3.9, where `type.__or__` does not exist.
302+
# `requires-python` is >=3.9, and this made the whole package unimportable
303+
# there. ruff's FA102 cannot see it precisely because the file already has
304+
# the future import. `cast` never evaluates its first argument, so a string
305+
# is both correct at runtime and understood by type checkers.
298306
def _get_cuda(self) -> CUDABatchDecoder | None:
299-
return cast(CUDABatchDecoder | None, self._get_decoder(Backend.CUDA))
307+
return cast("CUDABatchDecoder | None", self._get_decoder(Backend.CUDA))
300308

301309
def _get_opencl(self) -> OpenCLBatchDecoder | None:
302-
return cast(OpenCLBatchDecoder | None, self._get_decoder(Backend.OPENCL))
310+
return cast("OpenCLBatchDecoder | None", self._get_decoder(Backend.OPENCL))
303311

304312
# -- native auto decoder ------------------------------------------------
305313
def _try_native_auto(self, syndromes: np.ndarray) -> np.ndarray | None:

python/qector_decoder_v3/rest_api.py

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@
3535
import logging
3636
import time
3737
import uuid
38-
from typing import Any
38+
from typing import Any, Optional
3939

4040
import numpy as np
4141

@@ -123,7 +123,14 @@ def _rate_limit_allow(client_ip: str) -> bool:
123123
class DecodeRequest(BaseModel):
124124
check_to_qubits: list[list[int]]
125125
syndrome: list[int]
126-
n_qubits: int | None = None
126+
# `Optional[int]`, not `int | None`. Pydantic resolves model annotations
127+
# at runtime with `get_type_hints`, and `from __future__ import
128+
# annotations` only makes that worse here: the annotation reaches
129+
# pydantic as the *string* "int | None", which it then eval()s, and on
130+
# 3.9 that raises. It surfaced as six errors telling us to install
131+
# `eval_type_backport`. `list[list[int]]` above is fine — PEP 585
132+
# builtin subscripting landed in 3.9; only PEP 604 unions did not.
133+
n_qubits: Optional[int] = None
127134
use_batch: bool = False
128135

129136
class DecodeResponse(BaseModel):

0 commit comments

Comments
 (0)