Skip to content

Commit 42da4d1

Browse files
committed
Rewriting python finder to eventually fix a number of issue reports.
1 parent a5a6069 commit 42da4d1

22 files changed

+1966
-2207
lines changed

pipenv/vendor/pythonfinder/LICENSE.txt

Lines changed: 0 additions & 20 deletions
This file was deleted.
Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,9 @@
11
from __future__ import annotations
22

3-
from .exceptions import InvalidPythonVersion
4-
from .models import SystemPath
3+
from .exceptions import InvalidPythonVersion, PythonNotFound
4+
from .models.python_info import PythonInfo
55
from .pythonfinder import Finder
66

7-
__version__ = "2.1.0"
7+
__version__ = "3.0.0"
88

9-
10-
__all__ = ["Finder", "SystemPath", "InvalidPythonVersion"]
9+
__all__ = ["Finder", "PythonInfo", "InvalidPythonVersion", "PythonNotFound"]

pipenv/vendor/pythonfinder/environment.py

Lines changed: 84 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -6,61 +6,109 @@
66
import sys
77
from pathlib import Path
88

9+
# Environment variables and constants
910
PYENV_ROOT = os.path.expanduser(
1011
os.path.expandvars(os.environ.get("PYENV_ROOT", "~/.pyenv"))
1112
)
1213
PYENV_ROOT = Path(PYENV_ROOT)
1314
PYENV_INSTALLED = shutil.which("pyenv") is not None
15+
1416
ASDF_DATA_DIR = os.path.expanduser(
1517
os.path.expandvars(os.environ.get("ASDF_DATA_DIR", "~/.asdf"))
1618
)
1719
ASDF_INSTALLED = shutil.which("asdf") is not None
18-
IS_64BIT_OS = None
20+
1921
SYSTEM_ARCH = platform.architecture()[0]
22+
IS_64BIT_OS = None
2023

2124
if sys.maxsize > 2**32:
2225
IS_64BIT_OS = platform.machine() == "AMD64"
2326
else:
2427
IS_64BIT_OS = False
2528

26-
2729
IGNORE_UNSUPPORTED = bool(os.environ.get("PYTHONFINDER_IGNORE_UNSUPPORTED", False))
28-
SUBPROCESS_TIMEOUT = os.environ.get("PYTHONFINDER_SUBPROCESS_TIMEOUT", 5)
29-
"""The default subprocess timeout for determining python versions
30+
SUBPROCESS_TIMEOUT = int(os.environ.get("PYTHONFINDER_SUBPROCESS_TIMEOUT", 5))
3031

31-
Set to **5** by default.
32-
"""
3332

34-
35-
def set_asdf_paths():
33+
def get_python_paths() -> list[str]:
34+
"""
35+
Get a list of paths where Python executables might be found.
36+
37+
Returns:
38+
A list of paths to search for Python executables.
39+
"""
40+
paths = []
41+
42+
# Add paths from PATH environment variable
43+
if "PATH" in os.environ:
44+
paths.extend(os.environ["PATH"].split(os.pathsep))
45+
46+
# Add pyenv paths if installed
47+
if PYENV_INSTALLED:
48+
pyenv_paths = get_pyenv_paths()
49+
paths.extend(pyenv_paths)
50+
51+
# Add asdf paths if installed
3652
if ASDF_INSTALLED:
37-
python_versions = os.path.join(ASDF_DATA_DIR, "installs", "python")
38-
try:
39-
# Get a list of all files and directories in the given path
40-
all_files_and_dirs = os.listdir(python_versions)
41-
# Filter out files and keep only directories
42-
for name in all_files_and_dirs:
43-
if os.path.isdir(os.path.join(python_versions, name)):
44-
asdf_path = os.path.join(python_versions, name)
45-
asdf_path = os.path.join(asdf_path, "bin")
46-
os.environ["PATH"] = asdf_path + os.pathsep + os.environ["PATH"]
47-
except FileNotFoundError:
48-
pass
53+
asdf_paths = get_asdf_paths()
54+
paths.extend(asdf_paths)
55+
56+
# Add Windows registry paths if on Windows
57+
if os.name == "nt":
58+
from .finders.windows_registry import get_registry_python_paths
59+
registry_paths = get_registry_python_paths()
60+
paths.extend(registry_paths)
61+
62+
return paths
4963

5064

