diff --git a/pyproject.toml b/pyproject.toml index 82e0acc..e788a61 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -115,8 +115,10 @@ select = ["E", "F", "I", "B", "UP", "N", "RUF", "TID"] "scripts/inspect_r_api_update.py" = ["TID251"] "scripts/verify_release_provenance.py" = ["TID251"] +# No python_version pin: mypy targets the interpreter it runs under. The CI +# matrix runs mypy on 3.11-3.14, and pinning to 3.11 made mypy reject newer +# numpy stubs that use PEP 695 `type` statements (valid only on 3.12+). [tool.mypy] -python_version = "3.11" strict = true files = ["src/nns", "tests"] mypy_path = ["tests"] diff --git a/src/nns/central_tendencies.py b/src/nns/central_tendencies.py index f3c4cd4..f89746e 100644 --- a/src/nns/central_tendencies.py +++ b/src/nns/central_tendencies.py @@ -135,10 +135,12 @@ def _discrete_mode(values: NDArray[np.float64], multi: bool) -> float | NDArray[ integerized = _nearest_int_half_up_array(values) modes, counts = np.unique(integerized, return_counts=True) tied_modes = modes[counts == int(np.max(counts))] - tied_modes = np.sort(tied_modes.astype(np.float64)) + # Annotate the sorted result so the multi return is typed rather than Any + # under the more precise numpy stubs that mypy sees when targeting 3.12+. + sorted_modes: NDArray[np.float64] = np.sort(tied_modes.astype(np.float64)) if multi: - return tied_modes - return float(np.mean(tied_modes)) + return sorted_modes + return float(np.mean(sorted_modes)) def _continuous_mode(values: NDArray[np.float64], multi: bool) -> float | NDArray[np.float64]: diff --git a/src/nns/norm.py b/src/nns/norm.py index b9c4496..07dc3f7 100644 --- a/src/nns/norm.py +++ b/src/nns/norm.py @@ -22,7 +22,12 @@ def nns_norm(x: NDArray[np.float64], linear: bool = False) -> NDArray[np.float64 scale_factor = _scale_factor(values) scales = np.mean(ratio_grid * scale_factor, axis=0) - return cast(NDArray[np.float64], values * scales[np.newaxis, :]) + # Annotated assignment instead of cast(): under mypy targeting 3.12+ the + # numpy stubs type this product precisely, making an explicit cast redundant + # (warn_redundant_casts), while on 3.11 the stubs return Any and the + # annotation still narrows it without a no-any-return error. + scaled: NDArray[np.float64] = values * scales[np.newaxis, :] + return scaled def _scale_factor(values: NDArray[np.float64]) -> NDArray[np.float64]: