Skip to content

Commit 4d0d940

Browse files
committed
Add a command for running complete type checking with nox -s typecheck, including vendored dependencies
1 parent 758a172 commit 4d0d940

File tree

2 files changed

+158
-0
lines changed

2 files changed

+158
-0
lines changed

noxfile.py

Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
LOCATIONS = {
2424
"common-wheels": "tests/data/common_wheels",
2525
"protected-pip": "tools/protected_pip.py",
26+
"untracked-vendored-type-stubs": "tests/typing/untracked-vendored-stubs",
2627
}
2728

2829
AUTHORS_FILE = "AUTHORS.txt"
@@ -127,6 +128,119 @@ def test(session: nox.Session) -> None:
127128
)
128129

129130

131+
@nox.session
132+
def typecheck(session: nox.Session) -> None:
133+
runtime_typing_deps = [
134+
"freezegun",
135+
"installer",
136+
"keyring", # An optional runtime dependency.
137+
"nox",
138+
"packaging",
139+
"proxy.py",
140+
"pytest",
141+
"httpx",
142+
"rich",
143+
"ScriptTest",
144+
"tomli-w",
145+
"types-PyYAML", # Used in noxfile (needs to be part of the mypy environment)
146+
"werkzeug",
147+
]
148+
# Install test and test-types dependency groups
149+
run_with_protected_pip(
150+
session,
151+
"install",
152+
"mypy",
153+
*runtime_typing_deps,
154+
)
155+
156+
stubs_dir = Path(LOCATIONS["untracked-vendored-type-stubs"])
157+
if stubs_dir.exists():
158+
shutil.rmtree(stubs_dir)
159+
160+
# TODO: Let's have a single place where these are defined, as we should
161+
# have the exact versions that we are vendoring.
162+
# The versions could be taken from src/pip/_vendor/vendor.txt.
163+
vendored_and_needing_stubs = [
164+
# Stub libraries that contain type hints as a separate package:
165+
"types-docutils", # via sphinx (test dependency)
166+
"types-requests", # vendored
167+
# vendored (can be removed when we upgrade to urllib3 >= 2.0)
168+
"types-urllib3==1.*",
169+
"types-setuptools", # test dependency and used in distutils_hack
170+
"types-six", # via python-dateutil via freezegun (test dependency)
171+
"types-PyYAML", # update-rtd-redirects dependency
172+
]
173+
174+
run_with_protected_pip(
175+
session,
176+
"install",
177+
f"--target={stubs_dir}",
178+
*vendored_and_needing_stubs,
179+
)
180+
181+
# Generate real pip/__init__.pyi and pip/_vendor/__init__.pyi files. We are
182+
# obliged to have these files so that mypy understands that this is the pip
183+
# package, and that it should take these stubs into account.
184+
# We use stubgen, as the __init__.pyi files must be representative of what is
185+
# in the real pip source.
186+
real_pip_init = Path("src") / "pip" / "__init__.py"
187+
real_pip_vendor_init = Path("src/pip/_vendor/__init__.py")
188+
189+
# stubgen has a problem generating for pip/_vendored/__init__.py, so we
190+
# trick it by copying it to a different path and generating from there
191+
tmp_pip_vendor_init = stubs_dir / "pip_vendor.py"
192+
shutil.copy(real_pip_vendor_init, tmp_pip_vendor_init)
193+
194+
session.run(
195+
"stubgen",
196+
str(real_pip_init),
197+
str(tmp_pip_vendor_init),
198+
"--output",
199+
str(stubs_dir),
200+
)
201+
202+
# We now make a fake pip package in the stubs dir. When mypy finds this
203+
# directory it will use any file it finds, but continue to find files from the
204+
# real pip directory. Using a `pip-stubs` directory doesn't work for mypy.
205+
pip_stubs_dir = stubs_dir / "pip"
206+
pip_vendor_dir = pip_stubs_dir / "_vendor"
207+
pip_vendor_dir.mkdir()
208+
209+
tmp_pip_vendor_init.unlink()
210+
shutil.move(stubs_dir / "pip_vendor.pyi", pip_vendor_dir / "__init__.pyi")
211+
212+
# Move the vendored stub files into the pip vendored stubpackage.
213+
for stubs_directory in stubs_dir.glob("*-stubs"):
214+
stubs_directory.rename(pip_vendor_dir / stubs_directory.name.split("-stubs")[0])
215+
216+
# Clean up anything that is left over.
217+
for item in stubs_dir.iterdir():
218+
if item != pip_stubs_dir and item.is_dir():
219+
shutil.rmtree(item)
220+
221+
# Don't track the generated stubs, so git ignore the directory.
222+
(stubs_dir / ".gitignore").write_text("*")
223+
224+
mypy_cmd = [
225+
"mypy",
226+
"--config-file=tests/typing/pyproject.toml",
227+
]
228+
if session.posargs:
229+
# Allow passing specific files/directories to be checked.
230+
mypy_cmd.extend(session.posargs)
231+
else:
232+
# Otherwise, run against all important files.
233+
mypy_cmd.extend(
234+
[
235+
"src/pip",
236+
"tests",
237+
"tools",
238+
"noxfile.py",
239+
]
240+
)
241+
session.run(*mypy_cmd)
242+
243+
130244
@nox.session
131245
def docs(session: nox.Session) -> None:
132246
session.install("--group", "docs")

tests/typing/pyproject.toml

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
# Mypy configuration for pip type checking
2+
#
3+
# This configuration is used by `nox -s typecheck` and represents the target
4+
# mypy configuration once the pip codebase has proper type annotations.
5+
# Eventually, this configuration should be moved to the main pyproject.toml
6+
# to replace the current [tool.mypy] section.
7+
8+
[tool.mypy]
9+
mypy_path = [
10+
"tests/typing/untracked-vendored-stubs", # these are generated by nox typecheck
11+
"src",
12+
]
13+
strict = true
14+
no_implicit_reexport = false
15+
disallow_subclassing_any = false
16+
disallow_untyped_calls = false
17+
warn_return_any = true
18+
ignore_missing_imports = false
19+
warn_redundant_casts = true
20+
21+
exclude = [
22+
"src/pip/_vendor",
23+
# Tell mypy that it isn't type-checking this directory (it is just using it
24+
# for vendored library stubs)
25+
"tests/typing/untracked-vendored-stubs",
26+
"tests/data",
27+
]
28+
29+
[[tool.mypy.overrides]]
30+
module = "scripttest" # v2.0.post1 doesn't yet ship with py.typed
31+
ignore_missing_imports = true
32+
33+
[[tool.mypy.overrides]]
34+
module = "pip._internal.utils._jaraco_text"
35+
ignore_errors = true
36+
37+
[[tool.mypy.overrides]]
38+
module = "virtualenv"
39+
ignore_missing_imports = true
40+
41+
[[tool.mypy.overrides]]
42+
module = "pip._vendor.*"
43+
# Errors with the vendored libraries can be ignored (an upstream problem).
44+
ignore_errors = true

0 commit comments

Comments
 (0)