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
31 changes: 16 additions & 15 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -34,25 +34,20 @@ jobs:

- uses: astral-sh/[email protected]
with:
activate-environment: true
python-version: "3.13"
activate-environment: true

- name: ruff
run: |
uv run ruff check --output-format=github
uv run ruff format --check
- name: ruff check
run: uv run ruff check --output-format=github

- name: check version literals
run: uv run scripts/check_version_literals.py
- name: ruff format
run: uv run ruff format --check

# mypy_primer expects pyright to pass
- name: pyright
uses: jakebailey/[email protected]
with:
# see https://github.com/microsoft/pyright/issues/10832
version: 1.1.403

stubdefaulter:
validate_stubs:
runs-on: ubuntu-latest
timeout-minutes: 5
steps:
Expand All @@ -63,16 +58,19 @@ jobs:
with:
python-version: "3.13"

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

- name: check for unstubbed modules
run: uv run scripts/unstubbed_modules.py

# avoid stubdefaulter checking the tests as if they were stubs
- name: exclude tests
run: rm -rf tests

- name: stubdefaulter check
- name: run stubdefaulter
run: uv run stubdefaulter --check --exit-zero --packages .

# NOTE: mypy ignores `uv run --with=...` (and `--isolated` does not help), so we
# manually (re)install the desired version directly in the environment.

typecheck:
runs-on: ubuntu-latest
timeout-minutes: 5
Expand All @@ -86,6 +84,9 @@ jobs:
steps:
- uses: actions/[email protected]

# NOTE: mypy ignores `uv run --with=...` (and `--isolated` does not help), so we
# manually (re)install the desired version directly in the environment.

- name: setup uv
uses: astral-sh/[email protected]
with:
Expand Down
3 changes: 3 additions & 0 deletions lefthook.yml
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,9 @@ pre-commit:
- name: check-version-literals
run: uv {run} scripts/check_version_literals.py

- name: unstubbed-modules
run: uv {run} scripts/unstubbed_modules.py

post-checkout:
jobs:
- run: uv sync
Expand Down
56 changes: 38 additions & 18 deletions scripts/unstubbed_modules.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,15 @@

# ruff: noqa: T201, S101

import sys
import types
import warnings
from pathlib import Path
from typing import Any

import scipy

STUBS_PATH = Path(__file__).parent.parent / "scipy-stubs"
BUNDLED = (
"scipy._lib.array_api_compat",
"scipy._lib.array_api_extra",
Expand All @@ -21,6 +23,12 @@
)


def _check_stubs_path() -> None:
# sanity check
assert STUBS_PATH.is_dir()
assert (STUBS_PATH / "__init__.pyi").exists()


def modules(
mod: types.ModuleType, _seen: set[types.ModuleType] | None = None
) -> list[str]:
Expand Down Expand Up @@ -50,32 +58,44 @@ def modules(
return out


def is_stubbed(mod: str) -> bool:
if not mod.startswith("scipy."):
return False
def module_to_path(mod: str) -> Path | None:
_, *submods = mod.split(".")
if (path := STUBS_PATH.joinpath(*submods, "__init__.pyi")).is_file():
return path

stubs_path = Path(__file__).parent.parent / "scipy-stubs"
if not stubs_path.is_dir():
raise FileNotFoundError(stubs_path)
# https://github.com/facebook/pyrefly/issues/913#issuecomment-3367579203
assert submods, path # pyrefly: ignore[unbound-name]

_, *submods = mod.split(".")
if not submods:
return (stubs_path / "__init__.pyi").is_file()
if (path := STUBS_PATH.joinpath(*submods[:-1], f"{submods[-1]}.pyi")).is_file():
return path

*subpackages, submod = submods
subpackage_path = stubs_path.joinpath(*subpackages)
return (subpackage_path / f"{submod}.pyi").is_file() or (
subpackage_path / submod / "__init__.pyi"
).is_file()
return None


if __name__ == "__main__":
def is_stubbed(mod: str) -> bool:
return mod.startswith("scipy.") and module_to_path(mod) is not None


def main() -> int:
_check_stubs_path()

with warnings.catch_warnings():
warnings.simplefilter("ignore", DeprecationWarning)
warnings.simplefilter("ignore", FutureWarning)
module_list = modules(scipy)

module_list.sort()

exit_code = 0
for name in module_list:
if not any(map(name.startswith, BUNDLED)) and not is_stubbed(name):
print(name)
if any(map(name.startswith, BUNDLED)):
continue

if not is_stubbed(name):
print(name, file=sys.stderr)
exit_code = 1

return exit_code


if __name__ == "__main__":
sys.exit(main())