Skip to content

Commit

Permalink
Add --debug flag to base command
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
MrMino committed Sep 19, 2021
1 parent 4f4c310 commit edc6d31
Show file tree
Hide file tree
Showing 3 changed files with 70 additions and 41 deletions.
1 change: 1 addition & 0 deletions news/9349.feature.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Add "--debug" flag.
97 changes: 56 additions & 41 deletions src/pip/_internal/cli/base_command.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
"""Base Command class, and related routines"""

import functools
import logging
import logging.config
import optparse
import os
import sys
import traceback
from optparse import Values
from typing import List, Optional, Tuple
from typing import Any, Callable, List, Optional, Tuple

from pip._internal.cli import cmdoptions
from pip._internal.cli.command_context import CommandContextMixIn
Expand Down Expand Up @@ -169,46 +170,60 @@ def _main(self, args: List[str]) -> int:
"This will become an error in pip 21.0."
)

def intercepts_unhandled_exc(run_func):
# type: (Callable[..., int]) -> Callable[..., int]
@functools.wraps(run_func)
def exc_logging_wrapper(*args):
# type: (*Any) -> int
try:
status = run_func(*args)
assert isinstance(status, int)
return status
except PreviousBuildDirError as exc:
logger.critical(str(exc))
logger.debug("Exception information:", exc_info=True)

return PREVIOUS_BUILD_DIR_ERROR
except (
InstallationError,
UninstallationError,
BadCommand,
NetworkConnectionError,
) as exc:
logger.critical(str(exc))
logger.debug("Exception information:", exc_info=True)

return ERROR
except CommandError as exc:
logger.critical("%s", exc)
logger.debug("Exception information:", exc_info=True)

return ERROR
except BrokenStdoutLoggingError:
# Bypass our logger and write any remaining messages to
# stderr because stdout no longer works.
print("ERROR: Pipe to stdout was broken", file=sys.stderr)
if level_number <= logging.DEBUG:
traceback.print_exc(file=sys.stderr)

return ERROR
except KeyboardInterrupt:
logger.critical("Operation cancelled by user")
logger.debug("Exception information:", exc_info=True)

return ERROR
except BaseException:
logger.critical("Exception:", exc_info=True)

return UNKNOWN_ERROR

return exc_logging_wrapper

try:
status = self.run(options, args)
assert isinstance(status, int)
return status
except PreviousBuildDirError as exc:
logger.critical(str(exc))
logger.debug("Exception information:", exc_info=True)

return PREVIOUS_BUILD_DIR_ERROR
except (
InstallationError,
UninstallationError,
BadCommand,
NetworkConnectionError,
) as exc:
logger.critical(str(exc))
logger.debug("Exception information:", exc_info=True)

return ERROR
except CommandError as exc:
logger.critical("%s", exc)
logger.debug("Exception information:", exc_info=True)

return ERROR
except BrokenStdoutLoggingError:
# Bypass our logger and write any remaining messages to stderr
# because stdout no longer works.
print("ERROR: Pipe to stdout was broken", file=sys.stderr)
if level_number <= logging.DEBUG:
traceback.print_exc(file=sys.stderr)

return ERROR
except KeyboardInterrupt:
logger.critical("Operation cancelled by user")
logger.debug("Exception information:", exc_info=True)

return ERROR
except BaseException:
logger.critical("Exception:", exc_info=True)

return UNKNOWN_ERROR
if not options.debug_mode:
run = intercepts_unhandled_exc(self.run)
else:
run = self.run
return run(options, args)
finally:
self.handle_pip_version_check(options)
13 changes: 13 additions & 0 deletions src/pip/_internal/cli/cmdoptions.py
Original file line number Diff line number Diff line change
Expand Up @@ -151,6 +151,18 @@ class PipOption(Option):
help="Show help.",
)

debug_mode = partial(
Option,
"--debug",
dest="debug_mode",
action="store_true",
default=False,
help=(
"Let unhandled exceptions propagate outside the main subroutine, "
"instead of logging them to stderr."
),
) # type: Callable[..., Option]

isolated_mode: Callable[..., Option] = partial(
Option,
"--isolated",
Expand Down Expand Up @@ -974,6 +986,7 @@ def check_list_path_option(options: Values) -> None:
"name": "General Options",
"options": [
help_,
debug_mode,
isolated_mode,
require_virtualenv,
verbose,
Expand Down

0 comments on commit edc6d31

Please sign in to comment.