Skip to content

Commit d9f50f1

Browse files
authored
Add type hints for the writer module (jazzband#1310)
1 parent 4776ac9 commit d9f50f1

File tree

5 files changed

+82
-48
lines changed

5 files changed

+82
-48
lines changed

.bandit

+1
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,3 @@
11
[bandit]
22
exclude: tests,.tox,.eggs,.venv,.git
3+
skips: B101

.pre-commit-config.yaml

+1
Original file line numberDiff line numberDiff line change
@@ -30,4 +30,5 @@ repos:
3030
rev: 1.7.0
3131
hooks:
3232
- id: bandit
33+
args: [--ini, .bandit]
3334
exclude: ^tests/

piptools/logging.py

+4-3
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import contextlib
22
import logging
33
import sys
4+
from typing import Any
45

56
import click
67

@@ -17,7 +18,7 @@ def __init__(self, verbosity: int = 0, indent_width: int = 2):
1718
self.current_indent = 0
1819
self._indent_width = indent_width
1920

20-
def log(self, message, *args, **kwargs):
21+
def log(self, message: str, *args: Any, **kwargs: Any) -> None:
2122
kwargs.setdefault("err", True)
2223
prefix = " " * self.current_indent
2324
click.secho(prefix + message, *args, **kwargs)
@@ -26,11 +27,11 @@ def debug(self, *args, **kwargs):
2627
if self.verbosity >= 1:
2728
self.log(*args, **kwargs)
2829

29-
def info(self, *args, **kwargs):
30+
def info(self, *args: Any, **kwargs: Any) -> None:
3031
if self.verbosity >= 0:
3132
self.log(*args, **kwargs)
3233

33-
def warning(self, *args, **kwargs):
34+
def warning(self, *args: Any, **kwargs: Any) -> None:
3435
kwargs.setdefault("fg", "yellow")
3536
self.log(*args, **kwargs)
3637

piptools/utils.py

+18-9
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,15 @@
11
import shlex
22
from collections import OrderedDict
33
from itertools import chain
4+
from typing import Any, Iterable, Iterator, Optional, Set
45

5-
from click import style
6+
import click
67
from click.utils import LazyFile
8+
from pip._internal.req import InstallRequirement
79
from pip._internal.req.constructors import install_req_from_line
810
from pip._internal.utils.misc import redact_auth_from_url
911
from pip._internal.vcs import is_url
12+
from pip._vendor.packaging.markers import Marker
1013

1114
UNSAFE_PACKAGES = {"setuptools", "distribute", "pip"}
1215
COMPILE_EXCLUDE_OPTIONS = {
@@ -21,29 +24,29 @@
2124
}
2225

2326

24-
def key_from_ireq(ireq):
27+
def key_from_ireq(ireq: InstallRequirement) -> str:
2528
"""Get a standardized key for an InstallRequirement."""
2629
if ireq.req is None and ireq.link is not None:
2730
return str(ireq.link)
2831
else:
2932
return key_from_req(ireq.req)
3033

3134

32-
def key_from_req(req):
35+
def key_from_req(req: InstallRequirement) -> str:
3336
"""Get an all-lowercase version of the requirement's name."""
3437
if hasattr(req, "key"):
3538
# from pkg_resources, such as installed dists for pip-sync
3639
key = req.key
3740
else:
3841
# from packaging, such as install requirements from requirements.txt
3942
key = req.name
40-
43+
assert isinstance(key, str)
4144
key = key.replace("_", "-").lower()
4245
return key
4346

4447

4548
def comment(text: str) -> str:
46-
return style(text, fg="green")
49+
return click.style(text, fg="green")
4750

4851

4952
def make_install_requirement(name, version, extras, constraint=False):
@@ -58,15 +61,19 @@ def make_install_requirement(name, version, extras, constraint=False):
5861
)
5962

6063

61-
def is_url_requirement(ireq):
64+
def is_url_requirement(ireq: InstallRequirement) -> bool:
6265
"""
6366
Return True if requirement was specified as a path or URL.
6467
ireq.original_link will have been set by InstallRequirement.__init__
6568
"""
6669
return bool(ireq.original_link)
6770

6871

