Update pythonfinder

Signed-off-by: Dan Ryan <dan@danryan.co>
This commit is contained in:
Dan Ryan
2018-10-12 15:37:09 -04:00
parent cfd8bf40e2
commit 320c19f4cd
7 changed files with 88 additions and 21 deletions
+9 -1
View File
@@ -1,6 +1,14 @@
from __future__ import print_function, absolute_import
__version__ = '1.1.1'
__version__ = '1.1.2.dev0'
# Add NullHandler to "pythonfinder" logger, because Python2's default root
# logger has no handler and warnings like this would be reported:
#
# > No handlers could be found for logger "pythonfinder.models.pyenv"
import logging
logger = logging.getLogger(__name__)
logger.addHandler(logging.NullHandler())
__all__ = ["Finder", "WindowsFinder", "SystemPath", "InvalidPythonVersion"]
from .pythonfinder import Finder
+4 -4
View File
@@ -17,9 +17,10 @@ from .pythonfinder import Finder
@click.option(
"--version", is_flag=True, default=False, help="Display PythonFinder version."
)
# @click.version_option(prog_name=crayons.normal('pyfinder', bold=True), version=__version__)
@click.option("--ignore-unsupported/--no-unsupported", is_flag=True, default=True, help="Ignore unsupported python versions.")
@click.version_option(prog_name='pyfinder', version=__version__)
@click.pass_context
def cli(ctx, find=False, which=False, findall=False, version=False):
def cli(ctx, find=False, which=False, findall=False, version=False, ignore_unsupported=True):
if version:
click.echo(
"{0} version {1}".format(
@@ -27,7 +28,7 @@ def cli(ctx, find=False, which=False, findall=False, version=False):
)
)
sys.exit(0)
finder = Finder()
finder = Finder(ignore_unsupported=ignore_unsupported)
if findall:
versions = finder.find_all_python_versions()
if versions:
@@ -46,7 +47,6 @@ def cli(ctx, find=False, which=False, findall=False, version=False):
fg="red",
)
if find:
if any([find.startswith("{0}".format(n)) for n in range(10)]):
found = finder.find_python_version(find.strip())
else:
+6 -2
View File
@@ -37,6 +37,7 @@ class SystemPath(object):
pyenv_finder = attr.ib(default=None, validator=optional_instance_of("PyenvPath"))
system = attr.ib(default=False)
_version_dict = attr.ib(default=attr.Factory(defaultdict))
ignore_unsupported = attr.ib(default=False)
__finders = attr.ib(default=attr.Factory(dict))
@@ -129,7 +130,7 @@ class SystemPath(object):
pyenv_index = self.path_order.index(last_pyenv)
except ValueError:
return
self.pyenv_finder = PyenvFinder.create(root=PYENV_ROOT)
self.pyenv_finder = PyenvFinder.create(root=PYENV_ROOT, ignore_unsupported=self.ignore_unsupported)
# paths = (v.paths.values() for v in self.pyenv_finder.versions.values())
root_paths = (
p for path in self.pyenv_finder.expanded_paths for p in path if p.is_root
@@ -268,7 +269,7 @@ class SystemPath(object):
return ver
@classmethod
def create(cls, path=None, system=False, only_python=False, global_search=True):
def create(cls, path=None, system=False, only_python=False, global_search=True, ignore_unsupported=False):
"""Create a new :class:`pythonfinder.models.SystemPath` instance.
:param path: Search path to prepend when searching, defaults to None
@@ -277,6 +278,8 @@ class SystemPath(object):
:param system: bool, optional
:param only_python: Whether to search only for python executables, defaults to False
:param only_python: bool, optional
:param ignore_unsupported: Whether to ignore unsupported python versions, if False, an error is raised, defaults to True
:param ignore_unsupported: bool, optional
:return: A new :class:`pythonfinder.models.SystemPath` instance.
:rtype: :class:`pythonfinder.models.SystemPath`
"""
@@ -303,6 +306,7 @@ class SystemPath(object):
only_python=only_python,
system=system,
global_search=global_search,
ignore_unsupported=ignore_unsupported,
)
+49 -4
View File
@@ -1,35 +1,80 @@
# -*- coding=utf-8 -*-
from __future__ import absolute_import, print_function
import logging
from collections import defaultdict
import attr
import sysconfig
from vistir.compat import Path
from ..utils import ensure_path, optional_instance_of
from ..utils import ensure_path, optional_instance_of, get_python_version, filter_pythons
from .mixins import BaseFinder
from .path import VersionPath
from .python import PythonVersion
logger = logging.getLogger(__name__)
@attr.s
class PyenvFinder(BaseFinder):
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=False)
versions = attr.ib()
pythons = attr.ib()
@classmethod
def version_from_bin_dir(cls, base_dir):
pythons = [py for py in filter_pythons(base_dir)]
py_version = None
for py in pythons:
version = get_python_version(py.as_posix())
try:
py_version = PythonVersion.parse(version)
except Exception:
continue
if py_version:
return py_version
return
@versions.default
def get_versions(self):
versions = defaultdict(VersionPath)
bin_ = sysconfig._INSTALL_SCHEMES[sysconfig._get_default_scheme()]["scripts"]
for p in self.root.glob("versions/*"):
version = PythonVersion.parse(p.name)
if p.parent.name == "envs":
continue
try:
version = PythonVersion.parse(p.name)
except ValueError:
bin_dir = Path(bin_.format(base=p.as_posix()))
if bin_dir.exists():
version = self.version_from_bin_dir(bin_dir)
if not version:
if not self.ignore_unsupported:
raise
continue
except Exception:
if not self.ignore_unsupported:
raise
logger.warning(
'Unsupported Python version %r, ignoring...',
p.name, exc_info=True
)
continue
if not version:
version_tuple = (
version.get("major"),
version.get("minor"),
version.get("patch"),
version.get("is_prerelease"),
version.get("is_devrelease"),
version.get("is_debug")
)
versions[version_tuple] = VersionPath.create(
path=p.resolve(), only_python=True
@@ -47,6 +92,6 @@ class PyenvFinder(BaseFinder):
return pythons
@classmethod
def create(cls, root):
def create(cls, root, ignore_unsupported=False):
root = ensure_path(root)
return cls(root=root)
return cls(root=root, ignore_unsupported=ignore_unsupported)
+5 -3
View File
@@ -8,7 +8,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 ..environment import SYSTEM_ARCH
@@ -65,10 +65,11 @@ class PythonVersion(object):
self.patch,
self.is_prerelease,
self.is_devrelease,
self.is_debug
)
def matches(
self, major=None, minor=None, patch=None, pre=False, dev=False, arch=None
self, major=None, minor=None, patch=None, pre=False, dev=False, arch=None, debug=False
):
if arch and arch.isdigit():
arch = "{0}bit".format(arch)
@@ -79,6 +80,7 @@ class PythonVersion(object):
and (pre is None or self.is_prerelease == pre)
and (dev is None or self.is_devrelease == dev)
and (arch is None or self.architecture == arch)
and (debug is None or self.is_debug == debug)
)
def as_major(self):
@@ -108,7 +110,7 @@ class PythonVersion(object):
is_debug = False
if version.endswith("-debug"):
is_debug = True
version, _, _ = verson.rpartition("-")
version, _, _ = version.rpartition("-")
try:
version = parse_version(str(version))
except TypeError:
+7 -3
View File
@@ -7,27 +7,30 @@ from .models import SystemPath
class Finder(object):
def __init__(self, path=None, system=False, global_search=True):
def __init__(self, path=None, system=False, global_search=True, ignore_unsupported=False):
"""Finder A cross-platform Finder for locating python and other executables.
Searches for python and other specified binaries starting in `path`, if supplied,
but searching the bin path of `sys.executable` if `system=True`, and then
searching in the `os.environ['PATH']` if `global_search=True`. When `global_search`
is `False`, this search operation is restricted to the allowed locations of
`path` and `system`.
:param path: A bin-directory search location, defaults to None
:param path: str, optional
:param system: Whether to include the bin-dir of `sys.executable`, defaults to False
:param system: bool, optional
:param global_search: Whether to search the global path from os.environ, defaults to True
:param global_search: bool, optional
:param ignore_unsupported: Whether to ignore unsupported python versions, if False, an error is raised, defaults to True
:param ignore_unsupported: bool, optional
:returns: a :class:`~pythonfinder.pythonfinder.Finder` object.
"""
self.path_prepend = path
self.global_search = global_search
self.system = system
self.ignore_unsupported = ignore_unsupported
self._system_path = None
self._windows_finder = None
@@ -38,6 +41,7 @@ class Finder(object):
path=self.path_prepend,
system=self.system,
global_search=self.global_search,
ignore_unsupported=self.ignore_unsupported,
)
return self._system_path
+8 -4
View File
@@ -17,7 +17,12 @@ import vistir
from .exceptions import InvalidPythonVersion
PYTHON_IMPLEMENTATIONS = ("python", "ironpython", "jython", "pypy")
PYTHON_IMPLEMENTATIONS = (
"python", "ironpython", "jython", "pypy", "anaconda", "miniconda",
"stackless", "activepython"
)
RULES_BASE = ["*{0}", "*{0}?", "*{0}?.?", "*{0}?.?m"]
RULES = [rule.format(impl) for impl in PYTHON_IMPLEMENTATIONS for rule in RULES_BASE]
KNOWN_EXTS = {"exe", "py", "fish", "sh", ""}
KNOWN_EXTS = KNOWN_EXTS | set(
@@ -34,7 +39,7 @@ def get_python_version(path):
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
return out.strip()
def optional_instance_of(cls):
@@ -58,9 +63,8 @@ def path_is_known_executable(path):
def looks_like_python(name):
rules = ["*python", "*python?", "*python?.?", "*python?.?m"]
match_rules = []
for rule in rules:
for rule in RULES:
match_rules.extend(
[
"{0}.{1}".format(rule, ext) if ext else "{0}".format(rule)