mirror of https://github.com/pypa/pip
Add a progress bar mixin that cleans up after keyboard interrupts.
This is similar to the progress library's existing SigIntMixin, but it avoids several problems with that implementation.
This commit is contained in:
parent
b26195443d
commit
44eb9854cc
|
@ -3,6 +3,7 @@ from __future__ import division
|
|||
|
||||
import itertools
|
||||
import sys
|
||||
from signal import signal, SIGINT, default_int_handler
|
||||
|
||||
from pip.compat import WINDOWS
|
||||
from pip.utils import format_size
|
||||
|
@ -50,6 +51,61 @@ def _select_progress_class(preferred, fallback):
|
|||
_BaseBar = _select_progress_class(IncrementalBar, Bar)
|
||||
|
||||
|
||||
class InterruptibleMixin(object):
|
||||
"""
|
||||
Helper to ensure that self.finish() gets called on keyboard interrupt.
|
||||
|
||||
This allows downloads to be interrupted without leaving temporary state
|
||||
(like hidden cursors) behind.
|
||||
|
||||
This class is similar to the progress library's existing SigIntMixin
|
||||
helper, but as of version 1.2, that helper has the following problems:
|
||||
|
||||
1. It calls sys.exit().
|
||||
2. It discards the existing SIGINT handler completely.
|
||||
3. It leaves its own handler in place even after an uninterrupted finish,
|
||||
which will have unexpected delayed effects if the user triggers an
|
||||
unrelated keyboard interrupt some time after a progress-displaying
|
||||
download has already completed, for example.
|
||||
"""
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
"""
|
||||
Save the original SIGINT handler for later.
|
||||
"""
|
||||
super(InterruptibleMixin, self).__init__(*args, **kwargs)
|
||||
|
||||
self.original_handler = signal(SIGINT, self.handle_sigint)
|
||||
|
||||
# If signal() returns None, the previous handler was not installed from
|
||||
# Python, and we cannot restore it. This probably should not happen,
|
||||
# but if it does, we must restore something sensible instead, at least.
|
||||
# The least bad option should be Python's default SIGINT handler, which
|
||||
# just raises KeyboardInterrupt.
|
||||
if self.original_handler is None:
|
||||
self.original_handler = default_int_handler
|
||||
|
||||
def finish(self):
|
||||
"""
|
||||
Restore the original SIGINT handler after finishing.
|
||||
|
||||
This should happen regardless of whether the progress display finishes
|
||||
normally, or gets interrupted.
|
||||
"""
|
||||
super(InterruptibleMixin, self).finish()
|
||||
signal(SIGINT, self.original_handler)
|
||||
|
||||
def handle_sigint(self, signum, frame):
|
||||
"""
|
||||
Call self.finish() before delegating to the original SIGINT handler.
|
||||
|
||||
This handler should only be in place while the progress display is
|
||||
active.
|
||||
"""
|
||||
self.finish()
|
||||
self.original_handler(signum, frame)
|
||||
|
||||
|
||||
class DownloadProgressMixin(object):
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
|
@ -109,15 +165,16 @@ class WindowsMixin(object):
|
|||
self.file.flush = lambda: self.file.wrapped.flush()
|
||||
|
||||
|
||||
class DownloadProgressBar(WindowsMixin, DownloadProgressMixin, _BaseBar):
|
||||
class DownloadProgressBar(WindowsMixin, InterruptibleMixin,
|
||||
DownloadProgressMixin, _BaseBar):
|
||||
|
||||
file = sys.stdout
|
||||
message = "%(percent)d%%"
|
||||
suffix = "%(downloaded)s %(download_speed)s %(pretty_eta)s"
|
||||
|
||||
|
||||
class DownloadProgressSpinner(WindowsMixin, DownloadProgressMixin,
|
||||
WritelnMixin, Spinner):
|
||||
class DownloadProgressSpinner(WindowsMixin, InterruptibleMixin,
|
||||
DownloadProgressMixin, WritelnMixin, Spinner):
|
||||
|
||||
file = sys.stdout
|
||||
suffix = "%(downloaded)s %(download_speed)s"
|
||||
|
|
Loading…
Reference in New Issue