Skip to content

Commit 8aa9601

Browse files
authored
⚡️ Preliminary support for Pyrefly (#902)
2 parents 651a0d0 + cbf328e commit 8aa9601

34 files changed

+226
-138
lines changed

.github/workflows/ci.yml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,9 @@ jobs:
7777
- name: install dependencies
7878
run: uv pip install --exact --group type numpy==${{ matrix.np }}.* .
7979

80+
- name: pyrefly check scipy-stubs
81+
run: pyrefly check scipy-stubs
82+
8083
- name: basedpyright scipy-stubs
8184
run: basedpyright scipy-stubs
8285

@@ -115,6 +118,9 @@ jobs:
115118
- name: install dependencies
116119
run: uv pip install --exact --group type numpy==${{ matrix.np }}.* .
117120

121+
- name: pyrefly check tests
122+
run: pyrefly check tests
123+
118124
- name: basedpyright tests
119125
run: basedpyright tests
120126

CONTRIBUTING.md

Lines changed: 17 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ Ensure you have [`uv`](https://docs.astral.sh/uv/getting-started/installation/)
2424
installed. Now you can install the project with the dev dependencies:
2525

2626
```shell
27-
uv sync --python 3.11
27+
uv sync --exact --python 3.11
2828
```
2929

3030
Installing the lowest supported Python version (3.11 in this example) prevents
@@ -76,18 +76,22 @@ uvx tox p
7676
<summary>Output:</summary>
7777

7878
```plaintext
79-
lint: OK ✔ in 0.6 seconds
80-
pyright: OK ✔ in 11.97 seconds
81-
mypy: OK ✔ in 16.69 seconds
82-
3.11: OK ✔ in 18.05 seconds
83-
3.12: OK ✔ in 22.94 seconds
84-
lint: OK (0.60=setup[0.30]+cmd[0.16,0.11,0.04] seconds)
85-
pyright: OK (11.97=setup[0.30]+cmd[11.67] seconds)
86-
mypy: OK (16.69=setup[0.30]+cmd[16.39] seconds)
87-
3.13: OK (25.80=setup[0.30]+cmd[25.51] seconds)
88-
3.12: OK (22.94=setup[0.29]+cmd[22.64] seconds)
89-
3.11: OK (18.05=setup[0.30]+cmd[17.75] seconds)
90-
congratulations :) (25.84 seconds)
79+
lint: OK ✔ in 0.79 seconds
80+
pyrefly: OK ✔ in 1.19 seconds
81+
mypy: OK ✔ in 15.16 seconds
82+
pyright: OK ✔ in 17.69 seconds
83+
3.14: OK ✔ in 17.97 seconds
84+
3.13: OK ✔ in 18.37 seconds
85+
3.12: OK ✔ in 18.61 seconds
86+
lint: OK (0.79=setup[0.11]+cmd[0.43,0.06,0.14,0.04] seconds)
87+
pyrefly: OK (1.19=setup[0.18]+cmd[1.01] seconds)
88+
pyright: OK (17.69=setup[0.13]+cmd[17.56] seconds)
89+
mypy: OK (15.16=setup[0.29]+cmd[14.86] seconds)
90+
3.11: OK (18.72=setup[0.20]+cmd[18.52] seconds)
91+
3.12: OK (18.61=setup[0.26]+cmd[18.35] seconds)
92+
3.13: OK (18.37=setup[0.37]+cmd[18.00] seconds)
93+
3.14: OK (17.97=setup[0.17]+cmd[17.80] seconds)
94+
congratulations :) (18.74 seconds)
9195
```
9296

9397
</details>

README.md

Lines changed: 17 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -93,45 +93,39 @@ conda install conda-forge::scipy-typed
9393

9494
## Frequently Asked Questions
9595

96+
### Q: What static type-checkers are supported?
97+
98+
**A:** `scipy-stubs` is tested against the most recent stable versions of
99+
100+
- [Mypy](https://github.com/python/mypy),
101+
- [Pyright](https://github.com/microsoft/pyright) (a.k.a. Pylance),
102+
- [BasedPyright](https://github.com/detachhead/basedpyright), and
103+
- [Pyrefly](https://github.com/facebook/pyrefly) (currently in alpha, so it may not always work as expected).
104+
96105
### Q: Do I need to change my existing code?
97106

98107
**A:** No! `scipy-stubs` works with your existing code without any modifications.
99108
Just install it and your type checker and IDE will automatically use the type information.
100109

101-
### Q: Will this slow down my code?
102-
103-
**A:** Not at all. Type stubs are only used during development and type checking.
104-
They have zero runtime overhead since they're not imported when your code runs.
105-
106-
### Q: What if I don't use type hints in my code?
110+
### Q: How much of SciPy is covered?
107111

108-
**A:** You'll still benefit! Your IDE will provide better autocompletion and error detection
109-
even without explicit type annotations in your code.
112+
**A:** All of it! If you find any missing or incorrect type annotations, please open an issue on
113+
[GitHub](https://github.com/scipy/scipy-stubs/issues).
110114

111115
### Q: Can I use this with Jupyter notebooks?
112116

113-
**A:** Yes! Most modern Jupyter environments (JupyterLab, VS Code notebooks) support
117+
**A:** Yes! Most modern Jupyter environments (JupyterLab, VSCode notebooks) support
114118
type checking and will benefit from `scipy-stubs`.
115119

116-
### Q: What's the difference between this and the built-in scipy typing?
120+
### Q: What if I don't use type hints in my code?
117121

118-
**A:** SciPy itself has limited type annotations. `scipy-stubs` provides comprehensive,
119-
precise type information for the entire SciPy API, including shape-typing and advanced type features.
122+
**A:** You'll still benefit! Your IDE will provide better autocompletion and error detection
123+
even without explicit type annotations in your code.
120124

121125
### Q: How do I know if it's working?
122126

123127
**A:** You should see improved autocompletion in your IDE and more precise type information.
124-
You can also run `pyright` or another type checker on your code to see type checking in action.
125-
126-
### Q: How much of SciPy is covered?
127-
128-
**A:** All of it! If you find any missing or incorrect type annotations, please open an issue on [GitHub](https://github.com/scipy/scipy-stubs/issues).
129-
130-
### Q: What static type-checkers are supported?
131-
132-
**A:** `scipy-stubs` is compatible with [`pyright`](https://pyright.readthedocs.io/en/latest/index.html) (a.k.a. pylance),
133-
[`basedpyright`](https://github.com/DetachHead/basedpyright), and [`mypy`](https://github.com/python/mypy).
134-
We only support the latest versions of these type-checkers, so make sure to keep them up to date.
128+
You can also run `mypy`, `pyright` or another type checker on your code to see type checking in action.
135129

136130
## Versioning and requirements
137131

lefthook.yml

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ pre-commit:
88
glob: "*.{json,jsonc,md,toml,yaml,yml}"
99
stage_fixed: true
1010
run: uv {run} dprint fmt --incremental=false
11+
1112
- name: ruff
1213
glob: "*.{py,pyi}"
1314
stage_fixed: true
@@ -18,9 +19,15 @@ pre-commit:
1819
run: uv {run} ruff check --fix {staged_files}
1920
- name: format
2021
run: uv {run} ruff format {staged_files}
22+
2123
- name: basedpyright
2224
glob: "*.{py,pyi}"
2325
run: uv {run} basedpyright --threads=3 {staged_files}
26+
27+
- name: pyrefly
28+
glob: "*.{py,pyi}"
29+
run: uv {run} pyrefly check {staged_files}
30+
2431
- name: check-version-literals
2532
run: uv {run} scripts/check_version_literals.py
2633

pyproject.toml

Lines changed: 28 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,7 @@ type = [
6262
"array-api-compat==1.12.0", # bundled as `scipy._lib.array_api_compat`
6363
"basedpyright==1.31.6",
6464
"mypy[faster-cache]==1.18.2",
65+
"pyrefly==0.35.0",
6566
]
6667
dev = [
6768
{ include-group = "lint" },
@@ -106,6 +107,25 @@ warn_unreachable = false
106107
local_partial_types = true
107108
allow_redefinition_new = true
108109

110+
[tool.pyrefly]
111+
project-includes = ["scipy-stubs", "scripts", "tests"]
112+
113+
[[tool.pyrefly.sub-config]]
114+
matches = "scipy-stubs/*/__init__.pyi"
115+
errors = { deprecated = "ignore" }
116+
117+
[[tool.pyrefly.sub-config]]
118+
matches = "scipy-stubs/special/_ufuncs.pyi"
119+
errors = { bad-override = "ignore" }
120+
121+
[[tool.pyrefly.sub-config]]
122+
matches = "scipy-stubs/special/orthogonal.pyi"
123+
errors = { deprecated = "ignore" }
124+
125+
[[tool.pyrefly.sub-config]]
126+
matches = "scipy-stubs/stats/_distn_infrastructure.pyi"
127+
errors = { bad-override = "ignore" }
128+
109129
[tool.pyright]
110130
exclude = [
111131
"**/__pycache__",
@@ -204,7 +224,7 @@ split-on-trailing-comma = false
204224
[tool.tox]
205225
min_version = "4"
206226
requires = ["tox-uv>=1"]
207-
env_list = ["lint", "pyright", "mypy", "3.11", "3.12", "3.13", "3.14"]
227+
env_list = ["lint", "pyrefly", "pyright", "mypy", "3.11", "3.12", "3.13", "3.14"]
208228

209229
[tool.tox.env_run_base]
210230
description = "stubtest with {base_python}"
@@ -231,11 +251,17 @@ commands = [
231251
["ruff", "format", "--check"],
232252
]
233253

254+
[tool.tox.env.pyrefly]
255+
description = "pyrefly"
256+
runner = "uv-venv-lock-runner"
257+
dependency_groups = ["type"]
258+
commands = [["pyrefly", "check", { replace = "posargs", default = [], extend = true }]]
259+
234260
[tool.tox.env.pyright]
235261
description = "basedpyright"
236262
runner = "uv-venv-lock-runner"
237263
dependency_groups = ["type"]
238-
commands = [["basedpyright"]]
264+
commands = [["basedpyright", { replace = "posargs", default = [], extend = true }]]
239265

240266
[tool.tox.env.mypy]
241267
description = "mypy"
@@ -244,8 +270,6 @@ dependency_groups = ["type"]
244270
commands = [
245271
[
246272
"mypy",
247-
"--hide-error-context",
248-
"--hide-error-code-links",
249273
"--no-incremental",
250274
{ replace = "posargs", default = ["."], extend = true },
251275
],

scipy-stubs/fft/_backend.pyi

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,5 +26,5 @@ _named_backends: Final[dict[str, type[_Backend[Any]]]] = ...
2626
def _backend_from_arg(backend: _ToBackend[_T]) -> type[_Backend[_T]]: ... # undocumented
2727
def set_global_backend(backend: _ToBackend, coerce: bool = False, only: bool = False, try_last: bool = False) -> None: ...
2828
def register_backend(backend: _ToBackend) -> None: ...
29-
def set_backend(backend: _ToBackend, coerce: bool = False, only: bool = False) -> op.CanWith[None]: ...
30-
def skip_backend(backend: _ToBackend) -> op.CanWith[None]: ...
29+
def set_backend(backend: _ToBackend, coerce: bool = False, only: bool = False) -> op.CanWith[None, None]: ...
30+
def skip_backend(backend: _ToBackend) -> op.CanWith[None, None]: ...

scipy-stubs/optimize/_nonlin.pyi

Lines changed: 13 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -157,9 +157,9 @@ class GenericBroyden(Jacobian[_InexactT_co], Generic[_InexactT_co], metaclass=ab
157157
last_f: float
158158

159159
@override
160-
def setup(self, /, x0: _InexactND, f0: _InexactND, func: _ResidFunc) -> None: ... # pyright: ignore[reportIncompatibleMethodOverride]
160+
def setup(self, /, x0: _InexactND, f0: _InexactND, func: _ResidFunc) -> None: ... # pyright: ignore[reportIncompatibleMethodOverride] # pyrefly: ignore[bad-param-name-override]
161161
@override
162-
def update(self, /, x: _InexactND, f: _InexactND) -> None: ... # pyright: ignore[reportIncompatibleMethodOverride]
162+
def update(self, /, x: _InexactND, f: _InexactND) -> None: ... # pyright: ignore[reportIncompatibleMethodOverride] # pyrefly: ignore[bad-param-name-override]
163163

164164
class LowRankMatrix(Generic[_InexactT_co]):
165165
dtype: np.dtype[_InexactT_co]
@@ -189,9 +189,9 @@ class BroydenFirst(GenericBroyden[_InexactT_co], Generic[_InexactT_co]):
189189
self, /, alpha: float | None = None, reduction_method: _ReductionMethod = "restart", max_rank: int | None = None
190190
) -> None: ...
191191
@override
192-
def setup(self, /, x: _InexactND, F: _InexactND, func: _ResidFunc) -> None: ... # pyright: ignore[reportIncompatibleMethodOverride]
192+
def setup(self, /, x: _InexactND, F: _InexactND, func: _ResidFunc) -> None: ... # pyright: ignore[reportIncompatibleMethodOverride] # pyrefly: ignore[bad-param-name-override]
193193
@override
194-
def solve(self, /, f: _InexactND, tol: float = 0) -> onp.ArrayND[_InexactT_co]: ... # pyright: ignore[reportIncompatibleMethodOverride]
194+
def solve(self, /, f: _InexactND, tol: float = 0) -> onp.ArrayND[_InexactT_co]: ... # pyright: ignore[reportIncompatibleMethodOverride] # pyrefly: ignore[bad-param-name-override]
195195
def rsolve(self, /, f: _InexactND, tol: float = 0) -> onp.ArrayND[_InexactT_co]: ...
196196
def matvec(self, /, f: _InexactND) -> onp.Array1D[_InexactT_co]: ...
197197
def rmatvec(self, /, f: _InexactND) -> onp.Array1D[_InexactT_co]: ...
@@ -208,17 +208,17 @@ class Anderson(GenericBroyden[_InexactT_co], Generic[_InexactT_co]):
208208

209209
def __init__(self, /, alpha: float | None = None, w0: float = 0.01, M: float = 5) -> None: ...
210210
@override
211-
def solve(self, /, f: _InexactND, tol: float = 0) -> onp.Array2D[_InexactT_co]: ... # pyright: ignore[reportIncompatibleMethodOverride]
211+
def solve(self, /, f: _InexactND, tol: float = 0) -> onp.Array2D[_InexactT_co]: ... # pyright: ignore[reportIncompatibleMethodOverride] # pyrefly: ignore[bad-param-name-override]
212212
def matvec(self, /, f: _InexactND) -> onp.Array1D[_InexactT_co]: ...
213213

214214
class DiagBroyden(GenericBroyden[_InexactT_co], Generic[_InexactT_co]):
215215
d: onp.Array1D[_InexactT_co]
216216

217217
def __init__(self, /, alpha: float | None = None) -> None: ...
218218
@override
219-
def setup(self, /, x: _InexactND, F: _InexactND, func: _ResidFunc) -> None: ... # pyright: ignore[reportIncompatibleMethodOverride]
219+
def setup(self, /, x: _InexactND, F: _InexactND, func: _ResidFunc) -> None: ... # pyright: ignore[reportIncompatibleMethodOverride] # pyrefly: ignore[bad-param-name-override]
220220
@override
221-
def solve(self, /, f: _InexactND, tol: float = 0) -> onp.Array2D[_InexactT_co]: ... # pyright: ignore[reportIncompatibleMethodOverride]
221+
def solve(self, /, f: _InexactND, tol: float = 0) -> onp.Array2D[_InexactT_co]: ... # pyright: ignore[reportIncompatibleMethodOverride] # pyrefly: ignore[bad-param-name-override]
222222
def rsolve(self, /, f: _InexactND, tol: float = 0) -> onp.Array2D[_InexactT_co]: ...
223223
def matvec(self, /, f: _InexactND) -> onp.Array1D[_InexactT_co]: ...
224224
def rmatvec(self, /, f: _InexactND) -> onp.Array1D[_InexactT_co]: ...
@@ -227,7 +227,7 @@ class DiagBroyden(GenericBroyden[_InexactT_co], Generic[_InexactT_co]):
227227
class LinearMixing(GenericBroyden[_InexactT_co], Generic[_InexactT_co]):
228228
def __init__(self, /, alpha: float | None = None) -> None: ...
229229
@override
230-
def solve(self, /, f: _InexactND, tol: float = 0) -> onp.Array2D[_InexactT_co]: ... # pyright: ignore[reportIncompatibleMethodOverride]
230+
def solve(self, /, f: _InexactND, tol: float = 0) -> onp.Array2D[_InexactT_co]: ... # pyright: ignore[reportIncompatibleMethodOverride] # pyrefly: ignore[bad-param-name-override]
231231
def rsolve(self, /, f: _InexactND, tol: float = 0) -> onp.Array2D[_InexactT_co]: ...
232232
def matvec(self, /, f: _InexactND) -> onp.Array1D[_InexactT_co]: ...
233233
def rmatvec(self, /, f: _InexactND) -> onp.Array1D[_InexactT_co]: ...
@@ -239,9 +239,9 @@ class ExcitingMixing(GenericBroyden[_InexactT_co], Generic[_InexactT_co]):
239239

240240
def __init__(self, /, alpha: float | None = None, alphamax: float = 1.0) -> None: ...
241241
@override
242-
def setup(self, /, x: _InexactND, F: _InexactND, func: _ResidFunc) -> None: ... # pyright: ignore[reportIncompatibleMethodOverride]
242+
def setup(self, /, x: _InexactND, F: _InexactND, func: _ResidFunc) -> None: ... # pyright: ignore[reportIncompatibleMethodOverride] # pyrefly: ignore[bad-param-name-override]
243243
@override
244-
def solve(self, /, f: _InexactND, tol: float = 0) -> onp.Array2D[_InexactT_co]: ... # pyright: ignore[reportIncompatibleMethodOverride]
244+
def solve(self, /, f: _InexactND, tol: float = 0) -> onp.Array2D[_InexactT_co]: ... # pyright: ignore[reportIncompatibleMethodOverride] # pyrefly: ignore[bad-param-name-override]
245245
def rsolve(self, /, f: _InexactND, tol: float = 0) -> onp.Array2D[_InexactT_co]: ...
246246
def matvec(self, /, f: _InexactND) -> onp.Array1D[_InexactT_co]: ...
247247
def rmatvec(self, /, f: _InexactND) -> onp.Array1D[_InexactT_co]: ...
@@ -268,11 +268,11 @@ class KrylovJacobian(Jacobian[_InexactT_co], Generic[_InexactT_co]):
268268
) -> None: ...
269269
def matvec(self, /, v: _InexactND) -> onp.Array2D[_InexactT_co]: ...
270270
@override
271-
def solve(self, /, rhs: _InexactND, tol: float = 0) -> onp.Array2D[_InexactT_co]: ... # pyright: ignore[reportIncompatibleMethodOverride]
271+
def solve(self, /, rhs: _InexactND, tol: float = 0) -> onp.Array2D[_InexactT_co]: ... # pyright: ignore[reportIncompatibleMethodOverride] # pyrefly: ignore[bad-param-name-override]
272272
@override
273-
def update(self, /, x: _InexactND, f: _InexactND) -> None: ... # pyright: ignore[reportIncompatibleMethodOverride]
273+
def update(self, /, x: _InexactND, f: _InexactND) -> None: ... # pyright: ignore[reportIncompatibleMethodOverride] # pyrefly: ignore[bad-param-name-override]
274274
@override
275-
def setup(self, /, x: _InexactND, f: _InexactND, func: _ResidFunc) -> None: ... # pyright: ignore[reportIncompatibleMethodOverride]
275+
def setup(self, /, x: _InexactND, f: _InexactND, func: _ResidFunc) -> None: ... # pyright: ignore[reportIncompatibleMethodOverride] # pyrefly: ignore[bad-param-name-override]
276276

277277
# undocumented
278278
@overload

scipy-stubs/optimize/_trustregion_exact.pyi

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,4 +59,5 @@ class IterativeSubproblem(BaseQuadraticSubproblem):
5959
k_hard: onp.ToFloat = 0.2,
6060
) -> None: ...
6161
@override
62+
# pyrefly: ignore[bad-param-name-override]
6263
def solve(self, /, tr_radius: onp.ToFloat) -> tuple[onp.ArrayND[np.float64], bool]: ... # pyright: ignore[reportIncompatibleMethodOverride]

scipy-stubs/signal/bsplines.pyi

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
# This module is not meant for public use and will be removed in SciPy v2.0.0.
22
from typing_extensions import deprecated
33

4-
from .spline import sepfir2d
4+
from .spline import sepfir2d # pyrefly: ignore[deprecated]
55

66
__all__ = ["cspline1d", "cspline1d_eval", "cspline2d", "gauss_spline", "qspline1d", "qspline1d_eval", "sepfir2d", "spline_filter"]
77

scipy-stubs/signal/filter_design.pyi

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,7 @@ def group_delay(system: object, w: object = ..., whole: object = ..., fs: object
7979
@deprecated("will be removed in SciPy v2.0.0")
8080
def freqz_sos(sos: object, worN: object = ..., whole: object = ..., fs: object = ...) -> object: ...
8181

82-
sosfreqz = freqz_sos # pyright: ignore[reportDeprecated]
82+
sosfreqz = freqz_sos # pyright: ignore[reportDeprecated] # pyrefly: ignore[deprecated]
8383

8484
@deprecated("will be removed in SciPy v2.0.0")
8585
def tf2zpk(b: object, a: object) -> object: ...

0 commit comments

Comments
 (0)