Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,9 @@ jobs:
- name: install dependencies
run: uv pip install --exact --group type numpy==${{ matrix.np }}.* .

- name: pyrefly check scipy-stubs
run: pyrefly check scipy-stubs

- name: basedpyright scipy-stubs
run: basedpyright scipy-stubs

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

- name: pyrefly check tests
run: pyrefly check tests

- name: basedpyright tests
run: basedpyright tests

Expand Down
30 changes: 17 additions & 13 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ Ensure you have [`uv`](https://docs.astral.sh/uv/getting-started/installation/)
installed. Now you can install the project with the dev dependencies:

```shell
uv sync --python 3.11
uv sync --exact --python 3.11
```

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

```plaintext
lint: OK ✔ in 0.6 seconds
pyright: OK ✔ in 11.97 seconds
mypy: OK ✔ in 16.69 seconds
3.11: OK ✔ in 18.05 seconds
3.12: OK ✔ in 22.94 seconds
lint: OK (0.60=setup[0.30]+cmd[0.16,0.11,0.04] seconds)
pyright: OK (11.97=setup[0.30]+cmd[11.67] seconds)
mypy: OK (16.69=setup[0.30]+cmd[16.39] seconds)
3.13: OK (25.80=setup[0.30]+cmd[25.51] seconds)
3.12: OK (22.94=setup[0.29]+cmd[22.64] seconds)
3.11: OK (18.05=setup[0.30]+cmd[17.75] seconds)
congratulations :) (25.84 seconds)
lint: OK ✔ in 0.79 seconds
pyrefly: OK ✔ in 1.19 seconds
mypy: OK ✔ in 15.16 seconds
pyright: OK ✔ in 17.69 seconds
3.14: OK ✔ in 17.97 seconds
3.13: OK ✔ in 18.37 seconds
3.12: OK ✔ in 18.61 seconds
lint: OK (0.79=setup[0.11]+cmd[0.43,0.06,0.14,0.04] seconds)
pyrefly: OK (1.19=setup[0.18]+cmd[1.01] seconds)
pyright: OK (17.69=setup[0.13]+cmd[17.56] seconds)
mypy: OK (15.16=setup[0.29]+cmd[14.86] seconds)
3.11: OK (18.72=setup[0.20]+cmd[18.52] seconds)
3.12: OK (18.61=setup[0.26]+cmd[18.35] seconds)
3.13: OK (18.37=setup[0.37]+cmd[18.00] seconds)
3.14: OK (17.97=setup[0.17]+cmd[17.80] seconds)
congratulations :) (18.74 seconds)
```

</details>
Expand Down
40 changes: 17 additions & 23 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -93,45 +93,39 @@ conda install conda-forge::scipy-typed

## Frequently Asked Questions

### Q: What static type-checkers are supported?

**A:** `scipy-stubs` is tested against the most recent stable versions of

- [Mypy](https://github.com/python/mypy),
- [Pyright](https://github.com/microsoft/pyright) (a.k.a. Pylance),
- [BasedPyright](https://github.com/detachhead/basedpyright), and
- [Pyrefly](https://github.com/facebook/pyrefly) (currently in alpha, so it may not always work as expected).

### Q: Do I need to change my existing code?

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

### Q: Will this slow down my code?

**A:** Not at all. Type stubs are only used during development and type checking.
They have zero runtime overhead since they're not imported when your code runs.

### Q: What if I don't use type hints in my code?
### Q: How much of SciPy is covered?

**A:** You'll still benefit! Your IDE will provide better autocompletion and error detection
even without explicit type annotations in your code.
**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).

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

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

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

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

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

**A:** You should see improved autocompletion in your IDE and more precise type information.
You can also run `pyright` or another type checker on your code to see type checking in action.

### Q: How much of SciPy is covered?

**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).

### Q: What static type-checkers are supported?

**A:** `scipy-stubs` is compatible with [`pyright`](https://pyright.readthedocs.io/en/latest/index.html) (a.k.a. pylance),
[`basedpyright`](https://github.com/DetachHead/basedpyright), and [`mypy`](https://github.com/python/mypy).
We only support the latest versions of these type-checkers, so make sure to keep them up to date.
You can also run `mypy`, `pyright` or another type checker on your code to see type checking in action.

## Versioning and requirements

Expand Down
7 changes: 7 additions & 0 deletions lefthook.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ pre-commit:
glob: "*.{json,jsonc,md,toml,yaml,yml}"
stage_fixed: true
run: uv {run} dprint fmt --incremental=false

- name: ruff
glob: "*.{py,pyi}"
stage_fixed: true
Expand All @@ -18,9 +19,15 @@ pre-commit:
run: uv {run} ruff check --fix {staged_files}
- name: format
run: uv {run} ruff format {staged_files}

- name: basedpyright
glob: "*.{py,pyi}"
run: uv {run} basedpyright --threads=3 {staged_files}

- name: pyrefly
glob: "*.{py,pyi}"
run: uv {run} pyrefly check {staged_files}

- name: check-version-literals
run: uv {run} scripts/check_version_literals.py

Expand Down
32 changes: 28 additions & 4 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ type = [
"array-api-compat==1.12.0", # bundled as `scipy._lib.array_api_compat`
"basedpyright==1.31.5",
"mypy[faster-cache]==1.18.2",
"pyrefly==0.35.0",
]
dev = [
{ include-group = "lint" },
Expand Down Expand Up @@ -106,6 +107,25 @@ warn_unreachable = false
local_partial_types = true
allow_redefinition_new = true

[tool.pyrefly]
project-includes = ["scipy-stubs", "scripts", "tests"]

[[tool.pyrefly.sub-config]]
matches = "scipy-stubs/*/__init__.pyi"
errors = { deprecated = "ignore" }

[[tool.pyrefly.sub-config]]
matches = "scipy-stubs/special/_ufuncs.pyi"
errors = { bad-override = "ignore" }

[[tool.pyrefly.sub-config]]
matches = "scipy-stubs/special/orthogonal.pyi"
errors = { deprecated = "ignore" }

[[tool.pyrefly.sub-config]]
matches = "scipy-stubs/stats/_distn_infrastructure.pyi"
errors = { bad-override = "ignore" }

[tool.pyright]
exclude = [
"**/__pycache__",
Expand Down Expand Up @@ -205,7 +225,7 @@ split-on-trailing-comma = false
[tool.tox]
min_version = "4"
requires = ["tox-uv>=1"]
env_list = ["lint", "pyright", "mypy", "3.11", "3.12", "3.13", "3.14"]
env_list = ["lint", "pyrefly", "pyright", "mypy", "3.11", "3.12", "3.13", "3.14"]

[tool.tox.env_run_base]
description = "stubtest with {base_python}"
Expand All @@ -232,11 +252,17 @@ commands = [
["ruff", "format", "--check"],
]

[tool.tox.env.pyrefly]
description = "pyrefly"
runner = "uv-venv-lock-runner"
dependency_groups = ["type"]
commands = [["pyrefly", "check", { replace = "posargs", default = [], extend = true }]]

[tool.tox.env.pyright]
description = "basedpyright"
runner = "uv-venv-lock-runner"
dependency_groups = ["type"]
commands = [["basedpyright"]]
commands = [["basedpyright", { replace = "posargs", default = [], extend = true }]]

[tool.tox.env.mypy]
description = "mypy"
Expand All @@ -245,8 +271,6 @@ dependency_groups = ["type"]
commands = [
[
"mypy",
"--hide-error-context",
"--hide-error-code-links",
"--no-incremental",
{ replace = "posargs", default = ["."], extend = true },
],
Expand Down
4 changes: 2 additions & 2 deletions scipy-stubs/fft/_backend.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -26,5 +26,5 @@ _named_backends: Final[dict[str, type[_Backend[Any]]]] = ...
def _backend_from_arg(backend: _ToBackend[_T]) -> type[_Backend[_T]]: ... # undocumented
def set_global_backend(backend: _ToBackend, coerce: bool = False, only: bool = False, try_last: bool = False) -> None: ...
def register_backend(backend: _ToBackend) -> None: ...
def set_backend(backend: _ToBackend, coerce: bool = False, only: bool = False) -> op.CanWith[None]: ...
def skip_backend(backend: _ToBackend) -> op.CanWith[None]: ...
def set_backend(backend: _ToBackend, coerce: bool = False, only: bool = False) -> op.CanWith[None, None]: ...
def skip_backend(backend: _ToBackend) -> op.CanWith[None, None]: ...
26 changes: 13 additions & 13 deletions scipy-stubs/optimize/_nonlin.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -157,9 +157,9 @@ class GenericBroyden(Jacobian[_InexactT_co], Generic[_InexactT_co], metaclass=ab
last_f: float

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

class LowRankMatrix(Generic[_InexactT_co]):
dtype: np.dtype[_InexactT_co]
Expand Down Expand Up @@ -189,9 +189,9 @@ class BroydenFirst(GenericBroyden[_InexactT_co], Generic[_InexactT_co]):
self, /, alpha: float | None = None, reduction_method: _ReductionMethod = "restart", max_rank: int | None = None
) -> None: ...
@override
def setup(self, /, x: _InexactND, F: _InexactND, func: _ResidFunc) -> None: ... # pyright: ignore[reportIncompatibleMethodOverride]
def setup(self, /, x: _InexactND, F: _InexactND, func: _ResidFunc) -> None: ... # pyright: ignore[reportIncompatibleMethodOverride] # pyrefly: ignore[bad-param-name-override]
@override
def solve(self, /, f: _InexactND, tol: float = 0) -> onp.ArrayND[_InexactT_co]: ... # pyright: ignore[reportIncompatibleMethodOverride]
def solve(self, /, f: _InexactND, tol: float = 0) -> onp.ArrayND[_InexactT_co]: ... # pyright: ignore[reportIncompatibleMethodOverride] # pyrefly: ignore[bad-param-name-override]
def rsolve(self, /, f: _InexactND, tol: float = 0) -> onp.ArrayND[_InexactT_co]: ...
def matvec(self, /, f: _InexactND) -> onp.Array1D[_InexactT_co]: ...
def rmatvec(self, /, f: _InexactND) -> onp.Array1D[_InexactT_co]: ...
Expand All @@ -208,17 +208,17 @@ class Anderson(GenericBroyden[_InexactT_co], Generic[_InexactT_co]):

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

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

def __init__(self, /, alpha: float | None = None) -> None: ...
@override
def setup(self, /, x: _InexactND, F: _InexactND, func: _ResidFunc) -> None: ... # pyright: ignore[reportIncompatibleMethodOverride]
def setup(self, /, x: _InexactND, F: _InexactND, func: _ResidFunc) -> None: ... # pyright: ignore[reportIncompatibleMethodOverride] # pyrefly: ignore[bad-param-name-override]
@override
def solve(self, /, f: _InexactND, tol: float = 0) -> onp.Array2D[_InexactT_co]: ... # pyright: ignore[reportIncompatibleMethodOverride]
def solve(self, /, f: _InexactND, tol: float = 0) -> onp.Array2D[_InexactT_co]: ... # pyright: ignore[reportIncompatibleMethodOverride] # pyrefly: ignore[bad-param-name-override]
def rsolve(self, /, f: _InexactND, tol: float = 0) -> onp.Array2D[_InexactT_co]: ...
def matvec(self, /, f: _InexactND) -> onp.Array1D[_InexactT_co]: ...
def rmatvec(self, /, f: _InexactND) -> onp.Array1D[_InexactT_co]: ...
Expand All @@ -227,7 +227,7 @@ class DiagBroyden(GenericBroyden[_InexactT_co], Generic[_InexactT_co]):
class LinearMixing(GenericBroyden[_InexactT_co], Generic[_InexactT_co]):
def __init__(self, /, alpha: float | None = None) -> None: ...
@override
def solve(self, /, f: _InexactND, tol: float = 0) -> onp.Array2D[_InexactT_co]: ... # pyright: ignore[reportIncompatibleMethodOverride]
def solve(self, /, f: _InexactND, tol: float = 0) -> onp.Array2D[_InexactT_co]: ... # pyright: ignore[reportIncompatibleMethodOverride] # pyrefly: ignore[bad-param-name-override]
def rsolve(self, /, f: _InexactND, tol: float = 0) -> onp.Array2D[_InexactT_co]: ...
def matvec(self, /, f: _InexactND) -> onp.Array1D[_InexactT_co]: ...
def rmatvec(self, /, f: _InexactND) -> onp.Array1D[_InexactT_co]: ...
Expand All @@ -239,9 +239,9 @@ class ExcitingMixing(GenericBroyden[_InexactT_co], Generic[_InexactT_co]):

def __init__(self, /, alpha: float | None = None, alphamax: float = 1.0) -> None: ...
@override
def setup(self, /, x: _InexactND, F: _InexactND, func: _ResidFunc) -> None: ... # pyright: ignore[reportIncompatibleMethodOverride]
def setup(self, /, x: _InexactND, F: _InexactND, func: _ResidFunc) -> None: ... # pyright: ignore[reportIncompatibleMethodOverride] # pyrefly: ignore[bad-param-name-override]
@override
def solve(self, /, f: _InexactND, tol: float = 0) -> onp.Array2D[_InexactT_co]: ... # pyright: ignore[reportIncompatibleMethodOverride]
def solve(self, /, f: _InexactND, tol: float = 0) -> onp.Array2D[_InexactT_co]: ... # pyright: ignore[reportIncompatibleMethodOverride] # pyrefly: ignore[bad-param-name-override]
def rsolve(self, /, f: _InexactND, tol: float = 0) -> onp.Array2D[_InexactT_co]: ...
def matvec(self, /, f: _InexactND) -> onp.Array1D[_InexactT_co]: ...
def rmatvec(self, /, f: _InexactND) -> onp.Array1D[_InexactT_co]: ...
Expand All @@ -268,11 +268,11 @@ class KrylovJacobian(Jacobian[_InexactT_co], Generic[_InexactT_co]):
) -> None: ...
def matvec(self, /, v: _InexactND) -> onp.Array2D[_InexactT_co]: ...
@override
def solve(self, /, rhs: _InexactND, tol: float = 0) -> onp.Array2D[_InexactT_co]: ... # pyright: ignore[reportIncompatibleMethodOverride]
def solve(self, /, rhs: _InexactND, tol: float = 0) -> onp.Array2D[_InexactT_co]: ... # pyright: ignore[reportIncompatibleMethodOverride] # pyrefly: ignore[bad-param-name-override]
@override
def update(self, /, x: _InexactND, f: _InexactND) -> None: ... # pyright: ignore[reportIncompatibleMethodOverride]
def update(self, /, x: _InexactND, f: _InexactND) -> None: ... # pyright: ignore[reportIncompatibleMethodOverride] # pyrefly: ignore[bad-param-name-override]
@override
def setup(self, /, x: _InexactND, f: _InexactND, func: _ResidFunc) -> None: ... # pyright: ignore[reportIncompatibleMethodOverride]
def setup(self, /, x: _InexactND, f: _InexactND, func: _ResidFunc) -> None: ... # pyright: ignore[reportIncompatibleMethodOverride] # pyrefly: ignore[bad-param-name-override]

# undocumented
@overload
Expand Down
1 change: 1 addition & 0 deletions scipy-stubs/optimize/_trustregion_exact.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -59,4 +59,5 @@ class IterativeSubproblem(BaseQuadraticSubproblem):
k_hard: onp.ToFloat = 0.2,
) -> None: ...
@override
# pyrefly: ignore[bad-param-name-override]
def solve(self, /, tr_radius: onp.ToFloat) -> tuple[onp.ArrayND[np.float64], bool]: ... # pyright: ignore[reportIncompatibleMethodOverride]
2 changes: 1 addition & 1 deletion scipy-stubs/signal/bsplines.pyi
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# This module is not meant for public use and will be removed in SciPy v2.0.0.
from typing_extensions import deprecated

from .spline import sepfir2d
from .spline import sepfir2d # pyrefly: ignore[deprecated]

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

Expand Down
2 changes: 1 addition & 1 deletion scipy-stubs/signal/filter_design.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ def group_delay(system: object, w: object = ..., whole: object = ..., fs: object
@deprecated("will be removed in SciPy v2.0.0")
def freqz_sos(sos: object, worN: object = ..., whole: object = ..., fs: object = ...) -> object: ...

sosfreqz = freqz_sos # pyright: ignore[reportDeprecated]
sosfreqz = freqz_sos # pyright: ignore[reportDeprecated] # pyrefly: ignore[deprecated]

@deprecated("will be removed in SciPy v2.0.0")
def tf2zpk(b: object, a: object) -> object: ...
Expand Down
2 changes: 1 addition & 1 deletion scipy-stubs/signal/lti_conversion.pyi
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# This module is not meant for public use and will be removed in SciPy v2.0.0.
from typing_extensions import deprecated

from .filter_design import normalize, tf2zpk, zpk2tf
from .filter_design import normalize, tf2zpk, zpk2tf # pyrefly: ignore[deprecated]

__all__ = ["abcd_normalize", "cont2discrete", "normalize", "ss2tf", "ss2zpk", "tf2ss", "tf2zpk", "zpk2ss", "zpk2tf"]

Expand Down
4 changes: 2 additions & 2 deletions scipy-stubs/signal/ltisys.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@
from typing import Self
from typing_extensions import deprecated

from .filter_design import freqs, freqs_zpk, freqz, freqz_zpk, normalize, tf2zpk, zpk2tf
from .lti_conversion import abcd_normalize, cont2discrete, ss2tf, ss2zpk, tf2ss, zpk2ss
from .filter_design import freqs, freqs_zpk, freqz, freqz_zpk, normalize, tf2zpk, zpk2tf # pyrefly: ignore[deprecated]
from .lti_conversion import abcd_normalize, cont2discrete, ss2tf, ss2zpk, tf2ss, zpk2ss # pyrefly: ignore[deprecated]

__all__ = [
"StateSpace",
Expand Down
8 changes: 4 additions & 4 deletions scipy-stubs/signal/signaltools.pyi
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
# This module is not meant for public use and will be removed in SciPy v2.0.0.
from typing_extensions import deprecated

from .filter_design import cheby1
from .fir_filter_design import firwin
from .ltisys import dlti
from .windows.windows import get_window
from .filter_design import cheby1 # pyrefly: ignore[deprecated]
from .fir_filter_design import firwin # pyrefly: ignore[deprecated]
from .ltisys import dlti # pyrefly: ignore[deprecated]
from .windows.windows import get_window # pyrefly: ignore[deprecated]

__all__ = [
"cheby1",
Expand Down
2 changes: 1 addition & 1 deletion scipy-stubs/signal/spectral.pyi
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# This module is not meant for public use and will be removed in SciPy v2.0.0.
from typing_extensions import deprecated

from .windows.windows import get_window
from .windows.windows import get_window # pyrefly: ignore[deprecated]

__all__ = [
"check_COLA",
Expand Down
8 changes: 5 additions & 3 deletions scipy-stubs/sparse/_base.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -126,9 +126,11 @@ _SpArrayOut: TypeAlias = bsr_array[_ScalarT] | csc_array[_ScalarT] | csr_array[_
_ToCSRArray: TypeAlias = coo_array[_ScalarT] | csr_array[_ScalarT] | dia_array[_ScalarT] | dok_array[_ScalarT]
_ToCSRMatrix: TypeAlias = coo_matrix[_ScalarT] | csr_matrix[_ScalarT] | dia_matrix[_ScalarT] | dok_matrix[_ScalarT]

_SparseLike: TypeAlias = _T | _ScalarT | _spbase[_ScalarT]
_To2D: TypeAlias = Sequence[Sequence[_T | _ScalarT] | onp.CanArrayND[_ScalarT]] | onp.CanArrayND[_ScalarT]
_To2DLike: TypeAlias = Sequence[_T | _ScalarT] | _To2D[_T, _ScalarT]
_SparseLike = TypeAliasType("_SparseLike", _T | _ScalarT | _spbase[_ScalarT], type_params=(_T, _ScalarT))
_To2D = TypeAliasType(
"_To2D", Sequence[Sequence[_T | _ScalarT] | onp.CanArrayND[_ScalarT]] | onp.CanArrayND[_ScalarT], type_params=(_T, _ScalarT)
)
_To2DLike = TypeAliasType("_To2DLike", Sequence[_T | _ScalarT] | _To2D[_T, _ScalarT], type_params=(_T, _ScalarT))

_BinOp: TypeAlias = Callable[[object, object], Any]

Expand Down
Loading