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