Files
pipenv/pipenv/core.py
T
Dan Ryan a245be1819 Use more local tests
Signed-off-by: Dan Ryan <dan@danryan.co>
2018-07-01 14:16:48 -04:00

2580 lines
90 KiB
Python

# -*- coding=utf-8 -*-
import contextlib
import logging
import os
import sys
import shutil
import time
import tempfile
from glob import glob
import json as simplejson
import click
import click_completion
import crayons
import dotenv
import delegator
import pipfile
from blindspin import spinner
import six
from .project import Project, SourceNotFound
from .utils import (
convert_deps_to_pip,
is_required_version,
proper_case,
pep423_name,
split_file,
merge_deps,
venv_resolve_deps,
escape_grouped_arguments,
python_version,
find_windows_executable,
prepare_pip_source_args,
is_valid_url,
is_pypi_url,
create_mirror_source,
download_file,
is_pinned,
is_star,
rmtree,
split_argument,
fs_str,
clean_resolved_dep,
)
from ._compat import (
TemporaryDirectory,
Path
)
from . import pep508checker, progress
from .environments import (
PIPENV_COLORBLIND,
PIPENV_NOSPIN,
PIPENV_SHELL_FANCY,
PIPENV_TIMEOUT,
PIPENV_SKIP_VALIDATION,
PIPENV_HIDE_EMOJIS,
PIPENV_INSTALL_TIMEOUT,
PYENV_ROOT,
PYENV_INSTALLED,
PIPENV_YES,
PIPENV_DONT_LOAD_ENV,
PIPENV_DEFAULT_PYTHON_VERSION,
PIPENV_MAX_SUBPROCESS,
PIPENV_DONT_USE_PYENV,
SESSION_IS_INTERACTIVE,
PIPENV_DOTENV_LOCATION,
PIPENV_CACHE_DIR,
)
# Packages that should be ignored later.
BAD_PACKAGES = ('setuptools', 'pip', 'wheel', 'packaging', 'distribute')
# Are we using the default Python?
USING_DEFAULT_PYTHON = True
if not PIPENV_HIDE_EMOJIS:
now = time.localtime()
# Halloween easter-egg.
if ((now.tm_mon == 10) and (now.tm_mday == 30)) or (
(now.tm_mon == 10) and (now.tm_mday == 31)
):
INSTALL_LABEL = '🎃 '
# Christmas easter-egg.
elif ((now.tm_mon == 12) and (now.tm_mday == 24)) or (
(now.tm_mon == 12) and (now.tm_mday == 25)
):
INSTALL_LABEL = '🎅 '
else:
INSTALL_LABEL = '🐍 '
INSTALL_LABEL2 = crayons.normal('', bold=True)
STARTING_LABEL = ' '
else:
INSTALL_LABEL = ' '
INSTALL_LABEL2 = ' '
STARTING_LABEL = ' '
# Enable shell completion.
click_completion.init()
# Disable colors, for the color blind and others who do not prefer colors.
if PIPENV_COLORBLIND:
crayons.disable()
# Disable spinner, for cleaner build logs (the unworthy).
if PIPENV_NOSPIN:
@contextlib.contextmanager # noqa: F811
def spinner():
yield
def which(command, location=None, allow_global=False):
if not allow_global and location is None:
location = project.virtualenv_location or os.environ.get('VIRTUAL_ENV')
if not allow_global:
if os.name == 'nt':
p = find_windows_executable(
os.path.join(location, 'Scripts'), command,
)
else:
p = os.path.join(location, 'bin', command)
else:
if command == 'python':
p = sys.executable
if not os.path.exists(p):
if command == 'python':
p = sys.executable or system_which('python')
else:
p = system_which(command)
return p
project = Project(which=which)
def load_dot_env():
"""Loads .env file into sys.environ."""
if not PIPENV_DONT_LOAD_ENV:
# If the project doesn't exist yet, check current directory for a .env file
project_directory = project.project_directory or '.'
denv = dotenv.find_dotenv(
PIPENV_DOTENV_LOCATION or os.sep.join([project_directory, '.env'])
)
if os.path.isfile(denv):
click.echo(
crayons.normal(
'Loading .env environment variables...', bold=True
),
err=True,
)
dotenv.load_dotenv(denv, override=True)
def add_to_path(p):
"""Adds a given path to the PATH."""
if p not in os.environ['PATH']:
os.environ['PATH'] = '{0}{1}{2}'.format(
p, os.pathsep, os.environ['PATH']
)
def cleanup_virtualenv(bare=True):
"""Removes the virtualenv directory from the system."""
if not bare:
click.echo(crayons.red('Environment creation aborted.'))
try:
# Delete the virtualenv.
rmtree(project.virtualenv_location)
except OSError as e:
click.echo(
'{0} An error occurred while removing {1}!'.format(
crayons.red('Error: ', bold=True),
crayons.green(project.virtualenv_location),
),
err=True,
)
click.echo(crayons.blue(e), err=True)
def import_requirements(r=None, dev=False):
from .patched.notpip._vendor import requests as pip_requests
from .patched.notpip._internal.req.req_file import parse_requirements
# Parse requirements.txt file with Pip's parser.
# Pip requires a `PipSession` which is a subclass of requests.Session.
# Since we're not making any network calls, it's initialized to nothing.
if r:
assert os.path.isfile(r)
# Default path, if none is provided.
if r is None:
r = project.requirements_location
with open(r, 'r') as f:
contents = f.read()
indexes = []
# Find and add extra indexes.
for line in contents.split('\n'):
if line.startswith(('-i ', '--index ', '--index-url ')):
indexes.append(line.split()[1])
reqs = [f for f in parse_requirements(r, session=pip_requests)]
for package in reqs:
if package.name not in BAD_PACKAGES:
if package.link is not None:
package_string = (
'-e {0}'.format(package.link) if package.editable else str(
package.link
)
)
project.add_package_to_pipfile(package_string, dev=dev)
else:
project.add_package_to_pipfile(str(package.req), dev=dev)
for index in indexes:
project.add_index_to_pipfile(index)
project.recase_pipfile()
def ensure_environment():
# Skip this on Windows...
if os.name != 'nt':
if 'LANG' not in os.environ:
click.echo(
'{0}: the environment variable {1} is not set!'
'\nWe recommend setting this in {2} (or equivalent) for '
'proper expected behavior.'.format(
crayons.red('Warning', bold=True),
crayons.normal('LANG', bold=True),
crayons.green('~/.profile'),
),
err=True,
)
def import_from_code(path='.'):
from pipreqs import pipreqs
rs = []
try:
for r in pipreqs.get_all_imports(path):
if r not in BAD_PACKAGES:
rs.append(r)
pkg_names = pipreqs.get_pkg_names(rs)
return [proper_case(r) for r in pkg_names]
except Exception:
return []
def ensure_pipfile(validate=True, skip_requirements=False, system=False):
"""Creates a Pipfile for the project, if it doesn't exist."""
from .environments import PIPENV_VIRTUALENV
# Assert Pipfile exists.
python = which('python') if not (USING_DEFAULT_PYTHON or system) else None
if project.pipfile_is_empty:
# Show an error message and exit if system is passed and no pipfile exists
if system and not PIPENV_VIRTUALENV:
click.echo(
'{0}: --system is intended to be used for pre-existing Pipfile '
'installation, not installation of specific packages. Aborting.'.format(
crayons.red('Warning', bold=True)
),
err=True,
)
sys.exit(1)
# If there's a requirements file, but no Pipfile...
if project.requirements_exists and not skip_requirements:
click.echo(
crayons.normal(
u'requirements.txt found, instead of Pipfile! Converting...',
bold=True,
)
)
# Create a Pipfile...
project.create_pipfile(python=python)
with spinner():
# Import requirements.txt.
import_requirements()
# Warn the user of side-effects.
click.echo(
u'{0}: Your {1} now contains pinned versions, if your {2} did. \n'
'We recommend updating your {1} to specify the {3} version, instead.'
''.format(
crayons.red('Warning', bold=True),
crayons.normal('Pipfile', bold=True),
crayons.normal('requirements.txt', bold=True),
crayons.normal('"*"', bold=True),
)
)
else:
click.echo(
crayons.normal(
u'Creating a Pipfile for this project...', bold=True
),
err=True,
)
# Create the pipfile if it doesn't exist.
project.create_pipfile(python=python)
# Validate the Pipfile's contents.
if validate and project.virtualenv_exists and not PIPENV_SKIP_VALIDATION:
# Ensure that Pipfile is using proper casing.
p = project.parsed_pipfile
changed = project.ensure_proper_casing()
# Write changes out to disk.
if changed:
click.echo(
crayons.normal(u'Fixing package names in Pipfile...', bold=True),
err=True,
)
project.write_toml(p)
def find_python_from_py(python):
"""Find a Python executable from on Windows.
Ask py.exe for its opinion.
"""
py = system_which('py')
if not py:
return None
version_args = ['-{0}'.format(python[0])]
if len(python) >= 2:
version_args.append('-{0}.{1}'.format(python[0], python[2]))
import subprocess
for ver_arg in reversed(version_args):
try:
python_exe = subprocess.check_output(
[py, ver_arg, '-c', 'import sys; print(sys.executable)']
)
except subprocess.CalledProcessError:
continue
if not isinstance(python_exe, str):
python_exe = python_exe.decode(sys.getdefaultencoding())
python_exe = python_exe.strip()
version = python_version(python_exe)
if (version or '').startswith(python):
return python_exe
def find_python_in_path(python):
"""Find a Python executable from a version number.
This uses the PATH environment variable to locate an appropriate Python.
"""
possibilities = ['python', 'python{0}'.format(python[0])]
if len(python) >= 2:
possibilities.extend(
[
'python{0}{1}'.format(python[0], python[2]),
'python{0}.{1}'.format(python[0], python[2]),
'python{0}.{1}m'.format(python[0], python[2]),
]
)
# Reverse the list, so we find specific ones first.
possibilities = reversed(possibilities)
for possibility in possibilities:
# Windows compatibility.
if os.name == 'nt':
possibility = '{0}.exe'.format(possibility)
pythons = system_which(possibility, mult=True)
for p in pythons:
version = python_version(p)
if (version or '').startswith(python):
return p
def find_a_system_python(python):
"""Finds a system python, given a version (e.g. 2 / 2.7 / 3.6.2), or a full path."""
if python.startswith('py'):
return system_which(python)
elif os.path.isabs(python):
return python
python_from_py = find_python_from_py(python)
if python_from_py:
return python_from_py
return find_python_in_path(python)
def ensure_python(three=None, python=None):
# Support for the PIPENV_PYTHON environment variable.
from .environments import PIPENV_PYTHON
if PIPENV_PYTHON and python is False and three is None:
python = PIPENV_PYTHON
def abort():
click.echo(
'You can specify specific versions of Python with:\n {0}'.format(
crayons.red(
'$ pipenv --python {0}'.format(
os.sep.join(('path', 'to', 'python'))
)
)
),
err=True,
)
sys.exit(1)
def activate_pyenv():
from notpip._vendor.packaging.version import parse as parse_version
"""Adds all pyenv installations to the PATH."""
if PYENV_INSTALLED:
if PYENV_ROOT:
pyenv_paths = {}
for found in glob(
'{0}{1}versions{1}*'.format(PYENV_ROOT, os.sep)
):
pyenv_paths[os.path.split(found)[1]] = '{0}{1}bin'.format(
found, os.sep
)
for version_str, pyenv_path in pyenv_paths.items():
version = parse_version(version_str)
if version.is_prerelease and pyenv_paths.get(
version.base_version
):
continue
add_to_path(pyenv_path)
else:
click.echo(
'{0}: PYENV_ROOT is not set. New python paths will '
'probably not be exported properly after installation.'
''.format(crayons.red('Warning', bold=True),),
err=True,
)
global USING_DEFAULT_PYTHON
# Add pyenv paths to PATH.
activate_pyenv()
path_to_python = None
USING_DEFAULT_PYTHON = (three is None and not python)
# Find out which python is desired.
if not python:
python = convert_three_to_python(three, python)
if not python:
python = project.required_python_version
if not python:
python = PIPENV_DEFAULT_PYTHON_VERSION
if python:
path_to_python = find_a_system_python(python)
if not path_to_python and python is not None:
# We need to install Python.
click.echo(
u'{0}: Python {1} {2}'.format(
crayons.red('Warning', bold=True),
crayons.blue(python),
u'was not found on your system...',
),
err=True,
)
# Pyenv is installed
if not PYENV_INSTALLED:
abort()
else:
if (not PIPENV_DONT_USE_PYENV) and (SESSION_IS_INTERACTIVE or PIPENV_YES):
version_map = {
# TODO: Keep this up to date!
# These versions appear incompatible with pew:
# '2.5': '2.5.6',
'2.6': '2.6.9',
'2.7': '2.7.15',
# '3.1': '3.1.5',
# '3.2': '3.2.6',
'3.3': '3.3.7',
'3.4': '3.4.8',
'3.5': '3.5.5',
'3.6': '3.6.6',
'3.7': '3.7.0',
}
try:
if len(python.split('.')) == 2:
# Find the latest version of Python available.
version = version_map[python]
else:
version = python
except KeyError:
abort()
s = (
'{0} {1} {2}'.format(
'Would you like us to install',
crayons.green('CPython {0}'.format(version)),
'with pyenv?',
)
)
# Prompt the user to continue...
if not (PIPENV_YES or click.confirm(s, default=True)):
abort()
else:
# Tell the user we're installing Python.
click.echo(
u'{0} {1} {2} {3}{4}'.format(
crayons.normal(u'Installing', bold=True),
crayons.green(
u'CPython {0}'.format(version), bold=True
),
crayons.normal(u'with pyenv', bold=True),
crayons.normal(u'(this may take a few minutes)'),
crayons.normal(u'...', bold=True),
)
)
with spinner():
# Install Python.
c = delegator.run(
'pyenv install {0} -s'.format(version),
timeout=PIPENV_INSTALL_TIMEOUT,
block=False,
)
# Wait until the process has finished...
c.block()
try:
assert c.return_code == 0
except AssertionError:
click.echo(u'Something went wrong...')
click.echo(crayons.blue(c.err), err=True)
# Print the results, in a beautiful blue...
click.echo(crayons.blue(c.out), err=True)
# Add new paths to PATH.
activate_pyenv()
# Find the newly installed Python, hopefully.
path_to_python = find_a_system_python(version)
try:
assert python_version(path_to_python) == version
except AssertionError:
click.echo(
'{0}: The Python you just installed is not available on your {1}, apparently.'
''.format(
crayons.red('Warning', bold=True),
crayons.normal('PATH', bold=True),
),
err=True,
)
sys.exit(1)
return path_to_python
def ensure_virtualenv(three=None, python=None, site_packages=False, pypi_mirror=None):
"""Creates a virtualenv, if one doesn't exist."""
from .environments import PIPENV_USE_SYSTEM
def abort():
sys.exit(1)
global USING_DEFAULT_PYTHON
if not project.virtualenv_exists:
try:
# Ensure environment variables are set properly.
ensure_environment()
# Ensure Python is available.
python = ensure_python(three=three, python=python)
# Create the virtualenv.
# Abort if --system (or running in a virtualenv).
if PIPENV_USE_SYSTEM:
click.echo(
crayons.red(
'You are attempting to re-create a virtualenv that '
'Pipenv did not create. Aborting.'
)
)
sys.exit(1)
do_create_virtualenv(python=python, site_packages=site_packages, pypi_mirror=pypi_mirror)
except KeyboardInterrupt:
# If interrupted, cleanup the virtualenv.
cleanup_virtualenv(bare=False)
sys.exit(1)
# If --three, --two, or --python were passed...
elif (python) or (three is not None) or (site_packages is not False):
USING_DEFAULT_PYTHON = False
# Ensure python is installed before deleting existing virtual env
ensure_python(three=three, python=python)
click.echo(crayons.red('Virtualenv already exists!'), err=True)
# If VIRTUAL_ENV is set, there is a possibility that we are
# going to remove the active virtualenv that the user cares
# about, so confirm first.
if 'VIRTUAL_ENV' in os.environ:
if not (
PIPENV_YES or
click.confirm('Remove existing virtualenv?', default=True)
):
abort()
click.echo(
crayons.normal(u'Removing existing virtualenv...', bold=True),
err=True,
)
# Remove the virtualenv.
cleanup_virtualenv(bare=True)
# Call this function again.
ensure_virtualenv(
three=three, python=python, site_packages=site_packages, pypi_mirror=pypi_mirror
)
def ensure_project(
three=None,
python=None,
validate=True,
system=False,
warn=True,
site_packages=False,
deploy=False,
skip_requirements=False,
pypi_mirror=None,
):
"""Ensures both Pipfile and virtualenv exist for the project."""
from .environments import PIPENV_USE_SYSTEM
# Automatically use an activated virtualenv.
if PIPENV_USE_SYSTEM:
system = True
if not project.pipfile_exists and not deploy:
project.touch_pipfile()
# Skip virtualenv creation when --system was used.
if not system:
ensure_virtualenv(
three=three, python=python, site_packages=site_packages, pypi_mirror=pypi_mirror
)
if warn:
# Warn users if they are using the wrong version of Python.
if project.required_python_version:
path_to_python = which('python') or which('py')
if path_to_python and project.required_python_version not in (
python_version(path_to_python) or ''
):
click.echo(
'{0}: Your Pipfile requires {1} {2}, '
'but you are using {3} ({4}).'.format(
crayons.red('Warning', bold=True),
crayons.normal('python_version', bold=True),
crayons.blue(project.required_python_version),
crayons.blue(python_version(path_to_python)),
crayons.green(shorten_path(path_to_python)),
),
err=True,
)
if not deploy:
click.echo(
' {0} will surely fail.'
''.format(crayons.red('$ pipenv check')),
err=True,
)
else:
click.echo(crayons.red('Deploy aborted.'), err=True)
sys.exit(1)
# Ensure the Pipfile exists.
ensure_pipfile(validate=validate, skip_requirements=skip_requirements, system=system)
def shorten_path(location, bold=False):
"""Returns a visually shorter representation of a given system path."""
original = location
short = os.sep.join(
[
s[0] if len(s) > (len('2long4')) else s
for s in location.split(os.sep)
]
)
short = short.split(os.sep)
short[-1] = original.split(os.sep)[-1]
if bold:
short[-1] = str(crayons.normal(short[-1], bold=True))
return os.sep.join(short)
# return short
def do_where(virtualenv=False, bare=True):
"""Executes the where functionality."""
if not virtualenv:
location = project.pipfile_location
# Shorten the virtual display of the path to the virtualenv.
if not bare:
location = shorten_path(location)
if not location:
click.echo(
'No Pipfile present at project home. Consider running '
'{0} first to automatically generate a Pipfile for you.'
''.format(crayons.green('`pipenv install`')),
err=True,
)
elif not bare:
click.echo(
'Pipfile found at {0}.\n Considering this to be the project home.'
''.format(crayons.green(location)),
err=True,
)
pass
else:
click.echo(project.project_directory)
else:
location = project.virtualenv_location
if not bare:
click.echo(
'Virtualenv location: {0}'.format(crayons.green(location)),
err=True,
)
else:
click.echo(location)
def do_install_dependencies(
dev=False,
only=False,
bare=False,
requirements=False,
allow_global=False,
ignore_hashes=False,
skip_lock=False,
verbose=False,
concurrent=True,
requirements_dir=None,
pypi_mirror=False,
):
""""Executes the install functionality.
If requirements is True, simply spits out a requirements format to stdout.
"""
def cleanup_procs(procs, concurrent):
for c in procs:
if concurrent:
c.block()
if 'Ignoring' in c.out:
click.echo(crayons.yellow(c.out.strip()))
if verbose:
click.echo(crayons.blue(c.out or c.err))
# The Installation failed...
if c.return_code != 0:
# Save the Failed Dependency for later.
failed_deps_list.append((c.dep, c.ignore_hash))
# Alert the user.
click.echo(
'{0} {1}! Will try again.'.format(
crayons.red('An error occurred while installing'),
crayons.green(c.dep.split('--hash')[0].strip()),
)
)
if requirements:
bare = True
blocking = (not concurrent)
# Load the lockfile if it exists, or if only is being used (e.g. lock is being used).
if skip_lock or only or not project.lockfile_exists:
if not bare:
click.echo(
crayons.normal(
u'Installing dependencies from Pipfile...', bold=True
)
)
lockfile = split_file(project._lockfile)
else:
with open(project.lockfile_location) as f:
lockfile = split_file(simplejson.load(f))
if not bare:
click.echo(
crayons.normal(
u'Installing dependencies from Pipfile.lock ({0})...'.format(
lockfile['_meta'].get('hash', {}).get('sha256')[-6:]
),
bold=True,
)
)
# Allow pip to resolve dependencies when in skip-lock mode.
no_deps = (not skip_lock)
deps_list, dev_deps_list = merge_deps(
lockfile,
project,
dev=dev,
requirements=requirements,
ignore_hashes=ignore_hashes,
blocking=blocking,
only=only,
)
failed_deps_list = []
if requirements:
# Comment out packages that shouldn't be included in
# requirements.txt, for pip9.
# Additional package selectors, specific to pip's --hash checking mode.
for l in (deps_list, dev_deps_list):
for i, dep in enumerate(l):
l[i] = list(l[i])
if '--hash' in l[i][0]:
l[i][0] = (l[i][0].split('--hash')[0].strip())
index_args = prepare_pip_source_args(project.sources)
index_args = ' '.join(index_args).replace(' -', '\n-')
# Output only default dependencies
click.echo(index_args)
if not dev:
click.echo('\n'.join(d[0] for d in sorted(deps_list)))
sys.exit(0)
# Output only dev dependencies
if dev:
click.echo('\n'.join(d[0] for d in sorted(dev_deps_list)))
sys.exit(0)
procs = []
deps_list_bar = progress.bar(
deps_list, label=INSTALL_LABEL if os.name != 'nt' else ''
)
for dep, ignore_hash, block in deps_list_bar:
if len(procs) < PIPENV_MAX_SUBPROCESS:
# Use a specific index, if specified.
dep, index = split_argument(dep, short='i', long_='index', num=1)
dep, extra_indexes = split_argument(dep, long_='extra-index-url')
# Install the module.
c = pip_install(
dep,
ignore_hashes=ignore_hash,
allow_global=allow_global,
no_deps=no_deps,
verbose=verbose,
block=block,
index=index,
requirements_dir=requirements_dir,
extra_indexes=extra_indexes,
pypi_mirror=pypi_mirror,
)
c.dep = dep
c.ignore_hash = ignore_hash
procs.append(c)
if len(procs) >= PIPENV_MAX_SUBPROCESS or len(procs) == len(deps_list):
cleanup_procs(procs, concurrent)
procs = []
cleanup_procs(procs, concurrent)
# Iterate over the hopefully-poorly-packaged dependencies...
if failed_deps_list:
click.echo(
crayons.normal(
u'Installing initially failed dependencies...', bold=True
)
)
for dep, ignore_hash in progress.bar(
failed_deps_list, label=INSTALL_LABEL2
):
# Use a specific index, if specified.
dep, index = split_argument(dep, short='i', long_='index', num=1)
dep, extra_indexes = split_argument(dep, long_='extra-index-url')
# Install the module.
c = pip_install(
dep,
ignore_hashes=ignore_hash,
allow_global=allow_global,
no_deps=no_deps,
verbose=verbose,
index=index,
requirements_dir=requirements_dir,
extra_indexes=extra_indexes,
)
# The Installation failed...
if c.return_code != 0:
# We echo both c.out and c.err because pip returns error details on out.
click.echo(crayons.blue(format_pip_output(c.out)))
click.echo(crayons.blue(format_pip_error(c.err)), err=True)
# Return the subprocess' return code.
sys.exit(c.return_code)
else:
click.echo(
'{0} {1}{2}'.format(
crayons.green('Success installing'),
crayons.green(dep.split('--hash')[0].strip()),
crayons.green('!'),
)
)
def convert_three_to_python(three, python):
"""Converts a Three flag into a Python flag, and raises customer warnings
in the process, if needed.
"""
if not python:
if three is False:
return '2'
elif three is True:
return '3'
else:
return python
def do_create_virtualenv(python=None, site_packages=False, pypi_mirror=None):
"""Creates a virtualenv."""
click.echo(
crayons.normal(u'Creating a virtualenv for this project...', bold=True),
err=True,
)
click.echo(u'Pipfile: {0}'.format(
crayons.red(project.pipfile_location, bold=True),
), err=True)
# The user wants the virtualenv in the project.
if project.is_venv_in_project():
cmd = [
sys.executable, '-m', 'virtualenv',
project.virtualenv_location,
'--prompt=({0})'.format(project.name),
]
# Pass site-packages flag to virtualenv, if desired...
if site_packages:
cmd.append('--system-site-packages')
else:
# Default: use pew.
cmd = [
sys.executable,
'-m',
'pipenv.pew',
'new',
'-d',
'-a',
project.project_directory,
]
# Default to using sys.executable, if Python wasn't provided.
if not python:
python = sys.executable
click.echo(
u'{0} {1} {3} {2}'.format(
crayons.normal('Using', bold=True),
crayons.red(python, bold=True),
crayons.normal(u'to create virtualenv...', bold=True),
crayons.green('({0})'.format(python_version(python))),
),
err=True,
)
cmd = cmd + ['-p', python]
if not project.is_venv_in_project():
cmd = cmd + ['--', project.virtualenv_name]
# Actually create the virtualenv.
with spinner():
try:
pip_config = {'PIP_INDEX_URL': fs_str(pypi_mirror)} if pypi_mirror else {}
c = delegator.run(cmd, block=False, timeout=PIPENV_TIMEOUT, env=pip_config)
except OSError:
click.echo(
'{0}: it looks like {1} is not in your {2}. '
'We cannot continue until this is resolved.'
''.format(
crayons.red('Warning', bold=True),
crayons.red(cmd[0]),
crayons.normal('PATH', bold=True),
),
err=True,
)
sys.exit(1)
click.echo(crayons.blue(c.out), err=True)
# Enable site-packages, if desired...
if not project.is_venv_in_project() and site_packages:
click.echo(
crayons.normal(u'Making site-packages available...', bold=True),
err=True,
)
os.environ['VIRTUAL_ENV'] = project.virtualenv_location
delegator.run('pipenv run pewtwo toggleglobalsitepackages')
del os.environ['VIRTUAL_ENV']
# Say where the virtualenv is.
do_where(virtualenv=True, bare=False)
def parse_download_fname(fname, name):
fname, fextension = os.path.splitext(fname)
if fextension == '.whl':
fname = '-'.join(fname.split('-')[:-3])
if fname.endswith('.tar'):
fname, _ = os.path.splitext(fname)
# Substring out package name (plus dash) from file name to get version.
version = fname[len(name) + 1:]
# Ignore implicit post releases in version number.
if '-' in version and version.split('-')[1].isdigit():
version = version.split('-')[0]
return version
def get_downloads_info(names_map, section):
from .vendor.requirementslib import Requirement
info = []
p = project.parsed_pipfile
for fname in os.listdir(project.download_location):
# Get name from filename mapping.
name = Requirement.from_line(names_map[fname]).name
# Get the version info from the filenames.
version = parse_download_fname(fname, name)
# Get the hash of each file.
cmd = '{0} hash "{1}"'.format(
escape_grouped_arguments(which_pip()),
os.sep.join([project.download_location, fname]),
)
c = delegator.run(cmd)
hash = c.out.split('--hash=')[1].strip()
# Verify we're adding the correct version from Pipfile
# and not one from a dependency.
specified_version = p[section].get(name, '')
if is_required_version(version, specified_version):
info.append(dict(name=name, version=version, hash=hash))
return info
def do_lock(
verbose=False,
system=False,
clear=False,
pre=False,
keep_outdated=False,
write=True,
pypi_mirror=None,
):
"""Executes the freeze functionality."""
from .utils import get_vcs_deps
cached_lockfile = {}
if not pre:
pre = project.settings.get('allow_prereleases')
if keep_outdated:
if not project.lockfile_exists:
click.echo(
'{0}: Pipfile.lock must exist to use --keep-outdated!'.format(
crayons.red('Warning', bold=True)
)
)
sys.exit(1)
cached_lockfile = project.lockfile_content
# Create the lockfile.
lockfile = project._lockfile
# Cleanup lockfile.
for section in ('default', 'develop'):
for k, v in lockfile[section].copy().items():
if not hasattr(v, 'keys'):
del lockfile[section][k]
# Ensure that develop inherits from default.
dev_packages = project.dev_packages.copy()
for dev_package in project.dev_packages:
if dev_package in project.packages:
dev_packages[dev_package] = project.packages[dev_package]
# Resolve dev-package dependencies, with pip-tools.
pip_freeze = delegator.run(
'{0} freeze'.format(escape_grouped_arguments(which_pip(allow_global=system)))
).out
sections = {
'dev': {
'packages': project.dev_packages,
'vcs': project.vcs_dev_packages,
'pipfile_key': 'dev_packages',
'lockfile_key': 'develop',
'log_string': 'dev-packages',
'dev': True
},
'default': {
'packages': project.packages,
'vcs': project.vcs_packages,
'pipfile_key': 'packages',
'lockfile_key': 'default',
'log_string': 'packages',
'dev': False
}
}
for section_name in ['dev', 'default']:
settings = sections[section_name]
if write:
# Alert the user of progress.
click.echo(
u'{0} {1} {2}'.format(
crayons.normal('Locking'),
crayons.red('[{0}]'.format(settings['log_string'])),
crayons.normal('dependencies...'),
),
err=True,
)
deps = convert_deps_to_pip(
settings['packages'], project, r=False, include_index=True
)
results = venv_resolve_deps(
deps,
which=which,
verbose=verbose,
project=project,
clear=clear,
pre=pre,
allow_global=system,
pypi_mirror=pypi_mirror,
)
# Add dependencies to lockfile.
for dep in results:
is_top_level = dep['name'] in settings['packages']
pipfile_entry = settings['packages'][dep['name']] if is_top_level else None
dep_lockfile = clean_resolved_dep(dep, is_top_level=is_top_level, pipfile_entry=pipfile_entry)
lockfile[settings['lockfile_key']].update(dep_lockfile)
# Add refs for VCS installs.
# TODO: be smarter about this.
vcs_reqs, vcs_lockfile = get_vcs_deps(
project,
pip_freeze,
which=which,
verbose=verbose,
clear=clear,
pre=pre,
allow_global=system,
dev=settings['dev']
)
vcs_lines = [req.as_line() for req in vcs_reqs if req.editable]
vcs_results = venv_resolve_deps(
vcs_lines,
which=which,
verbose=verbose,
project=project,
clear=clear,
pre=pre,
allow_global=system,
pypi_mirror=pypi_mirror,
)
for dep in vcs_results:
normalized = pep423_name(dep['name'])
if not hasattr(dep, 'keys') or not hasattr(dep['name'], 'keys'):
continue
is_top_level = (
dep['name'] in vcs_lockfile or
normalized in vcs_lockfile
)
if is_top_level:
try:
pipfile_entry = vcs_lockfile[dep['name']]
except KeyError:
pipfile_entry = vcs_lockfile[normalized]
else:
pipfile_entry = None
dep_lockfile = clean_resolved_dep(
dep, is_top_level=is_top_level, pipfile_entry=pipfile_entry,
)
vcs_lockfile.update(dep_lockfile)
lockfile[settings['lockfile_key']].update(vcs_lockfile)
# Support for --keep-outdated...
if keep_outdated:
for section_name, section in (
('default', project.packages), ('develop', project.dev_packages)
):
for package_specified in section:
norm_name = pep423_name(package_specified)
if not is_pinned(section[package_specified]):
if norm_name in cached_lockfile[section_name]:
lockfile[section_name][norm_name] = cached_lockfile[
section_name
][
norm_name
]
# Overwrite any develop packages with default packages.
for default_package in lockfile['default']:
if default_package in lockfile['develop']:
lockfile['develop'][default_package] = lockfile['default'][
default_package
]
if write:
project.write_lockfile(lockfile)
click.echo(
'{0}'.format(
crayons.normal(
'Updated Pipfile.lock ({0})!'.format(
lockfile['_meta'].get('hash', {}).get('sha256')[-6:]
),
bold=True,
)
),
err=True,
)
else:
return lockfile
def do_purge(bare=False, downloads=False, allow_global=False, verbose=False):
"""Executes the purge functionality."""
from .vendor.requirementslib.models.requirements import Requirement
if downloads:
if not bare:
click.echo(
crayons.normal(u'Clearing out downloads directory...', bold=True)
)
shutil.rmtree(project.download_location)
return
freeze = delegator.run(
'{0} freeze'.format(
escape_grouped_arguments(which_pip(allow_global=allow_global))
)
).out
# Remove comments from the output, if any.
installed = [
line
for line in freeze.splitlines()
if not line.lstrip().startswith('#')
]
# Remove setuptools and friends from installed, if present.
for package_name in BAD_PACKAGES:
for i, package in enumerate(installed):
if package.startswith(package_name):
del installed[i]
actually_installed = []
for package in installed:
try:
dep = Requirement.from_line(package)
except AssertionError:
dep = None
if dep and not dep.is_vcs and not dep.editable:
dep = dep.name
actually_installed.append(dep)
if not bare:
click.echo(
u'Found {0} installed package(s), purging...'.format(
len(actually_installed)
)
)
command = '{0} uninstall {1} -y'.format(
escape_grouped_arguments(which_pip(allow_global=allow_global)),
' '.join(actually_installed),
)
if verbose:
click.echo('$ {0}'.format(command))
c = delegator.run(command)
if not bare:
click.echo(crayons.blue(c.out))
click.echo(crayons.green('Environment now purged and fresh!'))
def do_init(
dev=False,
requirements=False,
allow_global=False,
ignore_pipfile=False,
skip_lock=False,
verbose=False,
system=False,
concurrent=True,
deploy=False,
pre=False,
keep_outdated=False,
requirements_dir=None,
pypi_mirror=None,
):
"""Executes the init functionality."""
from .environments import PIPENV_VIRTUALENV
cleanup_reqdir = False
if not system:
if not project.virtualenv_exists:
try:
do_create_virtualenv(pypi_mirror=pypi_mirror)
except KeyboardInterrupt:
cleanup_virtualenv(bare=False)
sys.exit(1)
# Ensure the Pipfile exists.
if not deploy:
ensure_pipfile(system=system)
if not requirements_dir:
cleanup_reqdir = True
requirements_dir = TemporaryDirectory(
suffix='-requirements', prefix='pipenv-'
)
# Write out the lockfile if it doesn't exist, but not if the Pipfile is being ignored
if (project.lockfile_exists and not ignore_pipfile) and not skip_lock:
old_hash = project.get_lockfile_hash()
new_hash = project.calculate_pipfile_hash()
if new_hash != old_hash:
if deploy:
click.echo(
crayons.red(
'Your Pipfile.lock ({0}) is out of date. Expected: ({1}).'.format(
old_hash[-6:], new_hash[-6:]
)
)
)
click.echo(
crayons.normal('Aborting deploy.', bold=True), err=True
)
requirements_dir.cleanup()
sys.exit(1)
elif (system or allow_global) and not (PIPENV_VIRTUALENV):
click.echo(
crayons.red(
u'Pipfile.lock ({0}) out of date, but installation '
u'uses {1}... re-building lockfile must happen in '
u'isolation. Please rebuild lockfile in a virtualenv. '
u'Continuing anyway...'.format(
crayons.white(old_hash[-6:]),
crayons.white('--system')
),
bold=True,
),
err=True,
)
else:
click.echo(
crayons.red(
u'Pipfile.lock ({0}) out of date, updating to ({1})...'.format(
old_hash[-6:], new_hash[-6:]
),
bold=True,
),
err=True,
)
do_lock(system=system, pre=pre, keep_outdated=keep_outdated, write=True, pypi_mirror=pypi_mirror)
# Write out the lockfile if it doesn't exist.
if not project.lockfile_exists and not skip_lock:
# Unless we're in a virtualenv not managed by pipenv, abort if we're
# using the system's python.
if (system or allow_global) and not (PIPENV_VIRTUALENV):
click.echo(
'{0}: --system is intended to be used for Pipfile installation, '
'not installation of specific packages. Aborting.'.format(
crayons.red('Warning', bold=True)
),
err=True,
)
click.echo('See also: --deploy flag.', err=True)
requirements_dir.cleanup()
sys.exit(1)
else:
click.echo(
crayons.normal(u'Pipfile.lock not found, creating...', bold=True),
err=True,
)
do_lock(
system=system,
pre=pre,
keep_outdated=keep_outdated,
verbose=verbose,
write=True,
pypi_mirror=pypi_mirror,
)
do_install_dependencies(
dev=dev,
requirements=requirements,
allow_global=allow_global,
skip_lock=skip_lock,
verbose=verbose,
concurrent=concurrent,
requirements_dir=requirements_dir.name,
pypi_mirror=pypi_mirror,
)
if cleanup_reqdir:
requirements_dir.cleanup()
# Hint the user what to do to activate the virtualenv.
if not allow_global and not deploy and 'PIPENV_ACTIVE' not in os.environ:
click.echo(
"To activate this project's virtualenv, run {0}.\n"
"Alternatively, run a command "
"inside the virtualenv with {1}.".format(
crayons.red('pipenv shell'),
crayons.red('pipenv run'),
)
)
def pip_install(
package_name=None,
r=None,
allow_global=False,
ignore_hashes=False,
no_deps=True,
verbose=False,
block=True,
index=None,
pre=False,
selective_upgrade=False,
requirements_dir=None,
extra_indexes=None,
pypi_mirror=None,
):
from notpip._internal import logger as piplogger
from notpip._vendor.pyparsing import ParseException
from .vendor.requirementslib import Requirement
if verbose:
click.echo(
crayons.normal('Installing {0!r}'.format(package_name), bold=True),
err=True,
)
piplogger.setLevel(logging.INFO)
# Create files for hash mode.
if not package_name.startswith('-e ') and (not ignore_hashes) and (
r is None
):
fd, r = tempfile.mkstemp(
prefix='pipenv-', suffix='-requirement.txt', dir=requirements_dir
)
with os.fdopen(fd, 'w') as f:
f.write(package_name)
# Install dependencies when a package is a VCS dependency.
try:
req = Requirement.from_line(
package_name.split('--hash')[0].split('--trusted-host')[0]
).vcs
except (ParseException, ValueError) as e:
click.echo('{0}: {1}'.format(crayons.red('WARNING'), e), err=True)
click.echo(
'{0}... You will have to reinstall any packages that failed to install.'.format(
crayons.red('ABORTING INSTALL')
),
err=True,
)
click.echo(
'You may have to manually run {0} when you are finished.'.format(
crayons.normal('pipenv lock', bold=True)
)
)
sys.exit(1)
if req:
no_deps = False
# Don't specify a source directory when using --system.
if not allow_global and ('PIP_SRC' not in os.environ):
src = '--src {0}'.format(
escape_grouped_arguments(project.virtualenv_src_location)
)
else:
src = ''
else:
src = ''
# Try installing for each source in project.sources.
if index:
if not is_valid_url(index):
index = project.find_source(index).get('url')
sources = [{'url': index}]
if extra_indexes:
if isinstance(extra_indexes, six.string_types):
extra_indexes = [extra_indexes]
for idx in extra_indexes:
try:
extra_src = project.find_source(idx).get('url')
except SourceNotFound:
extra_src = idx
if extra_src != index:
sources.append({'url': extra_src})
else:
for idx in project.pipfile_sources:
if idx['url'] != sources[0]['url']:
sources.append({'url': idx['url']})
else:
sources = project.pipfile_sources
if pypi_mirror:
sources = [create_mirror_source(pypi_mirror) if is_pypi_url(source['url']) else source for source in sources]
if package_name.startswith('-e '):
install_reqs = ' -e "{0}"'.format(package_name.split('-e ')[1])
elif r:
install_reqs = ' -r {0}'.format(escape_grouped_arguments(r))
else:
install_reqs = ' "{0}"'.format(package_name)
# Skip hash-checking mode, when appropriate.
if r:
with open(r) as f:
if '--hash' not in f.read():
ignore_hashes = True
else:
if '--hash' not in install_reqs:
ignore_hashes = True
verbose_flag = '--verbose' if verbose else ''
if not ignore_hashes:
install_reqs += ' --require-hashes'
no_deps = '--no-deps' if no_deps else ''
pre = '--pre' if pre else ''
quoted_pip = which_pip(allow_global=allow_global)
quoted_pip = escape_grouped_arguments(quoted_pip)
upgrade_strategy = '--upgrade --upgrade-strategy=only-if-needed' if selective_upgrade else ''
pip_command = '{0} install {4} {5} {6} {7} {3} {1} {2} --exists-action w'.format(
quoted_pip,
install_reqs,
' '.join(prepare_pip_source_args(sources)),
no_deps,
pre,
src,
verbose_flag,
upgrade_strategy,
)
if verbose:
click.echo('$ {0}'.format(pip_command), err=True)
cache_dir = Path(PIPENV_CACHE_DIR)
pip_config = {
'PIP_CACHE_DIR': fs_str(cache_dir.as_posix()),
'PIP_WHEEL_DIR': fs_str(cache_dir.joinpath('wheels').as_posix()),
'PIP_DESTINATION_DIR': fs_str(cache_dir.joinpath('pkgs').as_posix()),
}
c = delegator.run(pip_command, block=block, env=pip_config)
return c
def pip_download(package_name):
cache_dir = Path(PIPENV_CACHE_DIR)
pip_config = {
'PIP_CACHE_DIR': fs_str(cache_dir.as_posix()),
'PIP_WHEEL_DIR': fs_str(cache_dir.joinpath('wheels').as_posix()),
'PIP_DESTINATION_DIR': fs_str(cache_dir.joinpath('pkgs').as_posix()),
}
for source in project.sources:
cmd = '{0} download "{1}" -i {2} -d {3}'.format(
escape_grouped_arguments(which_pip()),
package_name,
source['url'],
project.download_location,
)
c = delegator.run(cmd, env=pip_config)
if c.return_code == 0:
break
return c
def which_pip(allow_global=False):
"""Returns the location of virtualenv-installed pip."""
if allow_global:
if 'VIRTUAL_ENV' in os.environ:
return which('pip', location=os.environ['VIRTUAL_ENV'])
for p in ('pip', 'pip3', 'pip2'):
where = system_which(p)
if where:
return where
return which('pip')
def system_which(command, mult=False):
"""Emulates the system's which. Returns None if not found."""
_which = 'which -a' if not os.name == 'nt' else 'where'
c = delegator.run('{0} {1}'.format(_which, command))
try:
# Which Not found...
if c.return_code == 127:
click.echo(
'{}: the {} system utility is required for Pipenv to find Python installations properly.'
'\n Please install it.'.format(
crayons.red('Warning', bold=True), crayons.red(_which)
),
err=True,
)
assert c.return_code == 0
except AssertionError:
return None if not mult else []
result = c.out.strip() or c.err.strip()
if mult:
return result.split('\n')
else:
return result.split('\n')[0]
def format_help(help):
"""Formats the help string."""
help = help.replace('Options:', str(crayons.normal('Options:', bold=True)))
help = help.replace(
'Usage: pipenv',
str('Usage: {0}'.format(crayons.normal('pipenv', bold=True))),
)
help = help.replace(' check', str(crayons.red(' check', bold=True)))
help = help.replace(' clean', str(crayons.red(' clean', bold=True)))
help = help.replace(' graph', str(crayons.red(' graph', bold=True)))
help = help.replace(
' install', str(crayons.magenta(' install', bold=True))
)
help = help.replace(' lock', str(crayons.green(' lock', bold=True)))
help = help.replace(' open', str(crayons.red(' open', bold=True)))
help = help.replace(' run', str(crayons.yellow(' run', bold=True)))
help = help.replace(' shell', str(crayons.yellow(' shell', bold=True)))
help = help.replace(' sync', str(crayons.green(' sync', bold=True)))
help = help.replace(
' uninstall', str(crayons.magenta(' uninstall', bold=True))
)
help = help.replace(' update', str(crayons.green(' update', bold=True)))
additional_help = """
Usage Examples:
Create a new project using Python 3.6, specifically:
$ {1}
Install all dependencies for a project (including dev):
$ {2}
Create a lockfile containing pre-releases:
$ {6}
Show a graph of your installed dependencies:
$ {4}
Check your installed dependencies for security vulnerabilities:
$ {7}
Install a local setup.py into your virtual environment/Pipfile:
$ {5}
Use a lower-level pip command:
$ {8}
Commands:""".format(
crayons.red('pipenv --three'),
crayons.red('pipenv --python 3.6'),
crayons.red('pipenv install --dev'),
crayons.red('pipenv lock'),
crayons.red('pipenv graph'),
crayons.red('pipenv install -e .'),
crayons.red('pipenv lock --pre'),
crayons.red('pipenv check'),
crayons.red('pipenv run pip freeze'),
)
help = help.replace('Commands:', additional_help)
return help
def format_pip_error(error):
error = error.replace(
'Expected', str(crayons.green('Expected', bold=True))
)
error = error.replace('Got', str(crayons.red('Got', bold=True)))
error = error.replace(
'THESE PACKAGES DO NOT MATCH THE HASHES FROM THE REQUIREMENTS FILE',
str(
crayons.red(
'THESE PACKAGES DO NOT MATCH THE HASHES FROM Pipfile.lock!',
bold=True,
)
),
)
error = error.replace(
'someone may have tampered with them',
str(crayons.red('someone may have tampered with them')),
)
error = error.replace(
'option to pip install', 'option to \'pipenv install\''
)
return error
def format_pip_output(out, r=None):
def gen(out):
for line in out.split('\n'):
# Remove requirements file information from pip9 output.
if '(from -r' in line:
yield line[: line.index('(from -r')]
else:
yield line
out = '\n'.join([l for l in gen(out)])
return out
def warn_in_virtualenv():
from .environments import PIPENV_USE_SYSTEM, PIPENV_VIRTUALENV
# Only warn if pipenv isn't already active.
pipenv_active = os.environ.get('PIPENV_ACTIVE')
if (PIPENV_USE_SYSTEM or PIPENV_VIRTUALENV) and not pipenv_active:
click.echo(
'{0}: Pipenv found itself running within a virtual environment, '
'so it will automatically use that environment, instead of '
'creating its own for any project. You can set '
'{1} to force pipenv to ignore that environment and create '
'its own instead.'.format(
crayons.green('Courtesy Notice'),
crayons.normal('PIPENV_IGNORE_VIRTUALENVS=1', bold=True),
),
err=True,
)
def ensure_lockfile(keep_outdated=False, pypi_mirror=None):
"""Ensures that the lockfile is up-to-date."""
if not keep_outdated:
keep_outdated = project.settings.get('keep_outdated')
# Write out the lockfile if it doesn't exist, but not if the Pipfile is being ignored
if project.lockfile_exists:
old_hash = project.get_lockfile_hash()
new_hash = project.calculate_pipfile_hash()
if new_hash != old_hash:
click.echo(
crayons.red(
u'Pipfile.lock ({0}) out of date, updating to ({1})...'.format(
old_hash[-6:], new_hash[-6:]
),
bold=True,
),
err=True,
)
do_lock(keep_outdated=keep_outdated, pypi_mirror=pypi_mirror)
else:
do_lock(keep_outdated=keep_outdated, pypi_mirror=pypi_mirror)
def do_py(system=False):
try:
click.echo(which('python', allow_global=system))
except AttributeError:
click.echo(crayons.red('No project found!'))
def do_outdated(pypi_mirror=None):
from .vendor.requirementslib import Requirement
packages = {}
results = delegator.run('{0} freeze'.format(which('pip'))).out.strip(
).split(
'\n'
)
results = filter(bool, results)
for result in results:
dep = Requirement.from_line(result)
packages.update(dep.as_pipfile())
updated_packages = {}
lockfile = do_lock(write=False, pypi_mirror=pypi_mirror)
for section in ('develop', 'default'):
for package in lockfile[section]:
try:
updated_packages[package] = lockfile[section][package][
'version'
]
except KeyError:
pass
outdated = []
for package in packages:
norm_name = pep423_name(package)
if norm_name in updated_packages:
if updated_packages[norm_name] != packages[package]:
outdated.append(
(package, updated_packages[norm_name], packages[package])
)
for package, new_version, old_version in outdated:
click.echo(
'Package {0!r} out-of-date: {1!r} installed, {2!r} available.'.format(
package, old_version, new_version
)
)
sys.exit(bool(outdated))
def do_install(
package_name=False,
more_packages=False,
dev=False,
three=False,
python=False,
pypi_mirror=None,
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 .environments import PIPENV_VIRTUALENV, PIPENV_USE_SYSTEM
from notpip._internal.exceptions import PipError
requirements_directory = TemporaryDirectory(
suffix='-requirements', prefix='pipenv-'
)
if selective_upgrade:
keep_outdated = True
more_packages = more_packages or []
# Don't search for requirements.txt files if the user provides one
if requirements or package_name or project.pipfile_exists:
skip_requirements = True
else:
skip_requirements = False
concurrent = (not sequential)
# Ensure that virtualenv is available.
ensure_project(
three=three,
python=python,
system=system,
warn=True,
deploy=deploy,
skip_requirements=skip_requirements,
pypi_mirror=pypi_mirror,
)
# Load the --pre settings from the Pipfile.
if not pre:
pre = project.settings.get('allow_prereleases')
if not keep_outdated:
keep_outdated = project.settings.get('keep_outdated')
remote = requirements and is_valid_url(requirements)
# Warn and exit if --system is used without a pipfile.
if (system and package_name) and not (PIPENV_VIRTUALENV):
click.echo(
'{0}: --system is intended to be used for Pipfile installation, '
'not installation of specific packages. Aborting.'.format(
crayons.red('Warning', bold=True)
),
err=True,
)
click.echo('See also: --deploy flag.', err=True)
requirements_directory.cleanup()
sys.exit(1)
# Automatically use an activated virtualenv.
if PIPENV_USE_SYSTEM:
system = True
# Check if the file is remote or not
if remote:
fd, temp_reqs = tempfile.mkstemp(
prefix='pipenv-',
suffix='-requirement.txt',
dir=requirements_directory.name,
)
requirements_url = requirements
# Download requirements file
click.echo(
crayons.normal(
u'Remote requirements file provided! Downloading...', bold=True
),
err=True,
)
try:
download_file(requirements, temp_reqs)
except IOError:
click.echo(
crayons.red(
u'Unable to find requirements file at {0}.'.format(
crayons.normal(requirements)
)
),
err=True,
)
requirements_directory.cleanup()
sys.exit(1)
# Replace the url with the temporary requirements file
requirements = temp_reqs
remote = True
if requirements:
error, traceback = None, None
click.echo(
crayons.normal(
u'Requirements file provided! Importing into Pipfile...',
bold=True,
),
err=True,
)
try:
import_requirements(r=project.path_to(requirements), dev=dev)
except (UnicodeDecodeError, PipError) as e:
# Don't print the temp file path if remote since it will be deleted.
req_path = requirements_url if remote else project.path_to(
requirements
)
error = (
u'Unexpected syntax in {0}. Are you sure this is a '
'requirements.txt style file?'.format(req_path)
)
traceback = e
except AssertionError as e:
error = (
u'Requirements file doesn\'t appear to exist. Please ensure the file exists in your '
'project directory or you provided the correct path.'
)
traceback = e
finally:
# If requirements file was provided by remote url delete the temporary file
if remote:
os.close(fd) # Close for windows to allow file cleanup.
os.remove(project.path_to(temp_reqs))
if error and traceback:
click.echo(crayons.red(error))
click.echo(crayons.blue(str(traceback)), err=True)
requirements_directory.cleanup()
sys.exit(1)
if code:
click.echo(
crayons.normal(
u'Discovering imports from local codebase...', bold=True
)
)
for req in import_from_code(code):
click.echo(' Found {0}!'.format(crayons.green(req)))
project.add_package_to_pipfile(req)
# Capture -e argument and assign it to following package_name.
more_packages = list(more_packages)
if package_name == '-e':
if not more_packages:
raise click.BadArgumentUsage('Please provide path to editable package')
package_name = ' '.join([package_name, more_packages.pop(0)])
# capture indexes and extra indexes
line = [package_name] + more_packages
line = ' '.join(str(s) for s in line).strip()
index_indicators = ['-i', '--index', '--extra-index-url']
index, extra_indexes = None, None
if any(line.endswith(s) for s in index_indicators):
# check if cli option is not end of command
raise click.BadArgumentUsage('Please provide index value')
if any(s in line for s in index_indicators):
line, index = split_argument(line, short='i', long_='index', num=1)
line, extra_indexes = split_argument(line, long_='extra-index-url')
package_names = line.split()
package_name = package_names[0]
if len(package_names) > 1:
more_packages = package_names[1:]
else:
more_packages = []
# Capture . argument and assign it to nothing
if package_name == '.':
package_name = False
# Install editable local packages before locking - this gives us access to dist-info
if project.pipfile_exists and (
# double negatives are for english readability, leave them alone.
(not project.lockfile_exists and not deploy) or (not project.virtualenv_exists and not system)
):
section = project.editable_packages if not dev else project.editable_dev_packages
for package in section.keys():
converted = convert_deps_to_pip(
{package: section[package]}, project=project, r=False
)
if not package_name:
if converted:
package_name = converted.pop(0)
if converted:
more_packages.extend(converted)
# Allow more than one package to be provided.
package_names = [package_name] + more_packages
# Support for --selective-upgrade.
# We should do this part first to make sure that we actually do selectively upgrade
# the items specified
if selective_upgrade:
from .vendor.requirementslib import Requirement
for i, package_name in enumerate(package_names[:]):
section = project.packages if not dev else project.dev_packages
package = Requirement.from_line(package_name)
package__name, package__val = package.pipfile_entry
try:
if not is_star(section[package__name]) and is_star(
package__val
):
# Support for VCS dependencies.
package_names[i] = convert_deps_to_pip(
{package_name: section[package__name]},
project=project,
r=False,
)[
0
]
except KeyError:
pass
# Install all dependencies, if none was provided.
# This basically ensures that we have a pipfile and lockfile, then it locks and
# installs from the lockfile
if package_name is False:
# Update project settings with pre preference.
if pre:
project.update_settings({'allow_prereleases': pre})
do_init(
dev=dev,
allow_global=system,
ignore_pipfile=ignore_pipfile,
system=system,
skip_lock=skip_lock,
verbose=verbose,
concurrent=concurrent,
deploy=deploy,
pre=pre,
requirements_dir=requirements_directory,
pypi_mirror=pypi_mirror,
)
# This is for if the user passed in dependencies, then we want to maek sure we
else:
from .vendor.requirementslib import Requirement
for package_name in package_names:
click.echo(
crayons.normal(
u'Installing {0}...'.format(
crayons.green(package_name, bold=True)
),
bold=True,
)
)
# pip install:
with spinner():
c = pip_install(
package_name,
ignore_hashes=True,
allow_global=system,
selective_upgrade=selective_upgrade,
no_deps=False,
verbose=verbose,
pre=pre,
requirements_dir=requirements_directory.name,
index=index,
extra_indexes=extra_indexes,
pypi_mirror=pypi_mirror,
)
# Warn if --editable wasn't passed.
try:
converted = Requirement.from_line(package_name)
except ValueError as e:
click.echo('{0}: {1}'.format(crayons.red('WARNING'), e))
requirements_directory.cleanup()
sys.exit(1)
if converted.is_vcs and not converted.editable:
click.echo(
'{0}: You installed a VCS dependency in non-editable mode. '
'This will work fine, but sub-dependencies will not be resolved by {1}.'
'\n To enable this sub-dependency functionality, specify that this dependency is editable.'
''.format(
crayons.red('Warning', bold=True),
crayons.red('$ pipenv lock'),
)
)
click.echo(crayons.blue(format_pip_output(c.out)))
# Ensure that package was successfully installed.
try:
assert c.return_code == 0
except AssertionError:
click.echo(
'{0} An error occurred while installing {1}!'.format(
crayons.red('Error: ', bold=True),
crayons.green(package_name),
),
err=True,
)
click.echo(crayons.blue(format_pip_error(c.err)), err=True)
if 'setup.py egg_info' in c.err:
click.echo(
"This is likely caused by a bug in {0}. "
"Report this to its maintainers.".format(
crayons.green(package_name),
),
err=True,
)
requirements_directory.cleanup()
sys.exit(1)
click.echo(
'{0} {1} {2} {3}{4}'.format(
crayons.normal('Adding', bold=True),
crayons.green(package_name, bold=True),
crayons.normal("to Pipfile's", bold=True),
crayons.red(
'[dev-packages]' if dev else '[packages]', bold=True
),
crayons.normal('...', bold=True),
)
)
# Add the package to the Pipfile.
try:
project.add_package_to_pipfile(package_name, dev)
except ValueError as e:
click.echo(
'{0} {1}'.format(
crayons.red('ERROR (PACKAGE NOT INSTALLED):'), e
)
)
# Update project settings with pre preference.
if pre:
project.update_settings({'allow_prereleases': pre})
do_init(
dev=dev,
system=system,
allow_global=system,
concurrent=concurrent,
verbose=verbose,
keep_outdated=keep_outdated,
requirements_dir=requirements_directory,
deploy=deploy,
pypi_mirror=pypi_mirror,
skip_lock=skip_lock
)
requirements_directory.cleanup()
sys.exit(0)
def do_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,
pypi_mirror=None,
):
from .environments import PIPENV_USE_SYSTEM
# Automatically use an activated virtualenv.
if PIPENV_USE_SYSTEM:
system = True
# Ensure that virtualenv is available.
ensure_project(three=three, python=python, pypi_mirror=pypi_mirror)
package_names = (package_name,) + more_packages
pipfile_remove = True
# Un-install all dependencies, if --all was provided.
if all is True:
click.echo(
crayons.normal(
u'Un-installing all packages from virtualenv...', bold=True
)
)
do_purge(allow_global=system, verbose=verbose)
sys.exit(0)
# Uninstall [dev-packages], if --dev was provided.
if all_dev:
if 'dev-packages' not in project.parsed_pipfile:
click.echo(
crayons.normal(
'No {0} to uninstall.'.format(
crayons.red('[dev-packages]')
),
bold=True,
)
)
sys.exit(0)
click.echo(
crayons.normal(
u'Un-installing {0}...'.format(crayons.red('[dev-packages]')),
bold=True,
)
)
package_names = project.dev_packages.keys()
if package_name is False and not all_dev:
click.echo(crayons.red('No package provided!'), err=True)
sys.exit(1)
for package_name in package_names:
click.echo(u'Un-installing {0}...'.format(crayons.green(package_name)))
cmd = '{0} uninstall {1} -y'.format(
escape_grouped_arguments(which_pip(allow_global=system)),
package_name,
)
if verbose:
click.echo('$ {0}'.format(cmd))
c = delegator.run(cmd)
click.echo(crayons.blue(c.out))
if pipfile_remove:
in_packages = project.get_package_name_in_pipfile(
package_name, dev=False)
in_dev_packages = project.get_package_name_in_pipfile(
package_name, dev=True)
if not in_dev_packages and not in_packages:
click.echo(
'No package {0} to remove from Pipfile.'.format(
crayons.green(package_name)
)
)
continue
click.echo(
u'Removing {0} from Pipfile...'.format(
crayons.green(package_name)
)
)
# Remove package from both packages and dev-packages.
project.remove_package_from_pipfile(package_name, dev=True)
project.remove_package_from_pipfile(package_name, dev=False)
if lock:
do_lock(system=system, keep_outdated=keep_outdated, pypi_mirror=pypi_mirror)
def do_shell(three=None, python=False, fancy=False, shell_args=None, pypi_mirror=None):
# Ensure that virtualenv is available.
ensure_project(three=three, python=python, validate=False, pypi_mirror=pypi_mirror)
# Set an environment variable, so we know we're in the environment.
os.environ['PIPENV_ACTIVE'] = '1'
# Support shell compatibility mode.
if PIPENV_SHELL_FANCY:
fancy = True
from .shells import choose_shell
shell = choose_shell()
click.echo("Launching subshell in virtual environment…", err=True)
fork_args = (
project.virtualenv_location,
project.project_directory,
shell_args,
)
if fancy:
shell.fork(*fork_args)
return
try:
shell.fork_compat(*fork_args)
except (AttributeError, ImportError):
click.echo(
u'Compatibility mode not supported. '
u'Trying to continue as well-configured shell…',
err=True,
)
shell.fork(*fork_args)
def inline_activate_virtualenv():
try:
activate_this = which('activate_this.py')
if not activate_this or not os.path.exists(activate_this):
click.echo(
u'{0}: activate_this.py not found. Your environment is most '
u'certainly not activated. Continuing anyway...'
u''.format(crayons.red('Warning', bold=True)),
err=True,
)
return
with open(activate_this) as f:
code = compile(f.read(), activate_this, 'exec')
exec(code, dict(__file__=activate_this))
# Catch all errors, just in case.
except Exception:
click.echo(
u'{0}: There was an unexpected error while activating your virtualenv. Continuing anyway...'
''.format(crayons.red('Warning', bold=True)),
err=True,
)
def do_run_nt(script):
import subprocess
command = system_which(script.command)
options = {'universal_newlines': True}
if command: # Try to use CreateProcess directly if possible.
p = subprocess.Popen([command] + script.args, **options)
else: # Command not found, maybe this is a shell built-in?
p = subprocess.Popen(script.cmdify(), shell=True, **options)
p.communicate()
sys.exit(p.returncode)
def do_run_posix(script, command):
command_path = system_which(script.command)
if not command_path:
if project.has_script(command):
click.echo(
'{0}: the command {1} (from {2}) could not be found within {3}.'
''.format(
crayons.red('Error', bold=True),
crayons.red(script.command),
crayons.normal(command, bold=True),
crayons.normal('PATH', bold=True),
),
err=True,
)
else:
click.echo(
'{0}: the command {1} could not be found within {2} or Pipfile\'s {3}.'
''.format(
crayons.red('Error', bold=True),
crayons.red(command),
crayons.normal('PATH', bold=True),
crayons.normal('[scripts]', bold=True),
),
err=True,
)
sys.exit(1)
os.execl(command_path, command_path, *script.args)
def do_run(command, args, three=None, python=False, pypi_mirror=None):
"""Attempt to run command either pulling from project or interpreting as executable.
Args are appended to the command in [scripts] section of project if found.
"""
from .cmdparse import ScriptEmptyError
# Ensure that virtualenv is available.
ensure_project(three=three, python=python, validate=False, pypi_mirror=pypi_mirror)
load_dot_env()
# Activate virtualenv under the current interpreter's environment
inline_activate_virtualenv()
try:
script = project.build_script(command, args)
except ScriptEmptyError:
click.echo("Can't run script {0!r}-it's empty?", err=True)
if os.name == 'nt':
do_run_nt(script)
else:
do_run_posix(script, command=command)
def do_check(three=None, python=False, system=False, unused=False, ignore=None, args=None, pypi_mirror=None):
if not system:
# Ensure that virtualenv is available.
ensure_project(three=three, python=python, validate=False, warn=False, pypi_mirror=pypi_mirror)
if not args:
args = []
if unused:
deps_required = [k for k in project.packages.keys()]
deps_needed = import_from_code(unused)
for dep in deps_needed:
try:
deps_required.remove(dep)
except ValueError:
pass
if deps_required:
click.echo(
crayons.normal(
'The following dependencies appear unused, and may be safe for removal:'
)
)
for dep in deps_required:
click.echo(' - {0}'.format(crayons.green(dep)))
sys.exit(1)
else:
sys.exit(0)
click.echo(crayons.normal(u'Checking PEP 508 requirements...', bold=True))
if system:
python = system_which('python')
else:
python = which('python')
# Run the PEP 508 checker in the virtualenv.
c = delegator.run(
'"{0}" {1}'.format(
python,
escape_grouped_arguments(pep508checker.__file__.rstrip('cdo')),
)
)
results = simplejson.loads(c.out)
# Load the pipfile.
p = pipfile.Pipfile.load(project.pipfile_location)
failed = False
# Assert each specified requirement.
for marker, specifier in p.data['_meta']['requires'].items():
if marker in results:
try:
assert results[marker] == specifier
except AssertionError:
failed = True
click.echo(
'Specifier {0} does not match {1} ({2}).'
''.format(
crayons.green(marker),
crayons.blue(specifier),
crayons.red(results[marker]),
),
err=True,
)
if failed:
click.echo(crayons.red('Failed!'), err=True)
sys.exit(1)
else:
click.echo(crayons.green('Passed!'))
click.echo(
crayons.normal(u'Checking installed package safety...', bold=True)
)
path = pep508checker.__file__.rstrip('cdo')
path = os.sep.join(__file__.split(os.sep)[:-1] + ['patched', 'safety.zip'])
if not system:
python = which('python')
else:
python = system_which('python')
if ignore:
ignored = '--ignore {0}'.format('--ignore '.join(ignore))
click.echo(crayons.normal('Notice: Ignoring CVE(s) {0}'.format(crayons.yellow(', '.join(ignore)))), err=True)
else:
ignored = ''
c = delegator.run(
'"{0}" {1} check --json --key=1ab8d58f-5122e025-83674263-bc1e79e0 {2}'.format(
python, escape_grouped_arguments(path), ignored
)
)
try:
results = simplejson.loads(c.out)
except ValueError:
click.echo('An error occurred:', err=True)
click.echo(c.err, err=True)
sys.exit(1)
for (package, resolved, installed, description, vuln) in results:
click.echo(
'{0}: {1} {2} resolved ({3} installed)!'.format(
crayons.normal(vuln, bold=True),
crayons.green(package),
crayons.red(resolved, bold=False),
crayons.red(installed, bold=True),
)
)
click.echo('{0}'.format(description))
click.echo()
if not results:
click.echo(crayons.green('All good!'))
else:
sys.exit(1)
def do_graph(bare=False, json=False, json_tree=False, reverse=False):
import pipdeptree
try:
python_path = which('python')
except AttributeError:
click.echo(
u'{0}: {1}'.format(
crayons.red('Warning', bold=True),
u'Unable to display currently-installed dependency graph information here. '
u'Please run within a Pipenv project.',
),
err=True,
)
sys.exit(1)
if reverse and json:
click.echo(
u'{0}: {1}'.format(
crayons.red('Warning', bold=True),
u'Using both --reverse and --json together is not supported. '
u'Please select one of the two options.',
),
err=True,
)
sys.exit(1)
if reverse and json_tree:
click.echo(
u'{0}: {1}'.format(
crayons.red('Warning', bold=True),
u'Using both --reverse and --json-tree together is not supported. '
u'Please select one of the two options.',
),
err=True,
)
sys.exit(1)
if json and json_tree:
click.echo(
u'{0}: {1}'.format(
crayons.red('Warning', bold=True),
u'Using both --json and --json-tree together is not supported. '
u'Please select one of the two options.',
),
err=True,
)
sys.exit(1)
flag = ''
if json:
flag = '--json'
if json_tree:
flag = '--json-tree'
if reverse:
flag = '--reverse'
if not project.virtualenv_exists:
click.echo(
u'{0}: No virtualenv has been created for this project yet! Consider '
u'running {1} first to automatically generate one for you or see'
u'{2} for further instructions.'.format(
crayons.red('Warning', bold=True),
crayons.green('`pipenv install`'),
crayons.green('`pipenv install --help`'),
),
err=True,
)
sys.exit(1)
cmd = '"{0}" {1} {2}'.format(
python_path,
escape_grouped_arguments(pipdeptree.__file__.rstrip('cdo')),
flag,
)
# Run dep-tree.
c = delegator.run(cmd)
if not bare:
if json:
data = []
for d in simplejson.loads(c.out):
if d['package']['key'] not in BAD_PACKAGES:
data.append(d)
click.echo(simplejson.dumps(data, indent=4))
sys.exit(0)
elif json_tree:
def traverse(obj):
if isinstance(obj, list):
return [traverse(package) for package in obj if package['key'] not in BAD_PACKAGES]
else:
obj['dependencies'] = traverse(obj['dependencies'])
return obj
data = traverse(simplejson.loads(c.out))
click.echo(simplejson.dumps(data, indent=4))
sys.exit(0)
else:
for line in c.out.split('\n'):
# Ignore bad packages as top level.
if line.split('==')[0] in BAD_PACKAGES and not reverse:
continue
# Bold top-level packages.
if not line.startswith(' '):
click.echo(crayons.normal(line, bold=True))
# Echo the rest.
else:
click.echo(crayons.normal(line, bold=False))
else:
click.echo(c.out)
if c.return_code != 0:
click.echo(
'{0} {1}'.format(
crayons.red('ERROR: ', bold=True),
crayons.white('{0}'.format(c.err, bold=True)),
),
err=True
)
# Return its return code.
sys.exit(c.return_code)
def do_sync(
ctx,
dev=False,
three=None,
python=None,
bare=False,
dont_upgrade=False,
user=False,
verbose=False,
clear=False,
unused=False,
sequential=False,
pypi_mirror=None,
system=False,
deploy=False,
):
# The lock file needs to exist because sync won't write to it.
if not project.lockfile_exists:
click.echo(
'{0}: Pipfile.lock is missing! You need to run {1} first.'.format(
crayons.red('Error', bold=True),
crayons.red('$ pipenv lock', bold=True),
),
err=True,
)
sys.exit(1)
# Ensure that virtualenv is available if not system.
ensure_project(three=three, python=python, validate=False, deploy=deploy, pypi_mirror=pypi_mirror)
# Install everything.
requirements_dir = TemporaryDirectory(
suffix='-requirements', prefix='pipenv-'
)
do_init(
dev=dev,
verbose=verbose,
concurrent=(not sequential),
requirements_dir=requirements_dir,
ignore_pipfile=True, # Don't check if Pipfile and lock match.
pypi_mirror=pypi_mirror,
deploy=deploy,
system=system,
)
requirements_dir.cleanup()
click.echo(crayons.green('All dependencies are now up-to-date!'))
def do_clean(
ctx, three=None, python=None, dry_run=False, bare=False, verbose=False, pypi_mirror=None
):
# Ensure that virtualenv is available.
from .vendor.requirementslib import Requirement
ensure_project(three=three, python=python, validate=False, pypi_mirror=pypi_mirror)
ensure_lockfile(pypi_mirror=pypi_mirror)
installed_package_names = []
pip_freeze_command = delegator.run('{0} freeze'.format(which_pip()))
for line in pip_freeze_command.out.split('\n'):
installed = line.strip()
if not installed or installed.startswith('#'): # Comment or empty.
continue
r = Requirement.from_line(installed).requirement
# Ignore editable installations.
if not r.editable:
installed_package_names.append(r.name.lower())
else:
if verbose:
click.echo('Ignoring {0}.'.format(repr(r.name)), err=True)
# Remove known "bad packages" from the list.
for bad_package in BAD_PACKAGES:
if bad_package in installed_package_names:
if verbose:
click.echo('Ignoring {0}.'.format(repr(bad_package)), err=True)
del installed_package_names[
installed_package_names.index(bad_package)
]
# Intelligently detect if --dev should be used or not.
develop = [k.lower() for k in project.lockfile_content['develop'].keys()]
default = [k.lower() for k in project.lockfile_content['default'].keys()]
for used_package in set(develop + default):
if used_package in installed_package_names:
del installed_package_names[
installed_package_names.index(used_package)
]
failure = False
for apparent_bad_package in installed_package_names:
if dry_run:
click.echo(apparent_bad_package)
else:
click.echo(
crayons.white(
'Uninstalling {0}...'.format(repr(apparent_bad_package)),
bold=True,
)
)
# Uninstall the package.
c = delegator.run(
'{0} uninstall {1} -y'.format(
which_pip(), apparent_bad_package
)
)
if c.return_code != 0:
failure = True
sys.exit(int(failure))