69-
def format_requirement(ireq, marker=None, hashes=None):
72+
def format_requirement(
73+
ireq: InstallRequirement,
74+
marker: Optional[Marker] = None,
75+
hashes: Optional[Set[str]] = None,
76+
) -> str:
7077
"""
7178
Generic formatter for pretty printing InstallRequirements to the terminal
7279
in a less verbose way than using its `__str__` method.
@@ -223,7 +230,7 @@ def keyval(v):
223230
return dict(lut)
224231

225232

226-
def dedup(iterable):
233+
def dedup(iterable: Iterable[Any]) -> Iterator[Any]:
227234
"""Deduplicate an iterable object like iter(set(iterable)) but
228235
order-preserved.
229236
"""
@@ -253,7 +260,7 @@ def get_hashes_from_ireq(ireq):
253260
return result
254261

255262

256-
def get_compile_command(click_ctx):
263+
def get_compile_command(click_ctx: click.Context) -> str:
257264
"""
258265
Returns a normalized compile command depending on cli context.
259266
@@ -285,6 +292,8 @@ def get_compile_command(click_ctx):
285292
right_args.extend([shlex.quote(val) for val in value])
286293
continue
287294

295+
assert isinstance(option, click.Option)
296+
288297
# Get the latest option name (usually it'll be a long name)
289298
option_long_name = option.opts[-1]
290299

piptools/writer.py

+58-36
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,13 @@
11
import os
22
import re
33
from itertools import chain
4+
from typing import BinaryIO, Dict, Iterator, List, Optional, Sequence, Set, Tuple
45

56
from click import unstyle
7+
from click.core import Context
8+
from pip._internal.models.format_control import FormatControl
9+
from pip._internal.req.req_install import InstallRequirement
10+
from pip._vendor.packaging.markers import Marker
611

