mirror of
https://github.com/kennethreitz/pipenv.git
synced 2026-06-05 22:50:18 +00:00
+37
-6
@@ -26,7 +26,9 @@ from ..utils import (
|
||||
optional_instance_of,
|
||||
path_is_known_executable,
|
||||
unnest,
|
||||
normalize_path
|
||||
normalize_path,
|
||||
parse_pyenv_version_order,
|
||||
parse_asdf_version_order
|
||||
)
|
||||
from .python import PythonVersion
|
||||
|
||||
@@ -165,23 +167,26 @@ class SystemPath(object):
|
||||
self.path_order = new_order
|
||||
|
||||
def _setup_asdf(self):
|
||||
from .asdf import AsdfFinder
|
||||
from .python import PythonFinder
|
||||
asdf_index = self._get_last_instance(ASDF_DATA_DIR)
|
||||
if not asdf_index:
|
||||
# we are in a virtualenv without global pyenv on the path, so we should
|
||||
# not write pyenv to the path here
|
||||
return
|
||||
self.asdf_finder = AsdfFinder.create(root=ASDF_DATA_DIR, ignore_unsupported=True)
|
||||
self.asdf_finder = PythonFinder.create(
|
||||
root=ASDF_DATA_DIR, ignore_unsupported=True,
|
||||
sort_function=parse_asdf_version_order, version_glob_path="installs/python/*")
|
||||
root_paths = [p for p in self.asdf_finder.roots]
|
||||
self._slice_in_paths(asdf_index, root_paths)
|
||||
self.paths.update(self.asdf_finder.roots)
|
||||
self._register_finder("asdf", self.asdf_finder)
|
||||
|
||||
def _setup_pyenv(self):
|
||||
from .pyenv import PyenvFinder
|
||||
from .python import PythonFinder
|
||||
|
||||
self.pyenv_finder = PyenvFinder.create(
|
||||
root=PYENV_ROOT, ignore_unsupported=self.ignore_unsupported
|
||||
self.pyenv_finder = PythonFinder.create(
|
||||
root=PYENV_ROOT, sort_function=parse_pyenv_version_order,
|
||||
version_glob_path="versions/*", ignore_unsupported=self.ignore_unsupported
|
||||
)
|
||||
pyenv_index = self._get_last_instance(PYENV_ROOT)
|
||||
if not pyenv_index:
|
||||
@@ -585,3 +590,29 @@ class PathEntry(BasePath):
|
||||
return self.is_executable and (
|
||||
looks_like_python(self.path.name)
|
||||
)
|
||||
|
||||
|
||||
@attr.s
|
||||
class VersionPath(SystemPath):
|
||||
base = attr.ib(default=None, validator=optional_instance_of(Path))
|
||||
name = attr.ib(default=None)
|
||||
|
||||
@classmethod
|
||||
def create(cls, path, only_python=True, pythons=None, name=None):
|
||||
"""Accepts a path to a base python version directory.
|
||||
|
||||
Generates the version listings for it"""
|
||||
from .path import PathEntry
|
||||
path = ensure_path(path)
|
||||
path_entries = defaultdict(PathEntry)
|
||||
bin_ = "{base}/bin"
|
||||
if path.as_posix().endswith(Path(bin_).name):
|
||||
path = path.parent
|
||||
bin_dir = ensure_path(bin_.format(base=path.as_posix()))
|
||||
if not name:
|
||||
name = path.name
|
||||
current_entry = PathEntry.create(
|
||||
bin_dir, is_root=True, only_python=True, pythons=pythons, name=name
|
||||
)
|
||||
path_entries[bin_dir.as_posix()] = current_entry
|
||||
return cls(name=name, base=bin_dir, paths=path_entries)
|
||||
|
||||
-2
@@ -14,8 +14,6 @@ from vistir.compat import Path
|
||||
from ..utils import (
|
||||
ensure_path,
|
||||
optional_instance_of,
|
||||
get_python_version,
|
||||
filter_pythons,
|
||||
unnest,
|
||||
)
|
||||
from .mixins import BaseFinder, BasePath
|
||||
|
||||
+218
-3
@@ -3,23 +3,238 @@ from __future__ import absolute_import, print_function
|
||||
|
||||
import copy
|
||||
import platform
|
||||
import operator
|
||||
import logging
|
||||
|
||||
from collections import defaultdict
|
||||
|
||||
import attr
|
||||
|
||||
from packaging.version import Version, LegacyVersion
|
||||
from packaging.version import Version
|
||||
from packaging.version import parse as parse_version
|
||||
from vistir.compat import Path
|
||||
|
||||
from ..environment import SYSTEM_ARCH
|
||||
from ..environment import SYSTEM_ARCH, PYENV_ROOT, ASDF_DATA_DIR
|
||||
from .mixins import BaseFinder, BasePath
|
||||
from ..utils import (
|
||||
_filter_none,
|
||||
ensure_path,
|
||||
get_python_version,
|
||||
optional_instance_of,
|
||||
ensure_path,
|
||||
unnest,
|
||||
is_in_path,
|
||||
parse_pyenv_version_order,
|
||||
parse_asdf_version_order,
|
||||
)
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@attr.s(slots=True)
|
||||
class PythonFinder(BaseFinder, BasePath):
|
||||
root = attr.ib(default=None, validator=optional_instance_of(Path))
|
||||
#: ignore_unsupported should come before versions, because its value is used
|
||||
#: in versions's default initializer.
|
||||
ignore_unsupported = attr.ib(default=True)
|
||||
#: The function to use to sort version order when returning an ordered verion set
|
||||
sort_function = attr.ib(default=None)
|
||||
paths = attr.ib(default=attr.Factory(list))
|
||||
roots = attr.ib(default=attr.Factory(defaultdict))
|
||||
#: Glob path for python versions off of the root directory
|
||||
version_glob_path = attr.ib(default="versions/*")
|
||||
versions = attr.ib()
|
||||
pythons = attr.ib()
|
||||
|
||||
@property
|
||||
def expanded_paths(self):
|
||||
return (
|
||||
path for path in unnest(p for p in self.versions.values())
|
||||
if path is not None
|
||||
)
|
||||
|
||||
@property
|
||||
def is_pyenv(self):
|
||||
return is_in_path(str(self.root), PYENV_ROOT)
|
||||
|
||||
@property
|
||||
def is_asdf(self):
|
||||
return is_in_path(str(self.root), ASDF_DATA_DIR)
|
||||
|
||||
def get_version_order(self):
|
||||
version_paths = [
|
||||
p for p in self.root.glob(self.version_glob_path)
|
||||
if not (p.parent.name == "envs" or p.name == "envs")
|
||||
]
|
||||
versions = {v.name: v for v in version_paths}
|
||||
if self.is_pyenv:
|
||||
version_order = [versions[v] for v in parse_pyenv_version_order()]
|
||||
elif self.is_asdf:
|
||||
version_order = [versions[v] for v in parse_asdf_version_order()]
|
||||
for version in version_order:
|
||||
version_paths.remove(version)
|
||||
if version_order:
|
||||
version_order += version_paths
|
||||
else:
|
||||
version_order = version_paths
|
||||
return version_order
|
||||
|
||||
@classmethod
|
||||
def version_from_bin_dir(cls, base_dir, name=None):
|
||||
from .path import PathEntry
|
||||
py_version = None
|
||||
version_path = PathEntry.create(
|
||||
path=base_dir.absolute().as_posix(),
|
||||
only_python=True,
|
||||
name=base_dir.parent.name,
|
||||
)
|
||||
py_version = next(iter(version_path.find_all_python_versions()), None)
|
||||
return py_version
|
||||
|
||||
@versions.default
|
||||
def get_versions(self):
|
||||
from .path import PathEntry
|
||||
versions = defaultdict()
|
||||
bin_ = "{base}/bin"
|
||||
for p in self.get_version_order():
|
||||
bin_dir = Path(bin_.format(base=p.as_posix()))
|
||||
version_path = None
|
||||
if bin_dir.exists():
|
||||
version_path = PathEntry.create(
|
||||
path=bin_dir.absolute().as_posix(),
|
||||
only_python=False,
|
||||
name=p.name,
|
||||
is_root=True,
|
||||
)
|
||||
version = None
|
||||
try:
|
||||
version = PythonVersion.parse(p.name)
|
||||
except ValueError:
|
||||
entry = next(iter(version_path.find_all_python_versions()), None)
|
||||
if not entry:
|
||||
if self.ignore_unsupported:
|
||||
continue
|
||||
raise
|
||||
else:
|
||||
version = entry.py_version.as_dict()
|
||||
except Exception:
|
||||
if not self.ignore_unsupported:
|
||||
raise
|
||||
logger.warning(
|
||||
"Unsupported Python version %r, ignoring...", p.name, exc_info=True
|
||||
)
|
||||
continue
|
||||
if not version:
|
||||
continue
|
||||
version_tuple = (
|
||||
version.get("major"),
|
||||
version.get("minor"),
|
||||
version.get("patch"),
|
||||
version.get("is_prerelease"),
|
||||
version.get("is_devrelease"),
|
||||
version.get("is_debug"),
|
||||
)
|
||||
self.roots[p] = version_path
|
||||
versions[version_tuple] = version_path
|
||||
self.paths.append(version_path)
|
||||
return versions
|
||||
|
||||
@pythons.default
|
||||
def get_pythons(self):
|
||||
pythons = defaultdict()
|
||||
for p in self.paths:
|
||||
pythons.update(p.pythons)
|
||||
return pythons
|
||||
|
||||
@classmethod
|
||||
def create(cls, root, sort_function=None, version_glob_path=None, ignore_unsupported=True):
|
||||
root = ensure_path(root)
|
||||
if not version_glob_path:
|
||||
version_glob_path = "versions/*"
|
||||
return cls(root=root, ignore_unsupported=ignore_unsupported,
|
||||
sort_function=sort_function, version_glob_path=version_glob_path)
|
||||
|
||||
def find_all_python_versions(
|
||||
self,
|
||||
major=None,
|
||||
minor=None,
|
||||
patch=None,
|
||||
pre=None,
|
||||
dev=None,
|
||||
arch=None,
|
||||
name=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 int minor: Minor python version to search for, defaults to None
|
||||
:param int patch: Patch python version to search for, defaults to None
|
||||
:param bool pre: Search for prereleases (default None) - prioritize releases if None
|
||||
:param bool dev: Search for devreleases (default None) - prioritize releases if None
|
||||
:param str arch: Architecture to include, e.g. '64bit', defaults to None
|
||||
:param str name: The name of a python version, e.g. ``anaconda3-5.3.0``
|
||||
:return: A list of :class:`~pythonfinder.models.PathEntry` instances matching the version requested.
|
||||
:rtype: List[:class:`~pythonfinder.models.PathEntry`]
|
||||
"""
|
||||
|
||||
version_matcher = operator.methodcaller(
|
||||
"matches",
|
||||
major=major,
|
||||
minor=minor,
|
||||
patch=patch,
|
||||
pre=pre,
|
||||
dev=dev,
|
||||
arch=arch,
|
||||
name=name,
|
||||
)
|
||||
py = operator.attrgetter("as_python")
|
||||
pythons = (
|
||||
py_ver for py_ver in (py(p) for p in self.pythons.values() if p is not None)
|
||||
if py_ver is not None
|
||||
)
|
||||
# pythons = filter(None, [p.as_python for p in self.pythons.values()])
|
||||
matching_versions = filter(lambda py: version_matcher(py), pythons)
|
||||
version_sort = operator.attrgetter("version_sort")
|
||||
return sorted(matching_versions, key=version_sort, reverse=True)
|
||||
|
||||
def find_python_version(
|
||||
self,
|
||||
major=None,
|
||||
minor=None,
|
||||
patch=None,
|
||||
pre=None,
|
||||
dev=None,
|
||||
arch=None,
|
||||
name=None,
|
||||
):
|
||||
"""Search or self for the specified Python version and return the first match.
|
||||
|
||||
:param major: Major version number.
|
||||
:type major: int
|
||||
:param int minor: Minor python version to search for, defaults to None
|
||||
:param int patch: Patch python version to search for, defaults to None
|
||||
:param bool pre: Search for prereleases (default None) - prioritize releases if None
|
||||
:param bool dev: Search for devreleases (default None) - prioritize releases if None
|
||||
:param str arch: Architecture to include, e.g. '64bit', defaults to None
|
||||
:param str name: The name of a python version, e.g. ``anaconda3-5.3.0``
|
||||
:returns: A :class:`~pythonfinder.models.PathEntry` instance matching the version requested.
|
||||
"""
|
||||
|
||||
version_matcher = operator.methodcaller(
|
||||
"matches",
|
||||
major=major,
|
||||
minor=minor,
|
||||
patch=patch,
|
||||
pre=pre,
|
||||
dev=dev,
|
||||
arch=arch,
|
||||
name=name,
|
||||
)
|
||||
pythons = filter(None, [p.as_python for p in self.pythons.values()])
|
||||
matching_versions = filter(lambda py: version_matcher(py), pythons)
|
||||
version_sort = operator.attrgetter("version_sort")
|
||||
return next(iter(c for c in sorted(matching_versions, key=version_sort, reverse=True)), None)
|
||||
|
||||
|
||||
@attr.s(slots=True)
|
||||
class PythonVersion(object):
|
||||
|
||||
Vendored
+29
-6
@@ -7,10 +7,12 @@ import os
|
||||
from fnmatch import fnmatch
|
||||
|
||||
import attr
|
||||
import io
|
||||
import six
|
||||
|
||||
import vistir
|
||||
|
||||
from .environment import PYENV_INSTALLED, PYENV_ROOT, ASDF_INSTALLED, ASDF_DATA_DIR
|
||||
from .exceptions import InvalidPythonVersion
|
||||
|
||||
try:
|
||||
@@ -127,12 +129,6 @@ def filter_pythons(path):
|
||||
return filter(lambda x: path_is_python(x), path.iterdir())
|
||||
|
||||
|
||||
# def unnest(item):
|
||||
# if isinstance(next((i for i in item), None), (list, tuple)):
|
||||
# return chain(*filter(None, item))
|
||||
# return chain(filter(None, item))
|
||||
|
||||
|
||||
def unnest(item):
|
||||
if isinstance(item, Iterable) and not isinstance(item, six.string_types):
|
||||
item, target = itertools.tee(item, 2)
|
||||
@@ -145,3 +141,30 @@ def unnest(item):
|
||||
yield sub
|
||||
else:
|
||||
yield el
|
||||
|
||||
|
||||
def parse_pyenv_version_order(filename="version"):
|
||||
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:
|
||||
contents = fh.read()
|
||||
version_order = [v for v in contents.splitlines()]
|
||||
return version_order
|
||||
|
||||
|
||||
def parse_asdf_version_order(filename=".tool-versions"):
|
||||
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:
|
||||
contents = fh.read()
|
||||
python_section = next(iter(
|
||||
line for line in contents.splitlines() if line.startswith("python")
|
||||
), None)
|
||||
if python_section:
|
||||
python_key, versions = python_section.partition()
|
||||
if versions:
|
||||
return versions.split()
|
||||
|
||||
|
||||
def is_in_path(path, parent):
|
||||
return normalize_path(str(path)).startswith(normalize_path(str(parent)))
|
||||
|
||||
Reference in New Issue
Block a user