Affected File
core/predict.py:154,198
Current Code
# core/predict.py
import numpy as np
import torch
# ... (no import numpy.typing, no from __future__ import annotations)
def get_prediction(self, left_counts: np.typing.ArrayLike, right_counts: np.typing.ArrayLike): # L154
...
def get_prediction_with_terrain(self, full_features: np.typing.ArrayLike): # L198
...
No from __future__ import annotations — annotations are evaluated at definition time.
Root Cause
numpy.typing is only accessible as a module attribute (np.typing.XXX) in numpy >= 2.0.0 (via __getattr__). On numpy < 2.0, access without a prior import numpy.typing triggers:
AttributeError: module 'numpy' has no attribute 'typing'
The issue arises because numpy.typing becomes available on numpy<2 only if some earlier code in the import chain has executed import numpy.typing — once imported, it is injected into numpy.__dict__.
Dependency
pyproject.toml declares "numpy" and "pandas" with no version constraints:
dependencies = [
"numpy", # no version constraint
"pandas", # no version constraint
"matplotlib",
"onnxruntime",
...
]
This allows pip to resolve numpy<2 + pandas<3, where neither dependency triggers numpy.typing at import time. The uv.lock file does pin numpy 2.x, but this only protects users who install with uv — pip users have no such guarantee.
Upstream Dependency Protection
We investigated whether any dependency in the import chain triggers numpy.typing before core/predict.py is loaded. The chain is:
main.py → import numpy → import onnxruntime → import PyQt6 → ... → try: from core.predict import CannotModel
- pandas: declared in
pyproject.toml but not imported by main.py or core/predict.py — never loaded before the affected code. Even if pandas is installed, only pandas >= 3.0 triggers numpy.typing at import time.
- PyQt6, onnxruntime, torch: none trigger
numpy.typing.
Impact
The crash in core/predict.py is caught by a try/except in main.py that silently falls back to the ONNX backend:
try:
from core.predict import CannotModel
logger.info("Using PyTorch model for predictions.")
except:
from core.predict_onnx import CannotModel
logger.info("Using ONNX model for predictions.")
So numpy<2 users do not see a hard crash, but the PyTorch backend is silently unavailable — they get the ONNX fallback without any indication of the underlying cause.
Solution
Either:
- Add
numpy>=2 to pyproject.toml:
"numpy>=2", # was: "numpy"
- Or add a defensive import in
core/predict.py:
import numpy.typing # ensures np.typing.ArrayLike works on numpy<2
- Or add
from __future__ import annotations to defer annotation evaluation.
References
- NumPy 2.0 release notes —
numpy.typing exposed as module attribute via __getattr__
- Similar fix: numexpr#540
Affected File
core/predict.py:154,198Current Code
No
from __future__ import annotations— annotations are evaluated at definition time.Root Cause
numpy.typingis only accessible as a module attribute (np.typing.XXX) in numpy >= 2.0.0 (via__getattr__). On numpy < 2.0, access without a priorimport numpy.typingtriggers:The issue arises because
numpy.typingbecomes available on numpy<2 only if some earlier code in the import chain has executedimport numpy.typing— once imported, it is injected intonumpy.__dict__.Dependency
pyproject.tomldeclares"numpy"and"pandas"with no version constraints:This allows pip to resolve numpy<2 + pandas<3, where neither dependency triggers
numpy.typingat import time. Theuv.lockfile does pin numpy 2.x, but this only protects users who install withuv— pip users have no such guarantee.Upstream Dependency Protection
We investigated whether any dependency in the import chain triggers
numpy.typingbeforecore/predict.pyis loaded. The chain is:pyproject.tomlbut not imported bymain.pyorcore/predict.py— never loaded before the affected code. Even if pandas is installed, only pandas >= 3.0 triggersnumpy.typingat import time.numpy.typing.Impact
The crash in
core/predict.pyis caught by a try/except inmain.pythat silently falls back to the ONNX backend:So numpy<2 users do not see a hard crash, but the PyTorch backend is silently unavailable — they get the ONNX fallback without any indication of the underlying cause.
Solution
Either:
numpy>=2to pyproject.toml:core/predict.py:from __future__ import annotationsto defer annotation evaluation.References
numpy.typingexposed as module attribute via__getattr__