ducktools-pythonfinder 0.7.6__tar.gz → 0.7.8__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (43) hide show
  1. {ducktools_pythonfinder-0.7.6/src/ducktools_pythonfinder.egg-info → ducktools_pythonfinder-0.7.8}/PKG-INFO +1 -1
  2. ducktools_pythonfinder-0.7.8/src/ducktools/pythonfinder/_version.py +2 -0
  3. {ducktools_pythonfinder-0.7.6 → ducktools_pythonfinder-0.7.8}/src/ducktools/pythonfinder/linux/__init__.py +2 -2
  4. {ducktools_pythonfinder-0.7.6 → ducktools_pythonfinder-0.7.8}/src/ducktools/pythonfinder/linux/pyenv_search.py +22 -4
  5. {ducktools_pythonfinder-0.7.6 → ducktools_pythonfinder-0.7.8}/src/ducktools/pythonfinder/venv.py +9 -15
  6. {ducktools_pythonfinder-0.7.6 → ducktools_pythonfinder-0.7.8}/src/ducktools/pythonfinder/win32/pyenv_search.py +12 -3
  7. {ducktools_pythonfinder-0.7.6 → ducktools_pythonfinder-0.7.8/src/ducktools_pythonfinder.egg-info}/PKG-INFO +1 -1
  8. {ducktools_pythonfinder-0.7.6 → ducktools_pythonfinder-0.7.8}/tests/test_pyenv.py +30 -6
  9. {ducktools_pythonfinder-0.7.6 → ducktools_pythonfinder-0.7.8}/tests/test_venv_finder.py +3 -2
  10. ducktools_pythonfinder-0.7.6/src/ducktools/pythonfinder/_version.py +0 -2
  11. {ducktools_pythonfinder-0.7.6 → ducktools_pythonfinder-0.7.8}/.gitignore +0 -0
  12. {ducktools_pythonfinder-0.7.6 → ducktools_pythonfinder-0.7.8}/LICENSE +0 -0
  13. {ducktools_pythonfinder-0.7.6 → ducktools_pythonfinder-0.7.8}/MANIFEST.in +0 -0
  14. {ducktools_pythonfinder-0.7.6 → ducktools_pythonfinder-0.7.8}/README.md +0 -0
  15. {ducktools_pythonfinder-0.7.6 → ducktools_pythonfinder-0.7.8}/pyproject.toml +0 -0
  16. {ducktools_pythonfinder-0.7.6 → ducktools_pythonfinder-0.7.8}/scripts/build_zipapp.py +0 -0
  17. {ducktools_pythonfinder-0.7.6 → ducktools_pythonfinder-0.7.8}/scripts/detail_this_python.py +0 -0
  18. {ducktools_pythonfinder-0.7.6 → ducktools_pythonfinder-0.7.8}/scripts/list_python_venvs.py +0 -0
  19. {ducktools_pythonfinder-0.7.6 → ducktools_pythonfinder-0.7.8}/scripts/print_python_versions.py +0 -0
  20. {ducktools_pythonfinder-0.7.6 → ducktools_pythonfinder-0.7.8}/setup.cfg +0 -0
  21. {ducktools_pythonfinder-0.7.6 → ducktools_pythonfinder-0.7.8}/src/ducktools/pythonfinder/__init__.py +0 -0
  22. {ducktools_pythonfinder-0.7.6 → ducktools_pythonfinder-0.7.8}/src/ducktools/pythonfinder/__main__.py +0 -0
  23. {ducktools_pythonfinder-0.7.6 → ducktools_pythonfinder-0.7.8}/src/ducktools/pythonfinder/darwin/__init__.py +0 -0
  24. {ducktools_pythonfinder-0.7.6 → ducktools_pythonfinder-0.7.8}/src/ducktools/pythonfinder/details_script.py +0 -0
  25. {ducktools_pythonfinder-0.7.6 → ducktools_pythonfinder-0.7.8}/src/ducktools/pythonfinder/package_list_script.py +0 -0
  26. {ducktools_pythonfinder-0.7.6 → ducktools_pythonfinder-0.7.8}/src/ducktools/pythonfinder/pythonorg_search.py +0 -0
  27. {ducktools_pythonfinder-0.7.6 → ducktools_pythonfinder-0.7.8}/src/ducktools/pythonfinder/shared.py +0 -0
  28. {ducktools_pythonfinder-0.7.6 → ducktools_pythonfinder-0.7.8}/src/ducktools/pythonfinder/win32/__init__.py +0 -0
  29. {ducktools_pythonfinder-0.7.6 → ducktools_pythonfinder-0.7.8}/src/ducktools/pythonfinder/win32/registry_search.py +0 -0
  30. {ducktools_pythonfinder-0.7.6 → ducktools_pythonfinder-0.7.8}/src/ducktools_pythonfinder.egg-info/SOURCES.txt +0 -0
  31. {ducktools_pythonfinder-0.7.6 → ducktools_pythonfinder-0.7.8}/src/ducktools_pythonfinder.egg-info/dependency_links.txt +0 -0
  32. {ducktools_pythonfinder-0.7.6 → ducktools_pythonfinder-0.7.8}/src/ducktools_pythonfinder.egg-info/entry_points.txt +0 -0
  33. {ducktools_pythonfinder-0.7.6 → ducktools_pythonfinder-0.7.8}/src/ducktools_pythonfinder.egg-info/requires.txt +0 -0
  34. {ducktools_pythonfinder-0.7.6 → ducktools_pythonfinder-0.7.8}/src/ducktools_pythonfinder.egg-info/top_level.txt +0 -0
  35. {ducktools_pythonfinder-0.7.6 → ducktools_pythonfinder-0.7.8}/tests/conftest.py +0 -0
  36. {ducktools_pythonfinder-0.7.6 → ducktools_pythonfinder-0.7.8}/tests/sources/python_versions.txt +0 -0
  37. {ducktools_pythonfinder-0.7.6 → ducktools_pythonfinder-0.7.8}/tests/sources/release.json +0 -0
  38. {ducktools_pythonfinder-0.7.6 → ducktools_pythonfinder-0.7.8}/tests/sources/release_file.json +0 -0
  39. {ducktools_pythonfinder-0.7.6 → ducktools_pythonfinder-0.7.8}/tests/test_details_script.py +0 -0
  40. {ducktools_pythonfinder-0.7.6 → ducktools_pythonfinder-0.7.8}/tests/test_foldersearch.py +0 -0
  41. {ducktools_pythonfinder-0.7.6 → ducktools_pythonfinder-0.7.8}/tests/test_orgsearch.py +0 -0
  42. {ducktools_pythonfinder-0.7.6 → ducktools_pythonfinder-0.7.8}/tests/test_shared.py +0 -0
  43. {ducktools_pythonfinder-0.7.6 → ducktools_pythonfinder-0.7.8}/tests/test_uv_finder.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.2
