From 88c71c2d822d618b4846fab0c5c3b1bcaaf89387 Mon Sep 17 00:00:00 2001 From: JP Hutchins Date: Tue, 22 Sep 2026 15:36:41 -0700 Subject: [PATCH] refactor: drop the runtime `u8 = int` shim; `u8` is typing-only #137 bound `u8 = int` in an `else` branch of the `TYPE_CHECKING` import, so the checkers saw `types_bits.u8` (`Literal[0..255]`) while the interpreter saw `int`: an invisible divergence. The branch existed only because two expressions evaluated `u8` at runtime: the `sequence: Iterator[u8] | None` annotation on `SMPClient.__init__` (evaluated eagerly on 3.10-3.13) and the first argument of `cast()` in `wrapping_sequence()`. With `from __future__ import annotations`, annotations are stored as strings and never evaluated. `cast()`'s first argument is an ordinary expression, not an annotation, so it is quoted. Nothing binds `u8` at runtime any more, so the `else` branch and its explanatory comment go together. Why not declare `types-bits[rt]` instead: smp itself declares `types-bits<0.3,>=0.2` without the `rt` extra, so the library that defines `u8` treats it as typing-only. The extra only pulls in `annotated-types` (one of pydantic's dependencies, which #136 evicted), and nothing in smpclient validates a `u8` at runtime: `cast()` is a no-op, and the annotation is never read. Verified: `camas check` and `camas matrix` (3.10-3.14) green. A clean `uv pip install .` into a fresh 3.10 venv (what the `transport-extras` CI job does) has no `annotated_types`; `import smpclient` succeeds and `wrapping_sequence()` wraps 255 -> 0, with `u8` unbound at runtime. Co-Authored-By: Claude Opus 5.5 (1M context) --- src/smpclient/__init__.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/smpclient/__init__.py b/src/smpclient/__init__.py index 1d81e14..c471166 100644 --- a/src/smpclient/__init__.py +++ b/src/smpclient/__init__.py @@ -35,6 +35,8 @@ """ +from __future__ import annotations + import asyncio import itertools import logging @@ -60,8 +62,6 @@ if TYPE_CHECKING: from types_bits import u8 -else: # `types_bits` is typing-only; reaching its values at runtime needs its `rt` extra - u8 = int try: from asyncio import timeout # type: ignore @@ -143,7 +143,7 @@ def success( def wrapping_sequence() -> Iterator[u8]: """The default SMP sequence space: `0x00`-`0xFF`, wrapping.""" - return cast(Iterator[u8], itertools.cycle(range(0x100))) + return cast("Iterator[u8]", itertools.cycle(range(0x100))) def _hexdump(frame: bytes) -> str: