Skip to content

Commit 943af79

Browse files
committed
Run mypy on the tests directory
The tests are a large consumer of the pip API (both the internal API and otherwise). By running mypy on tests, we help to: 1. Ensure the internal API is consistent with regards to typing 2. Ensure the tests are representative of real life scenarios as the API are used correctly. 3. Helps to recognize unnecessary tests that simply pass junk data to functions which can be caught by the type checker. 4. Make sure test support code in tests/lib/ is correct and consistent. This is especially important when refactoring such code. For example, if we were to replace tests/lib/path.py with pathlib. As a first start, untyped defs are allowed. All existing typing issues have been resolved. Overtime, we can chip away at untyped defs and eventually remove the configuration option for stricter type checking of tests. The following changes were made to help make mypy pass: Remove unused record_callback argument from make_wheel() in tests. Unused since its introduction in 6d8a58f. Replace toml with tomli_w in tests/functional/test_pep517.py. Unlike the toml package, tomli_w contains inline typing annotations. Remove unnecessary make_no_network_finder(). Unnecessary since bab1e4f where the _get_pages method was removed.
1 parent 3e640bc commit 943af79

13 files changed

+78
-88
lines changed

.pre-commit-config.yaml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,12 +42,13 @@ repos:
4242
rev: v0.910
4343
hooks:
4444
- id: mypy
45-
exclude: tests
45+
exclude: tests/data
4646
args: ["--pretty", "--show-error-codes"]
4747
additional_dependencies: [
4848
'keyring==23.0.1',
4949
'nox==2021.6.12',
5050
'types-docutils==0.1.8',
51+
'types-setuptools==57.0.2',
5152
'types-six==0.1.9',
5253
]
5354

news/f231bb92-a022-4b48-b7fd-c83edefb4353.trivial.rst

Whitespace-only changes.

setup.cfg

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,10 @@ follow_imports = skip
5151
[mypy-pip._vendor.requests.*]
5252
follow_imports = skip
5353

54+
[mypy-tests.*]
55+
# TODO: The following option should be removed at some point in the future.
56+
allow_untyped_defs = True
57+
5458
[tool:pytest]
5559
addopts = --ignore src/pip/_vendor --ignore tests/tests_cache -r aR --color=yes
5660
markers =

tests/conftest.py

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
import sys
99
import time
1010
from contextlib import ExitStack, contextmanager
11-
from typing import Dict, Iterable
11+
from typing import TYPE_CHECKING, Dict, Iterable, List
1212
from unittest.mock import patch
1313

1414
import pytest
@@ -21,11 +21,14 @@
2121
from tests.lib.certs import make_tls_cert, serialize_cert, serialize_key
2222
from tests.lib.path import Path
2323
from tests.lib.server import MockServer as _MockServer
24-
from tests.lib.server import Responder, make_mock_server, server_running
24+
from tests.lib.server import make_mock_server, server_running
2525
from tests.lib.venv import VirtualEnvironment
2626

2727
from .lib.compat import nullcontext
2828

29+
if TYPE_CHECKING:
30+
from wsgi import WSGIApplication
31+
2932

