Skip to content

Commit

Permalink
Merge pull request #9428 from MrMino/debug_mode_flag
Browse files Browse the repository at this point in the history
  • Loading branch information
pradyunsg authored Sep 21, 2021
2 parents 0c86e3c + 10b0dd2 commit a8cf21e
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 a ``--debug`` flag, to enable a mode that doesn't log errors and propagates them to the top level instead. This is primarily to aid with debugging pip's crashes.
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: Callable[..., int]
) -> Callable[..., int]:
@functools.wraps(run_func)
def exc_logging_wrapper(*args: 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: Callable[..., Option] = 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."
),
)

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 a8cf21e

Please sign in to comment.