diff --git a/pipenv/vendor/pythonfinder/__init__.py b/pipenv/vendor/pythonfinder/__init__.py index 8059cda5..eb3d0363 100644 --- a/pipenv/vendor/pythonfinder/__init__.py +++ b/pipenv/vendor/pythonfinder/__init__.py @@ -1,7 +1,8 @@ from __future__ import print_function, absolute_import -__version__ = "0.1.2" +__version__ = "0.1.4.dev0" -__all__ = ["Finder", "WindowsFinder", "SystemPath"] +__all__ = ["Finder", "WindowsFinder", "SystemPath", "InvalidPythonVersion"] from .pythonfinder import Finder from .models import SystemPath, WindowsFinder +from .exceptions import InvalidPythonVersion diff --git a/pipenv/vendor/pythonfinder/_vendor/Makefile b/pipenv/vendor/pythonfinder/_vendor/Makefile new file mode 100644 index 00000000..5c44fea4 --- /dev/null +++ b/pipenv/vendor/pythonfinder/_vendor/Makefile @@ -0,0 +1,14 @@ +# Taken from pip: https://github.com/pypa/pip/blob/95bcf8c5f6394298035a7332c441868f3b0169f4/src/pip/_vendor/Makefile +all: clean vendor + +clean: + @# Delete vendored items + find . -maxdepth 1 -mindepth 1 -type d -exec rm -rf {} \; + +vendor: + @# Install vendored libraries + pip install -t . -r vendor.txt + + @# Cleanup .egg-info directories + rm -rf *.egg-info + rm -rf *.dist-info diff --git a/pipenv/vendor/pythonfinder/_vendor/vendor.txt b/pipenv/vendor/pythonfinder/_vendor/vendor.txt new file mode 100644 index 00000000..88752498 --- /dev/null +++ b/pipenv/vendor/pythonfinder/_vendor/vendor.txt @@ -0,0 +1 @@ +-e git+https://github.com/zooba/pep514tools.git@320e48745660b696e2dcaee888fc2e516b435e48#egg=pep514tools diff --git a/pipenv/vendor/pythonfinder/cli.py b/pipenv/vendor/pythonfinder/cli.py index 2bb80f3d..4e620b08 100644 --- a/pipenv/vendor/pythonfinder/cli.py +++ b/pipenv/vendor/pythonfinder/cli.py @@ -1,6 +1,6 @@ #!/usr/bin/env python # -*- coding=utf-8 -*- - +from __future__ import print_function, absolute_import import click import crayons import sys diff --git a/pipenv/vendor/pythonfinder/environment.py b/pipenv/vendor/pythonfinder/environment.py index 3f82ba10..2cdb5fd9 100644 --- a/pipenv/vendor/pythonfinder/environment.py +++ b/pipenv/vendor/pythonfinder/environment.py @@ -1,4 +1,5 @@ # -*- coding=utf-8 -*- +from __future__ import print_function, absolute_import import os import platform import sys diff --git a/pipenv/vendor/pythonfinder/exceptions.py b/pipenv/vendor/pythonfinder/exceptions.py new file mode 100644 index 00000000..13e56e2c --- /dev/null +++ b/pipenv/vendor/pythonfinder/exceptions.py @@ -0,0 +1,7 @@ +# -*- coding=utf-8 -*- +from __future__ import print_function, absolute_import + + +class InvalidPythonVersion(Exception): + """Raised when parsing an invalid python version""" + pass diff --git a/pipenv/vendor/pythonfinder/models/__init__.py b/pipenv/vendor/pythonfinder/models/__init__.py index beaef790..e455ffb0 100644 --- a/pipenv/vendor/pythonfinder/models/__init__.py +++ b/pipenv/vendor/pythonfinder/models/__init__.py @@ -1,4 +1,5 @@ # -*- coding=utf-8 -*- +from __future__ import print_function, absolute_import import abc import operator import six @@ -33,16 +34,13 @@ class BasePath(object): :returns: :class:`~pythonfinder.models.PathEntry` instance. """ - valid_names = [ + valid_names = [name] + [ "{0}.{1}".format(name, ext).lower() if ext else "{0}".format(name).lower() for ext in KNOWN_EXTS ] - finder = filter(operator.attrgetter("is_executable"), self.children.values()) - name_getter = operator.attrgetter("path.name") - return next( - (child for child in finder if name_getter(child).lower() in valid_names), - None, - ) + children = self.children + found = next((children[(self.path / child).as_posix()] for child in valid_names if (self.path / child).as_posix() in children), None) + return found def find_python_version(self, major, minor=None, patch=None, pre=None, dev=None): """Search or self for the specified Python version and return the first match. @@ -62,16 +60,16 @@ class BasePath(object): is_py = operator.attrgetter("is_python") py_version = operator.attrgetter("as_python") if not self.is_dir: - if self.is_python: - return self if version_matcher(self.as_python) else None + if self.is_python and self.as_python.matches(major, minor=minor, patch=patch, pre=pre, dev=dev): + return self return - finder = (c for c in self.children.values() if is_py(c) and py_version(c)) + finder = ((child, child.as_python) for child in self.children.values() if child.is_python and child.as_python) py_filter = filter( - None, filter(lambda c: version_matcher(py_version(c)), finder) + None, filter(lambda child: version_matcher(child[1]), finder) ) - version_sort = operator.attrgetter("py_version.version") + version_sort = operator.attrgetter("version") return next( - (c for c in sorted(py_filter, key=version_sort, reverse=True)), None + (c[0] for c in sorted(py_filter, key=lambda child: child[1].version, reverse=True)), None ) diff --git a/pipenv/vendor/pythonfinder/models/path.py b/pipenv/vendor/pythonfinder/models/path.py index e9d460e7..2c6ea950 100644 --- a/pipenv/vendor/pythonfinder/models/path.py +++ b/pipenv/vendor/pythonfinder/models/path.py @@ -1,4 +1,5 @@ # -*- coding=utf-8 -*- +from __future__ import print_function, absolute_import import attr import operator import os @@ -7,12 +8,14 @@ from collections import defaultdict from . import BasePath from .python import PythonVersion from ..environment import PYENV_INSTALLED, PYENV_ROOT +from ..exceptions import InvalidPythonVersion from ..utils import ( optional_instance_of, filter_pythons, path_is_known_executable, is_python_name, ensure_path, + fs_str ) try: @@ -50,7 +53,7 @@ class SystemPath(object): for p in self.python_executables: try: version_object = PythonVersion.from_path(p) - except ValueError: + except (ValueError, InvalidPythonVersion): continue version_dict[version_object.version_tuple].append(version_object) return version_dict @@ -64,11 +67,11 @@ class SystemPath(object): if PYENV_INSTALLED: self._setup_pyenv() venv = os.environ.get('VIRTUAL_ENV') + if os.name == 'nt': + bin_dir = 'Scripts' + else: + bin_dir = 'bin' if venv: - if os.name == 'nt': - bin_dir = 'Scripts' - else: - bin_dir = 'bin' p = Path(venv) self.path_order = [(p / bin_dir).as_posix()] + self.path_order self.paths[p] = PathEntry.create( @@ -76,9 +79,12 @@ class SystemPath(object): ) if self.system: syspath = Path(sys.executable) - self.path_order = [syspath.parent.as_posix()] + self.path_order - self.paths[syspath.parent.as_posix()] = PathEntry.create( - path=syspath.parent, is_root=True, only_python=True + syspath_bin = syspath.parent + if syspath_bin.name != bin_dir and syspath_bin.joinpath(bin_dir).exists(): + syspath_bin = syspath_bin / bin_dir + self.path_order = [syspath_bin.as_posix()] + self.path_order + self.paths[syspath_bin.as_posix()] = PathEntry.create( + path=syspath_bin, is_root=True, only_python=False ) def _setup_pyenv(self): @@ -111,12 +117,24 @@ class SystemPath(object): self.paths.update({p.path: p for p in root_paths}) def get_path(self, path): - _path = self.paths.get(path) - if not _path and path in self.path_order: - self.paths[path] = PathEntry.create( - path=path, is_root=True, only_python=self.only_python + path = Path(path) + _path = self.paths.get(path.as_posix()) + if not _path and path.as_posix() in self.path_order: + self.paths[path.as_posix()] = PathEntry.create( + path=path.resolve(), is_root=True, only_python=self.only_python ) - return self.paths.get(path) + return self.paths.get(path.as_posix()) + + def find_all(self, executable): + """Search the path for an executable. Return all copies. + + :param executable: Name of the executable + :type executable: str + :returns: List[PathEntry] + """ + sub_which = operator.methodcaller("which", name=executable) + filtered = filter(None, (sub_which(self.get_path(k)) for k in self.path_order)) + return [f for f in filtered] def which(self, executable): """Search for an executable on the path. @@ -126,9 +144,33 @@ class SystemPath(object): :returns: :class:`~pythonfinder.models.PathEntry` object. """ sub_which = operator.methodcaller("which", name=executable) - return next( - (sub_which(self.get_path(k)) for k in self.path_order), None + filtered = filter(None, (sub_which(self.get_path(k)) for k in self.path_order)) + return next((f for f in filtered), None) + + def find_all_python_versions(self, major, minor=None, patch=None, pre=None, dev=None): + """Search for a specific python version on the path. Return all copies + + :param major: Major python version to search for. + :type major: int + :param minor: Minor python version to search for, defaults to None + :param minor: int, optional + :param path: Patch python version to search for, defaults to None + :param path: int, optional + :return: A list of :class:`~pythonfinder.models.PathEntry` instances matching the version requested. + :rtype: List[:class:`~pythonfinder.models.PathEntry`] + """ + + sub_finder = operator.methodcaller( + "find_python_version", major, minor=minor, patch=patch, pre=pre, dev=dev ) + if os.name == "nt" and self.windows_finder: + windows_finder_version = sub_finder(self.windows_finder) + if windows_finder_version: + return windows_finder_version + paths = (self.get_path(k) for k in self.path_order) + path_filter = filter(None, (sub_finder(p) for p in paths if p is not None)) + version_sort = operator.attrgetter("as_python.version") + return [c for c in sorted(path_filter, key=version_sort, reverse=True)] def find_python_version(self, major, minor=None, patch=None, pre=None, dev=None): """Search for a specific python version on the path. @@ -150,8 +192,8 @@ class SystemPath(object): windows_finder_version = sub_finder(self.windows_finder) if windows_finder_version: return windows_finder_version - paths = [self.get_path(k) for k in self.path_order] - path_filter = filter(None, [sub_finder(p) for p in paths]) + paths = (self.get_path(k) for k in self.path_order) + path_filter = filter(None, (sub_finder(p) for p in paths if p is not None)) version_sort = operator.attrgetter("as_python.version") return next( (c for c in sorted(path_filter, key=version_sort, reverse=True)), None @@ -180,7 +222,7 @@ class SystemPath(object): path_entries.update( { p.as_posix(): PathEntry.create( - path=p, is_root=True, only_python=only_python + path=p.absolute(), is_root=True, only_python=only_python ) for p in _path_objects } @@ -197,6 +239,9 @@ class PathEntry(BasePath): py_version = attr.ib(default=None) pythons = attr.ib(default=None) + def __str__(self): + return fs_str('{0}'.format(self.path.as_posix())) + def _filter_children(self): if self.only_python: children = filter_pythons(self.path) @@ -219,9 +264,8 @@ class PathEntry(BasePath): if not self.py_version: try: from .python import PythonVersion - self.py_version = PythonVersion.from_path(self.path) - except ValueError: + except (ValueError, InvalidPythonVersion): self.py_version = None return self.py_version diff --git a/pipenv/vendor/pythonfinder/models/pyenv.py b/pipenv/vendor/pythonfinder/models/pyenv.py index befa2d24..8545ac59 100644 --- a/pipenv/vendor/pythonfinder/models/pyenv.py +++ b/pipenv/vendor/pythonfinder/models/pyenv.py @@ -1,4 +1,5 @@ # -*- coding=utf-8 -*- +from __future__ import print_function, absolute_import import attr from collections import defaultdict from . import BaseFinder @@ -30,7 +31,7 @@ class PyenvFinder(BaseFinder): version.get("is_prerelease"), version.get("is_devrelease"), ) - versions[version_tuple] = VersionPath.create(path=p, only_python=True) + versions[version_tuple] = VersionPath.create(path=p.resolve(), only_python=True) return versions @classmethod diff --git a/pipenv/vendor/pythonfinder/models/python.py b/pipenv/vendor/pythonfinder/models/python.py index af39798e..1f681767 100644 --- a/pipenv/vendor/pythonfinder/models/python.py +++ b/pipenv/vendor/pythonfinder/models/python.py @@ -1,4 +1,5 @@ # -*- coding=utf-8 -*- +from __future__ import print_function, absolute_import import attr import copy import platform @@ -122,7 +123,7 @@ class PythonVersion(object): if not path.is_python: raise ValueError("Not a valid python path: %s" % path.path) return - py_version, _ = get_python_version(str(path.path)) + py_version = get_python_version(str(path.path)) instance_dict = cls.parse(py_version) if not isinstance(instance_dict.get("version"), Version): raise ValueError("Not a valid python path: %s" % path.path) diff --git a/pipenv/vendor/pythonfinder/models/windows.py b/pipenv/vendor/pythonfinder/models/windows.py index 77db6ff5..b6998823 100644 --- a/pipenv/vendor/pythonfinder/models/windows.py +++ b/pipenv/vendor/pythonfinder/models/windows.py @@ -1,10 +1,12 @@ # -*- coding=utf-8 -*- +from __future__ import print_function, absolute_import import attr import operator from collections import defaultdict from . import BaseFinder from .path import PathEntry from .python import PythonVersion +from ..exceptions import InvalidPythonVersion from ..utils import ensure_path @@ -35,7 +37,10 @@ class WindowsFinder(BaseFinder): path = None for version_object in env_versions: path = ensure_path(version_object.info.install_path.__getattr__("")) - py_version = PythonVersion.from_windows_launcher(version_object) + try: + py_version = PythonVersion.from_windows_launcher(version_object) + except InvalidPythonVersion: + continue self.version_list.append(py_version) base_dir = PathEntry.create( path, diff --git a/pipenv/vendor/pythonfinder/pythonfinder.py b/pipenv/vendor/pythonfinder/pythonfinder.py index 548b707a..50e1b8d8 100644 --- a/pipenv/vendor/pythonfinder/pythonfinder.py +++ b/pipenv/vendor/pythonfinder/pythonfinder.py @@ -1,4 +1,5 @@ # -*- coding=utf-8 -*- +from __future__ import print_function, absolute_import import os import six from .models import SystemPath @@ -39,15 +40,23 @@ class Finder(object): and not dev and isinstance(major, six.string_types) ): + from .models import PythonVersion + version_dict = {} if "." in major: - from .models import PythonVersion - version_dict = PythonVersion.parse(major) - major = version_dict["major"] - minor = version_dict["minor"] - patch = version_dict["patch"] - pre = version_dict["is_prerelease"] - dev = version_dict["is_devrelease"] + elif len(major) == 1: + version_dict = { + 'major': int(major), + 'minor': None, + 'patch': None, + 'is_prerelease': False, + 'is_devrelease': False + } + major = version_dict.get("major", major) + minor = version_dict.get("minor", minor) + patch = version_dict.get("patch", patch) + pre = version_dict.get("is_prerelease", pre) + dev = version_dict.get("is_devrelease", dev) if os.name == "nt": match = self.windows_finder.find_python_version( major, minor=minor, patch=patch, pre=pre, dev=dev diff --git a/pipenv/vendor/pythonfinder/utils.py b/pipenv/vendor/pythonfinder/utils.py index b8615897..0cc5370a 100644 --- a/pipenv/vendor/pythonfinder/utils.py +++ b/pipenv/vendor/pythonfinder/utils.py @@ -1,9 +1,13 @@ # -*- coding=utf-8 -*- +from __future__ import print_function, absolute_import import attr import locale import os +import six import subprocess +import sys from fnmatch import fnmatch +from .exceptions import InvalidPythonVersion try: from pathlib import Path @@ -26,23 +30,26 @@ def _run(cmd): :returns: A 2-tuple of (output, error) """ encoding = locale.getdefaultlocale()[1] or "utf-8" - env = os.environ.copy() c = subprocess.Popen( cmd, - encoding=encoding, - env=env, - universal_newlines=True, + env=os.environ.copy(), stdout=subprocess.PIPE, stderr=subprocess.PIPE, ) - output, err = c.communicate() - return output.strip(), err.strip() + out, err = c.communicate() + return out.decode(encoding).strip(), err.decode(encoding).strip() def get_python_version(path): """Get python version string using subprocess from a given path.""" version_cmd = [path, "-c", "import sys; print(sys.version.split()[0])"] - return _run(version_cmd) + try: + out, _ = _run(version_cmd) + except OSError: + raise InvalidPythonVersion("%s is not a valid python path" % path) + if not out: + raise InvalidPythonVersion("%s is not a valid python path" % path) + return out def optional_instance_of(cls): @@ -111,3 +118,17 @@ def filter_pythons(path): if not path.is_dir(): return path if path_is_python(path) else None return filter(lambda x: path_is_python(x), path.iterdir()) + + +def fs_str(string): + """Encodes a string into the proper filesystem encoding + + Borrowed from pip-tools + """ + if isinstance(string, str): + return string + assert not isinstance(string, bytes) + return string.encode(_fs_encoding) + + +_fs_encoding = sys.getfilesystemencoding() or sys.getdefaultencoding()