445 lines
16 KiB
Python
445 lines
16 KiB
Python
import itertools
|
|
import sys
|
|
from collections import namedtuple
|
|
from traceback import format_tb
|
|
|
|
from pipenv.vendor import click
|
|
from pipenv.vendor.click.exceptions import ClickException, FileError, UsageError
|
|
|
|
KnownException = namedtuple(
|
|
"KnownException",
|
|
["exception_name", "match_string", "show_from_string", "prefix"],
|
|
)
|
|
KnownException.__new__.__defaults__ = (None, None, None, "")
|
|
|
|
KNOWN_EXCEPTIONS = [
|
|
KnownException("PermissionError", prefix="Permission Denied:"),
|
|
KnownException(
|
|
"VirtualenvCreationException",
|
|
match_string="do_create_virtualenv",
|
|
show_from_string=None,
|
|
),
|
|
]
|
|
|
|
|
|
def handle_exception(exc_type, exception, traceback, hook=sys.excepthook):
|
|
from pipenv import environments
|
|
|
|
if environments.Setting().is_verbose() or not issubclass(exc_type, ClickException):
|
|
hook(exc_type, exception, traceback)
|
|
else:
|
|
tb = format_tb(traceback, limit=-6)
|
|
lines = itertools.chain.from_iterable([frame.splitlines() for frame in tb])
|
|
formatted_lines = []
|
|
for line in lines:
|
|
line = line.strip("'").strip('"').strip("\n").strip()
|
|
if not line.startswith("File"):
|
|
line = f" {line}"
|
|
else:
|
|
line = f" {line}"
|
|
line = f"[{exception.__class__.__name__!s}]: {line}"
|
|
formatted_lines.append(line)
|
|
# use new exception prettification rules to format exceptions according to
|
|
# UX rules
|
|
click.echo(prettify_exc("\n".join(formatted_lines)), err=True)
|
|
exception.show()
|
|
|
|
|
|
sys.excepthook = handle_exception
|
|
|
|
|
|
class PipenvException(ClickException):
|
|
message = "{}: {{}}".format(click.style("ERROR", fg="red", bold=True))
|
|
|
|
def __init__(self, message=None, **kwargs):
|
|
if not message:
|
|
message = "Pipenv encountered a problem and had to exit."
|
|
extra = kwargs.pop("extra", [])
|
|
message = self.message.format(message)
|
|
ClickException.__init__(self, message)
|
|
self.extra = extra
|
|
|
|
def show(self, file=None):
|
|
if file is None:
|
|
file = sys.stderr
|
|
if self.extra:
|
|
if isinstance(self.extra, str):
|
|
self.extra = [self.extra]
|
|
for extra in self.extra:
|
|
extra = f"[pipenv.exceptions.{self.__class__.__name__}]: {extra}"
|
|
click.echo(extra, file=file)
|
|
click.echo(f"{self.message}", file=file)
|
|
|
|
|
|
class PipenvCmdError(PipenvException):
|
|
def __init__(self, cmd, out="", err="", exit_code=1):
|
|
self.cmd = cmd
|
|
self.out = out
|
|
self.err = err
|
|
self.exit_code = exit_code
|
|
message = f"Error running command: {cmd}"
|
|
PipenvException.__init__(self, message)
|
|
|
|
def show(self, file=None):
|
|
if file is None:
|
|
file = sys.stderr
|
|
click.echo(
|
|
"{} {}".format(
|
|
click.style("Error running command: ", fg="red"),
|
|
click.style(f"$ {self.cmd}", bold=True),
|
|
),
|
|
err=True,
|
|
file=file,
|
|
)
|
|
if self.out:
|
|
click.echo(
|
|
"{} {}".format("OUTPUT: ", self.out),
|
|
file=file,
|
|
err=True,
|
|
)
|
|
if self.err:
|
|
click.echo(
|
|
"{} {}".format("STDERR: ", self.err),
|
|
file=file,
|
|
err=True,
|
|
)
|
|
|
|
|
|
class JSONParseError(PipenvException):
|
|
def __init__(self, contents="", error_text=""):
|
|
self.error_text = error_text
|
|
PipenvException.__init__(self, contents)
|
|
|
|
def show(self, file=None):
|
|
if file is None:
|
|
file = sys.stderr
|
|
message = "{}\n{}".format(
|
|
click.style("Failed parsing JSON results:", bold=True),
|
|
print(self.message.strip(), file=file),
|
|
)
|
|
click.echo(message, err=True)
|
|
if self.error_text:
|
|
click.echo(
|
|
"{} {}".format(
|
|
click.style("ERROR TEXT:", bold=True),
|
|
print(self.error_text, file=file),
|
|
),
|
|
err=True,
|
|
)
|
|
|
|
|
|
class PipenvUsageError(UsageError):
|
|
def __init__(self, message=None, ctx=None, **kwargs):
|
|
formatted_message = "{0}: {1}"
|
|
msg_prefix = click.style("ERROR:", fg="red", bold=True)
|
|
if not message:
|
|
message = "Pipenv encountered a problem and had to exit."
|
|
message = formatted_message.format(msg_prefix, click.style(message, bold=True))
|
|
self.message = message
|
|
extra = kwargs.pop("extra", [])
|
|
UsageError.__init__(self, message, ctx)
|
|
self.extra = extra
|
|
|
|
def show(self, file=None):
|
|
if file is None:
|
|
file = sys.stderr
|
|
color = None
|
|
if self.ctx is not None:
|
|
color = self.ctx.color
|
|
if self.extra:
|
|
if isinstance(self.extra, str):
|
|
self.extra = [self.extra]
|
|
for extra in self.extra:
|
|
if color:
|
|
extra = click.style(extra, fg=color)
|
|
click.echo(extra, file=file)
|
|
hint = ""
|
|
if self.cmd is not None and self.cmd.get_help_option(self.ctx) is not None:
|
|
hint = f'Try "{self.ctx.command_path} {self.ctx.help_option_names[0]}" for help.\n'
|
|
if self.ctx is not None:
|
|
click.echo(self.ctx.get_usage() + f"\n{hint}", file=file, color=color)
|
|
click.echo(self.message, file=file)
|
|
|
|
|
|
class PipenvFileError(FileError):
|
|
formatted_message = "{} {{}} {{}}".format(click.style("ERROR:", fg="red", bold=True))
|
|
|
|
def __init__(self, filename, message=None, **kwargs):
|
|
extra = kwargs.pop("extra", [])
|
|
if not message:
|
|
message = click.style("Please ensure that the file exists!", bold=True)
|
|
message = self.formatted_message.format(
|
|
click.style(f"{filename} not found!", bold=True), message
|
|
)
|
|
FileError.__init__(self, filename=filename, hint=message, **kwargs)
|
|
self.extra = extra
|
|
|
|
def show(self, file=None):
|
|
if file is None:
|
|
file = sys.stderr
|
|
if self.extra:
|
|
if isinstance(self.extra, str):
|
|
self.extra = [self.extra]
|
|
for extra in self.extra:
|
|
click.echo(extra, file=file)
|
|
click.echo(self.message, file=file)
|
|
|
|
|
|
class PipfileNotFound(PipenvFileError):
|
|
def __init__(self, filename="Pipfile", extra=None, **kwargs):
|
|
extra = kwargs.pop("extra", [])
|
|
message = "{} {}".format(
|
|
click.style("Aborting!", bold=True, fg="red"),
|
|
click.style(
|
|
"Please ensure that the file exists and is located in your"
|
|
" project root directory.",
|
|
bold=True,
|
|
),
|
|
)
|
|
super().__init__(filename, message=message, extra=extra, **kwargs)
|
|
|
|
|
|
class LockfileNotFound(PipenvFileError):
|
|
def __init__(self, filename="Pipfile.lock", extra=None, **kwargs):
|
|
extra = kwargs.pop("extra", [])
|
|
message = "{} {} {}".format(
|
|
click.style("You need to run", bold=True),
|
|
click.style("$ pipenv lock", bold=True, fg="red"),
|
|
click.style("before you can continue.", bold=True),
|
|
)
|
|
super().__init__(filename, message=message, extra=extra, **kwargs)
|
|
|
|
|
|
class DeployException(PipenvUsageError):
|
|
def __init__(self, message=None, **kwargs):
|
|
if not message:
|
|
message = click.style("Aborting deploy", bold=True)
|
|
extra = kwargs.pop("extra", [])
|
|
PipenvUsageError.__init__(self, message=message, extra=extra, **kwargs)
|
|
|
|
|
|
class PipenvOptionsError(PipenvUsageError):
|
|
def __init__(self, option_name, message=None, ctx=None, **kwargs):
|
|
extra = kwargs.pop("extra", [])
|
|
PipenvUsageError.__init__(self, message=message, ctx=ctx, **kwargs)
|
|
self.extra = extra
|
|
self.option_name = option_name
|
|
|
|
|
|
class SystemUsageError(PipenvOptionsError):
|
|
def __init__(self, option_name="system", message=None, ctx=None, **kwargs):
|
|
extra = kwargs.pop("extra", [])
|
|
extra += [
|
|
"{}: --system is intended to be used for Pipfile installation, "
|
|
"not installation of specific packages. Aborting.".format(
|
|
click.style("Warning", bold=True, fg="red")
|
|
),
|
|
]
|
|
if message is None:
|
|
message = "{} --deploy flag".format(
|
|
click.style("See also: {}", fg="cyan"),
|
|
)
|
|
super().__init__(option_name, message=message, ctx=ctx, extra=extra, **kwargs)
|
|
|
|
|
|
class SetupException(PipenvException):
|
|
def __init__(self, message=None, **kwargs):
|
|
PipenvException.__init__(self, message, **kwargs)
|
|
|
|
|
|
class VirtualenvException(PipenvException):
|
|
def __init__(self, message=None, **kwargs):
|
|
if not message:
|
|
message = (
|
|
"There was an unexpected error while activating your virtualenv. "
|
|
"Continuing anyway..."
|
|
)
|
|
PipenvException.__init__(self, message, **kwargs)
|
|
|
|
|
|
class VirtualenvActivationException(VirtualenvException):
|
|
def __init__(self, message=None, **kwargs):
|
|
if not message:
|
|
message = (
|
|
"activate_this.py not found. Your environment is most certainly "
|
|
"not activated. Continuing anyway..."
|
|
)
|
|
self.message = message
|
|
VirtualenvException.__init__(self, message, **kwargs)
|
|
|
|
|
|
class VirtualenvCreationException(VirtualenvException):
|
|
def __init__(self, message=None, **kwargs):
|
|
if not message:
|
|
message = "Failed to create virtual environment."
|
|
self.message = message
|
|
extra = kwargs.pop("extra", None)
|
|
if extra is not None and isinstance(extra, str):
|
|
extra = click.unstyle(f"{extra}")
|
|
if "KeyboardInterrupt" in extra:
|
|
extra = click.style(
|
|
"Virtualenv creation interrupted by user", fg="red", bold=True
|
|
)
|
|
self.extra = extra = [extra]
|
|
VirtualenvException.__init__(self, message, extra=extra)
|
|
|
|
|
|
class UninstallError(PipenvException):
|
|
def __init__(self, package, command, return_values, return_code, **kwargs):
|
|
extra = [
|
|
"{} {}".format(
|
|
click.style("Attempted to run command: ", fg="cyan"),
|
|
click.style(f"$ {command!r}", bold=True, fg="yellow"),
|
|
)
|
|
]
|
|
extra.extend(
|
|
[click.style(line.strip(), fg="cyan") for line in return_values.splitlines()]
|
|
)
|
|
if isinstance(package, (tuple, list, set)):
|
|
package = " ".join(package)
|
|
message = "{!s} {!s}...".format(
|
|
click.style("Failed to uninstall package(s)", fg="reset"),
|
|
click.style(f"{package}!s", bold=True, fg="yellow"),
|
|
)
|
|
self.exit_code = return_code
|
|
PipenvException.__init__(self, message=message, extra=extra)
|
|
self.extra = extra
|
|
|
|
|
|
class InstallError(PipenvException):
|
|
def __init__(self, package, **kwargs):
|
|
package_message = ""
|
|
if package is not None:
|
|
package_message = "Couldn't install package: {}\n".format(
|
|
click.style(f"{package!s}", bold=True)
|
|
)
|
|
message = "{} {}".format(
|
|
f"{package_message}",
|
|
click.style("Package installation failed...", fg="yellow"),
|
|
)
|
|
extra = kwargs.pop("extra", [])
|
|
PipenvException.__init__(self, message=message, extra=extra, **kwargs)
|
|
|
|
|
|
class CacheError(PipenvException):
|
|
def __init__(self, path, **kwargs):
|
|
message = "{} {}\n{}".format(
|
|
click.style("Corrupt cache file", fg="cyan"),
|
|
click.style(f"{path!s}", fg="reset", bg="reset"),
|
|
click.style('Consider trying "pipenv lock --clear" to clear the cache.'),
|
|
)
|
|
PipenvException.__init__(self, message=message)
|
|
|
|
|
|
class DependencyConflict(PipenvException):
|
|
def __init__(self, message):
|
|
extra = [
|
|
"{} {}".format(
|
|
click.style("The operation failed...", bold=True, fg="red"),
|
|
click.style(
|
|
"A dependency conflict was detected and could not be resolved.",
|
|
fg="red",
|
|
),
|
|
)
|
|
]
|
|
PipenvException.__init__(self, message, extra=extra)
|
|
|
|
|
|
class ResolutionFailure(PipenvException):
|
|
def __init__(self, message, no_version_found=False):
|
|
extra = (
|
|
"{}: Your dependencies could not be resolved. You likely have a "
|
|
"mismatch in your sub-dependencies.\n "
|
|
"You can use {} to bypass this mechanism, then run "
|
|
"{} to inspect the versions actually installed in the virtualenv.\n "
|
|
"Hint: try {} if it is a pre-release dependency."
|
|
"".format(
|
|
click.style("Warning", fg="red", bold=True),
|
|
click.style("$ pipenv run pip install <requirement_name>", fg="yellow"),
|
|
click.style("$ pipenv graph", fg="yellow"),
|
|
click.style("$ pipenv lock --pre", fg="yellow"),
|
|
),
|
|
)
|
|
if "no version found at all" in message:
|
|
no_version_found = True
|
|
message = click.style(f"{message}", fg="yellow")
|
|
if no_version_found:
|
|
message = "{}\n{}".format(
|
|
message,
|
|
click.style(
|
|
"Please check your version specifier and version number. "
|
|
"See PEP440 for more information.",
|
|
fg="cyan",
|
|
),
|
|
)
|
|
PipenvException.__init__(self, message, extra=extra)
|
|
|
|
|
|
class RequirementError(PipenvException):
|
|
def __init__(self, req=None):
|
|
from pipenv.utils.constants import VCS_LIST
|
|
|
|
keys = (
|
|
(
|
|
"name",
|
|
"path",
|
|
)
|
|
+ VCS_LIST
|
|
+ ("line", "uri", "url", "relpath")
|
|
)
|
|
if req is not None:
|
|
possible_display_values = [getattr(req, value, None) for value in keys]
|
|
req_value = next(
|
|
iter(val for val in possible_display_values if val is not None), None
|
|
)
|
|
if not req_value:
|
|
getstate_fn = getattr(req, "__getstate__", None)
|
|
slots = getattr(req, "__slots__", None)
|
|
keys_fn = getattr(req, "keys", None)
|
|
if getstate_fn:
|
|
req_value = getstate_fn()
|
|
elif slots:
|
|
slot_vals = [
|
|
(k, getattr(req, k, None)) for k in slots if getattr(req, k, None)
|
|
]
|
|
req_value = "\n".join([f" {k}: {v}" for k, v in slot_vals])
|
|
elif keys_fn:
|
|
values = [(k, req.get(k)) for k in keys_fn() if req.get(k)]
|
|
req_value = "\n".join([f" {k}: {v}" for k, v in values])
|
|
else:
|
|
req_value = getattr(req.line_instance, "line", None)
|
|
message = click.style(
|
|
f"Failed creating requirement instance {req_value}",
|
|
bold=False,
|
|
fg="reset",
|
|
bg="reset",
|
|
)
|
|
extra = [str(req)]
|
|
PipenvException.__init__(self, message, extra=extra)
|
|
|
|
|
|
def prettify_exc(error):
|
|
"""Catch known errors and prettify them instead of showing the
|
|
entire traceback, for better UX"""
|
|
errors = []
|
|
for exc in KNOWN_EXCEPTIONS:
|
|
search_string = exc.match_string if exc.match_string else exc.exception_name
|
|
split_string = (
|
|
exc.show_from_string if exc.show_from_string else exc.exception_name
|
|
)
|
|
if search_string in error:
|
|
# for known exceptions with no display rules and no prefix
|
|
# we should simply show nothing
|
|
if not exc.show_from_string and not exc.prefix:
|
|
errors.append("")
|
|
continue
|
|
elif exc.prefix and exc.prefix in error:
|
|
_, error, info = error.rpartition(exc.prefix)
|
|
else:
|
|
_, error, info = error.rpartition(split_string)
|
|
errors.append(f"{error} {info}")
|
|
if not errors:
|
|
return error
|
|
|
|
return "\n".join(errors)
|