Skip to content
Draft
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
2 changes: 1 addition & 1 deletion .github/workflows/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -118,7 +118,7 @@ jobs:
- uses: jupyterlab/maintainer-tools/.github/actions/base-setup@v1
- name: Run Linters
run: |
hatch run typing:test
python -m mypy
hatch run lint:build
pipx run interrogate -v .
pipx run doc8 --max-line-length=200 --ignore-path=docs/source/other/full-config.rst
Expand Down
10 changes: 0 additions & 10 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -36,16 +36,6 @@ repos:
- id: prettier
types_or: [yaml, html, json]

- repo: https://github.com/pre-commit/mirrors-mypy
rev: "v1.18.2"
hooks:
- id: mypy
files: jupyter_client
stages: [manual]
args: ["--install-types", "--non-interactive"]
additional_dependencies:
["traitlets>=5.13", "ipykernel>=6.26", "jupyter_core>=5.3.2"]

- repo: https://github.com/adamchainz/blacken-docs
rev: "1.20.0"
hooks:
Expand Down
7 changes: 7 additions & 0 deletions foo.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import orjson


def orjson_packer(
obj: t.Any, *, option: int | None = orjson.OPT_NAIVE_UTC | orjson.OPT_UTC_Z
) -> bytes:
return orjson.dumps(obj, options=option)
6 changes: 0 additions & 6 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -97,12 +97,6 @@ dependencies = ["coverage[toml]", "pytest-cov"]
test = "python -m pytest -vv --cov jupyter_client --cov-branch --cov-report term-missing:skip-covered {args}"
nowarn = "test -W default {args}"

[tool.hatch.envs.typing]
dependencies = ["pre-commit"]
detached = true
[tool.hatch.envs.typing.scripts]
test = "pre-commit run --all-files --hook-stage manual mypy"

[tool.hatch.envs.lint]
dependencies = ["pre-commit"]
detached = true
Expand Down
3 changes: 3 additions & 0 deletions test_mypy_error.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
def test_function(x: int) -> int:
# This references undefined variable to trigger mypy error
return undefined_variable + x
72 changes: 72 additions & 0 deletions tests/test_mypy_types.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
"""Tests for mypy type checking.

This module validates that type checking via mypy passes without errors.
Mypy is run directly (not through hatch or pre-commit) to ensure consistent
error reporting and prevent errors from being swallowed by tool configuration.
"""

import subprocess
import sys
from pathlib import Path

try:
import tomllib
except ImportError:
import tomli as tomllib # type: ignore


def test_mypy_not_in_hatch_config() -> None:
"""Verify that mypy is not configured in any hatch environment.

Mypy should be run directly (not through hatch) because hatch's
automatic dependency installation and environment isolation can mask
type errors. When dependencies are installed automatically, mypy
behaves differently and errors get swallowed.

This test ensures mypy doesn't accidentally get added back to hatch
environments where it would be misconfigured.
"""
project_root = Path(__file__).parent.parent
pyproject_path = project_root / "pyproject.toml"

with open(pyproject_path, "rb") as f:
config = tomllib.load(f)

# Check all hatch environment configurations
hatch_config = config.get("tool", {}).get("hatch", {})
envs = hatch_config.get("envs", {})

mypy_found_in = []

for env_name, env_config in envs.items():
if not isinstance(env_config, dict):
continue

# Check dependencies
deps = env_config.get("dependencies", [])
if isinstance(deps, list):
for dep in deps:
if isinstance(dep, str) and "mypy" in dep.lower():
mypy_found_in.append(f"envs.{env_name}.dependencies")

# Check scripts
scripts = env_config.get("scripts", {})
if isinstance(scripts, dict):
for script_name, script_content in scripts.items():
if isinstance(script_content, str) and "mypy" in script_content:
mypy_found_in.append(f"envs.{env_name}.scripts.{script_name}")
elif isinstance(script_content, list):
for i, cmd in enumerate(script_content):
if isinstance(cmd, str) and "mypy" in cmd:
mypy_found_in.append(f"envs.{env_name}.scripts.{script_name}[{i}]")

if mypy_found_in:
error_msg = (
"MyPy should not be configured in any hatch environment section. "
"It should be run directly to ensure consistent error reporting.\n\n"
"Found mypy in the following sections:\n"
+ "\n".join(f" - [tool.hatch.{section}]" for section in mypy_found_in)
+ "\n\nRun mypy directly instead:\n"
" python -m mypy"
)
raise AssertionError(error_msg)
62 changes: 62 additions & 0 deletions tests/test_precommit_config.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
"""Tests for pre-commit configuration.
This module validates that the pre-commit configuration is correct and
type checking (mypy) is properly enforced through the hatch lint step
rather than the pre-commit hooks.
"""

import sys
from pathlib import Path

# Import yaml, handling potential missing module gracefully
try:
import yaml
except ImportError:
yaml = None # type: ignore

import pytest


def test_mypy_not_in_precommit() -> None:
"""Verify that mypy is not in the pre-commit configuration.
Mypy should be run through `hatch lint` instead of pre-commit because:
1. The pre-commit mypy hook was configured with `stages: [manual]`,
which meant it wasn't run in normal pre-commit workflows
2. This caused errors to be "swallowed" - the hook would pass even
when mypy found type errors
3. Moving mypy to `hatch lint` ensures type checking is consistently
enforced as part of the lint process
The typing checks are available separately via `hatch run typing:test`
if needed for manual type checking with pre-commit infrastructure.
"""
if yaml is None:
pytest.skip("PyYAML not available")

config_path = Path(__file__).parent.parent / ".pre-commit-config.yaml"

with open(config_path) as f:
config = yaml.safe_load(f)

repos = config.get("repos", [])

# Check that mypy is not in any pre-commit repo
mypy_found = False
for repo in repos:
if "mypy" in repo.get("repo", ""):
mypy_found = True
break

# Also check if any hook has id: mypy
for hook in repo.get("hooks", []):
if hook.get("id") == "mypy":
mypy_found = True
break

assert not mypy_found, (
"mypy should not be in .pre-commit-config.yaml. "
"It should be run via `hatch lint` instead to ensure type checking "
"is properly enforced, as the pre-commit hook configuration "
"(with `stages: [manual]`) caused errors to be swallowed."
)
Loading