627 lines
16 KiB
Python
627 lines
16 KiB
Python
import os
|
|
import re
|
|
|
|
from pipenv.project import Project
|
|
from pipenv.utils import err
|
|
from pipenv.utils.internet import is_valid_url
|
|
from pipenv.vendor.click import (
|
|
BadArgumentUsage,
|
|
BadParameter,
|
|
Group,
|
|
Option,
|
|
argument,
|
|
echo,
|
|
make_pass_decorator,
|
|
option,
|
|
)
|
|
from pipenv.vendor.click import types as click_types
|
|
from pipenv.vendor.click_didyoumean import DYMMixin
|
|
|
|
CONTEXT_SETTINGS = {"help_option_names": ["-h", "--help"], "auto_envvar_prefix": "PIPENV"}
|
|
|
|
|
|
class PipenvGroup(DYMMixin, Group):
|
|
"""Custom Group class provides formatted main help"""
|
|
|
|
def get_help_option(self, ctx):
|
|
from pipenv.utils.display import format_help
|
|
|
|
"""Override for showing formatted main help via --help and -h options"""
|
|
help_options = self.get_help_option_names(ctx)
|
|
if not help_options or not self.add_help_option:
|
|
return
|
|
|
|
def show_help(ctx, param, value):
|
|
if value and not ctx.resilient_parsing:
|
|
if not ctx.invoked_subcommand:
|
|
# legit main help
|
|
echo(format_help(ctx.get_help()))
|
|
else:
|
|
# legit sub-command help
|
|
echo(ctx.get_help(), color=ctx.color)
|
|
ctx.exit()
|
|
|
|
return Option(
|
|
help_options,
|
|
is_flag=True,
|
|
is_eager=True,
|
|
expose_value=False,
|
|
callback=show_help,
|
|
help="Show this message and exit.",
|
|
)
|
|
|
|
def main(self, *args, **kwargs):
|
|
"""
|
|
to specify the windows_expand_args option to avoid exceptions on Windows
|
|
see: https://github.com/pallets/click/issues/1901
|
|
"""
|
|
return super().main(*args, **kwargs, windows_expand_args=False)
|
|
|
|
|
|
class State:
|
|
def __init__(self):
|
|
self.index = None
|
|
self.verbose = False
|
|
self.quiet = False
|
|
self.pypi_mirror = None
|
|
self.python = None
|
|
self.site_packages = None
|
|
self.clear = False
|
|
self.system = False
|
|
self.project = Project()
|
|
self.installstate = InstallState()
|
|
self.lockoptions = LockOptions()
|
|
|
|
|
|
class InstallState:
|
|
def __init__(self):
|
|
self.dev = False
|
|
self.pre = False
|
|
self.ignore_pipfile = False
|
|
self.code = False
|
|
self.requirementstxt = None
|
|
self.deploy = False
|
|
self.packages = []
|
|
self.editables = []
|
|
self.extra_pip_args = []
|
|
self.categories = []
|
|
self.skip_lock = False
|
|
|
|
|
|
class LockOptions:
|
|
def __init__(self):
|
|
self.dev_only = False
|
|
|
|
|
|
pass_state = make_pass_decorator(State, ensure=True)
|
|
|
|
|
|
def index_option(f):
|
|
def callback(ctx, param, value):
|
|
state = ctx.ensure_object(State)
|
|
state.index = value
|
|
return value
|
|
|
|
return option(
|
|
"-i",
|
|
"--index",
|
|
expose_value=False,
|
|
envvar="PIP_INDEX_URL",
|
|
help="Specify target package index by url or index name from Pipfile.",
|
|
nargs=1,
|
|
callback=callback,
|
|
)(f)
|
|
|
|
|
|
def editable_option(f):
|
|
def callback(ctx, param, value):
|
|
state = ctx.ensure_object(State)
|
|
state.installstate.editables.extend(value)
|
|
return value
|
|
|
|
return option(
|
|
"-e",
|
|
"--editable",
|
|
expose_value=False,
|
|
multiple=True,
|
|
callback=callback,
|
|
type=click_types.Path(file_okay=False),
|
|
help="An editable Python package URL or path, often to a VCS repository.",
|
|
)(f)
|
|
|
|
|
|
def ignore_pipfile_option(f):
|
|
def callback(ctx, param, value):
|
|
state = ctx.ensure_object(State)
|
|
state.installstate.ignore_pipfile = value
|
|
return value
|
|
|
|
return option(
|
|
"--ignore-pipfile",
|
|
is_flag=True,
|
|
default=False,
|
|
expose_value=False,
|
|
help="Ignore Pipfile when installing, using the Pipfile.lock.",
|
|
callback=callback,
|
|
type=click_types.BOOL,
|
|
show_envvar=True,
|
|
)(f)
|
|
|
|
|
|
def _dev_option(f, help_text):
|
|
def callback(ctx, param, value):
|
|
state = ctx.ensure_object(State)
|
|
state.installstate.dev = value
|
|
return value
|
|
|
|
return option(
|
|
"--dev",
|
|
"-d",
|
|
is_flag=True,
|
|
default=False,
|
|
type=click_types.BOOL,
|
|
help=help_text,
|
|
callback=callback,
|
|
expose_value=False,
|
|
show_envvar=True,
|
|
)(f)
|
|
|
|
|
|
def categories_option(f):
|
|
def callback(ctx, param, value):
|
|
state = ctx.ensure_object(State)
|
|
if value:
|
|
state.installstate.categories += re.split(r", *| ", value)
|
|
return value
|
|
|
|
return option(
|
|
"--categories",
|
|
nargs=1,
|
|
required=False,
|
|
callback=callback,
|
|
expose_value=True,
|
|
type=click_types.STRING,
|
|
)(f)
|
|
|
|
|
|
def install_dev_option(f):
|
|
return _dev_option(f, "Install both develop and default packages")
|
|
|
|
|
|
def lock_dev_option(f):
|
|
return _dev_option(f, "Generate both develop and default requirements")
|
|
|
|
|
|
def uninstall_dev_option(f):
|
|
return _dev_option(
|
|
f, "Deprecated (as it has no effect). May be removed in a future release."
|
|
)
|
|
|
|
|
|
def pre_option(f):
|
|
def callback(ctx, param, value):
|
|
state = ctx.ensure_object(State)
|
|
state.installstate.pre = value
|
|
return value
|
|
|
|
return option(
|
|
"--pre",
|
|
is_flag=True,
|
|
default=False,
|
|
help="Allow pre-releases.",
|
|
callback=callback,
|
|
type=click_types.BOOL,
|
|
expose_value=False,
|
|
)(f)
|
|
|
|
|
|
def package_arg(f):
|
|
def callback(ctx, param, value):
|
|
state = ctx.ensure_object(State)
|
|
state.installstate.packages.extend(value)
|
|
return value
|
|
|
|
return argument(
|
|
"packages",
|
|
nargs=-1,
|
|
callback=callback,
|
|
expose_value=True,
|
|
type=click_types.STRING,
|
|
)(f)
|
|
|
|
|
|
def extra_pip_args(f):
|
|
def callback(ctx, param, value):
|
|
state = ctx.ensure_object(State)
|
|
if value:
|
|
state.installstate.extra_pip_args += value.split(" ")
|
|
return value
|
|
|
|
return option(
|
|
"--extra-pip-args",
|
|
nargs=1,
|
|
required=False,
|
|
callback=callback,
|
|
expose_value=True,
|
|
type=click_types.STRING,
|
|
)(f)
|
|
|
|
|
|
def python_option(f):
|
|
def callback(ctx, param, value):
|
|
state = ctx.ensure_object(State)
|
|
if value is not None:
|
|
state.python = validate_python_path(ctx, param, value)
|
|
return value
|
|
|
|
return option(
|
|
"--python",
|
|
default="",
|
|
nargs=1,
|
|
callback=callback,
|
|
help="Specify which version of Python virtualenv should use.",
|
|
expose_value=False,
|
|
allow_from_autoenv=False,
|
|
type=click_types.STRING,
|
|
)(f)
|
|
|
|
|
|
def pypi_mirror_option(f):
|
|
def callback(ctx, param, value):
|
|
state = ctx.ensure_object(State)
|
|
value = value or state.project.s.PIPENV_PYPI_MIRROR
|
|
if value is not None:
|
|
state.pypi_mirror = validate_pypi_mirror(ctx, param, value)
|
|
return value
|
|
|
|
return option(
|
|
"--pypi-mirror",
|
|
nargs=1,
|
|
callback=callback,
|
|
help="Specify a PyPI mirror.",
|
|
expose_value=False,
|
|
)(f)
|
|
|
|
|
|
def verbose_option(f):
|
|
def callback(ctx, param, value):
|
|
state = ctx.ensure_object(State)
|
|
if value:
|
|
if state.quiet:
|
|
raise BadArgumentUsage(
|
|
"--verbose and --quiet are mutually exclusive! Please choose one!",
|
|
ctx=ctx,
|
|
)
|
|
state.verbose = True
|
|
setup_verbosity(ctx, param, 1)
|
|
|
|
return option(
|
|
"--verbose",
|
|
"-v",
|
|
is_flag=True,
|
|
expose_value=False,
|
|
callback=callback,
|
|
help="Verbose mode.",
|
|
type=click_types.BOOL,
|
|
)(f)
|
|
|
|
|
|
def quiet_option(f):
|
|
def callback(ctx, param, value):
|
|
state = ctx.ensure_object(State)
|
|
if value:
|
|
if state.verbose:
|
|
raise BadArgumentUsage(
|
|
"--verbose and --quiet are mutually exclusive! Please choose one!",
|
|
ctx=ctx,
|
|
)
|
|
state.quiet = True
|
|
setup_verbosity(ctx, param, -1)
|
|
|
|
return option(
|
|
"--quiet",
|
|
"-q",
|
|
is_flag=True,
|
|
expose_value=False,
|
|
callback=callback,
|
|
help="Quiet mode.",
|
|
type=click_types.BOOL,
|
|
)(f)
|
|
|
|
|
|
def site_packages_option(f):
|
|
def callback(ctx, param, value):
|
|
state = ctx.ensure_object(State)
|
|
validate_bool_or_none(ctx, param, value)
|
|
state.site_packages = value
|
|
return value
|
|
|
|
return option(
|
|
"--site-packages/--no-site-packages",
|
|
is_flag=True,
|
|
default=None,
|
|
help="Enable site-packages for the virtualenv.",
|
|
callback=callback,
|
|
expose_value=False,
|
|
show_envvar=True,
|
|
)(f)
|
|
|
|
|
|
def clear_option(f):
|
|
def callback(ctx, param, value):
|
|
state = ctx.ensure_object(State)
|
|
state.clear = value
|
|
return value
|
|
|
|
return option(
|
|
"--clear",
|
|
is_flag=True,
|
|
callback=callback,
|
|
type=click_types.BOOL,
|
|
help="Clears caches (pipenv, pip).",
|
|
expose_value=False,
|
|
show_envvar=True,
|
|
)(f)
|
|
|
|
|
|
def system_option(f):
|
|
def callback(ctx, param, value):
|
|
state = ctx.ensure_object(State)
|
|
if value is not None:
|
|
state.system = value
|
|
return value
|
|
|
|
return option(
|
|
"--system",
|
|
is_flag=True,
|
|
default=False,
|
|
help="System pip management.",
|
|
callback=callback,
|
|
type=click_types.BOOL,
|
|
expose_value=False,
|
|
show_envvar=True,
|
|
)(f)
|
|
|
|
|
|
def requirementstxt_option(f):
|
|
def callback(ctx, param, value):
|
|
state = ctx.ensure_object(State)
|
|
if value:
|
|
state.installstate.requirementstxt = value
|
|
return value
|
|
|
|
return option(
|
|
"--requirements",
|
|
"-r",
|
|
nargs=1,
|
|
default="",
|
|
expose_value=False,
|
|
help="Import a requirements.txt file.",
|
|
callback=callback,
|
|
type=click_types.Path(dir_okay=False),
|
|
)(f)
|
|
|
|
|
|
def dev_only_flag(f):
|
|
def callback(ctx, param, value):
|
|
state = ctx.ensure_object(State)
|
|
if value:
|
|
state.lockoptions.dev_only = value
|
|
return value
|
|
|
|
return option(
|
|
"--dev-only",
|
|
default=False,
|
|
is_flag=True,
|
|
expose_value=False,
|
|
help="Emit development dependencies *only* (overrides --dev)",
|
|
callback=callback,
|
|
)(f)
|
|
|
|
|
|
def deploy_option(f):
|
|
def callback(ctx, param, value):
|
|
state = ctx.ensure_object(State)
|
|
state.installstate.deploy = value
|
|
return value
|
|
|
|
return option(
|
|
"--deploy",
|
|
is_flag=True,
|
|
default=False,
|
|
type=click_types.BOOL,
|
|
help="Abort if the Pipfile.lock is out-of-date, or Python version is wrong.",
|
|
callback=callback,
|
|
expose_value=False,
|
|
)(f)
|
|
|
|
|
|
def lock_only_option(f):
|
|
def callback(ctx, param, value):
|
|
state = ctx.ensure_object(State)
|
|
state.installstate.lock_only = value
|
|
return value
|
|
|
|
return option(
|
|
"--lock-only",
|
|
is_flag=True,
|
|
default=False,
|
|
help="Only update lock file (specifiers not added to Pipfile).",
|
|
callback=callback,
|
|
type=click_types.BOOL,
|
|
expose_value=False,
|
|
)(f)
|
|
|
|
|
|
def setup_verbosity(ctx, param, value):
|
|
if not value:
|
|
return
|
|
ctx.ensure_object(State).project.s.PIPENV_VERBOSITY = value
|
|
|
|
|
|
def validate_python_path(ctx, param, value):
|
|
# Validating the Python path is complicated by accepting a number of
|
|
# friendly options: the default will be boolean False to enable
|
|
# autodetection but it may also be a value which will be searched in
|
|
# the path or an absolute path. To report errors as early as possible
|
|
# we'll report absolute paths which do not exist:
|
|
if isinstance(value, (str, bytes)):
|
|
if os.path.isabs(value) and not os.path.isfile(value):
|
|
raise BadParameter(f"Expected Python at path {value} does not exist")
|
|
return value
|
|
|
|
|
|
def validate_bool_or_none(ctx, param, value):
|
|
if value is not None:
|
|
return click_types.BOOL(value)
|
|
return False
|
|
|
|
|
|
def validate_pypi_mirror(ctx, param, value):
|
|
if value and not is_valid_url(value):
|
|
raise BadParameter(f"Invalid PyPI mirror URL: {value}")
|
|
return value
|
|
|
|
|
|
def skip_lock_option(f):
|
|
def callback(ctx, param, value):
|
|
if value:
|
|
err.print(
|
|
"The flag --skip-lock has been reintroduced (but is not recommended). "
|
|
"Without the lock resolver it is difficult to manage multiple package indexes, and hash checking is not provided. "
|
|
"However it can help manage installs with current deficiencies in locking across platforms.",
|
|
style="yellow bold",
|
|
)
|
|
state = ctx.ensure_object(State)
|
|
state.installstate.skip_lock = value
|
|
return value
|
|
|
|
return option(
|
|
"--skip-lock",
|
|
is_flag=True,
|
|
default=False,
|
|
expose_value=True,
|
|
envvar="PIPENV_SKIP_LOCK",
|
|
help="Install from Pipfile bypassing lock mechanisms.",
|
|
callback=callback,
|
|
type=click_types.BOOL,
|
|
show_envvar=True,
|
|
)(f)
|
|
|
|
|
|
# OLD REMOVED COMMANDS THAT WE STILL DISPLAY HELP TEXT FOR WHEN USED #
|
|
def keep_outdated_option(f):
|
|
def callback(ctx, param, value):
|
|
state = ctx.ensure_object(State)
|
|
state.installstate.keep_outdated = value
|
|
if value:
|
|
err.print(
|
|
"The flag --keep-outdated has been removed. "
|
|
"The flag did not respect package resolver results and lead to inconsistent lock files. "
|
|
"Consider using the `pipenv upgrade` command to selectively upgrade packages.",
|
|
style="yellow bold",
|
|
)
|
|
raise ValueError("The flag --keep-outdated flag has been removed.")
|
|
return value
|
|
|
|
return option(
|
|
"--keep-outdated",
|
|
is_flag=True,
|
|
default=False,
|
|
expose_value=False,
|
|
callback=callback,
|
|
type=click_types.BOOL,
|
|
show_envvar=True,
|
|
hidden=True, # This hides the option from the help text.
|
|
)(f)
|
|
|
|
|
|
def selective_upgrade_option(f):
|
|
def callback(ctx, param, value):
|
|
state = ctx.ensure_object(State)
|
|
state.installstate.selective_upgrade = value
|
|
if value:
|
|
err.print(
|
|
"The flag --selective-upgrade has been removed. "
|
|
"The flag was buggy and lead to inconsistent lock files. "
|
|
"Consider using the `pipenv upgrade` command to selectively upgrade packages.",
|
|
style="yellow bold",
|
|
)
|
|
raise ValueError("The flag --selective-upgrade flag has been removed.")
|
|
return value
|
|
|
|
return option(
|
|
"--selective-upgrade",
|
|
is_flag=True,
|
|
default=False,
|
|
type=click_types.BOOL,
|
|
help="Update specified packages.",
|
|
callback=callback,
|
|
expose_value=False,
|
|
)(f)
|
|
|
|
|
|
def common_options(f):
|
|
f = pypi_mirror_option(f)
|
|
f = verbose_option(f)
|
|
f = quiet_option(f)
|
|
f = clear_option(f)
|
|
f = python_option(f)
|
|
return f
|
|
|
|
|
|
def install_base_options(f):
|
|
f = common_options(f)
|
|
f = pre_option(f)
|
|
f = extra_pip_args(f)
|
|
f = keep_outdated_option(f) # Removed, but still displayed in help text.
|
|
return f
|
|
|
|
|
|
def uninstall_options(f):
|
|
f = install_base_options(f)
|
|
f = categories_option(f)
|
|
f = uninstall_dev_option(f)
|
|
f = editable_option(f)
|
|
f = package_arg(f)
|
|
f = skip_lock_option(f) # Removed, but still displayed in help text.
|
|
return f
|
|
|
|
|
|
def lock_options(f):
|
|
f = install_base_options(f)
|
|
f = lock_dev_option(f)
|
|
f = dev_only_flag(f)
|
|
f = categories_option(f)
|
|
return f
|
|
|
|
|
|
def sync_options(f):
|
|
f = install_base_options(f)
|
|
f = install_dev_option(f)
|
|
f = categories_option(f)
|
|
return f
|
|
|
|
|
|
def install_options(f):
|
|
f = sync_options(f)
|
|
f = index_option(f)
|
|
f = requirementstxt_option(f)
|
|
f = ignore_pipfile_option(f)
|
|
f = editable_option(f)
|
|
f = package_arg(f)
|
|
f = skip_lock_option(f) # Removed, but still display help text.
|
|
f = selective_upgrade_option(f) # Removed, but still display help text.
|
|
return f
|
|
|
|
|
|
def upgrade_options(f):
|
|
f = lock_only_option(f)
|
|
return f
|
|
|
|
|
|
def general_options(f):
|
|
f = common_options(f)
|
|
f = site_packages_option(f)
|
|
return f
|