|
23 | 23 | LOCATIONS = { |
24 | 24 | "common-wheels": "tests/data/common_wheels", |
25 | 25 | "protected-pip": "tools/protected_pip.py", |
| 26 | + "untracked-vendored-type-stubs": "tests/typing/untracked-vendored-stubs", |
26 | 27 | } |
27 | 28 |
|
28 | 29 | AUTHORS_FILE = "AUTHORS.txt" |
@@ -127,6 +128,119 @@ def test(session: nox.Session) -> None: |
127 | 128 | ) |
128 | 129 |
|
129 | 130 |
|
| 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 | + |
130 | 244 | @nox.session |
131 | 245 | def docs(session: nox.Session) -> None: |
132 | 246 | session.install("--group", "docs") |
|
0 commit comments