diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 98e683914..185f4febf 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -106,6 +106,9 @@ jobs: - uses: actions/checkout@v6 - uses: jupyterlab/maintainer-tools/.github/actions/base-setup@v1 + - name: Check mypy deps are in sync with project deps + run: hatch run typing:check + - name: Run Linters run: | hatch run typing:test diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 41f3fbe6d..38abb2417 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -22,7 +22,7 @@ repos: - id: trailing-whitespace - repo: https://github.com/python-jsonschema/check-jsonschema - rev: 0.37.0 + rev: 0.37.2 hooks: - id: check-github-workflows @@ -39,7 +39,7 @@ repos: types_or: [yaml, html, json] - repo: https://github.com/pre-commit/mirrors-mypy - rev: "v1.19.1" + rev: "v2.1.0" hooks: - id: mypy files: ipykernel @@ -47,12 +47,34 @@ repos: args: ["--install-types", "--non-interactive"] additional_dependencies: [ - "traitlets>=5.13", - "ipython>=8.16.1", - "jupyter_client>=8.5", - "appnope", + "appnope>=0.1.2", + "comm>=0.1.1", + "debugpy>=1.6.5", + "ipython>=7.23.1", + "jupyter_client>=8.9.0", + "jupyter_core>=5.1", + "matplotlib-inline>=0.1", + "nest_asyncio2>=1.7.0", + "packaging>=22", + "psutil>=5.7", + "pyzmq>=25", + "tornado>=6.4.1", + "traitlets>=5.4.0", ] + - repo: local + hooks: + - id: check-mypy-deps + name: mypy deps in sync with project deps + entry: python scripts/check_mypy_deps.py + language: python + pass_filenames: false + always_run: true + additional_dependencies: + - "pyyaml" + - "packaging" + - "tomli; python_version < '3.11'" + - repo: https://github.com/adamchainz/blacken-docs rev: "1.20.0" hooks: @@ -60,7 +82,7 @@ repos: additional_dependencies: [black==23.7.0] - repo: https://github.com/codespell-project/codespell - rev: "v2.4.1" + rev: "v2.4.2" hooks: - id: codespell args: ["-L", "sur,nd"] @@ -73,7 +95,7 @@ repos: - id: rst-inline-touching-normal - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.15.4 + rev: v0.15.16 hooks: - id: ruff-check types_or: [python, jupyter] @@ -82,7 +104,7 @@ repos: types_or: [python, jupyter] - repo: https://github.com/scientific-python/cookie - rev: "2026.03.02" + rev: "2026.04.04" hooks: - id: sp-repo-review additional_dependencies: ["repo-review[cli]"] diff --git a/ipykernel/comm/comm.py b/ipykernel/comm/comm.py index a1b659e9a..9be5e23d0 100644 --- a/ipykernel/comm/comm.py +++ b/ipykernel/comm/comm.py @@ -16,7 +16,7 @@ # this is the class that will be created if we do comm.create_comm -class BaseComm(comm.base_comm.BaseComm): # type:ignore[misc] +class BaseComm(comm.base_comm.BaseComm): """The base class for comms.""" kernel: Optional["Kernel"] = None @@ -90,7 +90,7 @@ def __init__( kernel = kwargs.pop("kernel", None) if target_name: kwargs["target_name"] = target_name - BaseComm.__init__(self, data=data, metadata=metadata, buffers=buffers, **kwargs) # type:ignore[call-arg] + BaseComm.__init__(self, data=data, metadata=metadata, buffers=buffers, **kwargs) # only re-add kernel if explicitly provided if had_kernel: kwargs["kernel"] = kernel diff --git a/ipykernel/comm/manager.py b/ipykernel/comm/manager.py index aaef027ce..092754946 100644 --- a/ipykernel/comm/manager.py +++ b/ipykernel/comm/manager.py @@ -14,7 +14,7 @@ logger = logging.getLogger("ipykernel.comm") -class CommManager(comm.base_comm.CommManager, traitlets.config.LoggingConfigurable): # type:ignore[misc] +class CommManager(comm.base_comm.CommManager, traitlets.config.LoggingConfigurable): """A comm manager.""" kernel = traitlets.Instance("ipykernel.kernelbase.Kernel") diff --git a/ipykernel/eventloops.py b/ipykernel/eventloops.py index a767eb1a9..dd160674a 100644 --- a/ipykernel/eventloops.py +++ b/ipykernel/eventloops.py @@ -301,7 +301,7 @@ def _schedule_exit(delay): else: import asyncio - import nest_asyncio2 + import nest_asyncio2 # type: ignore[import-untyped] nest_asyncio2.apply() diff --git a/ipykernel/kernelbase.py b/ipykernel/kernelbase.py index 27bd371c9..02a0dd405 100644 --- a/ipykernel/kernelbase.py +++ b/ipykernel/kernelbase.py @@ -441,7 +441,7 @@ async def dispatch_shell(self, msg, /, subshell_id: str | None = None): # Only abort execute requests if msg_type == "execute_request": if subshell_id is None: - aborting = self._aborting # type:ignore[unreachable] + aborting = self._aborting else: aborting = self.shell_channel_thread.manager.get_subshell_aborting(subshell_id) if aborting: diff --git a/pyproject.toml b/pyproject.toml index 9d2273df1..4df9c0a3a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -113,6 +113,7 @@ dependencies = ["pre-commit"] detached = true [tool.hatch.envs.typing.scripts] test = "pre-commit run --all-files --hook-stage manual mypy" +check = "pre-commit run --all-files check-mypy-deps" [tool.hatch.envs.lint] dependencies = ["pre-commit"] @@ -120,7 +121,8 @@ detached = true [tool.hatch.envs.lint.scripts] build = [ "pre-commit run --all-files ruff-check", - "pre-commit run --all-files ruff-format" + "pre-commit run --all-files ruff-format", + "pre-commit run --all-files check-mypy-deps", ] [tool.mypy] diff --git a/scripts/check_mypy_deps.py b/scripts/check_mypy_deps.py new file mode 100755 index 000000000..1d75a2c87 --- /dev/null +++ b/scripts/check_mypy_deps.py @@ -0,0 +1,66 @@ +#!/usr/bin/env python +"""Verify that the mypy hook's additional_dependencies in .pre-commit-config.yaml +includes all runtime dependencies declared in pyproject.toml. + +Run directly or via pre-commit (check-mypy-deps hook). +""" + +from __future__ import annotations + +import sys + +try: + import tomllib +except ImportError: + import tomli as tomllib # type: ignore[no-reuse-def] + +import yaml +from packaging.requirements import Requirement + + +def normalize(name: str) -> str: + return name.lower().replace("-", "_").replace(".", "_") + + +def main() -> int: + with open("pyproject.toml", "rb") as f: + pyproject = tomllib.load(f) + + with open(".pre-commit-config.yaml") as f: + pre_commit = yaml.safe_load(f) + + project_deps: list[str] = pyproject["project"]["dependencies"] + project_packages = {normalize(Requirement(dep).name) for dep in project_deps} + + mypy_additional: list[str] | None = None + for repo in pre_commit["repos"]: + for hook in repo.get("hooks", []): + if hook["id"] == "mypy": + mypy_additional = hook.get("additional_dependencies", []) + break + if mypy_additional is not None: + break + + if mypy_additional is None: + print("ERROR: mypy hook not found in .pre-commit-config.yaml") + return 1 + + mypy_packages = {normalize(Requirement(dep).name) for dep in mypy_additional} + + missing = project_packages - mypy_packages + if missing: + print( + "ERROR: The following project dependencies are missing from the mypy\n" + "hook's additional_dependencies in .pre-commit-config.yaml:\n" + ) + for pkg in sorted(missing): + print(f" {pkg}") + print("\nAdd them to the `additional_dependencies` list of the mypy hook.") + return 1 + + print("OK: mypy additional_dependencies covers all project runtime dependencies.") + return 0 + + +if __name__ == "__main__": + sys.exit(main())