diff options
author | Shubham Saini <shubham6405@gmail.com> | 2018-12-11 10:01:23 +0000 |
---|---|---|
committer | Shubham Saini <shubham6405@gmail.com> | 2018-12-11 10:01:23 +0000 |
commit | 68df54d6629ec019142eb149dd037774f2d11e7c (patch) | |
tree | 345bc22d46b4e01a4ba8303b94278952a4ed2b9e /venv/lib/python3.7/site-packages/pip-10.0.1-py3.7.egg/pip/_internal/utils/ui.py |
First commit
Diffstat (limited to 'venv/lib/python3.7/site-packages/pip-10.0.1-py3.7.egg/pip/_internal/utils/ui.py')
-rw-r--r-- | venv/lib/python3.7/site-packages/pip-10.0.1-py3.7.egg/pip/_internal/utils/ui.py | 421 |
1 files changed, 421 insertions, 0 deletions
diff --git a/venv/lib/python3.7/site-packages/pip-10.0.1-py3.7.egg/pip/_internal/utils/ui.py b/venv/lib/python3.7/site-packages/pip-10.0.1-py3.7.egg/pip/_internal/utils/ui.py new file mode 100644 index 0000000..d97ea36 --- /dev/null +++ b/venv/lib/python3.7/site-packages/pip-10.0.1-py3.7.egg/pip/_internal/utils/ui.py | |||
@@ -0,0 +1,421 @@ | |||
1 | from __future__ import absolute_import, division | ||
2 | |||
3 | import contextlib | ||
4 | import itertools | ||
5 | import logging | ||
6 | import sys | ||
7 | import time | ||
8 | from signal import SIGINT, default_int_handler, signal | ||
9 | |||
10 | from pip._vendor import six | ||
11 | from pip._vendor.progress.bar import ( | ||
12 | Bar, ChargingBar, FillingCirclesBar, FillingSquaresBar, IncrementalBar, | ||
13 | ShadyBar, | ||
14 | ) | ||
15 | from pip._vendor.progress.helpers import HIDE_CURSOR, SHOW_CURSOR, WritelnMixin | ||
16 | from pip._vendor.progress.spinner import Spinner | ||
17 | |||
18 | from pip._internal.compat import WINDOWS | ||
19 | from pip._internal.utils.logging import get_indentation | ||
20 | from pip._internal.utils.misc import format_size | ||
21 | from pip._internal.utils.typing import MYPY_CHECK_RUNNING | ||
22 | |||
23 | if MYPY_CHECK_RUNNING: | ||
24 | from typing import Any | ||
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 | |||
33 | logger = logging.getLogger(__name__) | ||
34 | |||
35 | |||
36 | def _select_progress_class(preferred, fallback): | ||
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", six.text_type()), | ||
48 | getattr(preferred, "fill", six.text_type()), | ||
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 | six.text_type().join(characters).encode(encoding) | ||
57 | except UnicodeEncodeError: | ||
58 | return fallback | ||
59 | else: | ||
60 | return preferred | ||
61 | |||
62 | |||
63 | _BaseBar = _select_progress_class(IncrementalBar, Bar) # type: Any | ||
64 | |||
65 | |||
66 | class InterruptibleMixin(object): | ||
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, **kwargs): | ||
85 | """ | ||
86 | Save the original SIGINT handler for later. | ||
87 | """ | ||
88 | super(InterruptibleMixin, self).__init__(*args, **kwargs) | ||
89 | |||
90 | self.original_handler = signal(SIGINT, self.handle_sigint) | ||
91 | |||
92 | # If signal() returns None, the previous handler was not installed from | ||
93 | # Python, and we cannot restore it. This probably should not happen, | ||
94 | # but if it does, we must restore something sensible instead, at least. | ||
95 | # The least bad option should be Python's default SIGINT handler, which | ||
96 | # just raises KeyboardInterrupt. | ||
97 | if self.original_handler is None: | ||
98 | self.original_handler = default_int_handler | ||
99 | |||
100 | def finish(self): | ||
101 | """ | ||
102 | Restore the original SIGINT handler after finishing. | ||
103 | |||
104 | This should happen regardless of whether the progress display finishes | ||
105 | normally, or gets interrupted. | ||
106 | """ | ||
107 | super(InterruptibleMixin, self).finish() | ||
108 | signal(SIGINT, self.original_handler) | ||
109 | |||
110 | def handle_sigint(self, signum, frame): | ||
111 | """ | ||
112 | Call self.finish() before delegating to the original SIGINT handler. | ||
113 | |||
114 | This handler should only be in place while the progress display is | ||
115 | active. | ||
116 | """ | ||
117 | self.finish() | ||
118 | self.original_handler(signum, frame) | ||
119 | |||
120 | |||
121 | class SilentBar(Bar): | ||
122 | |||
123 | def update(self): | ||
124 | pass | ||
125 | |||
126 | |||
127 | class BlueEmojiBar(IncrementalBar): | ||
128 | |||
129 | suffix = "%(percent)d%%" | ||
130 | bar_prefix = " " | ||
131 | bar_suffix = " " | ||
132 | phases = (u"\U0001F539", u"\U0001F537", u"\U0001F535") # type: Any | ||
133 | |||
134 | |||
135 | class DownloadProgressMixin(object): | ||
136 | |||
137 | def __init__(self, *args, **kwargs): | ||
138 | super(DownloadProgressMixin, self).__init__(*args, **kwargs) | ||
139 | self.message = (" " * (get_indentation() + 2)) + self.message | ||
140 | |||
141 | @property | ||
142 | def downloaded(self): | ||
143 | return format_size(self.index) | ||
144 | |||
145 | @property | ||
146 | def download_speed(self): | ||
147 | # Avoid zero division errors... | ||
148 | if self.avg == 0.0: | ||
149 | return "..." | ||
150 | return format_size(1 / self.avg) + "/s" | ||
151 | |||
152 | @property | ||
153 | def pretty_eta(self): | ||
154 | if self.eta: | ||
155 | return "eta %s" % self.eta_td | ||
156 | return "" | ||
157 | |||
158 | def iter(self, it, n=1): | ||
159 | for x in it: | ||
160 | yield x | ||
161 | self.next(n) | ||
162 | self.finish() | ||
163 | |||
164 | |||
165 | class WindowsMixin(object): | ||
166 | |||
167 | def __init__(self, *args, **kwargs): | ||
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 neds 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: | ||
176 | self.hide_cursor = False | ||
177 | |||
178 | super(WindowsMixin, self).__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) | ||
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, | ||
195 | DownloadProgressMixin): | ||
196 | |||
197 | file = sys.stdout | ||
198 | message = "%(percent)d%%" | ||
199 | suffix = "%(downloaded)s %(download_speed)s %(pretty_eta)s" | ||
200 | |||
201 | # NOTE: The "type: ignore" comments on the following classes are there to | ||
202 | # work around https://github.com/python/typing/issues/241 | ||
203 | |||
204 | |||
205 | class DefaultDownloadProgressBar(BaseDownloadProgressBar, | ||
206 | _BaseBar): # type: ignore | ||
207 | pass | ||
208 | |||
209 | |||
210 | class DownloadSilentBar(BaseDownloadProgressBar, SilentBar): # type: ignore | ||
211 | pass | ||
212 | |||
213 | |||
214 | class DownloadIncrementalBar(BaseDownloadProgressBar, # type: ignore | ||
215 | IncrementalBar): | ||
216 | pass | ||
217 | |||
218 | |||
219 | class DownloadChargingBar(BaseDownloadProgressBar, # type: ignore | ||
220 | ChargingBar): | ||
221 | pass | ||
222 | |||
223 | |||
224 | class DownloadShadyBar(BaseDownloadProgressBar, ShadyBar): # type: ignore | ||
225 | pass | ||
226 | |||
227 | |||
228 | class DownloadFillingSquaresBar(BaseDownloadProgressBar, # type: ignore | ||
229 | FillingSquaresBar): | ||
230 | pass | ||
231 | |||
232 | |||
233 | class DownloadFillingCirclesBar(BaseDownloadProgressBar, # type: ignore | ||
234 | FillingCirclesBar): | ||
235 | pass | ||
236 | |||
237 | |||
238 | class DownloadBlueEmojiProgressBar(BaseDownloadProgressBar, # type: ignore | ||
239 | BlueEmojiBar): | ||
240 | pass | ||
241 | |||
242 | |||
243 | class DownloadProgressSpinner(WindowsMixin, InterruptibleMixin, | ||
244 | DownloadProgressMixin, WritelnMixin, Spinner): | ||
245 | |||
246 | file = sys.stdout | ||
247 | suffix = "%(downloaded)s %(download_speed)s" | ||
248 | |||
249 | def next_phase(self): | ||
250 | if not hasattr(self, "_phaser"): | ||
251 | self._phaser = itertools.cycle(self.phases) | ||
252 | return next(self._phaser) | ||
253 | |||
254 | def update(self): | ||
255 | message = self.message % self | ||
256 | phase = self.next_phase() | ||
257 | suffix = self.suffix % self | ||
258 | line = ''.join([ | ||
259 | message, | ||
260 | " " if message else "", | ||
261 | phase, | ||
262 | " " if suffix else "", | ||
263 | suffix, | ||
264 | ]) | ||
265 | |||
266 | self.writeln(line) | ||
267 | |||
268 | |||
269 | BAR_TYPES = { | ||
270 | "off": (DownloadSilentBar, DownloadSilentBar), | ||
271 | "on": (DefaultDownloadProgressBar, DownloadProgressSpinner), | ||
272 | "ascii": (DownloadIncrementalBar, DownloadProgressSpinner), | ||
273 | "pretty": (DownloadFillingCirclesBar, DownloadProgressSpinner), | ||
274 | "emoji": (DownloadBlueEmojiProgressBar, DownloadProgressSpinner) | ||
275 | } | ||
276 | |||
277 | |||
278 | def DownloadProgressProvider(progress_bar, max=None): | ||
279 | if max is None or max == 0: | ||
280 | return BAR_TYPES[progress_bar][1]().iter | ||
281 | else: | ||
282 | return BAR_TYPES[progress_bar][0](max=max).iter | ||
283 | |||
284 | |||
285 | ################################################################ | ||
286 | # Generic "something is happening" spinners | ||
287 | # | ||
288 | # We don't even try using progress.spinner.Spinner here because it's actually | ||
289 | # simpler to reimplement from scratch than to coerce their code into doing | ||
290 | # what we need. | ||
291 | ################################################################ | ||
292 | |||
293 | @contextlib.contextmanager | ||
294 | def hidden_cursor(file): | ||
295 | # The Windows terminal does not support the hide/show cursor ANSI codes, | ||
296 | # even via colorama. So don't even try. | ||
297 | if WINDOWS: | ||
298 | yield | ||
299 | # We don't want to clutter the output with control characters if we're | ||
300 | # writing to a file, or if the user is running with --quiet. | ||
301 | # See https://github.com/pypa/pip/issues/3418 | ||
302 | elif not file.isatty() or logger.getEffectiveLevel() > logging.INFO: | ||
303 | yield | ||
304 | else: | ||
305 | file.write(HIDE_CURSOR) | ||
306 | try: | ||
307 | yield | ||
308 | finally: | ||
309 | file.write(SHOW_CURSOR) | ||
310 | |||
311 | |||
312 | class RateLimiter(object): | ||
313 | def __init__(self, min_update_interval_seconds): | ||
314 | self._min_update_interval_seconds = min_update_interval_seconds | ||
315 | self._last_update = 0 | ||
316 | |||
317 | def ready(self): | ||
318 | now = time.time() | ||
319 | delta = now - self._last_update | ||
320 | return delta >= self._min_update_interval_seconds | ||
321 | |||
322 | def reset(self): | ||
323 | self._last_update = time.time() | ||
324 | |||
325 | |||
326 | class InteractiveSpinner(object): | ||
327 | def __init__(self, message, file=None, spin_chars="-\\|/", | ||
328 | # Empirically, 8 updates/second looks nice | ||
329 | min_update_interval_seconds=0.125): | ||
330 | self._message = message | ||
331 | if file is None: | ||
332 | file = sys.stdout | ||
333 | self._file = file | ||
334 | self._rate_limiter = RateLimiter(min_update_interval_seconds) | ||
335 | self._finished = False | ||
336 | |||
337 | self._spin_cycle = itertools.cycle(spin_chars) | ||
338 | |||
339 | self._file.write(" " * get_indentation() + self._message + " ... ") | ||
340 | self._width = 0 | ||
341 | |||
342 | def _write(self, status): | ||
343 | assert not self._finished | ||
344 | # Erase what we wrote before by backspacing to the beginning, writing | ||
345 | # spaces to overwrite the old text, and then backspacing again | ||
346 | backup = "\b" * self._width | ||
347 | self._file.write(backup + " " * self._width + backup) | ||
348 | # Now we have a blank slate to add our status | ||
349 | self._file.write(status) | ||
350 | self._width = len(status) | ||
351 | self._file.flush() | ||
352 | self._rate_limiter.reset() | ||
353 | |||
354 | def spin(self): | ||
355 | if self._finished: | ||
356 | return | ||
357 | if not self._rate_limiter.ready(): | ||
358 | return | ||
359 | self._write(next(self._spin_cycle)) | ||
360 | |||
361 | def finish(self, final_status): | ||
362 | if self._finished: | ||
363 | return | ||
364 | self._write(final_status) | ||
365 | self._file.write("\n") | ||
366 | self._file.flush() | ||
367 | self._finished = True | ||
368 | |||
369 | |||
370 | # Used for dumb terminals, non-interactive installs (no tty), etc. | ||
371 | # We still print updates occasionally (once every 60 seconds by default) to | ||
372 | # act as a keep-alive for systems like Travis-CI that take lack-of-output as | ||
373 | # an indication that a task has frozen. | ||
374 | class NonInteractiveSpinner(object): | ||
375 | def __init__(self, message, min_update_interval_seconds=60): | ||
376 | self._message = message | ||
377 | self._finished = False | ||
378 | self._rate_limiter = RateLimiter(min_update_interval_seconds) | ||
379 | self._update("started") | ||
380 | |||
381 | def _update(self, status): | ||
382 | assert not self._finished | ||
383 | self._rate_limiter.reset() | ||
384 | logger.info("%s: %s", self._message, status) | ||
385 | |||
386 | def spin(self): | ||
387 | if self._finished: | ||
388 | return | ||
389 | if not self._rate_limiter.ready(): | ||
390 | return | ||
391 | self._update("still running...") | ||
392 | |||
393 | def finish(self, final_status): | ||
394 | if self._finished: | ||
395 | return | ||
396 | self._update("finished with status '%s'" % (final_status,)) | ||
397 | self._finished = True | ||
398 | |||
399 | |||
400 | @contextlib.contextmanager | ||
401 | def open_spinner(message): | ||
402 | # Interactive spinner goes directly to sys.stdout rather than being routed | ||
403 | # through the logging system, but it acts like it has level INFO, | ||
404 | # i.e. it's only displayed if we're at level INFO or better. | ||
405 | # Non-interactive spinner goes through the logging system, so it is always | ||
406 | # in sync with logging configuration. | ||
407 | if sys.stdout.isatty() and logger.getEffectiveLevel() <= logging.INFO: | ||
408 | spinner = InteractiveSpinner(message) | ||
409 | else: | ||
410 | spinner = NonInteractiveSpinner(message) | ||
411 | try: | ||
412 | with hidden_cursor(sys.stdout): | ||
413 | yield spinner | ||
414 | except KeyboardInterrupt: | ||
415 | spinner.finish("canceled") | ||
416 | raise | ||
417 | except Exception: | ||
418 | spinner.finish("error") | ||
419 | raise | ||
420 | else: | ||
421 | spinner.finish("done") | ||