mirror of
https://github.com/kennethreitz/pipenv.git
synced 2026-06-05 22:50:18 +00:00
2515 lines
88 KiB
Python
2515 lines
88 KiB
Python
# -*- coding=utf-8 -*-
|
||
import contextlib
|
||
import logging
|
||
import os
|
||
import sys
|
||
import shutil
|
||
import time
|
||
import tempfile
|
||
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 environments, pep508checker, progress
|
||
from .environments import (
|
||
PIPENV_COLORBLIND,
|
||
PIPENV_NOSPIN,
|
||
PIPENV_SHELL_FANCY,
|
||
PIPENV_TIMEOUT,
|
||
PIPENV_SKIP_VALIDATION,
|
||
PIPENV_HIDE_EMOJIS,
|
||
PIPENV_YES,
|
||
PIPENV_DEFAULT_PYTHON_VERSION,
|
||
PIPENV_MAX_SUBPROCESS,
|
||
PIPENV_DONT_USE_PYENV,
|
||
SESSION_IS_INTERACTIVE,
|
||
PIPENV_CACHE_DIR,
|
||
)
|
||
|
||
# Packages that should be ignored later.
|
||
BAD_PACKAGES = (
|
||
"distribute",
|
||
"packaging",
|
||
"pip",
|
||
"pkg-resources",
|
||
"setuptools",
|
||
"wheel",
|
||
)
|
||
# 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 location and os.path.exists(location):
|
||
raise RuntimeError("virtualenv not created nor specified")
|
||
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 do_clear():
|
||
click.echo(crayons.white("Clearing caches…", bold=True))
|
||
try:
|
||
from pip._internal import locations
|
||
except ImportError: # pip 9.
|
||
from pip import locations
|
||
|
||
try:
|
||
shutil.rmtree(PIPENV_CACHE_DIR)
|
||
shutil.rmtree(locations.USER_CACHE_DIR)
|
||
except OSError as e:
|
||
# Ignore FileNotFoundError. This is needed for Python 2.7.
|
||
import errno
|
||
|
||
if e.errno == errno.ENOENT:
|
||
pass
|
||
raise
|
||
|
||
|
||
def load_dot_env():
|
||
"""Loads .env file into sys.environ."""
|
||
if not environments.PIPENV_DONT_LOAD_ENV:
|
||
# If the project doesn't exist yet, check current directory for a .env file
|
||
project_directory = project.project_directory or "."
|
||
dotenv_file = environments.PIPENV_DOTENV_LOCATION or os.sep.join(
|
||
[project_directory, ".env"]
|
||
)
|
||
|
||
if os.path.isfile(dotenv_file):
|
||
click.echo(
|
||
crayons.normal("Loading .env environment variables…", bold=True),
|
||
err=True,
|
||
)
|
||
else:
|
||
if environments.PIPENV_DOTENV_LOCATION:
|
||
click.echo(
|
||
"{0}: file {1}={2} does not exist!!\n{3}".format(
|
||
crayons.red("Warning", bold=True),
|
||
crayons.normal("PIPENV_DOTENV_LOCATION", bold=True),
|
||
crayons.normal(environments.PIPENV_DOTENV_LOCATION, bold=True),
|
||
crayons.red("Not loading environment variables.", bold=True),
|
||
),
|
||
err=True,
|
||
)
|
||
dotenv.load_dotenv(dotenv_file, 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_a_system_python(line):
|
||
"""Find a Python installation from a given line.
|
||
|
||
This tries to parse the line in various of ways:
|
||
|
||
* Looks like an absolute path? Use it directly.
|
||
* Looks like a py.exe call? Use py.exe to get the executable.
|
||
* Starts with "py" something? Looks like a python command. Try to find it
|
||
in PATH, and use it directly.
|
||
* Search for "python" and "pythonX.Y" executables in PATH to find a match.
|
||
* Nothing fits, return None.
|
||
"""
|
||
if not line:
|
||
return None
|
||
if os.path.isabs(line):
|
||
return line
|
||
from .vendor.pythonfinder import Finder
|
||
finder = Finder(system=False, global_search=True)
|
||
if ((line.startswith("py ") or line.startswith("py.exe "))
|
||
and os.name == 'nt'):
|
||
line = line.split(" ", 1)[1].lstrip("-")
|
||
elif line.startswith("py"):
|
||
python_entry = finder.which(line)
|
||
if python_entry:
|
||
return python_entry.path.as_posix()
|
||
return None
|
||
python_entry = finder.find_python_version(line)
|
||
if not python_entry:
|
||
python_entry = finder.which("python{0}".format(line))
|
||
if python_entry:
|
||
return python_entry.path.as_posix()
|
||
return None
|
||
|
||
|
||
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)
|
||
|
||
global USING_DEFAULT_PYTHON
|
||
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
|
||
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
|
||
from .vendor.pythonfinder.environment import PYENV_INSTALLED
|
||
if not PYENV_INSTALLED:
|
||
abort()
|
||
else:
|
||
if (not PIPENV_DONT_USE_PYENV) and (SESSION_IS_INTERACTIVE or PIPENV_YES):
|
||
from .pyenv import Runner, PyenvError
|
||
|
||
pyenv = Runner("pyenv")
|
||
try:
|
||
version = pyenv.find_version_to_install(python)
|
||
except ValueError:
|
||
abort()
|
||
except PyenvError as e:
|
||
click.echo(u"Something went wrong…")
|
||
click.echo(crayons.blue(e.err), err=True)
|
||
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():
|
||
try:
|
||
c = pyenv.install(version)
|
||
except PyenvError as e:
|
||
click.echo(u"Something went wrong…")
|
||
click.echo(crayons.blue(e.err), err=True)
|
||
# Print the results, in a beautiful blue…
|
||
click.echo(crayons.blue(c.out), err=True)
|
||
# Find the newly installed Python, hopefully.
|
||
version = str(version)
|
||
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,
|
||
clear=False,
|
||
):
|
||
"""Ensures both Pipfile and virtualenv exist for the project."""
|
||
from .environments import PIPENV_USE_SYSTEM
|
||
|
||
# Clear the caches, if appropriate.
|
||
if clear:
|
||
print("clearing")
|
||
sys.exit(1)
|
||
|
||
# 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,
|
||
)
|
||
click.echo(
|
||
" {0} and rebuilding the virtual environment "
|
||
"may resolve the issue.".format(
|
||
crayons.green("$ pipenv --rm"),
|
||
),
|
||
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,
|
||
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()))
|
||
elif environments.is_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,
|
||
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,
|
||
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,
|
||
)
|
||
|
||
# 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 = [
|
||
sys.executable,
|
||
"-m",
|
||
"virtualenv",
|
||
"--prompt=({0})".format(project.name),
|
||
"--python={0}".format(python),
|
||
project.get_location_for_virtualenv(),
|
||
]
|
||
|
||
# Pass site-packages flag to virtualenv, if desired…
|
||
if site_packages:
|
||
click.echo(
|
||
crayons.normal(u"Making site-packages available…", bold=True), err=True
|
||
)
|
||
cmd.append("--system-site-packages")
|
||
|
||
if pypi_mirror:
|
||
pip_config = {"PIP_INDEX_URL": fs_str(pypi_mirror)}
|
||
else:
|
||
pip_config = {}
|
||
|
||
# Actually create the virtualenv.
|
||
with spinner():
|
||
c = delegator.run(
|
||
cmd, block=False, timeout=PIPENV_TIMEOUT, env=pip_config,
|
||
)
|
||
c.block()
|
||
click.echo(crayons.blue("{0}".format(c.out)), err=True)
|
||
if c.return_code != 0:
|
||
click.echo(crayons.blue("{0}".format(c.err)), err=True)
|
||
click.echo(u"{0}: Failed to create virtual environment.".format(
|
||
crayons.red("Warning", bold=True),
|
||
), err=True)
|
||
sys.exit(1)
|
||
|
||
# Associate project directory with the environment.
|
||
# This mimics Pew's "setproject".
|
||
project_file_name = os.path.join(project.virtualenv_location, ".project")
|
||
with open(project_file_name, "w") as f:
|
||
f.write(fs_str(project.project_directory))
|
||
|
||
# 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(
|
||
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,
|
||
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,
|
||
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,
|
||
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):
|
||
"""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 environments.is_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,
|
||
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:
|
||
if old_hash:
|
||
msg = u"Pipfile.lock ({1}) out of date, updating to ({0})…"
|
||
else:
|
||
msg = u"Pipfile.lock is corrupted, replaced with ({0})…"
|
||
click.echo(crayons.red(
|
||
msg.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,
|
||
write=True,
|
||
pypi_mirror=pypi_mirror,
|
||
)
|
||
do_install_dependencies(
|
||
dev=dev,
|
||
requirements=requirements,
|
||
allow_global=allow_global,
|
||
skip_lock=skip_lock,
|
||
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,
|
||
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 environments.is_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
|
||
if not ignore_hashes:
|
||
install_reqs += " --require-hashes"
|
||
pip_args = {
|
||
"no_deps": "--no-deps" if no_deps else "",
|
||
"pre": "--pre" if pre else "",
|
||
"quoted_pip": escape_grouped_arguments(which_pip(allow_global=allow_global)),
|
||
"upgrade_strategy": (
|
||
"--upgrade --upgrade-strategy=only-if-needed" if selective_upgrade else ""
|
||
),
|
||
"sources": " ".join(prepare_pip_source_args(sources)),
|
||
"src": src,
|
||
"verbose_flag": "--verbose" if environments.is_verbose() else "",
|
||
"install_reqs": install_reqs
|
||
}
|
||
pip_command = "{quoted_pip} install {pre} {src} {verbose_flag} {upgrade_strategy} {no_deps} {install_reqs} {sources}".format(
|
||
**pip_args
|
||
)
|
||
if environments.is_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()),
|
||
"PIP_EXISTS_ACTION": fs_str("w"),
|
||
}
|
||
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.7, specifically:
|
||
$ {1}
|
||
|
||
Remove project virtualenv (inferred from current directory):
|
||
$ {9}
|
||
|
||
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.7"),
|
||
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"),
|
||
crayons.red("pipenv --rm"),
|
||
)
|
||
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():
|
||
# Only warn if pipenv isn't already active.
|
||
pipenv_active = os.environ.get("PIPENV_ACTIVE")
|
||
if ((environments.PIPENV_USE_SYSTEM or environments.PIPENV_VIRTUALENV) and
|
||
not (pipenv_active or environments.is_quiet())):
|
||
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. You can set {2} to suppress this "
|
||
"warning.".format(
|
||
crayons.green("Courtesy Notice"),
|
||
crayons.normal("PIPENV_IGNORE_VIRTUALENVS=1", bold=True),
|
||
crayons.normal("PIPENV_VERBOSITY=-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,
|
||
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,
|
||
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,
|
||
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,
|
||
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,
|
||
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)
|
||
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 environments.is_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 "
|
||
u"virtualenv. Continuing anyway...".format(
|
||
crayons.red("Warning", bold=True)
|
||
),
|
||
err=True,
|
||
)
|
||
|
||
|
||
def _inline_activate_venv():
|
||
"""Built-in venv doesn't have activate_this.py, but doesn't need it anyway.
|
||
|
||
As long as we find the correct executable, built-in venv sets up the
|
||
environment automatically.
|
||
|
||
See: https://bugs.python.org/issue21496#msg218455
|
||
"""
|
||
components = []
|
||
for name in ("bin", "Scripts"):
|
||
bindir = os.path.join(project.virtualenv_location, name)
|
||
if os.path.exists(bindir):
|
||
components.append(bindir)
|
||
if "PATH" in os.environ:
|
||
components.append(os.environ["PATH"])
|
||
os.environ["PATH"] = os.pathsep.join(components)
|
||
|
||
|
||
def inline_activate_virtual_environment():
|
||
root = project.virtualenv_location
|
||
if os.path.exists(os.path.join(root, "pyvenv.cfg")):
|
||
_inline_activate_venv()
|
||
else:
|
||
_inline_activate_virtualenv()
|
||
if "VIRTUAL_ENV" not in os.environ:
|
||
os.environ["VIRTUAL_ENV"] = root
|
||
|
||
|
||
def _launch_windows_subprocess(script):
|
||
import subprocess
|
||
|
||
command = system_which(script.command)
|
||
options = {"universal_newlines": True}
|
||
|
||
# Command not found, maybe this is a shell built-in?
|
||
if not command:
|
||
return subprocess.Popen(script.cmdify(), shell=True, **options)
|
||
|
||
# Try to use CreateProcess directly if possible. Specifically catch
|
||
# Windows error 193 "Command is not a valid Win32 application" to handle
|
||
# a "command" that is non-executable. See pypa/pipenv#2727.
|
||
try:
|
||
return subprocess.Popen([command] + script.args, **options)
|
||
except WindowsError as e:
|
||
if e.winerror != 193:
|
||
raise
|
||
|
||
# Try shell mode to use Windows's file association for file launch.
|
||
return subprocess.Popen(script.cmdify(), shell=True, **options)
|
||
|
||
|
||
def do_run_nt(script):
|
||
p = _launch_windows_subprocess(script)
|
||
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_virtual_environment()
|
||
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,
|
||
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,
|
||
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,
|
||
pypi_mirror=None,
|
||
):
|
||
# Ensure that virtualenv is available.
|
||
|
||
ensure_project(three=three, python=python, validate=False, pypi_mirror=pypi_mirror)
|
||
ensure_lockfile(pypi_mirror=pypi_mirror)
|
||
installed_package_names = [pkg.project_name for pkg in project.get_installed_packages()]
|
||
# Remove known "bad packages" from the list.
|
||
for bad_package in BAD_PACKAGES:
|
||
if bad_package in installed_package_names:
|
||
if environments.is_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))
|