Skip to content

Commit 3ab977c

Browse files
committed
Add --debug flag to base command
This flag makes the main subroutine (cli.base_command.Command.run) withold from intercepting unhandled exceptions. This means, that debugging via "python -m pdb -m pip" is now possible.
1 parent 4b5b151 commit 3ab977c

File tree

3 files changed

+70
-41
lines changed

3 files changed

+70
-41
lines changed

news/9349.feature.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Add "--debug" flag.

src/pip/_internal/cli/base_command.py

Lines changed: 56 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,14 @@
11
"""Base Command class, and related routines"""
22

3+
import functools
34
import logging
45
import logging.config
56
import optparse
67
import os
78
import sys
89
import traceback
910
from optparse import Values
10-
from typing import Any, List, Optional, Tuple
11+
from typing import Any, Callable, List, Optional, Tuple
1112

1213
from pip._internal.cli import cmdoptions
1314
from pip._internal.cli.command_context import CommandContextMixIn
@@ -176,46 +177,60 @@ def _main(self, args):
176177
"This will become an error in pip 21.0."
177178
)
178179

180+
def intercepts_unhandled_exc(run_func):
181+
# type: (Callable[..., int]) -> Callable[..., int]
182+
@functools.wraps(run_func)
183+
def exc_logging_wrapper(*args):
184+
# type: (*Any) -> int
185+
try:
186+
status = run_func(*args)
187+
assert isinstance(status, int)
188+
return status
189+
except PreviousBuildDirError as exc:
190+
logger.critical(str(exc))
191+
logger.debug("Exception information:", exc_info=True)
192+
193+
return PREVIOUS_BUILD_DIR_ERROR
194+
except (
195+
InstallationError,
196+
UninstallationError,
197+
BadCommand,
198+
NetworkConnectionError,
199+
) as exc:
200+
logger.critical(str(exc))
201+
logger.debug("Exception information:", exc_info=True)
202+
203+
return ERROR
204+
except CommandError as exc:
205+
logger.critical("%s", exc)
206+
logger.debug("Exception information:", exc_info=True)
207+
208+
return ERROR
209+
except BrokenStdoutLoggingError:
210+
# Bypass our logger and write any remaining messages to
211+
# stderr because stdout no longer works.
212+
print("ERROR: Pipe to stdout was broken", file=sys.stderr)
213+
if level_number <= logging.DEBUG:
214+
traceback.print_exc(file=sys.stderr)
215+
216+
return ERROR
217+
except KeyboardInterrupt:
218+
logger.critical("Operation cancelled by user")
219+
logger.debug("Exception information:", exc_info=True)
220+
221+
return ERROR
222+
except BaseException:
223+
logger.critical("Exception:", exc_info=True)
224+
225+
return UNKNOWN_ERROR
226+
227+
return exc_logging_wrapper
228+
179229
try:
180-
status = self.run(options, args)
181-
assert isinstance(status, int)
182-
return status
183-
except PreviousBuildDirError as exc:
184-
logger.critical(str(exc))
185-
logger.debug("Exception information:", exc_info=True)
186-
187-
return PREVIOUS_BUILD_DIR_ERROR
188-
except (
189-
InstallationError,
190-
UninstallationError,
191-
BadCommand,
192-
NetworkConnectionError,
193-
) as exc:
194-
logger.critical(str(exc))
195-
logger.debug("Exception information:", exc_info=True)
196-
197-
return ERROR
198-
except CommandError as exc:
199-
logger.critical("%s", exc)
200-
logger.debug("Exception information:", exc_info=True)
201-
202-
return ERROR
203-
except BrokenStdoutLoggingError:
204-
# Bypass our logger and write any remaining messages to stderr
205-
# because stdout no longer works.
206-
print("ERROR: Pipe to stdout was broken", file=sys.stderr)
207-
if level_number <= logging.DEBUG:
208-
traceback.print_exc(file=sys.stderr)
209-
210-
return ERROR
211-
except KeyboardInterrupt:
212-
logger.critical("Operation cancelled by user")
213-
logger.debug("Exception information:", exc_info=True)
214-
215-
return ERROR
216-
except BaseException:
217-
logger.critical("Exception:", exc_info=True)
218-
219-
return UNKNOWN_ERROR
230+
if not options.debug_mode:
231+
run = intercepts_unhandled_exc(self.run)
232+
else:
233+
run = self.run
234+
return run(options, args)
220235
finally:
221236
self.handle_pip_version_check(options)

src/pip/_internal/cli/cmdoptions.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -156,6 +156,18 @@ class PipOption(Option):
156156
help="Show help.",
157157
) # type: Callable[..., Option]
158158

159+
debug_mode = partial(
160+
Option,
161+
"--debug",
162+
dest="debug_mode",
163+
action="store_true",
164+
default=False,
165+
help=(
166+
"Let unhandled exceptions propagate outside the main subroutine, "
167+
"instead of logging them to stderr."
168+
),
169+
) # type: Callable[..., Option]
170+
159171
isolated_mode = partial(
160172
Option,
161173
"--isolated",
@@ -980,6 +992,7 @@ def check_list_path_option(options):
980992
"name": "General Options",
981993
"options": [
982994
help_,
995+
debug_mode,
983996
isolated_mode,
984997
require_virtualenv,
985998
verbose,

0 commit comments

Comments
 (0)