diff --git a/news/2998.bugfix b/news/2998.bugfix new file mode 100644 index 00000000..fd9c5d8c --- /dev/null +++ b/news/2998.bugfix @@ -0,0 +1 @@ +Fixed a bug which prevented installing pinned versions which used redirection symbols from the command line. diff --git a/news/3007.vendor b/news/3007.vendor new file mode 100644 index 00000000..7ca66a00 --- /dev/null +++ b/news/3007.vendor @@ -0,0 +1 @@ +Upgraded ``pythonfinder => 1.1.1`` and ``vistir => 0.1.7``. diff --git a/pipenv/core.py b/pipenv/core.py index eb42c5e7..7633bdfc 100644 --- a/pipenv/core.py +++ b/pipenv/core.py @@ -40,6 +40,7 @@ from .utils import ( rmtree, clean_resolved_dep, parse_indexes, + escape_cmd ) from . import environments, pep508checker, progress from .environments import ( @@ -1398,6 +1399,7 @@ def pip_install( pip_command.append("--upgrade-strategy=only-if-needed") if no_deps: pip_command.append("--no-deps") + install_reqs = [escape_cmd(req) for req in install_reqs] pip_command.extend(install_reqs) pip_command.extend(prepare_pip_source_args(sources)) if not ignore_hashes: diff --git a/pipenv/utils.py b/pipenv/utils.py index f3599301..b9345afa 100644 --- a/pipenv/utils.py +++ b/pipenv/utils.py @@ -1048,6 +1048,12 @@ def handle_remove_readonly(func, path, exc): raise +def escape_cmd(cmd): + if any(special_char in cmd for special_char in ["<", ">", "&", ".", "^", "|", "?"]): + cmd = '\"{0}\"'.format(cmd) + return cmd + + @contextmanager def atomic_open_for_write(target, binary=False, newline=None, encoding=None): """Atomically open `target` for writing. diff --git a/pipenv/vendor/pythonfinder/__init__.py b/pipenv/vendor/pythonfinder/__init__.py index 586be002..7c9c6662 100644 --- a/pipenv/vendor/pythonfinder/__init__.py +++ b/pipenv/vendor/pythonfinder/__init__.py @@ -1,6 +1,6 @@ from __future__ import print_function, absolute_import -__version__ = '1.1.0' +__version__ = '1.1.1' __all__ = ["Finder", "WindowsFinder", "SystemPath", "InvalidPythonVersion"] from .pythonfinder import Finder diff --git a/pipenv/vendor/pythonfinder/models/python.py b/pipenv/vendor/pythonfinder/models/python.py index 0fcce6c8..4d8b0361 100644 --- a/pipenv/vendor/pythonfinder/models/python.py +++ b/pipenv/vendor/pythonfinder/models/python.py @@ -25,6 +25,7 @@ class PythonVersion(object): is_prerelease = attr.ib(default=False) is_postrelease = attr.ib(default=False) is_devrelease = attr.ib(default=False) + is_debug = attr.ib(default=False) version = attr.ib(default=None, validator=optional_instance_of(Version)) architecture = attr.ib(default=None) comes_from = attr.ib(default=None) @@ -46,6 +47,8 @@ class PythonVersion(object): release_sort = 1 elif self.is_devrelease: release_sort = 0 + elif self.is_debug: + release_sort = 1 return (self.major, self.minor, self.patch if self.patch else 0, release_sort) @property @@ -102,6 +105,10 @@ class PythonVersion(object): :rtype: dict. """ + is_debug = False + if version.endswith("-debug"): + is_debug = True + version, _, _ = verson.rpartition("-") try: version = parse_version(str(version)) except TypeError: @@ -125,6 +132,7 @@ class PythonVersion(object): "is_prerelease": version.is_prerelease, "is_postrelease": version.is_postrelease, "is_devrelease": version.is_devrelease, + "is_debug": is_debug, "version": version, } @@ -206,7 +214,7 @@ class VersionMap(object): def add_entry(self, entry): version = entry.as_python if version: - entries = versions[version.version_tuple] + entries = self.versions[version.version_tuple] paths = {p.path for p in self.versions.get(version.version_tuple, [])} if entry.path not in paths: self.versions[version.version_tuple].append(entry) diff --git a/pipenv/vendor/vendor.txt b/pipenv/vendor/vendor.txt index 8d13cdba..a7761f66 100644 --- a/pipenv/vendor/vendor.txt +++ b/pipenv/vendor/vendor.txt @@ -21,7 +21,7 @@ pipdeptree==0.13.0 pipreqs==0.4.9 docopt==0.6.2 yarg==0.1.9 -pythonfinder==1.1.0 +pythonfinder==1.1.1 requests==2.19.1 chardet==3.0.4 idna==2.7 @@ -41,7 +41,7 @@ semver==2.8.1 shutilwhich==1.1.0 toml==0.10.0 cached-property==1.4.3 -vistir==0.1.6 +vistir==0.1.7 pip-shims==0.3.1 ptyprocess==0.6.0 enum34==1.1.6 diff --git a/pipenv/vendor/vistir/__init__.py b/pipenv/vendor/vistir/__init__.py index 4be472ab..fe6f884c 100644 --- a/pipenv/vendor/vistir/__init__.py +++ b/pipenv/vendor/vistir/__init__.py @@ -13,7 +13,7 @@ from .misc import load_path, partialclass, run, shell_escape from .path import mkdir_p, rmtree -__version__ = '0.1.6' +__version__ = '0.1.7' __all__ = [ diff --git a/pipenv/vendor/vistir/misc.py b/pipenv/vendor/vistir/misc.py index 723bb117..44607a98 100644 --- a/pipenv/vendor/vistir/misc.py +++ b/pipenv/vendor/vistir/misc.py @@ -75,7 +75,7 @@ def dedup(iterable): return iter(OrderedDict.fromkeys(iterable)) -def _spawn_subprocess(script, env={}, block=True, cwd=None): +def _spawn_subprocess(script, env={}, block=True, cwd=None, combine_stderr=True): from distutils.spawn import find_executable command = find_executable(script.command) @@ -83,7 +83,7 @@ def _spawn_subprocess(script, env={}, block=True, cwd=None): "env": env, "universal_newlines": True, "stdout": subprocess.PIPE, - "stderr": subprocess.PIPE if block else subprocess.STDOUT, + "stderr": subprocess.PIPE if not combine_stderr else subprocess.STDOUT, "shell": False, } if not block: @@ -117,58 +117,90 @@ def _create_subprocess( cwd=os.curdir, verbose=False, spinner=None, + combine_stderr=False, + display_limit=200 ): try: - c = _spawn_subprocess(cmd, env=env, block=block, cwd=cwd) + c = _spawn_subprocess(cmd, env=env, block=block, cwd=cwd, + combine_stderr=combine_stderr) except Exception as exc: print("Error %s while executing command %s", exc, " ".join(cmd._parts)) raise if not block: c.stdin.close() output = [] + err = [] spinner_orig_text = "" if spinner: spinner_orig_text = spinner.text - if c.stdout is not None: - while True: - line = to_text(c.stdout.readline()) + streams = { + "stdout": c.stdout, + "stderr": c.stderr + } + while True: + stdout_line = None + stderr_line = None + for outstream in streams.keys(): + stream = streams[outstream] + if not stream: + continue + line = to_text(stream.readline()) if not line: - break + continue line = line.rstrip() - output.append(line) - display_line = line - if len(line) > 200: - display_line = "{0}...".format(line[:200]) + if outstream == "stderr": + stderr_line = line + else: + stdout_line = line + if not (stdout_line or stderr_line): + break + if stderr_line: + err.append(line) + if stdout_line: + output.append(stdout_line) + display_line = stdout_line + if len(stdout_line) > display_limit: + display_line = "{0}...".format(stdout_line[:display_limit]) if verbose: spinner.write(display_line) - else: - spinner.text = "{0} {1}".format(spinner_orig_text, display_line) - continue + spinner.text = "{0} {1}".format(spinner_orig_text, display_line) + continue try: c.wait() finally: if c.stdout: c.stdout.close() + if c.stderr: + c.stderr.close() if spinner: + if c.returncode > 0: + spinner.fail("Failed...cleaning up...") spinner.text = "Complete!" spinner.ok("✔") - c.out = "".join(output) - c.err = "" + c.out = "\n".join(output) + c.err = "\n".join(err) if err else "" else: c.out, c.err = c.communicate() if not return_object: - return c.out.strip(), c.err.strip() + if not block: + c.wait() + out = c.out if c.out else "" + err = c.err if c.err else "" + return out.strip(), err.strip() return c def run( cmd, - env={}, + env=None, return_object=False, block=True, cwd=None, verbose=False, nospin=False, + spinner=None, + combine_stderr=True, + display_limit=200 ): """Use `subprocess.Popen` to get the output of a command and decode it. @@ -179,8 +211,18 @@ def run( :param str cwd: Current working directory contect to use for spawning the subprocess. :param bool verbose: Whether to print stdout in real time when non-blocking. :param bool nospin: Whether to disable the cli spinner. + :param str spinner: The name of the spinner to use if enabled, defaults to bouncingBar + :param bool combine_stderr: Optionally merge stdout and stderr in the subprocess, false if nonblocking. + :param int dispay_limit: The max width of output lines to display when using a spinner. :returns: A 2-tuple of (output, error) or a :class:`subprocess.Popen` object. + + .. Warning:: Merging standard out and standarad error in a nonblocking subprocess + can cause errors in some cases and may not be ideal. Consider disabling + this functionality. """ + + if not env: + env = os.environ.copy() if six.PY2: fs_encode = partial(to_bytes, encoding=locale_encoding) _env = {fs_encode(k): fs_encode(v) for k, v in os.environ.items()} @@ -188,6 +230,8 @@ def run( _env[fs_encode(key)] = fs_encode(val) else: _env = {k: fs_str(v) for k, v in os.environ.items()} + if not spinner: + spinner = "bouncingBar" if six.PY2: if isinstance(cmd, six.string_types): cmd = cmd.encode("utf-8") @@ -195,22 +239,35 @@ def run( cmd = [c.encode("utf-8") for c in cmd] if not isinstance(cmd, Script): cmd = Script.parse(cmd) + if block or not return_object: + combine_stderr = False + sigmap = {} if nospin is False: try: + import signal from yaspin import yaspin from yaspin import spinners + from yaspin.signal_handlers import fancy_handler except ImportError: raise RuntimeError( "Failed to import spinner! Reinstall vistir with command:" " pip install --upgrade vistir[spinner]" ) else: - spinner = yaspin - animation = spinners.Spinners.bouncingBar + animation = getattr(spinners.Spinners, spinner) + sigmap = { + signal.SIGINT: fancy_handler + } + if os.name == "nt": + sigmap.update({ + signal.CTRL_C_EVENT: fancy_handler, + signal.CTRL_BREAK_EVENT: fancy_handler + }) + spinner_func = yaspin else: @contextmanager - def spinner(spin_type, text): + def spinner_func(spin_type, text, **kwargs): class FakeClass(object): def __init__(self, text=""): self.text = text @@ -225,7 +282,7 @@ def run( yield myobj animation = None - with spinner(animation, text="Running...") as sp: + with spinner_func(animation, sigmap=sigmap, text="Running...") as sp: return _create_subprocess( cmd, env=_env, @@ -234,6 +291,7 @@ def run( cwd=cwd, verbose=verbose, spinner=sp, + combine_stderr=combine_stderr ) @@ -249,7 +307,8 @@ def load_path(python): """ python = Path(python).as_posix() - out, err = run([python, "-c", "import json, sys; print(json.dumps(sys.path))"]) + out, err = run([python, "-c", "import json, sys; print(json.dumps(sys.path))"], + nospin=True) if out: return json.loads(out) else: