Vendor boltons

Signed-off-by: Dan Ryan <dan@danryan.co>

Update vendored dependencies

Signed-off-by: Dan Ryan <dan@danryan.co>

Fix file handle leaks

- Fix #3020
- Fix #3088
- Patch delegator
- Add weakref finalizer for tempfiles

Signed-off-by: Dan Ryan <dan@danryan.co>

Fix spinner handlers on windows

Signed-off-by: Dan Ryan <dan@danryan.co>

Fix spinner output and encoding issue

Signed-off-by: Dan Ryan <dan@danryan.co>

fix encoding

Signed-off-by: Dan Ryan <dan@danryan.co>

Fix unicode output on windows, fix tomlkit imports

Signed-off-by: Dan Ryan <dan@danryan.co>

Unvendor boltons, fix compatibility, update merge functionalities

Signed-off-by: Dan Ryan <dan@danryan.co>

Update pythonfinder, vistir version, requirementslib version

Signed-off-by: Dan Ryan <dan@danryan.co>

Fix vendoring script

Signed-off-by: Dan Ryan <dan@danryan.co>

Silence pip version checks

Signed-off-by: Dan Ryan <dan@danryan.co>

Add debugging to locking

Signed-off-by: Dan Ryan <dan@danryan.co>
This commit is contained in:
Dan Ryan
2018-10-22 10:16:04 -04:00
parent e375eb3eaa
commit 4dac167657
42 changed files with 1339 additions and 566 deletions
+1
View File
@@ -0,0 +1 @@
Fixed a bug which caused ``Unexpected EOF`` errors to be thrown when PIP awaited input from users who put login credentials in their environment.
+1
View File
@@ -0,0 +1 @@
Added windows-compatible spinner via upgraded ``vistir`` dependency.
+11
View File
@@ -0,0 +1,11 @@
Updated vendored dependencies:
- ``certifi 2018.08.24 => 2018.10.15``
- ``urllib3 1.23 => 1.24``
- ``requests 2.19.1 => 2.20.0``
- ``shellingham ``1.2.6 => 1.2.7``
- ``tomlkit 0.4.4. => 0.4.6``
- ``vistir 0.1.6 => 0.1.8``
- ``pythonfinder 0.1.2 => 0.1.3``
- ``requirementslib 1.1.9 => 1.1.10``
- ``backports.functools_lru_cache 1.5.0 (new)``
- ``cursor 1.2.0 (new)``
+1
View File
@@ -14,6 +14,7 @@ PIPENV_PATCHED = os.sep.join([PIPENV_ROOT, "patched"])
sys.path.insert(0, PIPENV_VENDOR)
# Inject patched directory into system path.
sys.path.insert(0, PIPENV_PATCHED)
os.environ["PIP_DISABLE_PIP_VERSION_CHECK"] = "1"
# Hack to make things work better.
try:
if "concurrency" in sys.modules:
+3 -3
View File
@@ -59,10 +59,10 @@ except ImportError:
return False
if six.PY2:
from vistir.compat import ResourceWarning
class ResourceWarning(Warning):
pass
warnings.filterwarnings("ignore", category=ResourceWarning)
def pip_import(module_path, subimport=None, old_path=None):
+283 -252
View File
File diff suppressed because it is too large Load Diff
+8 -1
View File
@@ -1,7 +1,9 @@
# -*- coding=utf-8 -*-
import os
import sys
from appdirs import user_cache_dir
from .vendor.vistir.misc import fs_str
from .vendor.vistir.misc import fs_str, to_text
# HACK: avoid resolver.py uses the wrong byte code files.
@@ -259,3 +261,8 @@ def is_verbose(threshold=1):
def is_quiet(threshold=-1):
return PIPENV_VERBOSITY <= threshold
PIPENV_SPINNER_FAIL_TEXT = fs_str(to_text(u"{0}")) if not PIPENV_HIDE_EMOJIS else ("{0}")
PIPENV_SPINNER_OK_TEXT = fs_str(to_text(u"{0}")) if not PIPENV_HIDE_EMOJIS else ("{0}")
+75 -1
View File
@@ -10,6 +10,7 @@ import hashlib
import contoml
from first import first
from cached_property import cached_property
import operator
import pipfile
import pipfile.api
import six
@@ -144,7 +145,7 @@ class Project(object):
self._lockfile_newlines = DEFAULT_NEWLINES
self._requirements_location = None
self._original_dir = os.path.abspath(os.curdir)
self.which = which
self._which = which
self.python_version = python_version
# Hack to skip this during pipenv run, or -r.
if ("run" not in sys.argv) and chdir:
@@ -678,6 +679,54 @@ class Project(object):
data[u"requires"] = {"python_version": version[: len("2.7")]}
self.write_toml(data, "Pipfile")
def get_or_create_lockfile(self):
from requirementslib.models.lockfile import Lockfile as Req_Lockfile
lockfile = None
try:
lockfile = Req_Lockfile.load(self.lockfile_location)
except OSError:
lockfile = Req_Lockfile(self.lockfile_content)
return lockfile
else:
if lockfile._lockfile is not None:
return lockfile
if self.lockfile_exists and self.lockfile_content:
from .vendor.plette.lockfiles import Lockfile
lockfile_dict = self.lockfile_content.copy()
sources = lockfile_dict["_meta"].get("sources", [])
if not sources:
sources = self.pipfile_sources
elif not isinstance(sources, list):
sources = [sources,]
lockfile_dict["_meta"]["sources"] = [
{
"name": s["name"],
"url": s["url"],
"verify_ssl": (
s["verify_ssl"] if isinstance(s["verify_ssl"], bool) else (
True if s["verify_ssl"].lower() == "true" else False
)
)
} for s in sources
]
_created_lockfile = Lockfile(lockfile_dict)
lockfile._lockfile = lockfile.projectfile.model = _created_lockfile
return lockfile
elif self.pipfile_exists:
from .vendor.plette.lockfiles import Lockfile, PIPFILE_SPEC_CURRENT
lockfile_dict = {
"_meta": {
"hash": {"sha256": self.calculate_pipfile_hash()},
"pipfile-spec": PIPFILE_SPEC_CURRENT,
"sources": self.pipfile_sources,
"requires": self.parsed_pipfile.get("requires", {})
},
"default": self._lockfile["default"].copy(),
"develop": self._lockfile["develop"].copy()
}
lockfile._lockfile = Lockfile(lockfile_dict)
return lockfile
def write_toml(self, data, path=None):
"""Writes the given data structure out as TOML."""
if path is None:
@@ -939,6 +988,8 @@ class Project(object):
location = self.virtualenv_location if self.virtualenv_location else sys.prefix
prefix = vistir.compat.Path(location).as_posix()
scheme = sysconfig._get_default_scheme()
if not scheme:
scheme = "posix_prefix" if not sys.platform == "win32" else "nt"
config = {
"base": prefix,
"installed_base": prefix,
@@ -953,3 +1004,26 @@ class Project(object):
if "prefix" not in paths:
paths["prefix"] = prefix
return paths
@cached_property
def finders(self):
from .vendor.pythonfinder import Finder
finders = [
Finder(path=self.env_paths["scripts"], global_search=gs, system=False)
for gs in (False, True)
]
return finders
@property
def finder(self):
return next(iter(self.finders), None)
def which(self, search, as_path=True):
find = operator.methodcaller("which", search)
result = next(iter(filter(None, (find(finder) for finder in self.finders))), None)
if not result:
result = self._which(search)
else:
if as_path:
result = str(result.path)
return result
+49 -37
View File
@@ -7,53 +7,52 @@ os.environ["PIP_PYTHON_PATH"] = sys.executable
def _patch_path():
import site
pipenv_libdir = os.path.dirname(os.path.abspath(__file__))
pipenv_site_dir = os.path.dirname(pipenv_libdir)
site.addsitedir(pipenv_site_dir)
for _dir in ("vendor", "patched"):
sys.path.insert(0, os.path.join(pipenv_libdir, _dir))
site_packages_dir = os.path.dirname(pipenv_libdir)
if site_packages_dir not in sys.path:
sys.path.append(site_packages_dir)
def get_parser():
from argparse import ArgumentParser
parser = ArgumentParser("pipenvresolver")
parser.add_argument("--pre", action="store_true", default=False)
parser.add_argument("--clear", action="store_true", default=False)
parser.add_argument("--verbose", "-v", action="count", default=False)
parser.add_argument("--debug", action="store_true", default=False)
parser.add_argument("--system", action="store_true", default=False)
parser.add_argument("--requirements-dir", metavar="requirements_dir", action="store",
default=os.environ.get("PIPENV_REQ_DIR"))
parser.add_argument("packages", nargs="*")
return parser
def which(*args, **kwargs):
return sys.executable
def main():
do_pre = "--pre" in " ".join(sys.argv)
do_clear = "--clear" in " ".join(sys.argv)
is_verbose = "--verbose" in " ".join(sys.argv)
is_debug = "--debug" in " ".join(sys.argv)
system = "--system" in " ".join(sys.argv)
new_sys_argv = []
for v in sys.argv:
if v.startswith("--"):
continue
def handle_parsed_args(parsed):
if parsed.debug:
parsed.verbose = max(parsed.verbose, 2)
if parsed.verbose > 1:
logging.getLogger("notpip").setLevel(logging.DEBUG)
elif parsed.verbose > 0:
logging.getLogger("notpip").setLevel(logging.INFO)
if "PIPENV_PACKAGES" in os.environ:
parsed.packages += os.environ["PIPENV_PACKAGES"].strip().split("\n")
return parsed
else:
new_sys_argv.append(v)
sys.argv = new_sys_argv
def main(pre, clear, verbose, system, requirements_dir, packages):
os.environ["PIP_PYTHON_VERSION"] = ".".join([str(s) for s in sys.version_info[:3]])
os.environ["PIP_PYTHON_PATH"] = sys.executable
verbosity = int(os.environ.get("PIPENV_VERBOSITY", 0))
if is_debug:
verbosity = max(verbosity, 2)
elif is_verbose:
verbosity = max(verbosity, 1)
if verbosity > 1: # Shit's getting real at this point.
logging.getLogger("notpip").setLevel(logging.DEBUG)
elif verbosity > 0:
logging.getLogger("notpip").setLevel(logging.INFO)
import warnings
from pipenv.vendor.vistir.compat import ResourceWarning
warnings.filterwarnings("ignore", category=ResourceWarning)
if "PIPENV_PACKAGES" in os.environ:
packages = os.environ["PIPENV_PACKAGES"].strip().split("\n")
else:
packages = sys.argv[1:]
for i, package in enumerate(packages):
if package.startswith("--"):
del packages[i]
from pipenv.utils import create_mirror_source, resolve_deps, replace_pypi_sources
pypi_mirror_source = (
@@ -62,7 +61,7 @@ def main():
else None
)
def resolve(packages, pre, project, sources, clear, system):
def resolve(packages, pre, project, sources, clear, system, requirements_dir=None):
return resolve_deps(
packages,
which,
@@ -71,6 +70,7 @@ def main():
sources=sources,
clear=clear,
allow_global=system,
req_dir=requirements_dir
)
from pipenv.core import project
@@ -82,19 +82,31 @@ def main():
)
results = resolve(
packages,
pre=do_pre,
pre=pre,
project=project,
sources=sources,
clear=do_clear,
clear=clear,
system=system,
requirements_dir=requirements_dir,
)
print("RESULTS:")
if results:
print(json.dumps(results))
import traceback
if isinstance(results, (Exception, traceback.types.TracebackType)):
sys.stderr.write(traceback.print_tb(results))
sys.stderr.write(sys.exc_value())
else:
print(json.dumps(results))
else:
print(json.dumps([]))
if __name__ == "__main__":
os.environ["PIP_DISABLE_PIP_VERSION_CHECK"] = "1"
_patch_path()
main()
parser = get_parser()
parsed, remaining = parser.parse_known_intermixed_args()
sys.argv = remaining
parsed = handle_parsed_args(parsed)
main(parsed.pre, parsed.clear, parsed.verbose, parsed.system, parsed.requirements_dir,
parsed.packages)
+81 -114
View File
@@ -16,6 +16,11 @@ from click import echo as click_echo
from first import first
from vistir.misc import fs_str
six.add_move(six.MovedAttribute("Mapping", "collections", "collections.abc"))
from six.moves import Mapping
from vistir.compat import ResourceWarning
try:
from weakref import finalize
except ImportError:
@@ -38,14 +43,8 @@ from contextlib import contextmanager
from . import environments
from .pep508checker import lookup
six.add_move(six.MovedAttribute("Mapping", "collections", "collections.abc"))
from six.moves.urllib.parse import urlparse
from six.moves import Mapping
if six.PY2:
class ResourceWarning(Warning):
pass
from urllib3 import util as urllib3_util
specifiers = [k for k in lookup.keys()]
@@ -127,20 +126,14 @@ def parse_python_version(output):
def python_version(path_to_python):
import delegator
from .vendor.pythonfinder.utils import get_python_version
if not path_to_python:
return None
try:
c = delegator.run([path_to_python, "--version"], block=False)
version = get_python_version(path_to_python)
except Exception:
return None
c.block()
version = parse_python_version(c.out.strip() or c.err.strip())
try:
version = u"{major}.{minor}.{micro}".format(**version)
except TypeError:
return None
return version
@@ -194,7 +187,7 @@ def prepare_pip_source_args(sources, pip_args=None):
# Trust the host if it's not verified.
if not sources[0].get("verify_ssl", True):
pip_args.extend(
["--trusted-host", urlparse(sources[0]["url"]).hostname]
["--trusted-host", urllib3_util.parse_url(sources[0]["url"]).host]
)
# Add additional sources as extra indexes.
if len(sources) > 1:
@@ -203,7 +196,7 @@ def prepare_pip_source_args(sources, pip_args=None):
# Trust the host if it's not verified.
if not source.get("verify_ssl", True):
pip_args.extend(
["--trusted-host", urlparse(source["url"]).hostname]
["--trusted-host", urllib3_util.parse_url(source["url"]).host]
)
return pip_args
@@ -228,7 +221,7 @@ def actually_resolve_deps(
from pipenv.patched.piptools import logging as piptools_logging
from pipenv.patched.piptools.exceptions import NoCandidateFound
from .vendor.requirementslib.models.requirements import Requirement
from ._compat import TemporaryDirectory, NamedTemporaryFile
from .vendor.vistir.path import create_tracked_tempdir, create_tracked_tempfile
class PipCommand(basecommand.Command):
"""Needed for pip-tools."""
@@ -236,10 +229,8 @@ def actually_resolve_deps(
name = "PipCommand"
constraints = []
cleanup_req_dir = False
if not req_dir:
req_dir = TemporaryDirectory(suffix="-requirements", prefix="pipenv-")
cleanup_req_dir = True
req_dir = create_tracked_tempdir(suffix="-requirements", prefix="pipenv-")
for dep in deps:
if not dep:
continue
@@ -267,26 +258,26 @@ def actually_resolve_deps(
if sources:
pip_args = prepare_pip_source_args(sources, pip_args)
if environments.is_verbose():
print("Using pip: {0}".format(" ".join(pip_args)))
with NamedTemporaryFile(
click_echo(crayons.blue("Using pip: {0}".format(" ".join(pip_args))))
constraints_file = create_tracked_tempfile(
mode="w",
prefix="pipenv-",
suffix="-constraints.txt",
dir=req_dir.name,
dir=req_dir,
delete=False,
) as f:
if sources:
requirementstxt_sources = " ".join(pip_args) if pip_args else ""
requirementstxt_sources = requirementstxt_sources.replace(" --", "\n--")
f.write(u"{0}\n".format(requirementstxt_sources))
f.write(u"\n".join([_constraint for _constraint in constraints]))
constraints_file = f.name
)
if sources:
requirementstxt_sources = " ".join(pip_args) if pip_args else ""
requirementstxt_sources = requirementstxt_sources.replace(" --", "\n--")
constraints_file.write(u"{0}\n".format(requirementstxt_sources))
constraints_file.write(u"\n".join([_constraint for _constraint in constraints]))
constraints_file.close()
pip_options, _ = pip_command.parser.parse_args(pip_args)
pip_options.cache_dir = environments.PIPENV_CACHE_DIR
session = pip_command._build_session(pip_options)
pypi = PyPIRepository(pip_options=pip_options, use_json=False, session=session)
constraints = parse_requirements(
constraints_file, finder=pypi.finder, session=pypi.session, options=pip_options
constraints_file.name, finder=pypi.finder, session=pypi.session, options=pip_options
)
constraints = [c for c in constraints]
if environments.is_verbose():
@@ -326,11 +317,7 @@ def actually_resolve_deps(
"Please check your version specifier and version number. See PEP440 for more information."
)
)
if cleanup_req_dir:
req_dir.cleanup()
raise RuntimeError
if cleanup_req_dir:
req_dir.cleanup()
return (resolved_tree, hashes, markers_lookup, resolver)
@@ -343,43 +330,72 @@ def venv_resolve_deps(
allow_global=False,
pypi_mirror=None,
):
from .vendor.vistir.misc import fs_str
from .vendor.vistir.misc import fs_str, run
from .vendor.vistir.compat import Path
from .vendor.vistir.path import create_tracked_tempdir
from .cmdparse import Script
from .core import spinner
from .vendor.pexpect.exceptions import EOF
from .vendor import delegator
from . import resolver
import json
if not deps:
return []
resolver = escape_grouped_arguments(resolver.__file__.rstrip("co"))
cmd = "{0} {1} {2} {3} {4}".format(
escape_grouped_arguments(which("python", allow_global=allow_global)),
resolver,
"--pre" if pre else "",
"--clear" if clear else "",
"--system" if allow_global else "",
)
req_dir = create_tracked_tempdir(prefix="pipenv", suffix="requirements")
cmd = [
which("python", allow_global=allow_global),
Path(resolver.__file__.rstrip("co")).as_posix()
]
if pre:
cmd.append("--pre")
if clear:
cmd.append("--clear")
if allow_global:
cmd.append("--system")
with temp_environ():
os.environ = {fs_str(k): fs_str(val) for k, val in os.environ.items()}
os.environ["PIPENV_PACKAGES"] = str("\n".join(deps))
if pypi_mirror:
os.environ["PIPENV_PYPI_MIRROR"] = str(pypi_mirror)
os.environ["PIPENV_VERBOSITY"] = str(environments.PIPENV_VERBOSITY)
c = delegator.run(cmd, block=True)
try:
assert c.return_code == 0
except AssertionError:
if environments.is_verbose():
click_echo(c.out, err=True)
click_echo(c.err, err=True)
else:
click_echo(c.err[(int(len(c.err) / 2) - 1):], err=True)
sys.exit(c.return_code)
os.environ["PIPENV_REQ_DIR"] = fs_str(req_dir)
os.environ["PIP_NO_INPUT"] = fs_str("1")
out = ""
EOF.__module__ = "pexpect.exceptions"
with spinner(text=fs_str("Locking..."), spinner_name=environments.PIPENV_SPINNER,
nospin=environments.PIPENV_NOSPIN) as sp:
c = delegator.run(Script.parse(cmd).cmdify(), block=False, env=os.environ.copy())
_out = u""
while True:
result = c.expect(u"\n", timeout=-1)
if result is EOF or result is None:
break
_out = c.out
out += _out
sp.text = fs_str("Locking... {0}".format(_out[:100]))
if environments.is_verbose():
sp.write_err(_out.rstrip())
c.block()
if c.return_code != 0:
sp.red.fail(environments.PIPENV_SPINNER_FAIL_TEXT.format(
"Locking Failed!"
))
click_echo(c.err.strip(), err=True)
sys.exit(c.return_code)
else:
sp.green.ok(environments.PIPENV_SPINNER_OK_TEXT.format("Success!"))
if environments.is_verbose():
click_echo(c.out.split("RESULTS:")[0], err=True)
try:
return json.loads(c.out.split("RESULTS:")[1].strip())
except IndexError:
click_echo(c.out.strip())
click_echo(c.err.strip(), err=True)
raise RuntimeError("There was a problem with locking.")
@@ -392,13 +408,13 @@ def resolve_deps(
clear=False,
pre=False,
allow_global=False,
req_dir=None
):
"""Given a list of dependencies, return a resolved list of dependencies,
using pip-tools -- and their hashes, using the warehouse API / pip.
"""
from .patched.notpip._vendor.requests.exceptions import ConnectionError
from .vendor.requirementslib.models.requirements import Requirement
from ._compat import TemporaryDirectory
index_lookup = {}
markers_lookup = {}
@@ -408,7 +424,10 @@ def resolve_deps(
if not deps:
return results
# First (proper) attempt:
req_dir = TemporaryDirectory(prefix="pipenv-", suffix="-requirements")
req_dir = req_dir if req_dir else os.environ.get("req_dir", None)
if not req_dir:
from .vendor.vistir.path import create_tracked_tempdir
req_dir = create_tracked_tempdir(prefix="pipenv-", suffix="-requirements")
with HackedPythonVersion(python_version=python, python_path=python_path):
try:
resolved_tree, hashes, markers_lookup, resolver = actually_resolve_deps(
@@ -444,7 +463,6 @@ def resolve_deps(
req_dir=req_dir,
)
except RuntimeError:
req_dir.cleanup()
sys.exit(1)
for result in resolved_tree:
if not result.editable:
@@ -503,7 +521,6 @@ def resolve_deps(
entry.update({"markers": markers_lookup.get(result.name)})
entry = translate_markers(entry)
results.append(entry)
req_dir.cleanup()
return results
@@ -526,7 +543,6 @@ def is_pinned(val):
def convert_deps_to_pip(deps, project=None, r=True, include_index=True):
""""Converts a Pipfile-formatted dependency to a pip-formatted one."""
from ._compat import NamedTemporaryFile
from .vendor.requirementslib.models.requirements import Requirement
dependencies = []
@@ -541,7 +557,8 @@ def convert_deps_to_pip(deps, project=None, r=True, include_index=True):
return dependencies
# Write requirements.txt to tmp directory.
f = NamedTemporaryFile(suffix="-requirements.txt", delete=False)
from .vendor.vistir.path import create_tracked_tempfile
f = create_tracked_tempfile(suffix="-requirements.txt", delete=False)
f.write("\n".join(dependencies).encode("utf-8"))
f.close()
return f.name
@@ -1054,54 +1071,6 @@ def escape_cmd(cmd):
return cmd
@contextmanager
def atomic_open_for_write(target, binary=False, newline=None, encoding=None):
"""Atomically open `target` for writing.
This is based on Lektor's `atomic_open()` utility, but simplified a lot
to handle only writing, and skip many multi-process/thread edge cases
handled by Werkzeug.
How this works:
* Create a temp file (in the same directory of the actual target), and
yield for surrounding code to write to it.
* If some thing goes wrong, try to remove the temp file. The actual target
is not touched whatsoever.
* If everything goes well, close the temp file, and replace the actual
target with this new file.
"""
from ._compat import NamedTemporaryFile
mode = "w+b" if binary else "w"
f = NamedTemporaryFile(
dir=os.path.dirname(target),
prefix=".__atomic-write",
mode=mode,
encoding=encoding,
newline=newline,
delete=False,
)
# set permissions to 0644
os.chmod(f.name, stat.S_IWUSR | stat.S_IRUSR | stat.S_IRGRP | stat.S_IROTH)
try:
yield f
except BaseException:
f.close()
try:
os.remove(f.name)
except OSError:
pass
raise
else:
f.close()
try:
os.remove(target) # This is needed on Windows.
except OSError:
pass
os.rename(f.name, target) # No os.replace() on Python 2.
def safe_expandvars(value):
"""Call os.path.expandvars if value is a string, otherwise do nothing.
"""
@@ -1127,8 +1096,8 @@ def get_vcs_deps(
dev=False,
pypi_mirror=None,
):
from ._compat import TemporaryDirectory, Path
import atexit
from .vendor.vistir.compat import Path
from .vendor.vistir.path import create_tracked_tempdir
from .vendor.requirementslib.models.requirements import Requirement
section = "vcs_dev_packages" if dev else "vcs_packages"
@@ -1144,8 +1113,7 @@ def get_vcs_deps(
)
src_dir.mkdir(mode=0o775, exist_ok=True)
else:
src_dir = TemporaryDirectory(prefix="pipenv-lock-dir")
atexit.register(src_dir.cleanup)
src_dir = create_tracked_tempdir(prefix="pipenv-lock-dir")
for pkg_name, pkg_pipfile in packages.items():
requirement = Requirement.from_pipfile(pkg_name, pkg_pipfile)
name = requirement.normalized_name
@@ -1280,7 +1248,6 @@ def is_virtual_environment(path):
@contextmanager
def locked_repository(requirement):
from .vendor.vistir.path import create_tracked_tempdir
from .vendor.vistir.misc import fs_str
src_dir = create_tracked_tempdir(prefix="pipenv-src")
if not requirement.is_vcs:
return
+12 -5
View File
@@ -7,6 +7,8 @@ import locale
import errno
from pexpect.popen_spawn import PopenSpawn
import pexpect
pexpect.EOF.__module__ = "pexpect.exceptions"
# Include `unicode` in STR_TYPES for Python 2.X
try:
@@ -110,7 +112,7 @@ class Command(object):
if self.subprocess.before:
result += self.subprocess.before
if self.subprocess.after:
if self.subprocess.after and self.subprocess.after is not pexpect.EOF:
result += self.subprocess.after
result += self.subprocess.read()
@@ -206,7 +208,10 @@ class Command(object):
if self.blocking:
raise RuntimeError("expect can only be used on non-blocking commands.")
self.subprocess.expect(pattern=pattern, timeout=timeout)
try:
self.subprocess.expect(pattern=pattern, timeout=timeout)
except pexpect.EOF:
pass
def send(self, s, end=os.linesep, signal=False):
"""Sends the given string or signal to std_in."""
@@ -249,8 +254,11 @@ class Command(object):
self.subprocess.wait()
else:
self.subprocess.sendeof()
self.subprocess.wait()
self.subprocess.proc.stdout.close()
try:
self.subprocess.wait()
finally:
if self.subprocess.proc.stdout:
self.subprocess.proc.stdout.close()
def pipe(self, command, timeout=None, cwd=None):
"""Runs the current command and passes its output to the next
@@ -272,7 +280,6 @@ class Command(object):
c.run(block=False, cwd=cwd)
if data:
c.send(data)
c.subprocess.sendeof()
c.block()
return c
+1 -1
View File
@@ -1,6 +1,6 @@
from __future__ import print_function, absolute_import
__version__ = '1.1.3'
__version__ = '1.1.3.post1'
# Add NullHandler to "pythonfinder" logger, because Python2's default root
# logger has no handler and warnings like this would be reported:
-1
View File
@@ -341,7 +341,6 @@ class SystemPath(object):
self.python_version_dict[ver.as_python.version_tuple[:5]].append(ver)
else:
self.python_version_dict[ver.as_python.version_tuple[:5]] = [ver]
print(ver)
return ver
@classmethod
+6 -3
View File
@@ -1,6 +1,9 @@
# -*- coding=utf-8 -*-
__version__ = '1.1.10'
__version__ = '1.2.0'
from .models.requirements import Requirement
from .models.lockfile import Lockfile
from .models.pipfile import Pipfile
from .exceptions import RequirementError
from .models import Requirement, Lockfile, Pipfile
__all__ = ["Lockfile", "Pipfile", "Requirement"]
+2
View File
@@ -9,6 +9,8 @@ if six.PY2:
def __init__(self, *args, **kwargs):
self.errno = errno.EEXIST
super(FileExistsError, self).__init__(*args, **kwargs)
else:
from six.moves.builtins import FileExistsError
class RequirementError(Exception):
+7 -9
View File
@@ -5,21 +5,19 @@ import copy
import hashlib
import json
import os
import six
import sys
import requests
import pip_shims
import vistir
from appdirs import user_cache_dir
from pip_shims.shims import FAVORITE_HASH, SafeFileCache
from packaging.requirements import Requirement
from .utils import as_tuple, key_from_req, lookup_table, get_pinned_version
if six.PY2:
from ..exceptions import FileExistsError
from ..exceptions import FileExistsError
from ..utils import VCS_SUPPORT
CACHE_DIR = os.environ.get("PIPENV_CACHE_DIR", user_cache_dir("pipenv"))
@@ -189,7 +187,7 @@ class DependencyCache(object):
for dep_name in self.cache[name][version_and_extras])
class HashCache(pip_shims.SafeFileCache):
class HashCache(SafeFileCache):
"""Caches hashes of PyPI artifacts so we do not need to re-download them.
Hashes are only cached when the URL appears to contain a hash in it and the
@@ -206,7 +204,7 @@ class HashCache(pip_shims.SafeFileCache):
def get_hash(self, location):
# if there is no location hash (i.e., md5 / sha256 / etc) we on't want to store it
hash_value = None
vcs = pip_shims.VcsSupport()
vcs = VCS_SUPPORT
orig_scheme = location.scheme
new_location = copy.deepcopy(location)
if orig_scheme in vcs.all_schemes:
@@ -223,11 +221,11 @@ class HashCache(pip_shims.SafeFileCache):
return hash_value.decode('utf8')
def _get_file_hash(self, location):
h = hashlib.new(pip_shims.FAVORITE_HASH)
h = hashlib.new(FAVORITE_HASH)
with vistir.contextmanagers.open_file(location, self.session) as fp:
for chunk in iter(lambda: fp.read(8096), b""):
h.update(chunk)
return ":".join([pip_shims.FAVORITE_HASH, h.hexdigest()])
return ":".join([FAVORITE_HASH, h.hexdigest()])
class _JSONCache(object):
+26
View File
@@ -5,6 +5,7 @@ import copy
import os
import attr
import itertools
import plette.lockfiles
import six
@@ -50,6 +51,31 @@ class Lockfile(object):
def _get_lockfile(self):
return self.projectfile.lockfile
@property
def lockfile(self):
return self._lockfile
@property
def section_keys(self):
return ["default", "develop"]
@property
def extended_keys(self):
return [k for k in itertools.product(self.section_keys, ["", "vcs", "editable"])]
def get(self, k):
return self.__getitem__(k)
def __contains__(self, k):
check_lockfile = k in self.extended_keys or self.lockfile.__contains__(k)
if check_lockfile:
return True
return super(Lockfile, self).__contains__(k)
def __setitem__(self, k, v):
lockfile = self._lockfile
lockfile.__setitem__(k, v)
def __getitem__(self, k, *args, **kwargs):
retval = None
lockfile = self._lockfile
+11 -3
View File
@@ -75,11 +75,19 @@ class Pipfile(object):
def get_deps(self, dev=False, only=True):
deps = {}
if dev:
deps.update(self.pipfile["dev-packages"]._data)
deps.update(self.pipfile._data["dev-packages"])
if only:
return deps
deps = merge_items([deps, self.pipfile["packages"]._data])
return deps
return merge_items([deps, self.pipfile._data["packages"]])
def get(self, k):
return self.__getitem__(k)
def __contains__(self, k):
check_pipfile = k in self.extended_keys or self.pipfile.__contains__(k)
if check_pipfile:
return True
return super(Pipfile, self).__contains__(k)
def __getitem__(self, k, *args, **kwargs):
retval = None
+25 -23
View File
@@ -16,10 +16,7 @@ from packaging.markers import Marker
from packaging.requirements import Requirement as PackagingRequirement
from packaging.specifiers import Specifier, SpecifierSet
from packaging.utils import canonicalize_name
from pip_shims.shims import (
InstallRequirement, Link, Wheel, _strip_extras, parse_version, path_to_url,
url_to_path
)
from pip_shims.shims import _strip_extras, parse_version, path_to_url, url_to_path
from six.moves.urllib import parse as urllib_parse
from six.moves.urllib.parse import unquote
from vistir.compat import FileNotFoundError, Path
@@ -32,21 +29,16 @@ from vistir.path import (
from ..exceptions import RequirementError
from ..utils import VCS_LIST, is_installable_file, is_vcs, ensure_setup_py
from .baserequirement import BaseRequirement
from .dependencies import (
AbstractDependency, find_all_matches, get_abstract_dependencies,
get_dependencies, get_finder
)
from .markers import PipenvMarkers
from .utils import (
HASH_STRING, add_ssh_scheme_to_git_uri, build_vcs_link, extras_to_string,
filter_none, format_requirement, get_version, init_requirement,
is_pinned_requirement, make_install_requirement, optional_instance_of,
parse_extras, specs_to_string, split_markers_from_line,
is_pinned_requirement, make_install_requirement, optional_instance_of, parse_extras,
specs_to_string, split_markers_from_line, ireq_from_editable, ireq_from_line,
split_vcs_method_from_uri, strip_ssh_from_git_uri, validate_path,
validate_specifiers, validate_vcs, normalize_name,
validate_specifiers, validate_vcs, normalize_name, create_link,
Requirement as PkgResourcesRequirement
)
from .vcs import VCSRepository
@attr.s
@@ -128,7 +120,7 @@ class FileRequirement(BaseRequirement):
editable = attr.ib(default=False, type=bool)
extras = attr.ib(default=attr.Factory(list), type=list)
uri = attr.ib(type=six.string_types)
link = attr.ib(type=Link)
link = attr.ib()
name = attr.ib(type=six.string_types)
req = attr.ib(type=PkgResourcesRequirement)
_has_hashed_name = False
@@ -166,6 +158,7 @@ class FileRequirement(BaseRequirement):
See `https://bugs.python.org/issue23505#msg277350`.
"""
# Git allows `git@github.com...` lines that are not really URIs.
# Add "ssh://" so we can parse correctly, and restore afterwards.
fixed_line = add_ssh_scheme_to_git_uri(line)
@@ -176,7 +169,7 @@ class FileRequirement(BaseRequirement):
p = Path(fixed_line).absolute()
path = p.as_posix()
uri = p.as_uri()
link = Link(uri)
link = create_link(uri)
try:
relpath = get_converted_relative_path(path)
except ValueError:
@@ -225,7 +218,7 @@ class FileRequirement(BaseRequirement):
uri = strip_ssh_from_git_uri(original_uri)
# Re-attach VCS prefix to build a Link.
link = Link(
link = create_link(
urllib_parse.urlunsplit(parsed_url._replace(scheme=original_scheme))
)
@@ -246,6 +239,7 @@ class FileRequirement(BaseRequirement):
if self.link and self.link.egg_fragment:
return self.link.egg_fragment
elif self.link and self.link.is_wheel:
from pip_shims import Wheel
return Wheel(self.link.filename).name
if (
self._uri_scheme != "uri"
@@ -263,7 +257,7 @@ class FileRequirement(BaseRequirement):
except (FileNotFoundError, IOError) as e:
dist = None
except Exception as e:
from pip_shims.shims import InstallRequirement, make_abstract_dist
from pip_shims.shims import make_abstract_dist
try:
if not isinstance(Path, self.path):
@@ -271,9 +265,9 @@ class FileRequirement(BaseRequirement):
else:
_path = self.path
if self.editable:
_ireq = InstallRequirement.from_editable(_path.as_uri())
_ireq = ireq_from_editable(_path.as_uri())
else:
_ireq = InstallRequirement.from_line(_path.as_posix())
_ireq = ireq_from_line(_path.as_posix())
dist = make_abstract_dist(_ireq).get_dist()
name = dist.project_name
except (TypeError, ValueError, AttributeError) as e:
@@ -286,7 +280,7 @@ class FileRequirement(BaseRequirement):
self._has_hashed_name = True
name = hashed_name
if self.link and not self._has_hashed_name:
self.link = Link("{0}#egg={1}".format(self.link.url, name))
self.link = create_link("{0}#egg={1}".format(self.link.url, name))
return name
@link.default
@@ -294,7 +288,7 @@ class FileRequirement(BaseRequirement):
target = "{0}".format(self.uri)
if hasattr(self, "name"):
target = "{0}#egg={1}".format(target, self.name)
link = Link(target)
link = create_link(target)
return link
@req.default
@@ -359,6 +353,7 @@ class FileRequirement(BaseRequirement):
"uri_scheme": prefer,
}
if link and link.is_wheel:
from pip_shims import Wheel
arg_dict["name"] = Wheel(link.filename).name
elif link.egg_fragment:
arg_dict["name"] = link.egg_fragment
@@ -398,7 +393,7 @@ class FileRequirement(BaseRequirement):
if not uri:
uri = path_to_url(path)
link = Link(uri)
link = create_link(uri)
arg_dict = {
"name": name,
@@ -588,6 +583,7 @@ class VCSRequirement(FileRequirement):
return os.path.join(create_tracked_tempdir(prefix="requirementslib"), self.name)
def get_vcs_repo(self, src_dir=None):
from .vcs import VCSRepository
checkout_dir = self.get_checkout_dir(src_dir=src_dir)
url = "{0}#egg={1}".format(self.vcs_uri, self.name)
vcsrepo = VCSRepository(
@@ -825,6 +821,7 @@ class Requirement(object):
@classmethod
def from_line(cls, line):
from pip_shims import InstallRequirement
if isinstance(line, InstallRequirement):
line = format_requirement(line)
hashes = None
@@ -1074,9 +1071,9 @@ class Requirement(object):
if ireq_line.startswith("-e "):
ireq_line = ireq_line[len("-e "):]
with ensure_setup_py(self.req.path):
ireq = InstallRequirement.from_editable(ireq_line)
ireq = ireq_from_editable(ireq_line)
else:
ireq = InstallRequirement.from_line(ireq_line)
ireq = ireq_from_line(ireq_line)
if not getattr(ireq, "req", None):
ireq.req = self.req.req
else:
@@ -1103,6 +1100,8 @@ class Requirement(object):
:return: A set of requirement strings of the dependencies of this requirement.
:rtype: set(str)
"""
from .dependencies import get_dependencies
if not sources:
sources = [{
'name': 'pypi',
@@ -1122,6 +1121,7 @@ class Requirement(object):
:rtype: list[ :class:`~requirementslib.models.dependency.AbstractDependency` ]
"""
from .dependencies import AbstractDependency, get_dependencies, get_abstract_dependencies
if not self.abstract_dep:
parent = getattr(self, 'parent', None)
self.abstract_dep = AbstractDependency.from_requirement(self, parent=parent)
@@ -1144,6 +1144,8 @@ class Requirement(object):
:return: A list of Installation Candidates
:rtype: list[ :class:`~pip._internal.index.InstallationCandidate` ]
"""
from .dependencies import get_finder, find_all_matches
if not finder:
finder = get_finder(sources=sources)
return find_all_matches(finder, self.as_ireq())
+7 -4
View File
@@ -4,11 +4,10 @@ from contextlib import contextmanager
import attr
import six
from pip_shims.shims import VcsSupport, Wheel
from pip_shims.shims import Wheel
from ..utils import log
from ..utils import log, VCS_SUPPORT
from .cache import HashCache
from .dependencies import AbstractDependency, find_all_matches, get_finder
from .utils import format_requirement, is_pinned_requirement, version_from_ireq
@@ -41,6 +40,7 @@ class DependencyResolver(object):
@classmethod
def create(cls, finder=None, allow_prereleases=False, get_all_hashes=True):
if not finder:
from .dependencies import get_finder
finder_args = []
if allow_prereleases:
finder_args.append('--pre')
@@ -140,6 +140,7 @@ class DependencyResolver(object):
# Coerce input into AbstractDependency instances.
# We accept str, Requirement, and AbstractDependency as input.
from .dependencies import AbstractDependency
for dep in root_nodes:
if isinstance(dep, six.string_types):
dep = AbstractDependency.from_string(dep)
@@ -183,6 +184,7 @@ class DependencyResolver(object):
def get_hashes_for_one(self, ireq):
if not self.finder:
from .dependencies import get_finder
finder_args = []
if self.allow_prereleases:
finder_args.append('--pre')
@@ -191,7 +193,7 @@ class DependencyResolver(object):
if ireq.editable:
return set()
vcs = VcsSupport()
vcs = VCS_SUPPORT
if ireq.link and ireq.link.scheme in vcs.all_schemes and 'ssh' in ireq.link.scheme:
return set()
@@ -201,6 +203,7 @@ class DependencyResolver(object):
matching_candidates = set()
with self.allow_all_wheels():
from .dependencies import find_all_matches
matching_candidates = (
find_all_matches(self.finder, ireq, pre=self.allow_prereleases)
)
+19 -4
View File
@@ -19,7 +19,7 @@ from packaging.requirements import Requirement as PackagingRequirement
from pkg_resources import Requirement
from vistir.misc import dedup
from pip_shims.shims import InstallRequirement, Link
from ..utils import SCHEME_LIST, VCS_LIST, is_star
@@ -37,6 +37,21 @@ def optional_instance_of(cls):
return validators.optional(validators.instance_of(cls))
def create_link(link):
from pip_shims import Link
return Link(link)
def ireq_from_line(ireq):
from pip_shims import InstallRequirement
return InstallRequirement.from_line(ireq)
def ireq_from_editable(ireq):
from pip_shims import InstallRequirement
return InstallRequirement.from_editable(ireq)
def init_requirement(name):
req = Requirement.parse(name)
req.vcs = None
@@ -92,7 +107,7 @@ def build_vcs_link(vcs, uri, name=None, ref=None, subdirectory=None, extras=None
uri = "{0}{1}".format(uri, extras)
if subdirectory:
uri = "{0}&subdirectory={1}".format(uri, subdirectory)
return Link(uri)
return create_link(uri)
def get_version(pipfile_entry):
@@ -443,11 +458,11 @@ def make_install_requirement(name, version, extras, markers, constraint=False):
extras_string = "[{}]".format(",".join(sorted(extras)))
if not markers:
return InstallRequirement.from_line(
return ireq_from_line(
str('{}{}=={}'.format(name, extras_string, version)),
constraint=constraint)
else:
return InstallRequirement.from_line(
return ireq_from_line(
str('{}{}=={}; {}'.format(name, extras_string, version, str(markers))),
constraint=constraint)
+278 -5
View File
@@ -5,7 +5,6 @@ import contextlib
import logging
import os
import boltons.iterutils
import six
import tomlkit
@@ -30,8 +29,10 @@ VCS_LIST = ("git", "svn", "hg", "bzr")
VCS_SCHEMES = []
SCHEME_LIST = ("http://", "https://", "ftp://", "ftps://", "file://")
VCS_SUPPORT = VcsSupport()
if not VCS_SCHEMES:
VCS_SCHEMES = VcsSupport().all_schemes
VCS_SCHEMES = VCS_SUPPORT.all_schemes
def setup_logger():
@@ -197,6 +198,127 @@ def ensure_setup_py(base_dir):
setup_py.unlink()
_UNSET = object()
_REMAP_EXIT = object()
# The following functionality is either borrowed or modified from the itertools module
# in the boltons library by Mahmoud Hashemi and distributed under the BSD license
# the text of which is included below:
# (original text from https://github.com/mahmoud/boltons/blob/master/LICENSE)
# Copyright (c) 2013, Mahmoud Hashemi
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are
# met:
#
# * Redistributions of source code must retain the above copyright
# notice, this list of conditions and the following disclaimer.
#
# * Redistributions in binary form must reproduce the above
# copyright notice, this list of conditions and the following
# disclaimer in the documentation and/or other materials provided
# with the distribution.
#
# * The names of the contributors may not be used to endorse or
# promote products derived from this software without specific
# prior written permission.
#
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
class PathAccessError(KeyError, IndexError, TypeError):
"""An amalgamation of KeyError, IndexError, and TypeError,
representing what can occur when looking up a path in a nested
object.
"""
def __init__(self, exc, seg, path):
self.exc = exc
self.seg = seg
self.path = path
def __repr__(self):
cn = self.__class__.__name__
return '%s(%r, %r, %r)' % (cn, self.exc, self.seg, self.path)
def __str__(self):
return ('could not access %r from path %r, got error: %r'
% (self.seg, self.path, self.exc))
def get_path(root, path, default=_UNSET):
"""Retrieve a value from a nested object via a tuple representing the
lookup path.
>>> root = {'a': {'b': {'c': [[1], [2], [3]]}}}
>>> get_path(root, ('a', 'b', 'c', 2, 0))
3
The path format is intentionally consistent with that of
:func:`remap`.
One of get_path's chief aims is improved error messaging. EAFP is
great, but the error messages are not.
For instance, ``root['a']['b']['c'][2][1]`` gives back
``IndexError: list index out of range``
What went out of range where? get_path currently raises
``PathAccessError: could not access 2 from path ('a', 'b', 'c', 2,
1), got error: IndexError('list index out of range',)``, a
subclass of IndexError and KeyError.
You can also pass a default that covers the entire operation,
should the lookup fail at any level.
Args:
root: The target nesting of dictionaries, lists, or other
objects supporting ``__getitem__``.
path (tuple): A list of strings and integers to be successively
looked up within *root*.
default: The value to be returned should any
``PathAccessError`` exceptions be raised.
"""
if isinstance(path, six.string_types):
path = path.split('.')
cur = root
try:
for seg in path:
try:
cur = cur[seg]
except (KeyError, IndexError) as exc:
raise PathAccessError(exc, seg, path)
except TypeError as exc:
# either string index in a list, or a parent that
# doesn't support indexing
try:
seg = int(seg)
cur = cur[seg]
except (ValueError, KeyError, IndexError, TypeError):
if not getattr(cur, "__iter__", None):
exc = TypeError('%r object is not indexable'
% type(cur).__name__)
raise PathAccessError(exc, seg, path)
except PathAccessError:
if default is _UNSET:
raise
return default
return cur
def default_visit(path, key, value):
return key, value
_orig_default_visit = default_visit
# Modified from https://github.com/mahmoud/boltons/blob/master/boltons/iterutils.py
def dict_path_enter(path, key, value):
if isinstance(value, six.string_types):
@@ -249,6 +371,157 @@ def dict_path_exit(path, key, old_parent, new_parent, new_items):
return ret
def remap(root, visit=default_visit, enter=dict_path_enter, exit=dict_path_exit,
**kwargs):
"""The remap ("recursive map") function is used to traverse and
transform nested structures. Lists, tuples, sets, and dictionaries
are just a few of the data structures nested into heterogenous
tree-like structures that are so common in programming.
Unfortunately, Python's built-in ways to manipulate collections
are almost all flat. List comprehensions may be fast and succinct,
but they do not recurse, making it tedious to apply quick changes
or complex transforms to real-world data.
remap goes where list comprehensions cannot.
Here's an example of removing all Nones from some data:
>>> from pprint import pprint
>>> reviews = {'Star Trek': {'TNG': 10, 'DS9': 8.5, 'ENT': None},
... 'Babylon 5': 6, 'Dr. Who': None}
>>> pprint(remap(reviews, lambda p, k, v: v is not None))
{'Babylon 5': 6, 'Star Trek': {'DS9': 8.5, 'TNG': 10}}
Notice how both Nones have been removed despite the nesting in the
dictionary. Not bad for a one-liner, and that's just the beginning.
See `this remap cookbook`_ for more delicious recipes.
.. _this remap cookbook: http://sedimental.org/remap.html
remap takes four main arguments: the object to traverse and three
optional callables which determine how the remapped object will be
created.
Args:
root: The target object to traverse. By default, remap
supports iterables like :class:`list`, :class:`tuple`,
:class:`dict`, and :class:`set`, but any object traversable by
*enter* will work.
visit (callable): This function is called on every item in
*root*. It must accept three positional arguments, *path*,
*key*, and *value*. *path* is simply a tuple of parents'
keys. *visit* should return the new key-value pair. It may
also return ``True`` as shorthand to keep the old item
unmodified, or ``False`` to drop the item from the new
structure. *visit* is called after *enter*, on the new parent.
The *visit* function is called for every item in root,
including duplicate items. For traversable values, it is
called on the new parent object, after all its children
have been visited. The default visit behavior simply
returns the key-value pair unmodified.
enter (callable): This function controls which items in *root*
are traversed. It accepts the same arguments as *visit*: the
path, the key, and the value of the current item. It returns a
pair of the blank new parent, and an iterator over the items
which should be visited. If ``False`` is returned instead of
an iterator, the value will not be traversed.
The *enter* function is only called once per unique value. The
default enter behavior support mappings, sequences, and
sets. Strings and all other iterables will not be traversed.
exit (callable): This function determines how to handle items
once they have been visited. It gets the same three
arguments as the other functions -- *path*, *key*, *value*
-- plus two more: the blank new parent object returned
from *enter*, and a list of the new items, as remapped by
*visit*.
Like *enter*, the *exit* function is only called once per
unique value. The default exit behavior is to simply add
all new items to the new parent, e.g., using
:meth:`list.extend` and :meth:`dict.update` to add to the
new parent. Immutable objects, such as a :class:`tuple` or
:class:`namedtuple`, must be recreated from scratch, but
use the same type as the new parent passed back from the
*enter* function.
reraise_visit (bool): A pragmatic convenience for the *visit*
callable. When set to ``False``, remap ignores any errors
raised by the *visit* callback. Items causing exceptions
are kept. See examples for more details.
remap is designed to cover the majority of cases with just the
*visit* callable. While passing in multiple callables is very
empowering, remap is designed so very few cases should require
passing more than one function.
When passing *enter* and *exit*, it's common and easiest to build
on the default behavior. Simply add ``from boltons.iterutils import
default_enter`` (or ``default_exit``), and have your enter/exit
function call the default behavior before or after your custom
logic. See `this example`_.
Duplicate and self-referential objects (aka reference loops) are
automatically handled internally, `as shown here`_.
.. _this example: http://sedimental.org/remap.html#sort_all_lists
.. _as shown here: http://sedimental.org/remap.html#corner_cases
"""
# TODO: improve argument formatting in sphinx doc
# TODO: enter() return (False, items) to continue traverse but cancel copy?
if not callable(visit):
raise TypeError('visit expected callable, not: %r' % visit)
if not callable(enter):
raise TypeError('enter expected callable, not: %r' % enter)
if not callable(exit):
raise TypeError('exit expected callable, not: %r' % exit)
reraise_visit = kwargs.pop('reraise_visit', True)
if kwargs:
raise TypeError('unexpected keyword arguments: %r' % kwargs.keys())
path, registry, stack = (), {}, [(None, root)]
new_items_stack = []
while stack:
key, value = stack.pop()
id_value = id(value)
if key is _REMAP_EXIT:
key, new_parent, old_parent = value
id_value = id(old_parent)
path, new_items = new_items_stack.pop()
value = exit(path, key, old_parent, new_parent, new_items)
registry[id_value] = value
if not new_items_stack:
continue
elif id_value in registry:
value = registry[id_value]
else:
res = enter(path, key, value)
try:
new_parent, new_items = res
except TypeError:
# TODO: handle False?
raise TypeError('enter should return a tuple of (new_parent,'
' items_iterator), not: %r' % res)
if new_items is not False:
# traverse unless False is explicitly passed
registry[id_value] = new_parent
new_items_stack.append((path, []))
if value is not root:
path += (key,)
stack.append((_REMAP_EXIT, (key, new_parent, value)))
if new_items:
stack.extend(reversed(list(new_items)))
continue
if visit is _orig_default_visit:
# avoid function call overhead by inlining identity operation
visited_item = (key, value)
else:
try:
visited_item = visit(path, key, value)
except Exception:
if reraise_visit:
raise
visited_item = True
if visited_item is False:
continue # drop
elif visited_item is True:
visited_item = (key, value)
# TODO: typecheck?
# raise TypeError('expected (key, value) from visit(),'
# ' not: %r' % visited_item)
try:
new_items_stack[-1][1].append(visited_item)
except IndexError:
raise TypeError('expected remappable root, not: %r' % root)
return value
def merge_items(target_list, sourced=False):
if not sourced:
target_list = [(id(t), t) for t in target_list]
@@ -262,7 +535,7 @@ def merge_items(target_list, sourced=False):
new_parent = ret
try:
cur_val = boltons.iterutils.get_path(ret, path + (key,))
cur_val = get_path(ret, path + (key,))
except KeyError as ke:
pass
else:
@@ -279,9 +552,9 @@ def merge_items(target_list, sourced=False):
source_map[path + (key,)] = t_name
return True
else:
remerge_visit = boltons.iterutils.default_visit
remerge_visit = default_visit
ret = boltons.iterutils.remap(target, enter=remerge_enter, visit=remerge_visit,
ret = remap(target, enter=remerge_enter, visit=remerge_visit,
exit=remerge_exit)
if not sourced:
+1 -1
View File
@@ -18,7 +18,7 @@ from ._compat import unicode
from ._utils import escape_string
if PY2:
from functools32 import lru_cache
from pipenv.vendor.backports.functools_lru_cache import lru_cache
else:
from functools import lru_cache
+1 -1
View File
@@ -4,7 +4,7 @@ from ._compat import PY2
from ._compat import unicode
if PY2:
from functools32 import lru_cache
from pipenv.vendor.backports.functools_lru_cache import lru_cache
else:
from functools import lru_cache
+3 -3
View File
@@ -21,13 +21,13 @@ pipdeptree==0.13.0
pipreqs==0.4.9
docopt==0.6.2
yarg==0.1.9
pythonfinder==1.1.3
pythonfinder==1.1.3.post1
requests==2.20.0
chardet==3.0.4
idna==2.7
urllib3==1.24
certifi==2018.10.15
requirementslib==1.1.10
requirementslib==1.2.0
attrs==18.2.0
distlib==0.2.8
packaging==18.0
@@ -41,7 +41,7 @@ semver==2.8.1
shutilwhich==1.1.0
toml==0.10.0
cached-property==1.4.3
vistir==0.1.7
vistir==0.2.0
pip-shims==0.3.1
ptyprocess==0.6.0
enum34==1.1.6
+4 -3
View File
@@ -11,11 +11,11 @@ from .contextmanagers import (
spinner,
)
from .misc import load_path, partialclass, run, shell_escape
from .path import mkdir_p, rmtree, create_tracked_tempdir
from .path import mkdir_p, rmtree, create_tracked_tempdir, create_tracked_tempfile
from .spin import VistirSpinner, create_spinner
__version__ = '0.1.8'
__version__ = '0.2.0'
__all__ = [
@@ -36,5 +36,6 @@ __all__ = [
"spinner",
"VistirSpinner",
"create_spinner",
"create_tracked_tempdir"
"create_tracked_tempdir",
"create_tracked_tempfile",
]
+5 -1
View File
@@ -175,6 +175,7 @@ def NamedTemporaryFile(
prefix=None,
dir=None,
delete=True,
wrapper_class_override=None
):
"""Create and return a temporary file.
Arguments:
@@ -203,7 +204,10 @@ def NamedTemporaryFile(
file = io.open(
fd, mode, buffering=buffering, newline=newline, encoding=encoding
)
return _TemporaryFileWrapper(file, name, delete)
if wrapper_class_override is not None:
return wrapper_class_override(file, name, delete)
else:
return _TemporaryFileWrapper(file, name, delete)
except BaseException:
os.unlink(name)
+2 -1
View File
@@ -33,9 +33,10 @@ else:
from pathlib2 import Path
from pipenv.vendor.backports.functools_lru_cache import lru_cache
from .backports.tempfile import NamedTemporaryFile as _NamedTemporaryFile
if sys.version_info < (3, 3):
from pipenv.vendor.backports.shutil_get_terminal_size import get_terminal_size
from .backports.tempfile import NamedTemporaryFile
NamedTemporaryFile = _NamedTemporaryFile
else:
from tempfile import NamedTemporaryFile
from shutil import get_terminal_size
+15 -11
View File
@@ -231,12 +231,13 @@ def atomic_open_for_write(target, binary=False, newline=None, encoding=None):
@contextmanager
def open_file(link, session=None):
def open_file(link, session=None, stream=True):
"""
Open local or remote file for reading.
:type link: pip._internal.index.Link or str
:type session: requests.Session
:param bool stream: Try to stream if remote, default True
:raises ValueError: If link points to a local directory.
:return: a context manager to the opened file-like object
"""
@@ -255,11 +256,8 @@ def open_file(link, session=None):
if os.path.isdir(local_path):
raise ValueError("Cannot open directory for read: {}".format(link))
else:
try:
local_file = io.open(local_path, "rb")
yield local_file
finally:
local_file.close()
with io.open(local_path, "rb") as local_file:
yield local_file
else:
# Remote URL
headers = {"Accept-Encoding": "identity"}
@@ -267,8 +265,14 @@ def open_file(link, session=None):
from requests import Session
session = Session()
response = session.get(link, headers=headers, stream=True)
try:
yield response.raw
finally:
response.close()
with session.get(link, headers=headers, stream=stream) as resp:
try:
raw = getattr(resp, "raw", None)
result = raw if raw else resp
yield result
finally:
result.close()
if raw:
conn = getattr(raw, "_connection")
if conn is not None:
conn.close()
+3 -3
View File
@@ -49,7 +49,7 @@ def _get_logger(name=None, level="ERROR"):
formatter = logging.Formatter(
"%(levelname)s %(asctime)s %(module)s %(process)d %(thread)d %(message)s"
)
handler = logging.StreamHandler()
handler = logging.StreamHandler(stream=sys.stderr)
handler.setFormatter(formatter)
logger.addHandler(handler)
return logger
@@ -157,7 +157,7 @@ def _create_subprocess(
raise
if not block:
c.stdin.close()
log_level = "DEBUG" if verbose else "WARN"
log_level = "DEBUG" if verbose else "ERROR"
logger = _get_logger(cmd._parts[0], level=log_level)
output = []
err = []
@@ -199,7 +199,7 @@ def _create_subprocess(
display_line = "{0}...".format(stdout_line[:display_limit])
if verbose:
if spinner:
spinner.write(fs_str(display_line))
spinner.write_err(fs_str(display_line))
else:
logger.debug(display_line)
if spinner:
+68 -3
View File
@@ -15,7 +15,15 @@ import six
from six.moves import urllib_parse
from six.moves.urllib import request as urllib_request
from .compat import Path, _fs_encoding, TemporaryDirectory, ResourceWarning
from .backports.tempfile import _TemporaryFileWrapper
from .compat import (
_NamedTemporaryFile,
Path,
ResourceWarning,
TemporaryDirectory,
_fs_encoding,
finalize,
)
__all__ = [
@@ -28,6 +36,7 @@ __all__ = [
"mkdir_p",
"ensure_mkdir_p",
"create_tracked_tempdir",
"create_tracked_tempfile",
"path_to_url",
"rmtree",
"safe_expandvars",
@@ -37,6 +46,10 @@ __all__ = [
]
if os.name == "nt" and six.PY34:
warnings.filterwarnings("ignore", category=DeprecationWarning, message="The Windows bytes API has been deprecated.*")
def unicode_path(path):
# Paths are supposed to be represented as unicode here
if six.PY2 and not isinstance(path, six.text_type):
@@ -52,9 +65,10 @@ def native_path(path):
# once again thank you django...
# https://github.com/django/django/blob/fc6b90b/django/utils/_os.py
if six.PY3 or os.name == 'nt':
if six.PY3 or os.name == "nt":
abspathu = os.path.abspath
else:
def abspathu(path):
"""
Version of os.path.abspath that uses the unicode representation
@@ -74,6 +88,7 @@ def normalize_drive(path):
always converted to uppercase because it seems to be preferred.
"""
from .misc import to_text
if os.name != "nt" or not isinstance(path, six.string_types):
return path
@@ -110,6 +125,7 @@ def url_to_path(url):
Follows logic taken from pip's equivalent function
"""
from .misc import to_bytes
assert is_file_url(url), "Only file: urls can be converted to local paths"
_, netloc, path, _, _ = urllib_parse.urlsplit(url)
# Netlocs are UNC paths
@@ -123,6 +139,7 @@ def url_to_path(url):
def is_valid_url(url):
"""Checks if a given string is an url"""
from .misc import to_text
if not url:
return url
pieces = urllib_parse.urlparse(to_text(url))
@@ -132,6 +149,7 @@ def is_valid_url(url):
def is_file_url(url):
"""Returns true if the given url is a file url"""
from .misc import to_text
if not url:
return False
if not isinstance(url, six.string_types):
@@ -149,6 +167,7 @@ def is_readonly_path(fn):
Permissions check is `bool(path.stat & stat.S_IREAD)` or `not os.access(path, os.W_OK)`
"""
from .misc import to_bytes
fn = to_bytes(fn, encoding="utf-8")
if os.path.exists(fn):
return bool(os.stat(fn).st_mode & stat.S_IREAD) and not os.access(fn, os.W_OK)
@@ -164,6 +183,7 @@ def mkdir_p(newdir, mode=0o777):
"""
# http://code.activestate.com/recipes/82465-a-friendly-mkdir/
from .misc import to_bytes, to_text
newdir = to_bytes(newdir, "utf-8")
if os.path.exists(newdir):
if not os.path.isdir(newdir):
@@ -176,7 +196,9 @@ def mkdir_p(newdir, mode=0o777):
head, tail = os.path.split(to_bytes(newdir, encoding="utf-8"))
# Make sure the tail doesn't point to the asame place as the head
curdir = to_bytes(".", encoding="utf-8")
tail_and_head_match = os.path.relpath(tail, start=os.path.basename(head)) == curdir
tail_and_head_match = (
os.path.relpath(tail, start=os.path.basename(head)) == curdir
)
if tail and not tail_and_head_match and not os.path.isdir(newdir):
target = os.path.join(head, tail)
if os.path.exists(target) and os.path.isfile(target):
@@ -191,6 +213,7 @@ def mkdir_p(newdir, mode=0o777):
def ensure_mkdir_p(mode=0o777):
"""Decorator to ensure `mkdir_p` is called to the function's return value.
"""
def decorator(f):
@functools.wraps(f)
@@ -224,6 +247,19 @@ def create_tracked_tempdir(*args, **kwargs):
return tempdir.name
def create_tracked_tempfile(*args, **kwargs):
"""Create a tracked temporary file.
This uses the `NamedTemporaryFile` construct, but does not remove the file
until the interpreter exits.
The return value is the file object.
"""
kwargs["wrapper_class_override"] = _TrackedTempfileWrapper
return _NamedTemporaryFile(*args, **kwargs)
def set_write_bit(fn):
"""Set read-write permissions for the current user on the target path. Fail silently
if the path doesn't exist.
@@ -232,6 +268,7 @@ def set_write_bit(fn):
"""
from .misc import to_bytes, locale_encoding
fn = to_bytes(fn, encoding=locale_encoding)
if not os.path.exists(fn):
return
@@ -253,6 +290,7 @@ def rmtree(directory, ignore_errors=False):
"""
from .misc import locale_encoding, to_bytes
directory = to_bytes(directory, encoding=locale_encoding)
try:
shutil.rmtree(
@@ -278,10 +316,12 @@ def handle_remove_readonly(func, path, exc):
This function will call check :func:`is_readonly_path` before attempting to call
:func:`set_write_bit` on the target path and try again.
"""
# Check for read-only attribute
if six.PY2:
from .compat import ResourceWarning
from .misc import to_bytes
PERM_ERRORS = (errno.EACCES, errno.EPERM)
default_warning_message = (
"Unable to remove file due to permissions restriction: {!r}"
@@ -418,3 +458,28 @@ def safe_expandvars(value):
if isinstance(value, six.string_types):
return os.path.expandvars(value)
return value
class _TrackedTempfileWrapper(_TemporaryFileWrapper):
def __init__(self, *args, **kwargs):
super(_TrackedTempfileWrapper, self).__init__(*args, **kwargs)
self._finalizer = finalize(self, self.cleanup)
@classmethod
def _cleanup(cls, fileobj):
try:
fileobj.close()
finally:
os.unlink(fileobj.name)
def cleanup(self):
if self._finalizer.detach():
try:
self.close()
finally:
os.unlink(self.name)
else:
try:
self.close()
except OSError:
pass
+48 -1
View File
@@ -3,7 +3,7 @@ import os
import signal
import sys
from .termcolors import colored
from .termcolors import colored, COLORS
from .compat import fs_str
import cursor
@@ -15,6 +15,7 @@ except ImportError:
Spinners = None
else:
from yaspin.spinners import Spinners
from yaspin.constants import COLOR_MAP
handler = None
if yaspin and os.name == "nt":
@@ -41,6 +42,16 @@ class DummySpinner(object):
self.write_err(traceback)
return False
def __getattr__(self, k):
try:
retval = super(DummySpinner, self).__getattribute__(k)
except AttributeError:
if k in COLOR_MAP.keys() or k.upper() in COLORS:
return self
raise
else:
return retval
def fail(self, exitcode=1, text=None):
if text:
self.write_err(text)
@@ -125,6 +136,42 @@ class VistirSpinner(base_obj):
)
return fn
def _register_signal_handlers(self):
# SIGKILL cannot be caught or ignored, and the receiving
# process cannot perform any clean-up upon receiving this
# signal.
try:
if signal.SIGKILL in self._sigmap.keys():
raise ValueError(
"Trying to set handler for SIGKILL signal. "
"SIGKILL cannot be cought or ignored in POSIX systems."
)
except AttributeError:
pass
for sig, sig_handler in self._sigmap.items():
# A handler for a particular signal, once set, remains
# installed until it is explicitly reset. Store default
# signal handlers for subsequent reset at cleanup phase.
dfl_handler = signal.getsignal(sig)
self._dfl_sigmap[sig] = dfl_handler
# ``signal.SIG_DFL`` and ``signal.SIG_IGN`` are also valid
# signal handlers and are not callables.
if callable(sig_handler):
# ``signal.signal`` accepts handler function which is
# called with two arguments: signal number and the
# interrupted stack frame. ``functools.partial`` solves
# the problem of passing spinner instance into the handler
# function.
sig_handler = functools.partial(sig_handler, spinner=self)
signal.signal(sig, sig_handler)
def _reset_signal_handlers(self):
for sig, sig_handler in self._dfl_sigmap.items():
signal.signal(sig, sig_handler)
@staticmethod
def _hide_cursor():
cursor.hide()
+3 -1
View File
@@ -79,6 +79,7 @@ def colored(text, color=None, on_color=None, attrs=None):
style = "BRIGHT"
attrs.remove('bold')
if color is not None:
color = color.upper()
text = text = "%s%s%s%s%s" % (
getattr(colorama.Fore, color),
getattr(colorama.Style, style),
@@ -88,8 +89,9 @@ def colored(text, color=None, on_color=None, attrs=None):
)
if on_color is not None:
on_color = on_color.upper()
text = "%s%s%s%s" % (
getattr(colorama.Back, color),
getattr(colorama.Back, on_color),
text,
colorama.Back.RESET,
colorama.Style.NORMAL,
+17 -10
View File
@@ -16,6 +16,9 @@ import sys
import threading
import time
import colorama
import cursor
from .base_spinner import default_spinner
from .compat import PY2, basestring, builtin_str, bytes, iteritems, str
from .constants import COLOR_ATTRS, COLOR_MAP, ENCODING, SPINNER_ATTRS
@@ -23,6 +26,9 @@ from .helpers import to_unicode
from .termcolor import colored
colorama.init()
class Yaspin(object):
"""Implements a context manager that spawns a thread
to write spinner frames into a tty (stdout) during
@@ -369,11 +375,14 @@ class Yaspin(object):
# SIGKILL cannot be caught or ignored, and the receiving
# process cannot perform any clean-up upon receiving this
# signal.
if signal.SIGKILL in self._sigmap.keys():
raise ValueError(
"Trying to set handler for SIGKILL signal. "
"SIGKILL cannot be cought or ignored in POSIX systems."
)
try:
if signal.SIGKILL in self._sigmap.keys():
raise ValueError(
"Trying to set handler for SIGKILL signal. "
"SIGKILL cannot be cought or ignored in POSIX systems."
)
except AttributeError:
pass
for sig, sig_handler in iteritems(self._sigmap):
# A handler for a particular signal, once set, remains
@@ -521,14 +530,12 @@ class Yaspin(object):
@staticmethod
def _hide_cursor():
sys.stdout.write("\033[?25l")
sys.stdout.flush()
cursor.hide()
@staticmethod
def _show_cursor():
sys.stdout.write("\033[?25h")
sys.stdout.flush()
cursor.show()
@staticmethod
def _clear_line():
sys.stdout.write("\033[K")
sys.stdout.write(chr(27) + "[K")
+2
View File
@@ -34,6 +34,7 @@ PY2_DOWNLOAD = ['enum34',]
# from time to time, remove the no longer needed ones
HARDCODED_LICENSE_URLS = {
'pytoml': 'https://github.com/avakar/pytoml/raw/master/LICENSE',
'cursor': 'https://raw.githubusercontent.com/GijsTimmers/cursor/master/LICENSE',
'delegator.py': 'https://raw.githubusercontent.com/kennethreitz/delegator.py/master/LICENSE',
'click-didyoumean': 'https://raw.githubusercontent.com/click-contrib/click-didyoumean/master/LICENSE',
'click-completion': 'https://raw.githubusercontent.com/click-contrib/click-completion/master/LICENSE',
@@ -70,6 +71,7 @@ PATCHED_RENAMES = {
LIBRARY_RENAMES = {
'pip': 'pipenv.patched.notpip',
"functools32": "pipenv.vendor.backports.functools_lru_cache",
'enum34': 'enum',
}
@@ -1,8 +1,26 @@
diff --git a/pipenv/vendor/delegator.py b/pipenv/vendor/delegator.py
index 0c140cad..3ffb2e31 100644
index d15aeb97..56d12458 100644
--- a/pipenv/vendor/delegator.py
+++ b/pipenv/vendor/delegator.py
@@ -178,6 +178,7 @@ class Command(object):
@@ -7,6 +7,8 @@ import locale
import errno
from pexpect.popen_spawn import PopenSpawn
+import pexpect
+pexpect.EOF.__module__ = "pexpect.exceptions"
# Include `unicode` in STR_TYPES for Python 2.X
try:
@@ -110,7 +112,7 @@ class Command(object):
if self.subprocess.before:
result += self.subprocess.before
- if self.subprocess.after:
+ if self.subprocess.after and self.subprocess.after is not pexpect.EOF:
result += self.subprocess.after
result += self.subprocess.read()
@@ -178,6 +180,7 @@ class Command(object):
# Use subprocess.
if self.blocking:
popen_kwargs = self._default_popen_kwargs.copy()
@@ -10,11 +28,21 @@ index 0c140cad..3ffb2e31 100644
popen_kwargs["universal_newlines"] = not binary
if cwd:
popen_kwargs["cwd"] = cwd
@@ -233,18 +234,23 @@ class Command(object):
def block(self):
@@ -205,7 +208,10 @@ class Command(object):
if self.blocking:
raise RuntimeError("expect can only be used on non-blocking commands.")
- self.subprocess.expect(pattern=pattern, timeout=timeout)
+ try:
+ self.subprocess.expect(pattern=pattern, timeout=timeout)
+ except pexpect.EOF:
+ pass
def send(self, s, end=os.linesep, signal=False):
"""Sends the given string or signal to std_in."""
@@ -234,14 +240,25 @@ class Command(object):
"""Blocks until process is complete."""
if self._uses_subprocess:
- self.subprocess.stdin.close()
# consume stdout and stderr
- try:
- stdout, stderr = self.subprocess.communicate()
@@ -35,10 +63,21 @@ index 0c140cad..3ffb2e31 100644
+ self.std_err.close()
+ self.subprocess.wait()
else:
self.subprocess.sendeof()
- self.subprocess.proc.stdout.close()
self.subprocess.wait()
+ self.subprocess.proc.stdout.close()
- self.subprocess.wait()
+ self.subprocess.sendeof()
+ try:
+ self.subprocess.wait()
+ finally:
+ if self.subprocess.proc.stdout:
+ self.subprocess.proc.stdout.close()
def pipe(self, command, timeout=None, cwd=None):
"""Runs the current command and passes its output to the next
@@ -263,7 +280,6 @@ class Command(object):
c.run(block=False, cwd=cwd)
if data:
c.send(data)
- c.subprocess.sendeof()
c.block()
return c
+27 -22
View File
@@ -1,25 +1,3 @@
diff --git a/pipenv/vendor/vistir/compat.py b/pipenv/vendor/vistir/compat.py
index 1f1b7a96..0c865fe6 100644
--- a/pipenv/vendor/vistir/compat.py
+++ b/pipenv/vendor/vistir/compat.py
@@ -30,7 +30,7 @@ else:
from pathlib2 import Path
if sys.version_info < (3, 3):
- from backports.shutil_get_terminal_size import get_terminal_size
+ from pipenv.vendor.backports.shutil_get_terminal_size import get_terminal_size
from .backports.tempfile import NamedTemporaryFile
else:
from tempfile import NamedTemporaryFile
@@ -39,7 +39,7 @@ else:
try:
from weakref import finalize
except ImportError:
- from backports.weakref import finalize
+ from pipenv.vendor.backports.weakref import finalize
try:
from functools import partialmethod
diff --git a/pipenv/vendor/vistir/backports/tempfile.py b/pipenv/vendor/vistir/backports/tempfile.py
index 483a479a..43470a6e 100644
--- a/pipenv/vendor/vistir/backports/tempfile.py
@@ -33,3 +11,30 @@ index 483a479a..43470a6e 100644
__all__ = ["finalize", "NamedTemporaryFile"]
diff --git a/pipenv/vendor/vistir/compat.py b/pipenv/vendor/vistir/compat.py
index 9ae33fdc..ec3b65cb 100644
--- a/pipenv/vendor/vistir/compat.py
+++ b/pipenv/vendor/vistir/compat.py
@@ -31,11 +31,11 @@ if sys.version_info >= (3, 5):
from functools import lru_cache
else:
from pathlib2 import Path
- from backports.functools_lru_cache import lru_cache
+ from pipenv.vendor.backports.functools_lru_cache import lru_cache
from .backports.tempfile import NamedTemporaryFile as _NamedTemporaryFile
if sys.version_info < (3, 3):
- from backports.shutil_get_terminal_size import get_terminal_size
+ from pipenv.vendor.backports.shutil_get_terminal_size import get_terminal_size
NamedTemporaryFile = _NamedTemporaryFile
else:
from tempfile import NamedTemporaryFile
@@ -44,7 +44,7 @@ else:
try:
from weakref import finalize
except ImportError:
- from backports.weakref import finalize
+ from pipenv.vendor.backports.weakref import finalize
try:
from functools import partialmethod
@@ -0,0 +1,62 @@
diff --git a/pipenv/vendor/yaspin/core.py b/pipenv/vendor/yaspin/core.py
index d01fb98e..06b8b621 100644
--- a/pipenv/vendor/yaspin/core.py
+++ b/pipenv/vendor/yaspin/core.py
@@ -16,6 +16,9 @@ import sys
import threading
import time
+import colorama
+import cursor
+
from .base_spinner import default_spinner
from .compat import PY2, basestring, builtin_str, bytes, iteritems, str
from .constants import COLOR_ATTRS, COLOR_MAP, ENCODING, SPINNER_ATTRS
@@ -23,6 +26,9 @@ from .helpers import to_unicode
from .termcolor import colored
+colorama.init()
+
+
class Yaspin(object):
"""Implements a context manager that spawns a thread
to write spinner frames into a tty (stdout) during
@@ -369,11 +375,14 @@ class Yaspin(object):
# SIGKILL cannot be caught or ignored, and the receiving
# process cannot perform any clean-up upon receiving this
# signal.
- if signal.SIGKILL in self._sigmap.keys():
- raise ValueError(
- "Trying to set handler for SIGKILL signal. "
- "SIGKILL cannot be cought or ignored in POSIX systems."
- )
+ try:
+ if signal.SIGKILL in self._sigmap.keys():
+ raise ValueError(
+ "Trying to set handler for SIGKILL signal. "
+ "SIGKILL cannot be cought or ignored in POSIX systems."
+ )
+ except AttributeError:
+ pass
for sig, sig_handler in iteritems(self._sigmap):
# A handler for a particular signal, once set, remains
@@ -521,14 +530,12 @@ class Yaspin(object):
@staticmethod
def _hide_cursor():
- sys.stdout.write("\033[?25l")
- sys.stdout.flush()
+ cursor.hide()
@staticmethod
def _show_cursor():
- sys.stdout.write("\033[?25h")
- sys.stdout.flush()
+ cursor.show()
@staticmethod
def _clear_line():
- sys.stdout.write("\033[K")
+ sys.stdout.write(chr(27) + "[K")
+115 -23
View File
@@ -1,5 +1,6 @@
import json
import os
import sys
import warnings
import pytest
@@ -7,13 +8,12 @@ import pytest
from pipenv._compat import TemporaryDirectory, Path
from pipenv.vendor import delegator
from pipenv.vendor import requests
from pipenv.vendor import six
from pipenv.vendor import toml
from pytest_pypi.app import prepare_packages as prepare_pypi_packages
from vistir.compat import ResourceWarning
if six.PY2:
class ResourceWarning(Warning):
pass
warnings.filterwarnings("default", category=ResourceWarning)
HAS_WARNED_GITHUB = False
@@ -25,8 +25,8 @@ def check_internet():
resp = requests.get('http://httpbin.org/ip', timeout=1.0)
resp.raise_for_status()
except Exception:
warnings.warn('Cannot connect to HTTPBin...', ResourceWarning)
warnings.warn('Will skip tests requiring Internet', ResourceWarning)
warnings.warn('Cannot connect to HTTPBin...', RuntimeWarning)
warnings.warn('Will skip tests requiring Internet', RuntimeWarning)
return False
return True
@@ -46,10 +46,10 @@ def check_github_ssh():
global HAS_WARNED_GITHUB
if not res and not HAS_WARNED_GITHUB:
warnings.warn(
'Cannot connect to GitHub via SSH', ResourceWarning
'Cannot connect to GitHub via SSH', RuntimeWarning
)
warnings.warn(
'Will skip tests requiring SSH access to GitHub', ResourceWarning
'Will skip tests requiring SSH access to GitHub', RuntimeWarning
)
HAS_WARNED_GITHUB = True
return res
@@ -70,18 +70,109 @@ def pytest_runtest_setup(item):
pytest.skip('requires github ssh')
@pytest.yield_fixture
def pathlib_tmpdir(request, tmpdir):
yield Path(str(tmpdir))
tmpdir.remove(ignore_errors=True)
# Borrowed from pip's test runner filesystem isolation
@pytest.fixture(autouse=True)
def isolate(pathlib_tmpdir):
"""
Isolate our tests so that things like global configuration files and the
like do not affect our test results.
We use an autouse function scoped fixture because we want to ensure that
every test has it's own isolated home directory.
"""
warnings.filterwarnings("ignore", category=ResourceWarning)
warnings.filterwarnings("ignore", category=ResourceWarning, message="unclosed.*<ssl.SSLSocket.*>")
# Create a directory to use as our home location.
home_dir = os.path.join(str(pathlib_tmpdir), "home")
os.environ["PIPENV_NOSPIN"] = "1"
os.makedirs(home_dir)
# Create a directory to use as a fake root
fake_root = os.path.join(str(pathlib_tmpdir), "fake-root")
os.makedirs(fake_root)
# if sys.platform == 'win32':
# # Note: this will only take effect in subprocesses...
# home_drive, home_path = os.path.splitdrive(home_dir)
# os.environ.update({
# 'USERPROFILE': home_dir,
# 'HOMEDRIVE': home_drive,
# 'HOMEPATH': home_path,
# })
# for env_var, sub_path in (
# ('APPDATA', 'AppData/Roaming'),
# ('LOCALAPPDATA', 'AppData/Local'),
# ):
# path = os.path.join(home_dir, *sub_path.split('/'))
# os.environ[env_var] = path
# os.makedirs(path)
# else:
# # Set our home directory to our temporary directory, this should force
# # all of our relative configuration files to be read from here instead
# # of the user's actual $HOME directory.
# os.environ["HOME"] = home_dir
# # Isolate ourselves from XDG directories
# os.environ["XDG_DATA_HOME"] = os.path.join(home_dir, ".local", "share")
# os.environ["XDG_CONFIG_HOME"] = os.path.join(home_dir, ".config")
# os.environ["XDG_CACHE_HOME"] = os.path.join(home_dir, ".cache")
# os.environ["XDG_RUNTIME_DIR"] = os.path.join(home_dir, ".runtime")
# os.environ["XDG_DATA_DIRS"] = ":".join([
# os.path.join(fake_root, "usr", "local", "share"),
# os.path.join(fake_root, "usr", "share"),
# ])
# os.environ["XDG_CONFIG_DIRS"] = os.path.join(fake_root, "etc", "xdg")
# Configure git, because without an author name/email git will complain
# and cause test failures.
os.environ["GIT_CONFIG_NOSYSTEM"] = "1"
os.environ["GIT_AUTHOR_NAME"] = "pipenv"
os.environ["GIT_AUTHOR_EMAIL"] = "pipenv@pipenv.org"
# We want to disable the version check from running in the tests
os.environ["PIP_DISABLE_PIP_VERSION_CHECK"] = "true"
workon_home = os.path.join(home_dir, ".virtualenvs")
os.makedirs(workon_home)
os.environ["WORKON_HOME"] = workon_home
project_dir = os.path.join(home_dir, "pipenv_project")
os.makedirs(project_dir)
os.environ["PIPENV_PROJECT_DIR"] = project_dir
os.environ["CI"] = "1"
# Make sure tests don't share a requirements tracker.
os.environ.pop('PIP_REQ_TRACKER', None)
# FIXME: Windows...
os.makedirs(os.path.join(home_dir, ".config", "git"))
with open(os.path.join(home_dir, ".config", "git", "config"), "wb") as fp:
fp.write(
b"[user]\n\tname = pipenv\n\temail = pipenv@pipenv.org\n"
)
class _PipenvInstance(object):
"""An instance of a Pipenv Project..."""
def __init__(self, pypi=None, pipfile=True, chdir=False):
def __init__(self, pypi=None, pipfile=True, chdir=False, path=None):
self.pypi = pypi
self.original_umask = os.umask(0o007)
self.original_dir = os.path.abspath(os.curdir)
self._path = TemporaryDirectory(suffix='-project', prefix='pipenv-')
path = Path(self._path.name)
try:
self.path = str(path.resolve())
except OSError:
self.path = str(path.absolute())
path = os.environ.get("PIPENV_PROJECT_DIR", None)
if not path:
self._path = TemporaryDirectory(suffix='-project', prefix='pipenv-')
path = Path(self._path.name)
try:
self.path = str(path.resolve())
except OSError:
self.path = str(path.absolute())
else:
self._path = None
self.path = path
# set file creation perms
self.pipfile_path = None
self.chdir = chdir
@@ -101,6 +192,7 @@ class _PipenvInstance(object):
os.environ['PIPENV_DONT_USE_PYENV'] = '1'
os.environ['PIPENV_IGNORE_VIRTUALENVS'] = '1'
os.environ['PIPENV_VENV_IN_PROJECT'] = '1'
os.environ['PIPENV_NOSPIN'] = '1'
if self.chdir:
os.chdir(self.path)
return self
@@ -110,13 +202,13 @@ class _PipenvInstance(object):
if self.chdir:
os.chdir(self.original_dir)
self.path = None
try:
self._path.cleanup()
except OSError as e:
_warn_msg = warn_msg.format(e)
warnings.warn(_warn_msg, ResourceWarning)
finally:
os.umask(self.original_umask)
if self._path:
try:
self._path.cleanup()
except OSError as e:
_warn_msg = warn_msg.format(e)
warnings.warn(_warn_msg, ResourceWarning)
os.umask(self.original_umask)
def pipenv(self, cmd, block=True):
if self.pipfile_path:
@@ -162,7 +254,7 @@ class _PipenvInstance(object):
@pytest.fixture()
def PipenvInstance():
return _PipenvInstance
yield _PipenvInstance
@pytest.fixture(scope='module')
+2 -2
View File
@@ -37,9 +37,9 @@ flask = "==0.12.2"
""".strip()
f.write(contents)
req_list = ("requests==2.14.0")
req_list = ("requests==2.14.0",)
dev_req_list = ("flask==0.12.2")
dev_req_list = ("flask==0.12.2",)
c = p.pipenv('lock -r')
d = p.pipenv('lock -r -d')
+6 -5
View File
@@ -4,6 +4,7 @@ import pytest
from mock import patch, Mock
from first import first
import pipenv.utils
import pythonfinder.utils
# Pipfile format <-> requirements.txt format.
@@ -215,13 +216,13 @@ class TestUtils:
),
],
)
@patch("delegator.run")
# @patch(".vendor.pythonfinder.utils.get_python_version")
def test_python_version_output_variants(
self, mocked_delegator, version_output, version
self, monkeypatch, version_output, version
):
run_ret = Mock()
run_ret.out = version_output
mocked_delegator.return_value = run_ret
def mock_version(path):
return version_output.split()[1]
monkeypatch.setattr("pipenv.vendor.pythonfinder.utils.get_python_version", mock_version)
assert pipenv.utils.python_version("some/path") == version
@pytest.mark.utils