|
1 | 1 | import functools
|
2 |
| -import itertools |
3 |
| -import sys |
4 |
| -from signal import SIGINT, default_int_handler, signal |
5 |
| -from typing import Any, Callable, Iterator, Optional, Tuple |
| 2 | +from typing import Callable, Iterator, Optional, Tuple |
6 | 3 |
|
7 |
| -from pip._vendor.progress.bar import Bar, FillingCirclesBar, IncrementalBar |
8 |
| -from pip._vendor.progress.spinner import Spinner |
9 | 4 | from pip._vendor.rich.progress import (
|
10 | 5 | BarColumn,
|
11 | 6 | DownloadColumn,
|
|
19 | 14 | TransferSpeedColumn,
|
20 | 15 | )
|
21 | 16 |
|
22 |
| -from pip._internal.utils.compat import WINDOWS |
23 | 17 | from pip._internal.utils.logging import get_indentation
|
24 |
| -from pip._internal.utils.misc import format_size |
25 |
| - |
26 |
| -try: |
27 |
| - from pip._vendor import colorama |
28 |
| -# Lots of different errors can come from this, including SystemError and |
29 |
| -# ImportError. |
30 |
| -except Exception: |
31 |
| - colorama = None |
32 | 18 |
|
33 | 19 | DownloadProgressRenderer = Callable[[Iterator[bytes]], Iterator[bytes]]
|
34 | 20 |
|
35 | 21 |
|
36 |
| -def _select_progress_class(preferred: Bar, fallback: Bar) -> Bar: |
37 |
| - encoding = getattr(preferred.file, "encoding", None) |
38 |
| - |
39 |
| - # If we don't know what encoding this file is in, then we'll just assume |
40 |
| - # that it doesn't support unicode and use the ASCII bar. |
41 |
| - if not encoding: |
42 |
| - return fallback |
43 |
| - |
44 |
| - # Collect all of the possible characters we want to use with the preferred |
45 |
| - # bar. |
46 |
| - characters = [ |
47 |
| - getattr(preferred, "empty_fill", ""), |
48 |
| - getattr(preferred, "fill", ""), |
49 |
| - ] |
50 |
| - characters += list(getattr(preferred, "phases", [])) |
51 |
| - |
52 |
| - # Try to decode the characters we're using for the bar using the encoding |
53 |
| - # of the given file, if this works then we'll assume that we can use the |
54 |
| - # fancier bar and if not we'll fall back to the plaintext bar. |
55 |
| - try: |
56 |
| - "".join(characters).encode(encoding) |
57 |
| - except UnicodeEncodeError: |
58 |
| - return fallback |
59 |
| - else: |
60 |
| - return preferred |
61 |
| - |
62 |
| - |
63 |
| -_BaseBar: Any = _select_progress_class(IncrementalBar, Bar) |
64 |
| - |
65 |
| - |
66 |
| -class InterruptibleMixin: |
67 |
| - """ |
68 |
| - Helper to ensure that self.finish() gets called on keyboard interrupt. |
69 |
| -
|
70 |
| - This allows downloads to be interrupted without leaving temporary state |
71 |
| - (like hidden cursors) behind. |
72 |
| -
|
73 |
| - This class is similar to the progress library's existing SigIntMixin |
74 |
| - helper, but as of version 1.2, that helper has the following problems: |
75 |
| -
|
76 |
| - 1. It calls sys.exit(). |
77 |
| - 2. It discards the existing SIGINT handler completely. |
78 |
| - 3. It leaves its own handler in place even after an uninterrupted finish, |
79 |
| - which will have unexpected delayed effects if the user triggers an |
80 |
| - unrelated keyboard interrupt some time after a progress-displaying |
81 |
| - download has already completed, for example. |
82 |
| - """ |
83 |
| - |
84 |
| - def __init__(self, *args: Any, **kwargs: Any) -> None: |
85 |
| - """ |
86 |
| - Save the original SIGINT handler for later. |
87 |
| - """ |
88 |
| - # https://github.com/python/mypy/issues/5887 |
89 |
| - super().__init__(*args, **kwargs) |
90 |
| - |
91 |
| - self.original_handler = signal(SIGINT, self.handle_sigint) |
92 |
| - |
93 |
| - # If signal() returns None, the previous handler was not installed from |
94 |
| - # Python, and we cannot restore it. This probably should not happen, |
95 |
| - # but if it does, we must restore something sensible instead, at least. |
96 |
| - # The least bad option should be Python's default SIGINT handler, which |
97 |
| - # just raises KeyboardInterrupt. |
98 |
| - if self.original_handler is None: |
99 |
| - self.original_handler = default_int_handler |
100 |
| - |
101 |
| - def finish(self) -> None: |
102 |
| - """ |
103 |
| - Restore the original SIGINT handler after finishing. |
104 |
| -
|
105 |
| - This should happen regardless of whether the progress display finishes |
106 |
| - normally, or gets interrupted. |
107 |
| - """ |
108 |
| - super().finish() # type: ignore |
109 |
| - signal(SIGINT, self.original_handler) |
110 |
| - |
111 |
| - def handle_sigint(self, signum, frame): # type: ignore |
112 |
| - """ |
113 |
| - Call self.finish() before delegating to the original SIGINT handler. |
114 |
| -
|
115 |
| - This handler should only be in place while the progress display is |
116 |
| - active. |
117 |
| - """ |
118 |
| - self.finish() |
119 |
| - self.original_handler(signum, frame) |
120 |
| - |
121 |
| - |
122 |
| -class SilentBar(Bar): |
123 |
| - def update(self) -> None: |
124 |
| - pass |
125 |
| - |
126 |
| - |
127 |
| -class BlueEmojiBar(IncrementalBar): |
128 |
| - |
129 |
| - suffix = "%(percent)d%%" |
130 |
| - bar_prefix = " " |
131 |
| - bar_suffix = " " |
132 |
| - phases = ("\U0001F539", "\U0001F537", "\U0001F535") |
133 |
| - |
134 |
| - |
135 |
| -class DownloadProgressMixin: |
136 |
| - def __init__(self, *args: Any, **kwargs: Any) -> None: |
137 |
| - super().__init__(*args, **kwargs) |
138 |
| - self.message: str = (" " * (get_indentation() + 2)) + self.message |
139 |
| - |
140 |
| - @property |
141 |
| - def downloaded(self) -> str: |
142 |
| - return format_size(self.index) # type: ignore |
143 |
| - |
144 |
| - @property |
145 |
| - def download_speed(self) -> str: |
146 |
| - # Avoid zero division errors... |
147 |
| - if self.avg == 0.0: # type: ignore |
148 |
| - return "..." |
149 |
| - return format_size(1 / self.avg) + "/s" # type: ignore |
150 |
| - |
151 |
| - @property |
152 |
| - def pretty_eta(self) -> str: |
153 |
| - if self.eta: # type: ignore |
154 |
| - return f"eta {self.eta_td}" # type: ignore |
155 |
| - return "" |
156 |
| - |
157 |
| - def iter(self, it): # type: ignore |
158 |
| - for x in it: |
159 |
| - yield x |
160 |
| - # B305 is incorrectly raised here |
161 |
| - # https://github.com/PyCQA/flake8-bugbear/issues/59 |
162 |
| - self.next(len(x)) # noqa: B305 |
163 |
| - self.finish() |
164 |
| - |
165 |
| - |
166 |
| -class WindowsMixin: |
167 |
| - def __init__(self, *args: Any, **kwargs: Any) -> None: |
168 |
| - # The Windows terminal does not support the hide/show cursor ANSI codes |
169 |
| - # even with colorama. So we'll ensure that hide_cursor is False on |
170 |
| - # Windows. |
171 |
| - # This call needs to go before the super() call, so that hide_cursor |
172 |
| - # is set in time. The base progress bar class writes the "hide cursor" |
173 |
| - # code to the terminal in its init, so if we don't set this soon |
174 |
| - # enough, we get a "hide" with no corresponding "show"... |
175 |
| - if WINDOWS and self.hide_cursor: # type: ignore |
176 |
| - self.hide_cursor = False |
177 |
| - |
178 |
| - super().__init__(*args, **kwargs) |
179 |
| - |
180 |
| - # Check if we are running on Windows and we have the colorama module, |
181 |
| - # if we do then wrap our file with it. |
182 |
| - if WINDOWS and colorama: |
183 |
| - self.file = colorama.AnsiToWin32(self.file) # type: ignore |
184 |
| - # The progress code expects to be able to call self.file.isatty() |
185 |
| - # but the colorama.AnsiToWin32() object doesn't have that, so we'll |
186 |
| - # add it. |
187 |
| - self.file.isatty = lambda: self.file.wrapped.isatty() |
188 |
| - # The progress code expects to be able to call self.file.flush() |
189 |
| - # but the colorama.AnsiToWin32() object doesn't have that, so we'll |
190 |
| - # add it. |
191 |
| - self.file.flush = lambda: self.file.wrapped.flush() |
192 |
| - |
193 |
| - |
194 |
| -class BaseDownloadProgressBar(WindowsMixin, InterruptibleMixin, DownloadProgressMixin): |
195 |
| - |
196 |
| - file = sys.stdout |
197 |
| - message = "%(percent)d%%" |
198 |
| - suffix = "%(downloaded)s %(download_speed)s %(pretty_eta)s" |
199 |
| - |
200 |
| - |
201 |
| -class DefaultDownloadProgressBar(BaseDownloadProgressBar, _BaseBar): |
202 |
| - pass |
203 |
| - |
204 |
| - |
205 |
| -class DownloadSilentBar(BaseDownloadProgressBar, SilentBar): |
206 |
| - pass |
207 |
| - |
208 |
| - |
209 |
| -class DownloadBar(BaseDownloadProgressBar, Bar): |
210 |
| - pass |
211 |
| - |
212 |
| - |
213 |
| -class DownloadFillingCirclesBar(BaseDownloadProgressBar, FillingCirclesBar): |
214 |
| - pass |
215 |
| - |
216 |
| - |
217 |
| -class DownloadBlueEmojiProgressBar(BaseDownloadProgressBar, BlueEmojiBar): |
218 |
| - pass |
219 |
| - |
220 |
| - |
221 |
| -class DownloadProgressSpinner( |
222 |
| - WindowsMixin, InterruptibleMixin, DownloadProgressMixin, Spinner |
223 |
| -): |
224 |
| - |
225 |
| - file = sys.stdout |
226 |
| - suffix = "%(downloaded)s %(download_speed)s" |
227 |
| - |
228 |
| - def next_phase(self) -> str: |
229 |
| - if not hasattr(self, "_phaser"): |
230 |
| - self._phaser = itertools.cycle(self.phases) |
231 |
| - return next(self._phaser) |
232 |
| - |
233 |
| - def update(self) -> None: |
234 |
| - message = self.message % self |
235 |
| - phase = self.next_phase() |
236 |
| - suffix = self.suffix % self |
237 |
| - line = "".join( |
238 |
| - [ |
239 |
| - message, |
240 |
| - " " if message else "", |
241 |
| - phase, |
242 |
| - " " if suffix else "", |
243 |
| - suffix, |
244 |
| - ] |
245 |
| - ) |
246 |
| - |
247 |
| - self.writeln(line) |
248 |
| - |
249 |
| - |
250 |
| -BAR_TYPES = { |
251 |
| - "off": (DownloadSilentBar, DownloadSilentBar), |
252 |
| - "on": (DefaultDownloadProgressBar, DownloadProgressSpinner), |
253 |
| - "ascii": (DownloadBar, DownloadProgressSpinner), |
254 |
| - "pretty": (DownloadFillingCirclesBar, DownloadProgressSpinner), |
255 |
| - "emoji": (DownloadBlueEmojiProgressBar, DownloadProgressSpinner), |
256 |
| -} |
257 |
| - |
258 |
| - |
259 |
| -def _legacy_progress_bar( |
260 |
| - progress_bar: str, max: Optional[int] |
261 |
| -) -> DownloadProgressRenderer: |
262 |
| - if max is None or max == 0: |
263 |
| - return BAR_TYPES[progress_bar][1]().iter # type: ignore |
264 |
| - else: |
265 |
| - return BAR_TYPES[progress_bar][0](max=max).iter |
266 |
| - |
267 |
| - |
268 |
| -# |
269 |
| -# Modern replacement, for our legacy progress bars. |
270 |
| -# |
271 | 22 | def _rich_progress_bar(
|
272 | 23 | iterable: Iterator[bytes],
|
273 | 24 | *,
|
@@ -313,7 +64,5 @@ def get_download_progress_renderer(
|
313 | 64 | """
|
314 | 65 | if bar_type == "on":
|
315 | 66 | return functools.partial(_rich_progress_bar, bar_type=bar_type, size=size)
|
316 |
| - elif bar_type == "off": |
317 |
| - return iter # no-op, when passed an iterator |
318 | 67 | else:
|
319 |
| - return _legacy_progress_bar(bar_type, size) |
| 68 | + return iter # no-op, when passed an iterator |
0 commit comments