2
2
  Name: ducktools-pythonfinder
3
- Version: 0.7.6
3
+ Version: 0.7.8
4
4
  Summary: Cross platform tool to find available python installations
5
5
  Author: David C Ellis
6
6
  Project-URL: Homepage, https://github.com/davidcellis/ducktools-pythonfinder
@@ -0,0 +1,2 @@
1
+ __version__ = "0.7.8"
2
+ __version_tuple__ = (0, 7, 8)
@@ -28,14 +28,14 @@ import itertools
28
28
  from _collections_abc import Iterator
29
29
 
30
30
  from ..shared import PythonInstall, get_folder_pythons, get_uv_pythons, get_uv_python_path
31
- from .pyenv_search import get_pyenv_pythons
31
+ from .pyenv_search import get_pyenv_pythons, get_pyenv_root
32
32
 
33
33
 
34
34
  def get_path_pythons() -> Iterator[PythonInstall]:
35
35
  exe_names = set()
36
36
 
37
37
  path_folders = os.environ.get("PATH", "").split(":")
38
- pyenv_root = os.environ.get("PYENV_ROOT")
38
+ pyenv_root = get_pyenv_root()
39
39
  uv_root = get_uv_python_path()
40
40
 
41
41
  excluded_folders = [pyenv_root, uv_root]
@@ -31,28 +31,46 @@ import os
31
31
  import os.path
32
32
  from _collections_abc import Iterator
33
33
 
34
- from ducktools.lazyimporter import LazyImporter, ModuleImport
34
+ from ducktools.lazyimporter import LazyImporter, FromImport, ModuleImport
35
35
 
36
36
  from ..shared import PythonInstall, get_install_details, FULL_PY_VER_RE, version_str_to_tuple
37
37
 
38
38
  _laz = LazyImporter(
39
39
  [
40
40
  ModuleImport("re"),
41
+ FromImport("subprocess", "run"),
41
42
  ]
42
43
  )
43
44
 
44
45
  # pyenv folder name for pypy
45
46
  PYPY_VER_RE = r"^pypy(?P<pyversion>\d{1,2}\.\d+)-(?P<pypyversion>[\d\.]*)$"
46
47
 
47
- PYENV_VERSIONS_FOLDER = os.path.join(os.environ.get("PYENV_ROOT", ""), "versions")
48
+
49
+ def get_pyenv_root() -> str | None:
50
+ # Check if the environment variable exists, if so use that
51
+ # As a backup try to run pyenv to obtain the root folder
52
+ pyenv_root = os.environ.get("PYENV_ROOT")
53
+ if not pyenv_root:
54
+ try:
55
+ output = _laz.run(["pyenv", "root"], capture_output=True, text=True)
56
+ except FileNotFoundError:
57
+ return None
58
+
59
+ pyenv_root = output.stdout.strip()
60
+
61
+ return pyenv_root
48
62
 
49
63
 
50
64
  def get_pyenv_pythons(
51
- versions_folder: str | os.PathLike = PYENV_VERSIONS_FOLDER,
65
+ versions_folder: str | os.PathLike | None = None,
52
66
  *,
53
67
  query_executables: bool = True,
54
68
  ) -> Iterator[PythonInstall]:
55
- if not os.path.exists(versions_folder):
69
+ if versions_folder is None:
70
+ if pyenv_root := get_pyenv_root():
71
+ versions_folder = os.path.join(pyenv_root, "versions")
72
+
73
+ if versions_folder is None or not os.path.exists(versions_folder):
56
74
  return
57
75
 
58
76
  # Sorting puts standard python versions before alternate implementations
@@ -175,27 +175,21 @@ class PythonVEnv(Prefab):
175
175
  :param cfg_path: Path to a virtualenv config file
176
176
  :return: PythonVEnv with details relative to that config file
177
177
  """
178
- parent_path, version_str, parent_exe = None, None, None
179
178
  venv_base = os.path.dirname(cfg_path)
180
179
 
181
180
  with open(cfg_path, 'r') as f:
181
+ conf = {}
182
182
  for line in f:
183
- key, value = (item.strip() for item in line.split("="))
183
+ key, _, value = [item.strip() for item in line.partition("=")]
184
+ conf[key] = value
184
185
 
185
- if key == "home":
186
- parent_path = value
187
- elif key in {"version", "version_info"}:
188
- # venv and uv use different key names :)
189
- version_str = value
190
- elif key == "executable":
191
- parent_exe = value
186
+ parent_path = conf.get("home")
187
+ version_str = conf.get("version", conf.get("version_info"))
188
+ parent_exe = conf.get("executable", conf.get("base-executable"))
192
189
 
193
- if parent_path and version_str and parent_exe:
194
- break
195
-
196
- if parent_path is None or version_str is None:
197
- # Not a valid venv
198
- raise InvalidVEnvError(f"Path or version not defined in {cfg_path}")
190
+ if parent_path is None or version_str is None:
191
+ # Not a valid venv
192
+ raise InvalidVEnvError(f"Path or version not defined in {cfg_path}")
199
193
 
200
194
  if sys.platform == "win32":
201
195
  venv_exe = os.path.join(venv_base, "Scripts", "python.exe")
@@ -29,15 +29,24 @@ from _collections_abc import Iterator
29
29
  from ..shared import PythonInstall, get_install_details
30
30
 
31
31
 
32
- PYENV_VERSIONS_FOLDER = os.path.join(os.environ.get("PYENV_ROOT", ""), "versions")
32
+ def get_pyenv_root() -> str | None:
33
+ # Check if the environment variable exists, if so use that
34
+ # Windows PYENV does not have the `pyenv root` command to use as a backup.
35
+ pyenv_root = os.environ.get("PYENV_ROOT")
36
+ return pyenv_root
33
37
 
34
38
 
35
39
  def get_pyenv_pythons(
36
- versions_folder: str | os.PathLike = PYENV_VERSIONS_FOLDER,
40
+ versions_folder: str | os.PathLike | None = None,
37
41
  *,
38
42
  query_executables: bool = True,
39
43
  ) -> Iterator[PythonInstall]:
40
- if not os.path.exists(versions_folder):
44
+
45
+ if versions_folder is None:
46
+ if pyenv_root := get_pyenv_root():
47
+ versions_folder = os.path.join(pyenv_root, "versions")
48
+
49
+ if versions_folder is None or not os.path.exists(versions_folder):
41
50
  return
42
51
 
43
52
  for p in os.scandir(versions_folder):
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.2
2
2
  Name: ducktools-pythonfinder
3
- Version: 0.7.6
3
+ Version: 0.7.8
4
4
  Summary: Cross platform tool to find available python installations
5
5
  Author: David C Ellis
6
6
  Project-URL: Homepage, https://github.com/davidcellis/ducktools-pythonfinder
@@ -25,6 +25,7 @@ import sys
25
25
  import os
26
26
  import os.path
27
27
  import textwrap
28
+ import types
28
29
  from pathlib import Path
29
30
 
30
31
  import pytest
@@ -36,18 +37,39 @@ from ducktools.pythonfinder import details_script
36
37
  if sys.platform == "win32":
37
38
  from ducktools.pythonfinder.win32.pyenv_search import (
38
39
  get_pyenv_pythons,
39
- PYENV_VERSIONS_FOLDER,
40
+ get_pyenv_root,
40
41
  )
41
42
  else:
42
43
  from ducktools.pythonfinder.linux.pyenv_search import (
43
44
  get_pyenv_pythons,
44
- PYENV_VERSIONS_FOLDER,
45
+ get_pyenv_root,
45
46
  )
46
47
 
47
48
 
48
49
  details_text = Path(details_script.__file__).read_text()
49
50
 
50
51
 
52
+ def test_get_pyenv_root_env():
53
+ fake_path = "path/to/pyenv"
54
+ with patch.dict(os.environ, {"PYENV_ROOT": fake_path}):
55
+ assert get_pyenv_root() == fake_path
56
+
57
+
58
+ @pytest.mark.skipif(sys.platform == "win32", reason="Test for non-Windows only")
59
+ def test_get_pyenv_root_backup():
60
+ with patch.dict(os.environ) as patched:
61
+ if "PYENV_ROOT" in patched:
62
+ del patched["PYENV_ROOT"]
63
+
64
+ from ducktools.pythonfinder.linux.pyenv_search import _laz
65
+ with patch.object(_laz, "run") as run_mock:
66
+ run_mock.return_value = types.SimpleNamespace(stdout="path/to/pyenv\n")
67
+ pyenv_root = get_pyenv_root()
68
+ run_mock.assert_called_with(["pyenv", "root"], text=True, capture_output=True)
69
+
70
+ assert pyenv_root == "path/to/pyenv"
71
+
72
+
51
73
  def test_no_versions_folder():
52
74
  with patch("os.path.exists") as exists_mock:
53
75
  exists_mock.return_value = False
@@ -59,18 +81,20 @@ def test_mock_versions_folder():
59
81
 
60
82
  out_ver = "3.12.1"
61
83
  if sys.platform == "win32":
62
- out_executable = os.path.join(PYENV_VERSIONS_FOLDER, out_ver, "python.exe")
84
+ versions_folder = os.path.join("c:", "fake", "versions")
85
+ out_executable = os.path.join(versions_folder, out_ver, "python.exe")
63
86
  else:
64
- out_executable = os.path.join(PYENV_VERSIONS_FOLDER, out_ver, "bin/python")
87
+ versions_folder = "~/fake/versions"
88
+ out_executable = os.path.join(versions_folder, out_ver, "bin/python")
65
89
 
66
90
  mock_dir_entry.name = out_ver
67
- mock_dir_entry.path = os.path.join(PYENV_VERSIONS_FOLDER, out_ver)
91
+ mock_dir_entry.path = os.path.join(versions_folder, out_ver)
68
92
 
69
93
  with patch("os.path.exists") as exists_mock, patch("os.scandir") as scandir_mock:
70
94
  exists_mock.return_value = True
71
95
  scandir_mock.return_value = iter([mock_dir_entry])
72
96
 
73
- python_versions = list(get_pyenv_pythons())
97
+ python_versions = list(get_pyenv_pythons(versions_folder="~/fake/versions"))
74
98
 
75
99
  assert python_versions == [PythonInstall.from_str(version=out_ver, executable=out_executable, managed_by="pyenv")]
76
100
 
@@ -127,8 +127,9 @@ def test_found_parent(with_venvs, this_python, this_venv):
127
127
  parent = venv_ex.get_parent_install()
128
128
  assert os.path.dirname(parent.executable) == os.path.dirname(this_python.executable)
129
129
 
130
- # venv version str works same as parent
131
- assert venv_ex.version_str == parent.version_str
130
+ # venvs created by the venv module don't record prerelease details in the version
131
+ # That's not my fault that's venv!
132
+ assert venv_ex.version[:3] == parent.version[:3]
132
133
 
133
134
 
134
135
  def test_found_parent_cache(with_venvs, this_python):
@@ -1,2 +0,0 @@
1
- __version__ = "0.7.6"
2
- __version_tuple__ = (0, 7, 6)