Files
pipenv/pipenv/cli.py
T
Dan Ryan 31301b1536 Fix deps and testing infrastructure
- Fix requirement parsing
- Add appveyor config
- cutover from pathlib to pathlib2 if needed
- Pin pathlib2==2.1.0 to avoid scandir
- Windows script runner fix
- Backport `shlex.quote()` for use in `pipenv run`
- Update tests and appveyor

Signed-off-by: Dan Ryan <dan@danryan.co>
2018-04-06 23:19:32 -04:00

979 lines
24 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# -*- coding: utf-8 -*-
import logging
import os
import sys
import click
import click_completion
import crayons
import delegator
from click_didyoumean import DYMCommandCollection
from .__version__ import __version__
from . import environments
from .environments import *
# Enable shell completion.
click_completion.init()
CONTEXT_SETTINGS = dict(help_option_names=['-h', '--help'])
class PipenvGroup(click.Group):
"""Custom Group class provides formatted main help"""
def get_help_option(self, ctx):
from . import core
"""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
click.echo(core.format_help(ctx.get_help()))
else:
# legit sub-command help
click.echo(ctx.get_help(), color=ctx.color)
ctx.exit()
return click.Option(
help_options,
is_flag=True,
is_eager=True,
expose_value=False,
callback=show_help,
help='Show this message and exit.',
)
def setup_verbose(ctx, param, value):
if value:
logging.getLogger('pip').setLevel(logging.INFO)
return value
@click.group(
cls=PipenvGroup,
invoke_without_command=True,
context_settings=CONTEXT_SETTINGS,
)
@click.option(
'--where',
is_flag=True,
default=False,
help="Output project home information.",
)
@click.option(
'--venv',
is_flag=True,
default=False,
help="Output virtualenv information.",
)
@click.option(
'--py',
is_flag=True,
default=False,
help="Output Python interpreter information.",
)
@click.option(
'--envs',
is_flag=True,
default=False,
help="Output Environment Variable options.",
)
@click.option(
'--rm', is_flag=True, default=False, help="Remove the virtualenv."
)
@click.option('--bare', is_flag=True, default=False, help="Minimal output.")
@click.option(
'--completion',
is_flag=True,
default=False,
help="Output completion (to be eval'd).",
)
@click.option('--man', is_flag=True, default=False, help="Display manpage.")
@click.option(
'--three/--two',
is_flag=True,
default=None,
help="Use Python 3/2 when creating virtualenv.",
)
@click.option(
'--python',
default=False,
nargs=1,
help="Specify which version of Python virtualenv should use.",
)
@click.option(
'--site-packages',
is_flag=True,
default=False,
help="Enable site-packages for the virtualenv.",
)
@click.version_option(
prog_name=crayons.normal('pipenv', bold=True), version=__version__
)
@click.pass_context
def cli(
ctx,
where=False,
venv=False,
rm=False,
bare=False,
three=False,
python=False,
help=False,
py=False,
site_packages=False,
envs=False,
man=False,
completion=False,
):
if completion: # Handle this ASAP to make shell startup fast.
if PIPENV_SHELL:
click.echo(
click_completion.get_code(
shell=PIPENV_SHELL.split(os.sep)[-1], prog_name='pipenv'
)
)
else:
click.echo(
'Please ensure that the {0} environment variable '
'is set.'.format(crayons.normal('SHELL', bold=True)),
err=True,
)
sys.exit(1)
sys.exit(0)
from . import core
if man:
if core.system_which('man'):
path = os.sep.join([os.path.dirname(__file__), 'pipenv.1'])
os.execle(core.system_which('man'), 'man', path, os.environ)
else:
click.echo(
'man does not appear to be available on your system.', err=True
)
if envs:
click.echo(
'The following environment variables can be set, to do various things:\n'
)
for key in environments.__dict__:
if key.startswith('PIPENV'):
click.echo(' - {0}'.format(crayons.normal(key, bold=True)))
click.echo(
'\nYou can learn more at:\n {0}'.format(
crayons.green(
'http://docs.pipenv.org/advanced/#configuration-with-environment-variables'
)
)
)
sys.exit(0)
core.warn_in_virtualenv()
if ctx.invoked_subcommand is None:
# --where was passed...
if where:
core.do_where(bare=True)
sys.exit(0)
elif py:
core.do_py()
sys.exit()
# --venv was passed...
elif venv:
# There is no virtualenv yet.
if not core.project.virtualenv_exists:
click.echo(
crayons.red(
'No virtualenv has been created for this project yet!'
),
err=True,
)
sys.exit(1)
else:
click.echo(core.project.virtualenv_location)
sys.exit(0)
# --rm was passed...
elif rm:
# Abort if --system (or running in a virtualenv).
if PIPENV_USE_SYSTEM:
click.echo(
crayons.red(
'You are attempting to remove a virtualenv that '
'Pipenv did not create. Aborting.'
)
)
sys.exit(1)
if core.project.virtualenv_exists:
loc = core.project.virtualenv_location
click.echo(
crayons.normal(
u'{0} ({1})…'.format(
crayons.normal('Removing virtualenv', bold=True),
crayons.green(loc),
)
)
)
with core.spinner():
# Remove the virtualenv.
core.cleanup_virtualenv(bare=True)
sys.exit(0)
else:
click.echo(
crayons.red(
'No virtualenv has been created for this project yet!',
bold=True,
),
err=True,
)
sys.exit(1)
# --two / --three was passed...
if (python or three is not None) or site_packages:
core.ensure_project(
three=three, python=python, warn=True, site_packages=site_packages
)
# Check this again before exiting for empty ``pipenv`` command.
elif ctx.invoked_subcommand is None:
# Display help to user, if no commands were passed.
click.echo(core.format_help(ctx.get_help()))
@click.command(
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),
)
@click.argument('package_name', default=False)
@click.argument('more_packages', nargs=-1)
@click.option(
'--dev',
'-d',
is_flag=True,
default=False,
help="Install package(s) in [dev-packages].",
)
@click.option(
'--three/--two',
is_flag=True,
default=None,
help="Use Python 3/2 when creating virtualenv.",
)
@click.option(
'--python',
default=False,
nargs=1,
help="Specify which version of Python virtualenv should use.",
)
@click.option(
'--system', is_flag=True, default=False, help="System pip management."
)
@click.option(
'--requirements',
'-r',
nargs=1,
default=False,
help="Import a requirements.txt file.",
)
@click.option(
'--code', '-c', nargs=1, default=False, help="Import from codebase."
)
@click.option(
'--verbose',
'-v',
is_flag=True,
default=False,
help="Verbose mode.",
callback=setup_verbose,
)
@click.option(
'--ignore-pipfile',
is_flag=True,
default=False,
help="Ignore Pipfile when installing, using the Pipfile.lock.",
)
@click.option(
'--sequential',
is_flag=True,
default=False,
help="Install dependencies one-at-a-time, instead of concurrently.",
)
@click.option(
'--skip-lock',
is_flag=True,
default=False,
help=u"Ignore locking mechanisms when installing—use the Pipfile, instead.",
)
@click.option(
'--deploy',
is_flag=True,
default=False,
help=u"Abort if the Pipfile.lock is outofdate, or Python version is wrong.",
)
@click.option(
'--pre', is_flag=True, default=False, help=u"Allow prereleases."
)
@click.option(
'--keep-outdated',
is_flag=True,
default=False,
help=u"Keep outdated dependencies from being updated in Pipfile.lock.",
)
@click.option(
'--selective-upgrade',
is_flag=True,
default=False,
help="Update specified packages.",
)
def install(
package_name=False,
more_packages=False,
dev=False,
three=False,
python=False,
system=False,
lock=True,
ignore_pipfile=False,
skip_lock=False,
verbose=False,
requirements=False,
sequential=False,
pre=False,
code=False,
deploy=False,
keep_outdated=False,
selective_upgrade=False,
):
from . import core
core.do_install(
package_name=package_name,
more_packages=more_packages,
dev=dev,
three=three,
python=python,
system=system,
lock=lock,
ignore_pipfile=ignore_pipfile,
skip_lock=skip_lock,
verbose=verbose,
requirements=requirements,
sequential=sequential,
pre=pre,
code=code,
deploy=deploy,
keep_outdated=keep_outdated,
selective_upgrade=selective_upgrade,
)
@click.command(
short_help="Un-installs a provided package and removes it from Pipfile."
)
@click.argument('package_name', default=False)
@click.argument('more_packages', nargs=-1)
@click.option(
'--three/--two',
is_flag=True,
default=None,
help="Use Python 3/2 when creating virtualenv.",
)
@click.option(
'--python',
default=False,
nargs=1,
help="Specify which version of Python virtualenv should use.",
)
@click.option(
'--system', is_flag=True, default=False, help="System pip management."
)
@click.option(
'--verbose',
'-v',
is_flag=True,
default=False,
help="Verbose mode.",
callback=setup_verbose,
)
@click.option('--lock', is_flag=True, default=True, help="Lock afterwards.")
@click.option(
'--all-dev',
is_flag=True,
default=False,
help="Un-install all package from [dev-packages].",
)
@click.option(
'--all',
is_flag=True,
default=False,
help="Purge all package(s) from virtualenv. Does not edit Pipfile.",
)
@click.option(
'--keep-outdated',
is_flag=True,
default=False,
help=u"Keep outdated dependencies from being updated in Pipfile.lock.",
)
def uninstall(
package_name=False,
more_packages=False,
three=None,
python=False,
system=False,
lock=False,
all_dev=False,
all=False,
verbose=False,
keep_outdated=False,
):
from . import core
core.do_uninstall(
package_name=package_name,
more_packages=more_packages,
three=three,
python=python,
system=system,
lock=lock,
all_dev=all_dev,
all=all,
verbose=verbose,
keep_outdated=keep_outdated,
)
@click.command(short_help="Generates Pipfile.lock.")
@click.option(
'--three/--two',
is_flag=True,
default=None,
help="Use Python 3/2 when creating virtualenv.",
)
@click.option(
'--python',
default=False,
nargs=1,
help="Specify which version of Python virtualenv should use.",
)
@click.option(
'--verbose',
'-v',
is_flag=True,
default=False,
help="Verbose mode.",
callback=setup_verbose,
)
@click.option(
'--requirements',
'-r',
is_flag=True,
default=False,
help="Generate output compatible with requirements.txt.",
)
@click.option(
'--dev',
'-d',
is_flag=True,
default=False,
help="Generate output compatible with requirements.txt for the development dependencies.",
)
@click.option(
'--clear', is_flag=True, default=False, help="Clear the dependency cache."
)
@click.option(
'--pre', is_flag=True, default=False, help=u"Allow prereleases."
)
@click.option(
'--keep-outdated',
is_flag=True,
default=False,
help=u"Keep outdated dependencies from being updated in Pipfile.lock.",
)
def lock(
three=None,
python=False,
verbose=False,
requirements=False,
dev=False,
clear=False,
pre=False,
keep_outdated=False,
):
from . import core
# Ensure that virtualenv is available.
core.ensure_project(three=three, python=python)
if requirements:
core.do_init(dev=dev, requirements=requirements)
core.do_lock(
verbose=verbose, clear=clear, pre=pre, keep_outdated=keep_outdated
)
@click.command(
short_help="Spawns a shell within the virtualenv.",
context_settings=dict(ignore_unknown_options=True, allow_extra_args=True),
)
@click.option(
'--three/--two',
is_flag=True,
default=None,
help="Use Python 3/2 when creating virtualenv.",
)
@click.option(
'--python',
default=False,
nargs=1,
help="Specify which version of Python virtualenv should use.",
)
@click.option(
'--fancy',
is_flag=True,
default=False,
help="Run in shell in fancy mode (for elegantly configured shells).",
)
@click.option(
'--anyway',
is_flag=True,
default=False,
help="Always spawn a subshell, even if one is already spawned.",
)
@click.argument('shell_args', nargs=-1)
def shell(
three=None, python=False, fancy=False, shell_args=None, anyway=False
):
from . import core
# Prevent user from activating nested environments.
if 'PIPENV_ACTIVE' in os.environ:
# If PIPENV_ACTIVE is set, VIRTUAL_ENV should always be set too.
venv_name = os.environ.get(
'VIRTUAL_ENV', 'UNKNOWN_VIRTUAL_ENVIRONMENT'
)
if not anyway:
click.echo(
'{0} {1} {2}\nNo action taken to avoid nested environments.'.format(
crayons.normal('Shell for'),
crayons.green(venv_name, bold=True),
crayons.normal('already activated.', bold=True),
),
err=True,
)
sys.exit(1)
# Load .env file.
core.load_dot_env()
# Use fancy mode for Windows.
if os.name == 'nt':
fancy = True
core.do_shell(
three=three, python=python, fancy=fancy, shell_args=shell_args
)
@click.command(
add_help_option=False,
short_help="Spawns a command installed into the virtualenv.",
context_settings=dict(
ignore_unknown_options=True,
allow_interspersed_args=False,
allow_extra_args=True,
),
)
@click.argument('command')
@click.argument('args', nargs=-1)
@click.option(
'--three/--two',
is_flag=True,
default=None,
help="Use Python 3/2 when creating virtualenv.",
)
@click.option(
'--python',
default=False,
nargs=1,
help="Specify which version of Python virtualenv should use.",
)
def run(command, args, three=None, python=False):
from . import core
core.do_run(command=command, args=args, three=three, python=python)
@click.command(
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),
)
@click.option(
'--three/--two',
is_flag=True,
default=None,
help="Use Python 3/2 when creating virtualenv.",
)
@click.option(
'--python',
default=False,
nargs=1,
help="Specify which version of Python virtualenv should use.",
)
@click.option(
'--system', is_flag=True, default=False, help="Use system Python."
)
@click.option(
'--unused',
nargs=1,
default=False,
help="Given a code path, show potentially unused dependencies.",
)
@click.argument('args', nargs=-1)
def check(
three=None,
python=False,
system=False,
unused=False,
style=False,
args=None,
):
from . import core
core.do_check(
three=three, python=python, system=system, unused=unused, args=args
)
@click.command(short_help="Runs lock, then sync.")
@click.argument('more_packages', nargs=-1)
@click.option(
'--three/--two',
is_flag=True,
default=None,
help="Use Python 3/2 when creating virtualenv.",
)
@click.option(
'--python',
default=False,
nargs=1,
help="Specify which version of Python virtualenv should use.",
)
@click.option(
'--verbose',
'-v',
is_flag=True,
default=False,
help="Verbose mode.",
callback=setup_verbose,
)
@click.option(
'--dev',
'-d',
is_flag=True,
default=False,
help="Install package(s) in [dev-packages].",
)
@click.option(
'--clear', is_flag=True, default=False, help="Clear the dependency cache."
)
@click.option('--bare', is_flag=True, default=False, help="Minimal output.")
@click.option(
'--pre', is_flag=True, default=False, help=u"Allow prereleases."
)
@click.option(
'--keep-outdated',
is_flag=True,
default=False,
help=u"Keep outdated dependencies from being updated in Pipfile.lock.",
)
@click.option(
'--sequential',
is_flag=True,
default=False,
help="Install dependencies one-at-a-time, instead of concurrently.",
)
@click.option(
'--outdated',
is_flag=True,
default=False,
help=u"List outofdate dependencies.",
)
@click.option(
'--dry-run',
is_flag=True,
default=None,
help=u"List outofdate dependencies.",
)
@click.argument('package', default=False)
@click.pass_context
def update(
ctx,
three=None,
python=False,
system=False,
verbose=False,
clear=False,
keep_outdated=False,
pre=False,
dev=False,
bare=False,
sequential=False,
package=None,
dry_run=None,
outdated=False,
more_packages=None,
):
from . import core
core.ensure_project(three=three, python=python, warn=True)
if not outdated:
outdated = bool(dry_run)
if outdated:
core.do_outdated()
if not package:
click.echo(
'{0} {1} {2} {3}{4}'.format(
crayons.white('Running', bold=True),
crayons.red('$ pipenv lock', bold=True),
crayons.white('then', bold=True),
crayons.red('$ pipenv sync', bold=True),
crayons.white('.', bold=True),
)
)
core.do_lock(
verbose=verbose, clear=clear, pre=pre, keep_outdated=keep_outdated
)
core.do_sync(
ctx=ctx,
install=install,
dev=dev,
three=three,
python=python,
bare=bare,
dont_upgrade=False,
user=False,
verbose=verbose,
clear=clear,
unused=False,
sequential=sequential,
)
else:
for package in ([package] + list(more_packages) or []):
if package not in core.project.all_packages:
click.echo(
'{0}: {1} was not found in your Pipfile! Aborting.'
''.format(
crayons.red('Warning', bold=True),
crayons.green(package, bold=True),
),
err=True,
)
sys.exit(1)
core.ensure_lockfile(keep_outdated=core.project.lockfile_exists)
# Install the dependencies.
core.do_install(
package_name=package,
more_packages=more_packages,
dev=dev,
three=three,
python=python,
system=system,
lock=True,
ignore_pipfile=False,
skip_lock=False,
verbose=verbose,
requirements=False,
sequential=sequential,
pre=pre,
code=False,
deploy=False,
keep_outdated=True,
selective_upgrade=True,
)
@click.command(
short_help=u"Displays currentlyinstalled dependency graph information."
)
@click.option('--bare', is_flag=True, default=False, help="Minimal output.")
@click.option('--json', is_flag=True, default=False, help="Output JSON.")
@click.option(
'--reverse', is_flag=True, default=False, help="Reversed dependency graph."
)
def graph(bare=False, json=False, reverse=False):
from . import core
core.do_graph(bare=bare, json=json, reverse=reverse)
@click.command(short_help="View a given module in your editor.", name="open")
@click.option(
'--three/--two',
is_flag=True,
default=None,
help="Use Python 3/2 when creating virtualenv.",
)
@click.option(
'--python',
default=False,
nargs=1,
help="Specify which version of Python virtualenv should use.",
)
@click.argument('module', nargs=1)
def run_open(module, three=None, python=None):
from . import core
# Ensure that virtualenv is available.
core.ensure_project(three=three, python=python, validate=False)
c = delegator.run(
'{0} -c "import {1}; print({1}.__file__);"'.format(
core.which('python'), module
)
)
try:
assert c.return_code == 0
except AssertionError:
click.echo(crayons.red('Module not found!'))
sys.exit(1)
if '__init__.py' in c.out:
p = os.path.dirname(c.out.strip().rstrip('cdo'))
else:
p = c.out.strip().rstrip('cdo')
click.echo(
crayons.normal('Opening {0!r} in your EDITOR.'.format(p), bold=True)
)
click.edit(filename=p)
sys.exit(0)
@click.command(short_help="Installs all packages specified in Pipfile.lock.")
@click.option(
'--verbose',
'-v',
is_flag=True,
default=False,
help="Verbose mode.",
callback=setup_verbose,
)
@click.option(
'--dev',
'-d',
is_flag=True,
default=False,
help="Additionally install package(s) in [dev-packages].",
)
@click.option(
'--three/--two',
is_flag=True,
default=None,
help="Use Python 3/2 when creating virtualenv.",
)
@click.option(
'--python',
default=False,
nargs=1,
help="Specify which version of Python virtualenv should use.",
)
@click.option('--bare', is_flag=True, default=False, help="Minimal output.")
@click.option(
'--clear', is_flag=True, default=False, help="Clear the dependency cache."
)
@click.option(
'--sequential',
is_flag=True,
default=False,
help="Install dependencies one-at-a-time, instead of concurrently.",
)
@click.pass_context
def sync(
ctx,
dev=False,
three=None,
python=None,
bare=False,
dont_upgrade=False,
user=False,
verbose=False,
clear=False,
unused=False,
package_name=None,
sequential=False,
):
from . import core
core.do_sync(
ctx=ctx,
install=install,
dev=dev,
three=three,
python=python,
bare=bare,
dont_upgrade=dont_upgrade,
user=user,
verbose=verbose,
clear=clear,
unused=unused,
sequential=sequential,
)
@click.command(
short_help="Uninstalls all packages not specified in Pipfile.lock."
)
@click.option(
'--verbose',
'-v',
is_flag=True,
default=False,
help="Verbose mode.",
callback=setup_verbose,
)
@click.option(
'--three/--two',
is_flag=True,
default=None,
help="Use Python 3/2 when creating virtualenv.",
)
@click.option(
'--python',
default=False,
nargs=1,
help="Specify which version of Python virtualenv should use.",
)
@click.option(
'--dry-run',
is_flag=True,
default=False,
help="Just output unneeded packages.",
)
@click.pass_context
def clean(
ctx,
three=None,
python=None,
dry_run=False,
bare=False,
user=False,
verbose=False,
):
from . import core
core.do_clean(
ctx=ctx, three=three, python=python, dry_run=dry_run, verbose=verbose
)
# Install click commands.
cli.add_command(graph)
cli.add_command(install)
cli.add_command(uninstall)
cli.add_command(sync)
cli.add_command(lock)
cli.add_command(check)
cli.add_command(clean)
cli.add_command(shell)
cli.add_command(run)
cli.add_command(update)
cli.add_command(run_open)
# Only invoke the "did you mean" when an argument wasn't passed (it breaks those).
if '-' not in ''.join(sys.argv) and len(sys.argv) > 1:
cli = DYMCommandCollection(sources=[cli])
if __name__ == '__main__':
cli()