712
from .logging import log
813
from .utils import (
@@ -39,7 +44,7 @@
3944
strip_comes_from_line_re = re.compile(r" \(line \d+\)$")
4045

4146

42-
def _comes_from_as_string(ireq):
47+
def _comes_from_as_string(ireq: InstallRequirement) -> str:
4348
if isinstance(ireq.comes_from, str):
4449
return strip_comes_from_line_re.sub("", ireq.comes_from)
4550
return key_from_ireq(ireq.comes_from)
@@ -48,22 +53,22 @@ def _comes_from_as_string(ireq):
4853
class OutputWriter:
4954
def __init__(
5055
self,
51-
dst_file,
52-
click_ctx,
53-
dry_run,
54-
emit_header,
55-
emit_index_url,
56-
emit_trusted_host,
57-
annotate,
58-
generate_hashes,
59-
default_index_url,
60-
index_urls,
61-
trusted_hosts,
62-
format_control,
63-
allow_unsafe,
64-
find_links,
65-
emit_find_links,
66-
):
56+
dst_file: BinaryIO,
57+
click_ctx: Context,
58+
dry_run: bool,
59+
emit_header: bool,
60+
emit_index_url: bool,
61+
emit_trusted_host: bool,
62+
annotate: bool,
63+
generate_hashes: bool,
64+
default_index_url: str,
65+
index_urls: Sequence[str],
66+
trusted_hosts: Sequence[str],
67+
format_control: FormatControl,
68+
allow_unsafe: bool,
69+
find_links: List[str],
70+
emit_find_links: bool,
71+
) -> None:
6772
self.dst_file = dst_file
6873
self.click_ctx = click_ctx
6974
self.dry_run = dry_run
@@ -80,10 +85,10 @@ def __init__(
8085
self.find_links = find_links
8186
self.emit_find_links = emit_find_links
8287

83-
def _sort_key(self, ireq):
88+
def _sort_key(self, ireq: InstallRequirement) -> Tuple[bool, str]:
8489
return (not ireq.editable, str(ireq.req).lower())
8590

86-
def write_header(self):
91+
def write_header(self) -> Iterator[str]:
8792
if self.emit_header:
8893
yield comment("#")
8994
yield comment("# This file is autogenerated by pip-compile")
@@ -95,31 +100,31 @@ def write_header(self):
95100
yield comment(f"# {compile_command}")
96101
yield comment("#")
97102

98-
def write_index_options(self):
103+
def write_index_options(self) -> Iterator[str]:
99104
if self.emit_index_url:
100105
for index, index_url in enumerate(dedup(self.index_urls)):
101106
if index_url.rstrip("/") == self.default_index_url:
102107
continue
103108
flag = "--index-url" if index == 0 else "--extra-index-url"
104109
yield f"{flag} {index_url}"
105110

106-
def write_trusted_hosts(self):
111+
def write_trusted_hosts(self) -> Iterator[str]:
107112
if self.emit_trusted_host:
108113
for trusted_host in dedup(self.trusted_hosts):
109114
yield f"--trusted-host {trusted_host}"
110115

111-
def write_format_controls(self):
116+
def write_format_controls(self) -> Iterator[str]:
112117
for nb in dedup(sorted(self.format_control.no_binary)):
113118
yield f"--no-binary {nb}"
114119
for ob in dedup(sorted(self.format_control.only_binary)):
115120
yield f"--only-binary {ob}"
116121

117-
def write_find_links(self):
122+
def write_find_links(self) -> Iterator[str]:
118123
if self.emit_find_links:
119124
for find_link in dedup(self.find_links):
120125
yield f"--find-links {find_link}"
121126

122-
def write_flags(self):
127+
def write_flags(self) -> Iterator[str]:
123128
emitted = False
124129
for line in chain(
125130
self.write_index_options(),
@@ -132,9 +137,15 @@ def write_flags(self):
132137
if emitted:
133138
yield ""
134139

135-
def _iter_lines(self, results, unsafe_requirements=None, markers=None, hashes=None):
140+
def _iter_lines(
141+
self,
142+
results: Set[InstallRequirement],
143+
unsafe_requirements: Optional[Set[InstallRequirement]] = None,
144+
markers: Optional[Dict[str, Marker]] = None,
145+
hashes: Optional[Dict[InstallRequirement, Set[str]]] = None,
146+
) -> Iterator[str]:
136147
# default values
137-
unsafe_requirements = unsafe_requirements or []
148+
unsafe_requirements = unsafe_requirements or set()
138149
markers = markers or {}
139150
hashes = hashes or {}
140151

@@ -160,8 +171,7 @@ def _iter_lines(self, results, unsafe_requirements=None, markers=None, hashes=No
160171
packages = {r for r in results if r.name not in UNSAFE_PACKAGES}
161172

162173
if packages:
163-
packages = sorted(packages, key=self._sort_key)
164-
for ireq in packages:
174+
for ireq in sorted(packages, key=self._sort_key):
165175
if has_hashes and not hashes.get(ireq):
166176
yield MESSAGE_UNHASHED_PACKAGE
167177
warn_uninstallable = True
@@ -172,7 +182,6 @@ def _iter_lines(self, results, unsafe_requirements=None, markers=None, hashes=No
172182
yielded = True
173183

174184
if unsafe_requirements:
175-
unsafe_requirements = sorted(unsafe_requirements, key=self._sort_key)
176185
yield ""
177186
yielded = True
178187
if has_hashes and not self.allow_unsafe:
@@ -181,7 +190,7 @@ def _iter_lines(self, results, unsafe_requirements=None, markers=None, hashes=No
181190
else:
182191
yield MESSAGE_UNSAFE_PACKAGES
183192

184-
for ireq in unsafe_requirements:
193+
for ireq in sorted(unsafe_requirements, key=self._sort_key):
185194
ireq_key = key_from_ireq(ireq)
186195
if not self.allow_unsafe:
187196
yield comment(f"# {ireq_key}")
@@ -198,15 +207,26 @@ def _iter_lines(self, results, unsafe_requirements=None, markers=None, hashes=No
198207
if warn_uninstallable:
199208
log.warning(MESSAGE_UNINSTALLABLE)
200209

201-
def write(self, results, unsafe_requirements, markers, hashes):
210+
def write(
211+
self,
212+
results: Set[InstallRequirement],
213+
unsafe_requirements: Set[InstallRequirement],
214+
markers: Dict[str, Marker],
215+
hashes: Optional[Dict[InstallRequirement, Set[str]]],
216+
) -> None:
202217

203218
for line in self._iter_lines(results, unsafe_requirements, markers, hashes):
204219
log.info(line)
205220
if not self.dry_run:
206221
self.dst_file.write(unstyle(line).encode())
207222
self.dst_file.write(os.linesep.encode())
208223

209-
def _format_requirement(self, ireq, marker=None, hashes=None):
224+
def _format_requirement(
225+
self,
226+
ireq: InstallRequirement,
227+
marker: Optional[Marker] = None,
228+
hashes: Optional[Dict[InstallRequirement, Set[str]]] = None,
229+
) -> str:
210230
ireq_hashes = (hashes if hashes is not None else {}).get(ireq)
211231

212232
line = format_requirement(ireq, marker=marker, hashes=ireq_hashes)
@@ -224,15 +244,17 @@ def _format_requirement(self, ireq, marker=None, hashes=None):
224244
}
225245
elif ireq.comes_from:
226246
required_by.add(_comes_from_as_string(ireq))
247+
227248
if required_by:
228-
required_by = sorted(required_by)
229-
if len(required_by) == 1:
230-
source = required_by[0]
249+
sorted_required_by = sorted(required_by)
250+
if len(sorted_required_by) == 1:
251+
source = sorted_required_by[0]
231252
annotation = " # via " + source
232253
else:
233254
annotation_lines = [" # via"]
234-
for source in required_by:
255+
for source in sorted_required_by:
235256
annotation_lines.append(" # " + source)
236257
annotation = "\n".join(annotation_lines)
237258
line = f"{line}\n{comment(annotation)}"
259+
238260
return line

0 commit comments

Comments
 (0)