summaryrefslogtreecommitdiff
path: root/venv/lib/python3.7/site-packages/pip-10.0.1-py3.7.egg/pip/_internal/utils/ui.py
diff options
context:
space:
mode:
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.py421
1 files changed, 0 insertions, 421 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
deleted file mode 100644
index d97ea36..0000000
--- a/venv/lib/python3.7/site-packages/pip-10.0.1-py3.7.egg/pip/_internal/utils/ui.py
+++ /dev/null
@@ -1,421 +0,0 @@
1from __future__ import absolute_import, division
2
3import contextlib
4import itertools
5import logging
6import sys
7import time
8from signal import SIGINT, default_int_handler, signal
9
10from pip._vendor import six
11from pip._vendor.progress.bar import (
12 Bar, ChargingBar, FillingCirclesBar, FillingSquaresBar, IncrementalBar,
13 ShadyBar,
14)
15from pip._vendor.progress.helpers import HIDE_CURSOR, SHOW_CURSOR, WritelnMixin
16from pip._vendor.progress.spinner import Spinner
17
18from pip._internal.compat import WINDOWS
19from pip._internal.utils.logging import get_indentation
20from pip._internal.utils.misc import format_size
21from pip._internal.utils.typing import MYPY_CHECK_RUNNING
22
23if MYPY_CHECK_RUNNING:
24 from typing import Any
25
26try:
27 from pip._vendor import colorama
28# Lots of different errors can come from this, including SystemError and
29# ImportError.
30except Exception:
31 colorama = None
32
33logger = logging.getLogger(__name__)
34
35
36def _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
66class 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
121class SilentBar(Bar):
122
123 def update(self):
124 pass
125
126
127class 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
135class 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
165class 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
194class 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
205class DefaultDownloadProgressBar(BaseDownloadProgressBar,
206 _BaseBar): # type: ignore
207 pass
208
209
210class DownloadSilentBar(BaseDownloadProgressBar, SilentBar): # type: ignore
211 pass
212
213
214class DownloadIncrementalBar(BaseDownloadProgressBar, # type: ignore
215 IncrementalBar):
216 pass
217
218
219class DownloadChargingBar(BaseDownloadProgressBar, # type: ignore
220 ChargingBar):
221 pass
222
223
224class DownloadShadyBar(BaseDownloadProgressBar, ShadyBar): # type: ignore
225 pass
226
227
228class DownloadFillingSquaresBar(BaseDownloadProgressBar, # type: ignore
229 FillingSquaresBar):
230 pass
231
232
233class DownloadFillingCirclesBar(BaseDownloadProgressBar, # type: ignore
234 FillingCirclesBar):
235 pass
236
237
238class DownloadBlueEmojiProgressBar(BaseDownloadProgressBar, # type: ignore
239 BlueEmojiBar):
240 pass
241
242
243class 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
269BAR_TYPES = {
270 "off": (DownloadSilentBar, DownloadSilentBar),
271 "on": (DefaultDownloadProgressBar, DownloadProgressSpinner),
272 "ascii": (DownloadIncrementalBar, DownloadProgressSpinner),
273 "pretty": (DownloadFillingCirclesBar, DownloadProgressSpinner),
274 "emoji": (DownloadBlueEmojiProgressBar, DownloadProgressSpinner)
275}
276
277
278def 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
294def 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
312class 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
326class 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.
374class 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
401def 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")