From da1fa557a119ec9ab7292b54abd46386f598e040 Mon Sep 17 00:00:00 2001 From: Dan Ryan Date: Fri, 31 Aug 2018 01:53:55 -0400 Subject: [PATCH] Refactor CLI options - Allow parsing of multiple packages, editable and non-editable, interspersed - Handle `--index` and `--extra-index-url` with click's native parsing - Split commonly used options out into separate groups --- pipenv/cli.py | 515 +++++++++++++++++--------------------------------- 1 file changed, 176 insertions(+), 339 deletions(-) diff --git a/pipenv/cli.py b/pipenv/cli.py index 908c6f99..8333e6b2 100644 --- a/pipenv/cli.py +++ b/pipenv/cli.py @@ -5,6 +5,7 @@ from click import ( argument, command, echo, + secho, edit, group, Group, @@ -87,6 +88,109 @@ def validate_pypi_mirror(ctx, param, value): return value +def pypi_mirror(fn): + return option( + "--pypi-mirror", + default=environments.PIPENV_PYPI_MIRROR, + nargs=1, + callback=validate_pypi_mirror, + help="Specify a PyPI mirror.", + )(fn) + + +def python_group(fn): + fn = option( + "--three/--two", + is_flag=True, + default=None, + help="Use Python 3/2 when creating virtualenv.", + )(fn) + fn = option( + "--python", + default=False, + nargs=1, + callback=validate_python_path, + help="Specify which version of Python virtualenv should use.", + )(fn) + return fn + + +def package_group(fn): + fn = option( + "--editable", "-e", + help=u"An editable package to install.", + multiple=True + )(fn) + fn = argument( + "packages", + nargs=-1 + )(fn) + return fn + + +def common_group(fn): + fn = pypi_mirror(fn) + fn = option( + "--verbose", + "-v", + is_flag=True, + expose_value=False, + callback=setup_verbosity, + help="Verbose mode.", + )(fn) + return fn + + +def install_group(fn): + fn = option( + "--sequential", + is_flag=True, + default=False, + help="Install dependencies one-at-a-time, instead of concurrently.", + )(fn) + fn = option( + "--dev", + "-d", + is_flag=True, + default=False, + help="Install package(s) in [dev-packages].", + )(fn) + return fn + + +def upgrade_strategy_group(fn): + fn = option("--pre", is_flag=True, default=False, help=u"Allow pre-releases.")(fn) + fn = option( + "--keep-outdated", + is_flag=True, + default=False, + help=u"Keep out-dated dependencies from being updated in Pipfile.lock.", + )(fn) + fn = option( + "--selective-upgrade", + is_flag=True, + default=False, + help="Update specified packages.", + )(fn) + return fn + + +def index_group(fn): + fn = option( + "--index", "-i", + envvar="PIP_INDEX_URL", + nargs=1, + default=False, + help=u"Default PyPI compatible index URL to query for package lookups." + )(fn) + fn = option( + "--extra-index-url", + multiple=True, + help=u"URLs to the extra PyPI compatible indexes to query for package lookups." + )(fn) + return fn + + @group(cls=PipenvGroup, invoke_without_command=True, context_settings=CONTEXT_SETTINGS) @option("--where", is_flag=True, default=False, help="Output project home information.") @option("--venv", is_flag=True, default=False, help="Output virtualenv information.") @@ -105,32 +209,14 @@ def validate_pypi_mirror(ctx, param, value): help="Output completion (to be eval'd).", ) @option("--man", is_flag=True, default=False, help="Display manpage.") -@option( - "--three/--two", - is_flag=True, - default=None, - help="Use Python 3/2 when creating virtualenv.", -) -@option( - "--python", - default=False, - nargs=1, - callback=validate_python_path, - help="Specify which version of Python virtualenv should use.", -) +@python_group @option( "--site-packages", is_flag=True, default=False, help="Enable site-packages for the virtualenv.", ) -@option( - "--pypi-mirror", - default=environments.PIPENV_PYPI_MIRROR, - nargs=1, - callback=validate_pypi_mirror, - help="Specify a PyPI mirror.", -) +@pypi_mirror @option( "--support", is_flag=True, @@ -169,9 +255,9 @@ def cli( "variable.".format(crayons.normal("PIPENV_SHELL", bold=True)), err=True, ) - sys.exit(1) + ctx.abort() print(click_completion.get_code(shell=shell, prog_name="pipenv")) - sys.exit(0) + return 0 from .core import ( system_which, @@ -190,8 +276,10 @@ def cli( if system_which("man"): path = os.sep.join([os.path.dirname(__file__), "pipenv.1"]) os.execle(system_which("man"), "man", path, os.environ) + return 0 else: - echo("man does not appear to be available on your system.", err=True) + secho("man does not appear to be available on your system.", fg="yellow", bold=True, err=True) + return 1 if envs: echo("The following environment variables can be set, to do various things:\n") for key in environments.__dict__: @@ -204,26 +292,26 @@ def cli( ) ) ) - sys.exit(0) + return 0 warn_in_virtualenv() if ctx.invoked_subcommand is None: # --where was passed… if where: do_where(bare=True) - sys.exit(0) + return 0 elif py: do_py() - sys.exit() + return 0 # --support was passed… elif support: from .help import get_pipenv_diagnostics get_pipenv_diagnostics() - sys.exit(0) + return 0 # --clear was passed… elif clear: do_clear() - sys.exit(0) + return 0 # --venv was passed… elif venv: @@ -233,10 +321,10 @@ def cli( crayons.red("No virtualenv has been created for this project yet!"), err=True, ) - sys.exit(1) + ctx.abort() else: echo(project.virtualenv_location) - sys.exit(0) + return 0 # --rm was passed… elif rm: # Abort if --system (or running in a virtualenv). @@ -247,7 +335,7 @@ def cli( "Pipenv did not create. Aborting." ) ) - sys.exit(1) + ctx.abort() if project.virtualenv_exists: loc = project.virtualenv_location echo( @@ -261,7 +349,7 @@ def cli( with spinner(): # Remove the virtualenv. cleanup_virtualenv(bare=True) - sys.exit(0) + return 0 else: echo( crayons.red( @@ -270,7 +358,7 @@ def cli( ), err=True, ) - sys.exit(1) + ctx.abort() # --two / --three was passed… if (python or three is not None) or site_packages: ensure_project( @@ -291,35 +379,9 @@ def cli( short_help="Installs provided packages and adds them to Pipfile, or (if none is given), installs all packages.", context_settings=dict(ignore_unknown_options=True, allow_extra_args=True), ) -@argument("package_name", default=False) -@argument("more_packages", nargs=-1) -@option( - "--dev", - "-d", - is_flag=True, - default=False, - help="Install package(s) in [dev-packages].", -) -@option( - "--three/--two", - is_flag=True, - default=None, - help="Use Python 3/2 when creating virtualenv.", -) -@option( - "--python", - default=False, - nargs=1, - callback=validate_python_path, - help="Specify which version of Python virtualenv should use.", -) -@option( - "--pypi-mirror", - default=environments.PIPENV_PYPI_MIRROR, - nargs=1, - callback=validate_pypi_mirror, - help="Specify a PyPI mirror.", -) +@install_group +@python_group +@common_group @option("--system", is_flag=True, default=False, help="System pip management.") @option( "--requirements", @@ -330,12 +392,10 @@ def cli( ) @option("--code", "-c", nargs=1, default=False, help="Import from codebase.") @option( - "--verbose", - "-v", + "--skip-lock", is_flag=True, - expose_value=False, - callback=setup_verbosity, - help="Verbose mode.", + default=False, + help=u"Ignore locking mechanisms when installing—use the Pipfile, instead.", ) @option( "--ignore-pipfile", @@ -343,40 +403,16 @@ def cli( default=False, help="Ignore Pipfile when installing, using the Pipfile.lock.", ) -@option( - "--sequential", - is_flag=True, - default=False, - help="Install dependencies one-at-a-time, instead of concurrently.", -) -@option( - "--skip-lock", - is_flag=True, - default=False, - help=u"Ignore locking mechanisms when installing—use the Pipfile, instead.", -) @option( "--deploy", is_flag=True, default=False, help=u"Abort if the Pipfile.lock is out-of-date, or Python version is wrong.", ) -@option("--pre", is_flag=True, default=False, help=u"Allow pre-releases.") -@option( - "--keep-outdated", - is_flag=True, - default=False, - help=u"Keep out-dated dependencies from being updated in Pipfile.lock.", -) -@option( - "--selective-upgrade", - is_flag=True, - default=False, - help="Update specified packages.", -) +@upgrade_strategy_group +@index_group +@package_group def install( - package_name=False, - more_packages=False, dev=False, three=False, python=False, @@ -392,13 +428,15 @@ def install( deploy=False, keep_outdated=False, selective_upgrade=False, + index=False, + extra_index_url=False, + editable=False, + packages=False, ): """Installs provided packages and adds them to Pipfile, or (if none is given), installs all packages.""" from .core import do_install - do_install( - package_name=package_name, - more_packages=more_packages, + retcode = do_install( dev=dev, three=three, python=python, @@ -414,35 +452,20 @@ def install( deploy=deploy, keep_outdated=keep_outdated, selective_upgrade=selective_upgrade, + index_url=index, + extra_index_url=extra_index_url, + packages=packages, + editable_packages=editable, ) + if retcode: + ctx.abort() @command(short_help="Un-installs a provided package and removes it from Pipfile.") -@argument("package_name", default=False) -@argument("more_packages", nargs=-1) -@option( - "--three/--two", - is_flag=True, - default=None, - help="Use Python 3/2 when creating virtualenv.", -) -@option( - "--python", - default=False, - nargs=1, - callback=validate_python_path, - help="Specify which version of Python virtualenv should use.", -) +@python_group @option("--system", is_flag=True, default=False, help="System pip management.") -@option( - "--verbose", - "-v", - is_flag=True, - expose_value=False, - help="Verbose mode.", - callback=setup_verbosity, -) @option("--lock", is_flag=True, default=True, help="Lock afterwards.") +@common_group @option( "--all-dev", is_flag=True, @@ -461,13 +484,7 @@ def install( default=False, help=u"Keep out-dated dependencies from being updated in Pipfile.lock.", ) -@option( - "--pypi-mirror", - default=environments.PIPENV_PYPI_MIRROR, - nargs=1, - callback=validate_pypi_mirror, - help="Specify a PyPI mirror.", -) +@package_group def uninstall( package_name=False, more_packages=False, @@ -479,13 +496,15 @@ def uninstall( all=False, keep_outdated=False, pypi_mirror=None, + editable=False, + packages=False, ): """Un-installs a provided package and removes it from Pipfile.""" from .core import do_uninstall - do_uninstall( - package_name=package_name, - more_packages=more_packages, + retcode = do_uninstall( + packages=packages, + editable_packages=editable, three=three, python=python, system=system, @@ -495,37 +514,13 @@ def uninstall( keep_outdated=keep_outdated, pypi_mirror=pypi_mirror, ) + if retcode: + ctx.abort() @command(short_help="Generates Pipfile.lock.") -@option( - "--three/--two", - is_flag=True, - default=None, - help="Use Python 3/2 when creating virtualenv.", -) -@option( - "--python", - default=False, - nargs=1, - callback=validate_python_path, - help="Specify which version of Python virtualenv should use.", -) -@option( - "--pypi-mirror", - default=environments.PIPENV_PYPI_MIRROR, - nargs=1, - callback=validate_pypi_mirror, - help="Specify a PyPI mirror.", -) -@option( - "--verbose", - "-v", - is_flag=True, - expose_value=False, - help="Verbose mode.", - callback=setup_verbosity, -) +@python_group +@common_group @option( "--requirements", "-r", @@ -577,19 +572,7 @@ def lock( short_help="Spawns a shell within the virtualenv.", context_settings=dict(ignore_unknown_options=True, allow_extra_args=True), ) -@option( - "--three/--two", - is_flag=True, - default=None, - help="Use Python 3/2 when creating virtualenv.", -) -@option( - "--python", - default=False, - nargs=1, - callback=validate_python_path, - help="Specify which version of Python virtualenv should use.", -) +@python_group @option( "--fancy", is_flag=True, @@ -602,13 +585,7 @@ def lock( default=False, help="Always spawn a subshell, even if one is already spawned.", ) -@option( - "--pypi-mirror", - default=environments.PIPENV_PYPI_MIRROR, - nargs=1, - callback=validate_pypi_mirror, - help="Specify a PyPI mirror.", -) +@pypi_mirror @argument("shell_args", nargs=-1) def shell( three=None, @@ -660,26 +637,8 @@ def shell( ) @argument("command") @argument("args", nargs=-1) -@option( - "--three/--two", - is_flag=True, - default=None, - help="Use Python 3/2 when creating virtualenv.", -) -@option( - "--python", - default=False, - nargs=1, - callback=validate_python_path, - help="Specify which version of Python virtualenv should use.", -) -@option( - "--pypi-mirror", - default=environments.PIPENV_PYPI_MIRROR, - nargs=1, - callback=validate_pypi_mirror, - help="Specify a PyPI mirror.", -) +@python_group +@pypi_mirror def run(command, args, three=None, python=False, pypi_mirror=None): """Spawns a command installed into the virtualenv.""" from .core import do_run @@ -693,19 +652,7 @@ def run(command, args, three=None, python=False, pypi_mirror=None): short_help="Checks for security vulnerabilities and against PEP 508 markers provided in Pipfile.", context_settings=dict(ignore_unknown_options=True, allow_extra_args=True), ) -@option( - "--three/--two", - is_flag=True, - default=None, - help="Use Python 3/2 when creating virtualenv.", -) -@option( - "--python", - default=False, - nargs=1, - callback=validate_python_path, - help="Specify which version of Python virtualenv should use.", -) +@python_group @option("--system", is_flag=True, default=False, help="Use system Python.") @option( "--unused", @@ -719,13 +666,7 @@ def run(command, args, three=None, python=False, pypi_mirror=None): multiple=True, help="Ignore specified vulnerability during safety checks.", ) -@option( - "--pypi-mirror", - default=environments.PIPENV_PYPI_MIRROR, - nargs=1, - callback=validate_pypi_mirror, - help="Specify a PyPI mirror.", -) +@pypi_mirror @argument("args", nargs=-1) def check( three=None, @@ -753,41 +694,9 @@ def check( @command(short_help="Runs lock, then sync.") @argument("more_packages", nargs=-1) -@option( - "--three/--two", - is_flag=True, - default=None, - help="Use Python 3/2 when creating virtualenv.", -) -@option( - "--python", - default=False, - nargs=1, - callback=validate_python_path, - help="Specify which version of Python virtualenv should use.", -) -@option( - "--pypi-mirror", - default=environments.PIPENV_PYPI_MIRROR, - nargs=1, - callback=validate_pypi_mirror, - help="Specify a PyPI mirror.", -) -@option( - "--verbose", - "-v", - is_flag=True, - expose_value=False, - help="Verbose mode.", - callback=setup_verbosity, -) -@option( - "--dev", - "-d", - is_flag=True, - default=False, - help="Install package(s) in [dev-packages].", -) +@python_group +@common_group +@install_group @option("--clear", is_flag=True, default=False, help="Clear the dependency cache.") @option("--bare", is_flag=True, default=False, help="Minimal output.") @option("--pre", is_flag=True, default=False, help=u"Allow pre-releases.") @@ -797,17 +706,11 @@ def check( default=False, help=u"Keep out-dated dependencies from being updated in Pipfile.lock.", ) -@option( - "--sequential", - is_flag=True, - default=False, - help="Install dependencies one-at-a-time, instead of concurrently.", -) @option( "--outdated", is_flag=True, default=False, help=u"List out-of-date dependencies." ) @option("--dry-run", is_flag=True, default=None, help=u"List out-of-date dependencies.") -@argument("package", default=False) +@package_group @pass_context def update( ctx, @@ -821,10 +724,10 @@ def update( dev=False, bare=False, sequential=False, - package=None, dry_run=None, outdated=False, - more_packages=None, + packages=False, + editable=False, ): """Runs lock, then sync.""" from .core import ( @@ -840,7 +743,9 @@ def update( outdated = bool(dry_run) if outdated: do_outdated(pypi_mirror=pypi_mirror) - if not package: + packages = [p for p in packages if p] + editable = [p for p in editable if p] + if not packages: echo( "{0} {1} {2} {3}{4}".format( crayons.white("Running", bold=True), @@ -851,7 +756,7 @@ def update( ) ) else: - for package in [package] + list(more_packages) or []: + for package in packages + editable: if package not in project.all_packages: echo( "{0}: {1} was not found in your Pipfile! Aborting." @@ -861,7 +766,7 @@ def update( ), err=True, ) - sys.exit(1) + ctx.abort do_lock( clear=clear, @@ -897,26 +802,8 @@ def graph(bare=False, json=False, json_tree=False, reverse=False): @command(short_help="View a given module in your editor.", name="open") -@option( - "--three/--two", - is_flag=True, - default=None, - help="Use Python 3/2 when creating virtualenv.", -) -@option( - "--python", - default=False, - nargs=1, - callback=validate_python_path, - help="Specify which version of Python virtualenv should use.", -) -@option( - "--pypi-mirror", - default=environments.PIPENV_PYPI_MIRROR, - nargs=1, - callback=validate_pypi_mirror, - help="Specify a PyPI mirror.", -) +@python_group +@pypi_mirror @argument("module", nargs=1) def run_open(module, three=None, python=None, pypi_mirror=None): """View a given module in your editor.""" @@ -942,49 +829,11 @@ def run_open(module, three=None, python=None, pypi_mirror=None): @command(short_help="Installs all packages specified in Pipfile.lock.") -@option( - "--verbose", - "-v", - is_flag=True, - expose_value=False, - help="Verbose mode.", - callback=setup_verbosity, -) -@option( - "--dev", - "-d", - is_flag=True, - default=False, - help="Additionally install package(s) in [dev-packages].", -) -@option( - "--three/--two", - is_flag=True, - default=None, - help="Use Python 3/2 when creating virtualenv.", -) -@option( - "--python", - default=False, - nargs=1, - callback=validate_python_path, - help="Specify which version of Python virtualenv should use.", -) -@option( - "--pypi-mirror", - default=environments.PIPENV_PYPI_MIRROR, - nargs=1, - callback=validate_pypi_mirror, - help="Specify a PyPI mirror.", -) +@common_group +@install_group +@python_group @option("--bare", is_flag=True, default=False, help="Minimal output.") @option("--clear", is_flag=True, default=False, help="Clear the dependency cache.") -@option( - "--sequential", - is_flag=True, - default=False, - help="Install dependencies one-at-a-time, instead of concurrently.", -) @pass_context def sync( ctx, @@ -1027,19 +876,7 @@ def sync( help="Verbose mode.", callback=setup_verbosity, ) -@option( - "--three/--two", - is_flag=True, - default=None, - help="Use Python 3/2 when creating virtualenv.", -) -@option( - "--python", - default=False, - nargs=1, - callback=validate_python_path, - help="Specify which version of Python virtualenv should use.", -) +@python_group @option( "--dry-run", is_flag=True,