3033
def pytest_addoption(parser):
3134
parser.addoption(
@@ -521,7 +524,7 @@ def port(self):
521524
def host(self):
522525
return self._server.host
523526

524-
def set_responses(self, responses: Iterable[Responder]) -> None:
527+
def set_responses(self, responses: Iterable["WSGIApplication"]) -> None:
525528
assert not self._running, "responses cannot be set on running server"
526529
self._server.mock.side_effect = responses
527530

@@ -542,7 +545,7 @@ def stop(self) -> None:
542545
assert self._running, "idle server cannot be stopped"
543546
self.context.close()
544547

545-
def get_requests(self) -> Dict[str, str]:
548+
def get_requests(self) -> List[Dict[str, str]]:
546549
"""Get environ for each received request."""
547550
assert not self._running, "cannot get mock from running server"
548551
# Legacy: replace call[0][0] with call.args[0]

tests/functional/test_pep517.py

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,5 @@
11
import pytest
2-
3-
# The vendored `tomli` package is not used here because it doesn't
4-
# have write capability
5-
import toml
2+
import tomli_w
63

74
from pip._internal.build_env import BuildEnvironment
85
from pip._internal.req import InstallRequirement
@@ -18,7 +15,7 @@ def make_project(tmpdir, requires=None, backend=None, backend_path=None):
1815
buildsys["build-backend"] = backend
1916
if backend_path:
2017
buildsys["backend-path"] = backend_path
21-
data = toml.dumps({"build-system": buildsys})
18+
data = tomli_w.dumps({"build-system": buildsys})
2219
project_dir.joinpath("pyproject.toml").write_text(data)
2320
return project_dir
2421

@@ -189,7 +186,7 @@ def make_pyproject_with_setup(tmpdir, build_system=True, set_backend=True):
189186
if set_backend:
190187
buildsys["build-backend"] = "setuptools.build_meta"
191188
expect_script_dir_on_path = False
192-
project_data = toml.dumps({"build-system": buildsys})
189+
project_data = tomli_w.dumps({"build-system": buildsys})
193190
else:
194191
project_data = ""
195192

tests/lib/local_repos.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@ def local_checkout(
5454
repo_url_path = os.path.join(repo_url_path, "trunk")
5555
else:
5656
vcs_backend = vcs.get_backend(vcs_name)
57+
assert vcs_backend is not None
5758
vcs_backend.obtain(repo_url_path, url=hide_url(remote_repo))
5859

5960
return "{}+{}".format(vcs_name, path_to_url(repo_url_path))

tests/lib/server.py

Lines changed: 17 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -5,23 +5,18 @@
55
from base64 import b64encode
66
from contextlib import contextmanager
77
from textwrap import dedent
8-
from types import TracebackType
9-
from typing import Any, Callable, Dict, Iterable, List, Optional, Tuple, Type
8+
from typing import TYPE_CHECKING, Any, Callable, Dict, Iterable, Iterator
109
from unittest.mock import Mock
1110

1211
from werkzeug.serving import BaseWSGIServer, WSGIRequestHandler
1312
from werkzeug.serving import make_server as _make_server
1413

1514
from .compat import nullcontext
1615

17-
Environ = Dict[str, str]
18-
Status = str
19-
Headers = Iterable[Tuple[str, str]]
20-
ExcInfo = Tuple[Type[BaseException], BaseException, TracebackType]
21-
Write = Callable[[bytes], None]
22-
StartResponse = Callable[[Status, Headers, Optional[ExcInfo]], Write]
23-
Body = List[bytes]
24-
Responder = Callable[[Environ, StartResponse], Body]
16+
if TYPE_CHECKING:
17+
from wsgi import StartResponse, WSGIApplication, WSGIEnvironment
18+
19+
Body = Iterable[bytes]
2520

2621

2722
class MockServer(BaseWSGIServer):
@@ -78,13 +73,13 @@ def make_environ(self):
7873

7974

8075
def _mock_wsgi_adapter(
81-
mock: Callable[[Environ, StartResponse], Responder]
82-
) -> Responder:
76+
mock: Callable[["WSGIEnvironment", "StartResponse"], "WSGIApplication"]
77+
) -> "WSGIApplication":
8378
"""Uses a mock to record function arguments and provide
8479
the actual function that should respond.
8580
"""
8681

87-
def adapter(environ: Environ, start_response: StartResponse) -> Body:
82+
def adapter(environ: "WSGIEnvironment", start_response: "StartResponse") -> Body:
8883
try:
8984
responder = mock(environ, start_response)
9085
except StopIteration:
@@ -134,7 +129,7 @@ def make_mock_server(**kwargs: Any) -> MockServer:
134129

135130

136131
@contextmanager
137-
def server_running(server: BaseWSGIServer) -> None:
132+
def server_running(server: BaseWSGIServer) -> Iterator[None]:
138133
"""Context manager for running the provided server in a separate thread."""
139134
thread = threading.Thread(target=server.serve_forever)
140135
thread.daemon = True
@@ -150,8 +145,8 @@ def server_running(server: BaseWSGIServer) -> None:
150145
# Helper functions for making responses in a declarative way.
151146

152147

153-
def text_html_response(text: str) -> Responder:
154-
def responder(environ: Environ, start_response: StartResponse) -> Body:
148+
def text_html_response(text: str) -> "WSGIApplication":
149+
def responder(environ: "WSGIEnvironment", start_response: "StartResponse") -> Body:
155150
start_response(
156151
"200 OK",
157152
[
@@ -180,24 +175,24 @@ def html5_page(text: str) -> str:
180175
)
181176

182177

183-
def index_page(spec: Dict[str, str]) -> Responder:
178+
def index_page(spec: Dict[str, str]) -> "WSGIApplication":
184179
def link(name, value):
185180
return '<a href="{}">{}</a>'.format(value, name)
186181

187182
links = "".join(link(*kv) for kv in spec.items())
188183
return text_html_response(html5_page(links))
189184

190185

191-
def package_page(spec: Dict[str, str]) -> Responder:
186+
def package_page(spec: Dict[str, str]) -> "WSGIApplication":
192187
def link(name, value):
193188
return '<a href="{}">{}</a>'.format(value, name)
194189

195190
links = "".join(link(*kv) for kv in spec.items())
196191
return text_html_response(html5_page(links))
197192

198193

199-
def file_response(path: str) -> Responder:
200-
def responder(environ: Environ, start_response: StartResponse) -> Body:
194+
def file_response(path: str) -> "WSGIApplication":
195+
def responder(environ: "WSGIEnvironment", start_response: "StartResponse") -> Body:
201196
size = os.stat(path).st_size
202197
start_response(
203198
"200 OK",
@@ -213,10 +208,10 @@ def responder(environ: Environ, start_response: StartResponse) -> Body:
213208
return responder
214209

215210

216-
def authorization_response(path: str) -> Responder:
211+
def authorization_response(path: str) -> "WSGIApplication":
217212
correct_auth = "Basic " + b64encode(b"USERNAME:PASSWORD").decode("ascii")
218213

219-
def responder(environ: Environ, start_response: StartResponse) -> Body:
214+
def responder(environ: "WSGIEnvironment", start_response: "StartResponse") -> Body:
220215

221216
if environ.get("HTTP_AUTHORIZATION") == correct_auth:
222217
size = os.stat(path).st_size

tests/lib/wheel.py

Lines changed: 16 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,6 @@
1212
from io import BytesIO, StringIO
1313
from typing import (
1414
AnyStr,
15-
Callable,
1615
Dict,
1716
Iterable,
1817
List,
@@ -28,9 +27,6 @@
2827

2928
from tests.lib.path import Path
3029

31-
# path, digest, size
32-
RecordLike = Tuple[str, str, str]
33-
RecordCallback = Callable[[List["Record"]], Union[str, bytes, List[RecordLike]]]
3430
# As would be used in metadata
3531
HeaderValue = Union[str, List[str]]
3632

@@ -82,7 +78,7 @@ def make_metadata_file(
8278
value: Defaulted[Optional[AnyStr]],
8379
updates: Defaulted[Dict[str, HeaderValue]],
8480
body: Defaulted[AnyStr],
85-
) -> File:
81+
) -> Optional[File]:
8682
if value is None:
8783
return None
8884

@@ -199,9 +195,8 @@ def digest(contents: bytes) -> str:
199195
def record_file_maker_wrapper(
200196
name: str,
201197
version: str,
202-
files: List[File],
198+
files: Iterable[File],
203199
record: Defaulted[Optional[AnyStr]],
204-
record_callback: Defaulted[RecordCallback],
205200
) -> Iterable[File]:
206201
records: List[Record] = []
207202
for file in files:
@@ -221,19 +216,22 @@ def record_file_maker_wrapper(
221216

222217
records.append(Record(record_path, "", ""))
223218

224-
if record_callback is not _default:
225-
records = record_callback(records)
226-
227219
with StringIO(newline="") as buf:
228220
writer = csv.writer(buf)
229-
for record in records:
230-
writer.writerow(record)
221+
for r in records:
222+
writer.writerow(r)
231223
contents = buf.getvalue().encode("utf-8")
232224

233225
yield File(record_path, contents)
234226

235227

236-
def wheel_name(name: str, version: str, pythons: str, abis: str, platforms: str) -> str:
228+
def wheel_name(
229+
name: str,
230+
version: str,
231+
pythons: Iterable[str],
232+
abis: Iterable[str],
233+
platforms: Iterable[str],
234+
) -> str:
237235
stem = "-".join(
238236
[
239237
name,
@@ -249,7 +247,7 @@ def wheel_name(name: str, version: str, pythons: str, abis: str, platforms: str)
249247
class WheelBuilder:
250248
"""A wheel that can be saved or converted to several formats."""
251249

252-
def __init__(self, name: str, files: List[File]) -> None:
250+
def __init__(self, name: str, files: Iterable[File]) -> None:
253251
self._name = name
254252
self._files = files
255253

@@ -259,9 +257,9 @@ def save_to_dir(self, path: Union[Path, str]) -> str:
259257
260258
:returns the wheel file path
261259
"""
262-
path = Path(path) / self._name
263-
path.write_bytes(self.as_bytes())
264-
return str(path)
260+
p = Path(path) / self._name
261+
p.write_bytes(self.as_bytes())
262+
return str(p)
265263

266264
def save_to(self, path: Union[Path, str]) -> str:
267265
"""Generate wheel file, saving to the provided path. Any parent
@@ -298,7 +296,6 @@ def make_wheel(
298296
console_scripts: Defaulted[List[str]] = _default,
299297
entry_points: Defaulted[Dict[str, List[str]]] = _default,
300298
record: Defaulted[Optional[AnyStr]] = _default,
301-
record_callback: Defaulted[RecordCallback] = _default,
302299
) -> WheelBuilder:
303300
"""
304301
Helper function for generating test wheels which are compliant by default.
@@ -359,9 +356,6 @@ def make_wheel(
359356
:param entry_points:
360357
:param record: if provided and None, then no RECORD file is generated;
361358
else if a string then sets the content of the RECORD file
362-
:param record_callback: callback function that receives and can edit the
363-
records before they are written to RECORD, ignored if record is
364-
provided
365359
"""
366360
pythons = ["py2", "py3"]
367361
abis = ["none"]
@@ -388,7 +382,7 @@ def make_wheel(
388382
actual_files = filter(None, possible_files)
389383

390384
files_and_record_file = record_file_maker_wrapper(
391-
name, version, actual_files, record, record_callback
385+
name, version, actual_files, record
392386
)
393387
wheel_file_name = wheel_name(name, version, pythons, abis, platforms)
394388

tests/requirements.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,4 +10,4 @@ setuptools
1010
virtualenv < 20.0
1111
werkzeug
1212
wheel
13-
toml
13+
tomli-w

tests/unit/resolution_resolvelib/__init__.py

Whitespace-only changes.

0 commit comments

Comments
 (0)