diff --git a/pipenv/vendor/pythonfinder/__init__.py b/pipenv/vendor/pythonfinder/__init__.py index fd9ec1be..8e12a198 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.9' +__version__ = '1.1.10' # Add NullHandler to "pythonfinder" logger, because Python2's default root # logger has no handler and warnings like this would be reported: diff --git a/pipenv/vendor/pythonfinder/utils.py b/pipenv/vendor/pythonfinder/utils.py index ea2b71de..d8edb239 100644 --- a/pipenv/vendor/pythonfinder/utils.py +++ b/pipenv/vendor/pythonfinder/utils.py @@ -13,7 +13,9 @@ import six import vistir -from .environment import PYENV_ROOT, ASDF_DATA_DIR +from packaging.version import LegacyVersion, Version + +from .environment import PYENV_ROOT, ASDF_DATA_DIR, MYPY_RUNNING from .exceptions import InvalidPythonVersion six.add_move(six.MovedAttribute("Iterable", "collections", "collections.abc")) @@ -24,6 +26,10 @@ try: except ImportError: from backports.functools_lru_cache import lru_cache +if MYPY_RUNNING: + from typing import Any, Union, List, Callable, Iterable, Set, Tuple, Dict, Optional + from attr.validators import _OptionalValidator + version_re = re.compile(r"(?P\d+)\.(?P\d+)\.?(?P(?<=\.)[0-9]+)?\.?" r"(?:(?P[abc]|rc|dev)(?:(?P\d+(?:\.\d+)*))?)" @@ -54,6 +60,7 @@ for rule in RULES: @lru_cache(maxsize=1024) def get_python_version(path): + # type: (str) -> str """Get python version string using subprocess from a given path.""" version_cmd = [path, "-c", "import sys; print(sys.version.split()[0])"] try: @@ -68,28 +75,30 @@ def get_python_version(path): @lru_cache(maxsize=1024) def parse_python_version(version_str): + # type: (str) -> Dict[str, Union[str, int, Version]] from packaging.version import parse as parse_version is_debug = False if version_str.endswith("-debug"): is_debug = True - version_str, _, _ = version.rpartition("-") + version_str, _, _ = version_str.rpartition("-") m = version_re.match(version_str) if not m: raise InvalidPythonVersion("%s is not a python version" % version_str) - version_dict = m.groupdict() - major = int(version_dict.get("major")) - minor = int(version_dict.get("minor")) - patch = version_dict.get("patch") + version_dict = m.groupdict() # type: Dict[str, str] + major = int(version_dict.get("major", 0)) if version_dict.get("major") else None + minor = int(version_dict.get("minor", 0)) if version_dict.get("minor") else None + patch = int(version_dict.get("patch", 0)) if version_dict.get("patch") else None is_postrelease = True if version_dict.get("post") else False is_prerelease = True if version_dict.get("prerel") else False is_devrelease = True if version_dict.get("dev") else False if patch: patch = int(patch) + version = None # type: Optional[Union[Version, LegacyVersion]] try: version = parse_version(version_str) except TypeError: version_parts = [str(v) for v in [major, minor, patch] if v is not None] - version = parse_version(".".join(v)) + version = parse_version(".".join(version_parts)) return { "major": major, "minor": minor, @@ -103,15 +112,42 @@ def parse_python_version(version_str): def optional_instance_of(cls): + # type: (Any) -> _OptionalValidator + """ + Return an validator to determine whether an input is an optional instance of a class. + + :return: A validator to determine optional instance membership. + :rtype: :class:`~attr.validators._OptionalValidator` + """ + return attr.validators.optional(attr.validators.instance_of(cls)) def path_is_executable(path): + # type: (str) -> bool + """ + Determine whether the supplied path is executable. + + :return: Whether the provided path is executable. + :rtype: bool + """ + return os.access(str(path), os.X_OK) @lru_cache(maxsize=1024) def path_is_known_executable(path): + # type: (vistir.compat.Path) -> bool + """ + Returns whether a given path is a known executable from known executable extensions + or has the executable bit toggled. + + :param path: The path to the target executable. + :type path: :class:`~vistir.compat.Path` + :return: True if the path has chmod +x, or is a readable, known executable extension. + :rtype: bool + """ + return ( path_is_executable(path) or os.access(str(path), os.R_OK) @@ -121,6 +157,15 @@ def path_is_known_executable(path): @lru_cache(maxsize=1024) def looks_like_python(name): + # type: (str) -> bool + """ + Determine whether the supplied filename looks like a possible name of python. + + :param str name: The name of the provided file. + :return: Whether the provided name looks like python. + :rtype: bool + """ + if not any(name.lower().startswith(py_name) for py_name in PYTHON_IMPLEMENTATIONS): return False return any(fnmatch(name, rule) for rule in MATCH_RULES) @@ -128,11 +173,22 @@ def looks_like_python(name): @lru_cache(maxsize=1024) def path_is_python(path): + # type: (vistir.compat.Path) -> bool + """ + Determine whether the supplied path is executable and looks like a possible path to python. + + :param path: The path to an executable. + :type path: :class:`~vistir.compat.Path` + :return: Whether the provided path is an executable path to python. + :rtype: bool + """ + return path_is_executable(path) and looks_like_python(path.name) @lru_cache(maxsize=1024) def ensure_path(path): + # type: (Union[vistir.compat.Path, str]) -> bool """ Given a path (either a string or a Path object), expand variables and return a Path object. @@ -149,6 +205,7 @@ def ensure_path(path): def _filter_none(k, v): + # type: (Any, Any) -> bool if v: return True return False @@ -156,6 +213,7 @@ def _filter_none(k, v): # TODO: Reimplement in vistir def normalize_path(path): + # type: (str) -> str return os.path.normpath(os.path.normcase( os.path.abspath(os.path.expandvars(os.path.expanduser(str(path)))) )) @@ -163,6 +221,7 @@ def normalize_path(path): @lru_cache(maxsize=1024) def filter_pythons(path): + # type: (Union[str, vistir.compat.Path]) -> Iterable """Return all valid pythons in a given path""" if not isinstance(path, vistir.compat.Path): path = vistir.compat.Path(str(path)) @@ -173,6 +232,8 @@ def filter_pythons(path): # TODO: Port to vistir def unnest(item): + # type: (Any) -> Iterable[Any] + target = None # type: Optional[Iterable] if isinstance(item, Iterable) and not isinstance(item, six.string_types): item, target = itertools.tee(item, 2) else: @@ -187,6 +248,7 @@ def unnest(item): def parse_pyenv_version_order(filename="version"): + # type: (str) -> List[str] version_order_file = normalize_path(os.path.join(PYENV_ROOT, filename)) if os.path.exists(version_order_file) and os.path.isfile(version_order_file): with io.open(version_order_file, encoding="utf-8") as fh: @@ -197,6 +259,7 @@ def parse_pyenv_version_order(filename="version"): def parse_asdf_version_order(filename=".tool-versions"): + # type: (str) -> List[str] version_order_file = normalize_path(os.path.join("~", filename)) if os.path.exists(version_order_file) and os.path.isfile(version_order_file): with io.open(version_order_file, encoding="utf-8") as fh: