mirror of
https://github.com/kennethreitz/pipenv.git
synced 2026-06-05 22:50:18 +00:00
Merge pull request #3259 from pypa/bugfix/resilient-parser
Update pythonfinder for resilient parsing
This commit is contained in:
+1
-1
@@ -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:
|
||||
|
||||
+10
@@ -4,6 +4,15 @@ import os
|
||||
import platform
|
||||
import sys
|
||||
|
||||
|
||||
def is_type_checking():
|
||||
try:
|
||||
from typing import TYPE_CHECKING
|
||||
except ImportError:
|
||||
return False
|
||||
return TYPE_CHECKING
|
||||
|
||||
|
||||
PYENV_INSTALLED = bool(os.environ.get("PYENV_SHELL")) or bool(
|
||||
os.environ.get("PYENV_ROOT")
|
||||
)
|
||||
@@ -24,3 +33,4 @@ else:
|
||||
|
||||
|
||||
IGNORE_UNSUPPORTED = bool(os.environ.get("PYTHONFINDER_IGNORE_UNSUPPORTED", False))
|
||||
MYPY_RUNNING = os.environ.get("MYPY_RUNNING", is_type_checking())
|
||||
|
||||
+5
-44
@@ -10,7 +10,7 @@ from collections import defaultdict
|
||||
|
||||
import attr
|
||||
|
||||
from packaging.version import Version
|
||||
from packaging.version import Version, LegacyVersion
|
||||
from packaging.version import parse as parse_version
|
||||
from vistir.compat import Path
|
||||
|
||||
@@ -355,49 +355,10 @@ class PythonVersion(object):
|
||||
:rtype: dict.
|
||||
"""
|
||||
|
||||
is_debug = False
|
||||
if version.endswith("-debug"):
|
||||
is_debug = True
|
||||
version, _, _ = version.rpartition("-")
|
||||
try:
|
||||
version = parse_version(str(version))
|
||||
except TypeError:
|
||||
try:
|
||||
version_dict = parse_python_version(str(version))
|
||||
except Exception:
|
||||
raise ValueError("Unable to parse version: %s" % version)
|
||||
else:
|
||||
if not version_dict:
|
||||
raise ValueError("Not a valid python version: %r" % version)
|
||||
major = int(version_dict.get("major"))
|
||||
minor = int(version_dict.get("minor"))
|
||||
patch = version_dict.get("patch")
|
||||
if patch:
|
||||
patch = int(patch)
|
||||
version = ".".join([v for v in [major, minor, patch] if v is not None])
|
||||
version = parse_version(version)
|
||||
else:
|
||||
if not version or not version.release:
|
||||
raise ValueError("Not a valid python version: %r" % version)
|
||||
if len(version.release) >= 3:
|
||||
major, minor, patch = version.release[:3]
|
||||
elif len(version.release) == 2:
|
||||
major, minor = version.release
|
||||
patch = None
|
||||
else:
|
||||
major = version.release[0]
|
||||
minor = None
|
||||
patch = None
|
||||
return {
|
||||
"major": major,
|
||||
"minor": minor,
|
||||
"patch": patch,
|
||||
"is_prerelease": version.is_prerelease,
|
||||
"is_postrelease": version.is_postrelease,
|
||||
"is_devrelease": version.is_devrelease,
|
||||
"is_debug": is_debug,
|
||||
"version": version,
|
||||
}
|
||||
version_dict = parse_python_version(str(version))
|
||||
if not version_dict:
|
||||
raise ValueError("Not a valid python version: %r" % version)
|
||||
return version_dict
|
||||
|
||||
def get_architecture(self):
|
||||
if self.architecture:
|
||||
|
||||
Vendored
+96
-3
@@ -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,8 +26,14 @@ 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<major>[0-9]+)\.(?P<minor>[0-9]+)\.?(?P<patch>(?<=\.)[0-9]+)")
|
||||
|
||||
version_re = re.compile(r"(?P<major>\d+)\.(?P<minor>\d+)\.?(?P<patch>(?<=\.)[0-9]+)?\.?"
|
||||
r"(?:(?P<prerel>[abc]|rc|dev)(?:(?P<prerelversion>\d+(?:\.\d+)*))?)"
|
||||
r"?(?P<postdev>(\.post(?P<post>\d+))?(\.dev(?P<dev>\d+))?)?")
|
||||
|
||||
|
||||
PYTHON_IMPLEMENTATIONS = (
|
||||
@@ -52,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:
|
||||
@@ -66,22 +75,79 @@ 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_str.rpartition("-")
|
||||
m = version_re.match(version_str)
|
||||
if not m:
|
||||
raise InvalidPythonVersion("%s is not a python version" % version_str)
|
||||
return m.groupdict()
|
||||
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(version_parts))
|
||||
return {
|
||||
"major": major,
|
||||
"minor": minor,
|
||||
"patch": patch,
|
||||
"is_postrelease": is_postrelease,
|
||||
"is_prerelease": is_prerelease,
|
||||
"is_devrelease": is_devrelease,
|
||||
"is_debug": is_debug,
|
||||
"version": version
|
||||
}
|
||||
|
||||
|
||||
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)
|
||||
@@ -91,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)
|
||||
@@ -98,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.
|
||||
|
||||
@@ -119,6 +205,7 @@ def ensure_path(path):
|
||||
|
||||
|
||||
def _filter_none(k, v):
|
||||
# type: (Any, Any) -> bool
|
||||
if v:
|
||||
return True
|
||||
return False
|
||||
@@ -126,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))))
|
||||
))
|
||||
@@ -133,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))
|
||||
@@ -143,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:
|
||||
@@ -157,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:
|
||||
@@ -167,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:
|
||||
|
||||
Reference in New Issue
Block a user