51-
def set_pyenv_paths():
52-
if PYENV_INSTALLED:
53-
python_versions = os.path.join(PYENV_ROOT, "versions")
54-
is_windows = os.name == "nt"
55-
try:
56-
# Get a list of all files and directories in the given path
57-
all_files_and_dirs = os.listdir(python_versions)
58-
# Filter out files and keep only directories
59-
for name in all_files_and_dirs:
60-
if os.path.isdir(os.path.join(python_versions, name)):
61-
pyenv_path = os.path.join(python_versions, name)
62-
if not is_windows:
63-
pyenv_path = os.path.join(pyenv_path, "bin")
64-
os.environ["PATH"] = pyenv_path + os.pathsep + os.environ["PATH"]
65-
except FileNotFoundError:
66-
pass
65+
def get_pyenv_paths() -> list[str]:
66+
"""
67+
Get a list of paths where pyenv Python executables might be found.
68+
69+
Returns:
70+
A list of paths to search for pyenv Python executables.
71+
"""
72+
paths = []
73+
python_versions = os.path.join(PYENV_ROOT, "versions")
74+
is_windows = os.name == "nt"
75+
76+
try:
77+
# Get a list of all files and directories in the given path
78+
all_files_and_dirs = os.listdir(python_versions)
79+
# Filter out files and keep only directories
80+
for name in all_files_and_dirs:
81+
version_path = os.path.join(python_versions, name)
82+
if os.path.isdir(version_path):
83+
if not is_windows:
84+
version_path = os.path.join(version_path, "bin")
85+
paths.append(version_path)
86+
except FileNotFoundError:
87+
pass
88+
89+
return paths
90+
91+
92+
def get_asdf_paths() -> list[str]:
93+
"""
94+
Get a list of paths where asdf Python executables might be found.
95+
96+
Returns:
97+
A list of paths to search for asdf Python executables.
98+
"""
99+
paths = []
100+
python_versions = os.path.join(ASDF_DATA_DIR, "installs", "python")
101+
102+
try:
103+
# Get a list of all files and directories in the given path
104+
all_files_and_dirs = os.listdir(python_versions)
105+
# Filter out files and keep only directories
106+
for name in all_files_and_dirs:
107+
version_path = os.path.join(python_versions, name)
108+
if os.path.isdir(version_path):
109+
bin_path = os.path.join(version_path, "bin")
110+
paths.append(bin_path)
111+
except FileNotFoundError:
112+
pass
113+
114+
return paths

pipenv/vendor/pythonfinder/exceptions.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,5 +3,9 @@
33

44
class InvalidPythonVersion(Exception):
55
"""Raised when parsing an invalid python version"""
6+
pass
7+
68

9+
class PythonNotFound(Exception):
10+
"""Raised when a requested Python version is not found"""
711
pass
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
from __future__ import annotations
2+
3+
from .base_finder import BaseFinder
4+
from .path_finder import PathFinder
5+
from .system_finder import SystemFinder
6+
from .pyenv_finder import PyenvFinder
7+
from .asdf_finder import AsdfFinder
8+
9+
__all__ = [
10+
"BaseFinder",
11+
"PathFinder",
12+
"SystemFinder",
13+
"PyenvFinder",
14+
"AsdfFinder",
15+
]
16+
17+
# Import Windows registry finder if on Windows
18+
import os
19+
if os.name == "nt":
20+
from .windows_registry import WindowsRegistryFinder
21+
__all__.append("WindowsRegistryFinder")
Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
from __future__ import annotations
2+
3+
import os
4+
from pathlib import Path
5+
from typing import Dict, List, Optional, Union
6+
7+
from ..environment import ASDF_DATA_DIR, ASDF_INSTALLED
8+
from ..utils.path_utils import ensure_path
9+
from ..utils.version_utils import parse_asdf_version_order
10+
from .path_finder import PathFinder
11+
12+
13+
class AsdfFinder(PathFinder):
14+
"""
15+
Finder that searches for Python in asdf installations.
16+
"""
17+
18+
def __init__(
19+
self,
20+
data_dir: Optional[Union[str, Path]] = None,
21+
ignore_unsupported: bool = True,
22+
):
23+
"""
24+
Initialize a new AsdfFinder.
25+
26+
Args:
27+
data_dir: The data directory of the asdf installation.
28+
ignore_unsupported: Whether to ignore unsupported Python versions.
29+
"""
30+
if not ASDF_INSTALLED:
31+
super().__init__(paths=[], ignore_unsupported=ignore_unsupported)
32+
return
33+
34+
self.data_dir = ensure_path(data_dir or ASDF_DATA_DIR)
35+
self.installs_dir = self.data_dir / "installs" / "python"
36+
37+
if not self.installs_dir.exists():
38+
super().__init__(paths=[], ignore_unsupported=ignore_unsupported)
39+
return
40+
41+
# Get the asdf version order
42+
version_order = parse_asdf_version_order()
43+
44+
# Get all version directories
45+
version_dirs = {}
46+
for path in self.installs_dir.iterdir():
47+
if path.is_dir():
48+
version_dirs[path.name] = path
49+
50+
# Sort the version directories according to the asdf version order
51+
paths = []
52+
53+
# First add the versions in the asdf version order
54+
for version in version_order:
55+
if version in version_dirs:
56+
bin_dir = version_dirs[version] / "bin"
57+
if bin_dir.exists():
58+
paths.append(bin_dir)
59+
del version_dirs[version]
60+
61+
# Then add the remaining versions
62+
for version_dir in version_dirs.values():
63+
bin_dir = version_dir / "bin"
64+
if bin_dir.exists():
65+
paths.append(bin_dir)
66+
67+
super().__init__(
68+
paths=paths,
69+
only_python=True,
70+
ignore_unsupported=ignore_unsupported,
71+
)

0 commit comments

Comments
 (0)