ducktools-pythonfinder 0.5.4__tar.gz → 0.6.0__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.
- {ducktools_pythonfinder-0.5.4/src/ducktools_pythonfinder.egg-info → ducktools_pythonfinder-0.6.0}/PKG-INFO +1 -1
- {ducktools_pythonfinder-0.5.4 → ducktools_pythonfinder-0.6.0}/src/ducktools/pythonfinder/__main__.py +39 -17
- ducktools_pythonfinder-0.6.0/src/ducktools/pythonfinder/_version.py +2 -0
- {ducktools_pythonfinder-0.5.4 → ducktools_pythonfinder-0.6.0}/src/ducktools/pythonfinder/details_script.py +40 -1
- {ducktools_pythonfinder-0.5.4 → ducktools_pythonfinder-0.6.0}/src/ducktools/pythonfinder/shared.py +21 -5
- {ducktools_pythonfinder-0.5.4 → ducktools_pythonfinder-0.6.0/src/ducktools_pythonfinder.egg-info}/PKG-INFO +1 -1
- {ducktools_pythonfinder-0.5.4 → ducktools_pythonfinder-0.6.0}/src/ducktools_pythonfinder.egg-info/SOURCES.txt +1 -0
- {ducktools_pythonfinder-0.5.4 → ducktools_pythonfinder-0.6.0}/tests/conftest.py +23 -0
- ducktools_pythonfinder-0.6.0/tests/test_uv_finder.py +146 -0
- ducktools_pythonfinder-0.5.4/src/ducktools/pythonfinder/_version.py +0 -2
- {ducktools_pythonfinder-0.5.4 → ducktools_pythonfinder-0.6.0}/.gitignore +0 -0
- {ducktools_pythonfinder-0.5.4 → ducktools_pythonfinder-0.6.0}/LICENSE +0 -0
- {ducktools_pythonfinder-0.5.4 → ducktools_pythonfinder-0.6.0}/MANIFEST.in +0 -0
- {ducktools_pythonfinder-0.5.4 → ducktools_pythonfinder-0.6.0}/README.md +0 -0
- {ducktools_pythonfinder-0.5.4 → ducktools_pythonfinder-0.6.0}/pyproject.toml +0 -0
- {ducktools_pythonfinder-0.5.4 → ducktools_pythonfinder-0.6.0}/scripts/build_zipapp.py +0 -0
- {ducktools_pythonfinder-0.5.4 → ducktools_pythonfinder-0.6.0}/scripts/detail_this_python.py +0 -0
- {ducktools_pythonfinder-0.5.4 → ducktools_pythonfinder-0.6.0}/scripts/print_python_versions.py +0 -0
- {ducktools_pythonfinder-0.5.4 → ducktools_pythonfinder-0.6.0}/setup.cfg +0 -0
- {ducktools_pythonfinder-0.5.4 → ducktools_pythonfinder-0.6.0}/src/ducktools/pythonfinder/__init__.py +0 -0
- {ducktools_pythonfinder-0.5.4 → ducktools_pythonfinder-0.6.0}/src/ducktools/pythonfinder/darwin/__init__.py +0 -0
- {ducktools_pythonfinder-0.5.4 → ducktools_pythonfinder-0.6.0}/src/ducktools/pythonfinder/linux/__init__.py +0 -0
- {ducktools_pythonfinder-0.5.4 → ducktools_pythonfinder-0.6.0}/src/ducktools/pythonfinder/linux/pyenv_search.py +0 -0
- {ducktools_pythonfinder-0.5.4 → ducktools_pythonfinder-0.6.0}/src/ducktools/pythonfinder/pythonorg_search.py +0 -0
- {ducktools_pythonfinder-0.5.4 → ducktools_pythonfinder-0.6.0}/src/ducktools/pythonfinder/win32/__init__.py +0 -0
- {ducktools_pythonfinder-0.5.4 → ducktools_pythonfinder-0.6.0}/src/ducktools/pythonfinder/win32/pyenv_search.py +0 -0
- {ducktools_pythonfinder-0.5.4 → ducktools_pythonfinder-0.6.0}/src/ducktools/pythonfinder/win32/registry_search.py +0 -0
- {ducktools_pythonfinder-0.5.4 → ducktools_pythonfinder-0.6.0}/src/ducktools_pythonfinder.egg-info/dependency_links.txt +0 -0
- {ducktools_pythonfinder-0.5.4 → ducktools_pythonfinder-0.6.0}/src/ducktools_pythonfinder.egg-info/entry_points.txt +0 -0
- {ducktools_pythonfinder-0.5.4 → ducktools_pythonfinder-0.6.0}/src/ducktools_pythonfinder.egg-info/requires.txt +0 -0
- {ducktools_pythonfinder-0.5.4 → ducktools_pythonfinder-0.6.0}/src/ducktools_pythonfinder.egg-info/top_level.txt +0 -0
- {ducktools_pythonfinder-0.5.4 → ducktools_pythonfinder-0.6.0}/tests/sources/python_versions.txt +0 -0
- {ducktools_pythonfinder-0.5.4 → ducktools_pythonfinder-0.6.0}/tests/sources/release.json +0 -0
- {ducktools_pythonfinder-0.5.4 → ducktools_pythonfinder-0.6.0}/tests/sources/release_file.json +0 -0
- {ducktools_pythonfinder-0.5.4 → ducktools_pythonfinder-0.6.0}/tests/test_details_script.py +0 -0
- {ducktools_pythonfinder-0.5.4 → ducktools_pythonfinder-0.6.0}/tests/test_foldersearch.py +0 -0
- {ducktools_pythonfinder-0.5.4 → ducktools_pythonfinder-0.6.0}/tests/test_orgsearch.py +0 -0
- {ducktools_pythonfinder-0.5.4 → ducktools_pythonfinder-0.6.0}/tests/test_pyenv.py +0 -0
- {ducktools_pythonfinder-0.5.4 → ducktools_pythonfinder-0.6.0}/tests/test_shared.py +0 -0
{ducktools_pythonfinder-0.5.4 → ducktools_pythonfinder-0.6.0}/src/ducktools/pythonfinder/__main__.py
RENAMED
|
@@ -20,6 +20,7 @@
|
|
|
20
20
|
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
21
21
|
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
22
22
|
# SOFTWARE.
|
|
23
|
+
from __future__ import annotations
|
|
23
24
|
|
|
24
25
|
|
|
25
26
|
import sys
|
|
@@ -140,23 +141,16 @@ def display_local_installs(
|
|
|
140
141
|
compatible = tuple(int(i) for i in compatible.split("."))
|
|
141
142
|
|
|
142
143
|
installs = list_python_installs(query_executables=query_executables)
|
|
143
|
-
headings = ["Python Version", "Executable Location"]
|
|
144
|
-
max_executable_len = max(
|
|
145
|
-
len(headings[1]), max(len(inst.executable) for inst in installs)
|
|
146
|
-
)
|
|
147
|
-
headings_str = f"| {headings[0]} | {headings[1]:<{max_executable_len}s} |"
|
|
148
144
|
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
print(headings_str)
|
|
159
|
-
print(f"| {'-' * len(headings[0])} | {'-' * max_executable_len} |")
|
|
145
|
+
headings = ["Version", "Executable Location"]
|
|
146
|
+
|
|
147
|
+
install_collection: list[tuple[str, str]] = []
|
|
148
|
+
max_version_len = len(headings[0])
|
|
149
|
+
max_executable_len = len(headings[1])
|
|
150
|
+
|
|
151
|
+
alternate_implementations = False
|
|
152
|
+
|
|
153
|
+
# First collect the strings
|
|
160
154
|
for install in installs:
|
|
161
155
|
if min_ver and install.version < min_ver:
|
|
162
156
|
continue
|
|
@@ -190,7 +184,35 @@ def display_local_installs(
|
|
|
190
184
|
if install.shadowed:
|
|
191
185
|
version_str = f"[{version_str}]"
|
|
192
186
|
|
|
193
|
-
|
|
187
|
+
if install.implementation != "cpython":
|
|
188
|
+
alternate_implementations = True
|
|
189
|
+
version_str = f"({install.implementation_version_str}) {version_str}"
|
|
190
|
+
|
|
191
|
+
max_version_len = max(max_version_len, len(version_str))
|
|
192
|
+
max_executable_len = max(max_executable_len, len(install.executable))
|
|
193
|
+
|
|
194
|
+
install_collection.append((version_str, install.executable))
|
|
195
|
+
|
|
196
|
+
print("Discoverable Python Installs")
|
|
197
|
+
print()
|
|
198
|
+
if alternate_implementations:
|
|
199
|
+
print("Alternate implementation versions are listed in parentheses")
|
|
200
|
+
|
|
201
|
+
if sys.platform == "win32":
|
|
202
|
+
print("+ - Listed in the Windows Registry ")
|
|
203
|
+
print("^ - This is a 32-bit Python install")
|
|
204
|
+
if sys.platform != "win32":
|
|
205
|
+
print("[] - This Python install is shadowed by another on Path")
|
|
206
|
+
print("* - This is the active Python executable used to call this module")
|
|
207
|
+
print("** - This is the parent Python executable of the venv used to call this module")
|
|
208
|
+
print()
|
|
209
|
+
|
|
210
|
+
headings_str = f"| {headings[0]:<{max_version_len}s} | {headings[1]:<{max_executable_len}s} |"
|
|
211
|
+
print(headings_str)
|
|
212
|
+
print(f"| {'-' * max_version_len} | {'-' * max_executable_len} |")
|
|
213
|
+
|
|
214
|
+
for version_str, executable in install_collection:
|
|
215
|
+
print(f"| {version_str:>{max_version_len}s} | {executable:<{max_executable_len}s} |")
|
|
194
216
|
|
|
195
217
|
|
|
196
218
|
def display_remote_binaries(
|
|
@@ -26,6 +26,35 @@ Get the details from a python install as JSON
|
|
|
26
26
|
"""
|
|
27
27
|
import sys
|
|
28
28
|
|
|
29
|
+
FULL_PY_VER_RE = r"(?P<major>\d+)\.(?P<minor>\d+)\.?(?P<micro>\d*)-?(?P<releaselevel>[a-zA-Z]*)(?P<serial>\d*)"
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def version_str_to_tuple(version):
|
|
33
|
+
# Needed to parse GraalPy versions only available as strings
|
|
34
|
+
import re
|
|
35
|
+
|
|
36
|
+
parsed_version = re.fullmatch(FULL_PY_VER_RE, version)
|
|
37
|
+
|
|
38
|
+
major, minor, micro, releaselevel, serial = parsed_version.groups()
|
|
39
|
+
|
|
40
|
+
if releaselevel in {"a", "dev"}:
|
|
41
|
+
releaselevel = "alpha"
|
|
42
|
+
elif releaselevel == "b":
|
|
43
|
+
releaselevel = "beta"
|
|
44
|
+
elif releaselevel == "rc":
|
|
45
|
+
releaselevel = "candidate"
|
|
46
|
+
else:
|
|
47
|
+
releaselevel = "final"
|
|
48
|
+
|
|
49
|
+
version_tuple = (
|
|
50
|
+
int(major),
|
|
51
|
+
int(minor),
|
|
52
|
+
int(micro) if micro else 0,
|
|
53
|
+
releaselevel,
|
|
54
|
+
int(serial if serial != "" else 0),
|
|
55
|
+
)
|
|
56
|
+
return version_tuple
|
|
57
|
+
|
|
29
58
|
|
|
30
59
|
def get_details():
|
|
31
60
|
try:
|
|
@@ -37,7 +66,17 @@ def get_details():
|
|
|
37
66
|
implementation = platform.python_implementation().lower()
|
|
38
67
|
metadata = {}
|
|
39
68
|
else:
|
|
40
|
-
if implementation
|
|
69
|
+
if implementation == "graalpy":
|
|
70
|
+
# Special case GraalPy as it erroneously reports the CPython target
|
|
71
|
+
# instead of the Graal versiion
|
|
72
|
+
try:
|
|
73
|
+
ver = __graalpython__.get_graalvm_version()
|
|
74
|
+
metadata = {
|
|
75
|
+
"graalpy_version": version_str_to_tuple(ver)
|
|
76
|
+
}
|
|
77
|
+
except NameError:
|
|
78
|
+
metadata = {"{}_version".format(implementation): sys.implementation.version}
|
|
79
|
+
elif implementation != "cpython": # pragma: no cover
|
|
41
80
|
metadata = {"{}_version".format(implementation): sys.implementation.version}
|
|
42
81
|
else:
|
|
43
82
|
metadata = {}
|
{ducktools_pythonfinder-0.5.4 → ducktools_pythonfinder-0.6.0}/src/ducktools/pythonfinder/shared.py
RENAMED
|
@@ -46,7 +46,7 @@ _laz = LazyImporter(
|
|
|
46
46
|
)
|
|
47
47
|
|
|
48
48
|
|
|
49
|
-
FULL_PY_VER_RE = r"(?P<major>\d+)\.(?P<minor>\d+)\.?(?P<micro>\d*)(?P<releaselevel>[a-zA-Z]*)(?P<serial>\d*)"
|
|
49
|
+
FULL_PY_VER_RE = r"(?P<major>\d+)\.(?P<minor>\d+)\.?(?P<micro>\d*)-?(?P<releaselevel>[a-zA-Z]*)(?P<serial>\d*)"
|
|
50
50
|
|
|
51
51
|
|
|
52
52
|
def version_str_to_tuple(version):
|
|
@@ -57,7 +57,7 @@ def version_str_to_tuple(version):
|
|
|
57
57
|
|
|
58
58
|
major, minor, micro, releaselevel, serial = parsed_version.groups()
|
|
59
59
|
|
|
60
|
-
if releaselevel
|
|
60
|
+
if releaselevel in {"a", "dev"}:
|
|
61
61
|
releaselevel = "alpha"
|
|
62
62
|
elif releaselevel == "b":
|
|
63
63
|
releaselevel = "beta"
|
|
@@ -131,6 +131,7 @@ class PythonInstall(Prefab):
|
|
|
131
131
|
implementation: str = "cpython"
|
|
132
132
|
metadata: dict = attribute(default_factory=dict)
|
|
133
133
|
shadowed: bool = False
|
|
134
|
+
_implementation_version: tuple[int, int, int, str, int] | None = attribute(default=None, private=True)
|
|
134
135
|
|
|
135
136
|
def __prefab_post_init__(
|
|
136
137
|
self,
|
|
@@ -147,6 +148,22 @@ class PythonInstall(Prefab):
|
|
|
147
148
|
def version_str(self) -> str:
|
|
148
149
|
return version_tuple_to_str(self.version)
|
|
149
150
|
|
|
151
|
+
@property
|
|
152
|
+
def implementation_version(self) -> tuple[int, int, int, str, int] | None:
|
|
153
|
+
if self._implementation_version is None:
|
|
154
|
+
if self.implementation == "cpython":
|
|
155
|
+
self._implementation_version = self.version
|
|
156
|
+
elif implementation_ver := self.metadata.get(f"{self.implementation}_version"):
|
|
157
|
+
if len(implementation_ver) == 3:
|
|
158
|
+
self._implementation_version = tuple([*implementation_ver, "final", 0]) # type: ignore
|
|
159
|
+
else:
|
|
160
|
+
self._implementation_version = implementation_ver
|
|
161
|
+
return self._implementation_version
|
|
162
|
+
|
|
163
|
+
@property
|
|
164
|
+
def implementation_version_str(self) -> str:
|
|
165
|
+
return version_tuple_to_str(self.implementation_version)
|
|
166
|
+
|
|
150
167
|
@classmethod
|
|
151
168
|
def from_str(
|
|
152
169
|
cls,
|
|
@@ -242,7 +259,6 @@ def get_install_details(executable: str) -> PythonInstall | None:
|
|
|
242
259
|
try:
|
|
243
260
|
output = _laz.json.loads(detail_output)
|
|
244
261
|
except _laz.json.JSONDecodeError as e:
|
|
245
|
-
print(e)
|
|
246
262
|
return None
|
|
247
263
|
|
|
248
264
|
return PythonInstall.from_json(**output)
|
|
@@ -275,7 +291,7 @@ def get_uv_python_path() -> str | None:
|
|
|
275
291
|
text=True,
|
|
276
292
|
capture_output=True
|
|
277
293
|
)
|
|
278
|
-
except _laz.subprocess.CalledProcessError:
|
|
294
|
+
except (_laz.subprocess.CalledProcessError, FileNotFoundError):
|
|
279
295
|
uv_python_dir = None
|
|
280
296
|
else:
|
|
281
297
|
# remove newline
|
|
@@ -302,7 +318,7 @@ def _implementation_from_uv_dir(
|
|
|
302
318
|
if query_executables:
|
|
303
319
|
install = get_install_details(python_path)
|
|
304
320
|
else:
|
|
305
|
-
if implementation in {"cpython"
|
|
321
|
+
if implementation in {"cpython"}:
|
|
306
322
|
install = PythonInstall.from_str(
|
|
307
323
|
version=version,
|
|
308
324
|
executable=python_path,
|
|
@@ -35,3 +35,26 @@ def sources_folder():
|
|
|
35
35
|
@pytest.fixture
|
|
36
36
|
def uses_details_script(fs):
|
|
37
37
|
fs.add_real_file(details_script.__file__)
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def pytest_addoption(parser):
|
|
41
|
+
parser.addoption(
|
|
42
|
+
"--run-uv-python",
|
|
43
|
+
action="store_true",
|
|
44
|
+
default=False,
|
|
45
|
+
help="Run tests that involve installing UV pythons",
|
|
46
|
+
)
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def pytest_configure(config):
|
|
50
|
+
config.addinivalue_line(
|
|
51
|
+
"markers", "uv_python: only run test if --run-uv-python is specified"
|
|
52
|
+
)
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def pytest_collection_modifyitems(config, items):
|
|
56
|
+
if not config.getoption("--run-uv-python"):
|
|
57
|
+
skipper = pytest.mark.skip(reason="Only run when --run-uv-python is given")
|
|
58
|
+
for item in items:
|
|
59
|
+
if "uv_python" in item.keywords:
|
|
60
|
+
item.add_marker(skipper)
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
# ducktools-pythonfinder
|
|
2
|
+
# MIT License
|
|
3
|
+
#
|
|
4
|
+
# Copyright (c) 2013-2014 David C Ellis
|
|
5
|
+
#
|
|
6
|
+
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
7
|
+
# of this software and associated documentation files (the "Software"), to deal
|
|
8
|
+
# in the Software without restriction, including without limitation the rights
|
|
9
|
+
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
10
|
+
# copies of the Software, and to permit persons to whom the Software is
|
|
11
|
+
# furnished to do so, subject to the following conditions:
|
|
12
|
+
#
|
|
13
|
+
# The above copyright notice and this permission notice shall be included in all
|
|
14
|
+
# copies or substantial portions of the Software.
|
|
15
|
+
#
|
|
16
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
17
|
+
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
18
|
+
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
19
|
+
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
20
|
+
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
21
|
+
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
22
|
+
# SOFTWARE.
|
|
23
|
+
|
|
24
|
+
import os
|
|
25
|
+
import subprocess
|
|
26
|
+
|
|
27
|
+
from tempfile import TemporaryDirectory
|
|
28
|
+
|
|
29
|
+
import unittest.mock as mock
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
import pytest
|
|
33
|
+
|
|
34
|
+
from ducktools.pythonfinder.shared import (
|
|
35
|
+
get_uv_python_path,
|
|
36
|
+
_implementation_from_uv_dir,
|
|
37
|
+
get_uv_pythons,
|
|
38
|
+
)
|
|
39
|
+
|
|
40
|
+
UV_REASON = "UV is not installed - skipping tests that run UV"
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
@pytest.fixture
|
|
44
|
+
def uv_pythondir():
|
|
45
|
+
# Set the UV python folder to a temporary folder and
|
|
46
|
+
# yield the temporary folder value
|
|
47
|
+
uv_python_envkey = "UV_PYTHON_INSTALL_DIR"
|
|
48
|
+
old_uv_python_dir = os.environ.get(uv_python_envkey)
|
|
49
|
+
try:
|
|
50
|
+
with TemporaryDirectory() as tempdir:
|
|
51
|
+
os.environ[uv_python_envkey] = tempdir
|
|
52
|
+
yield tempdir
|
|
53
|
+
finally:
|
|
54
|
+
if old_uv_python_dir is None:
|
|
55
|
+
del os.environ[uv_python_envkey]
|
|
56
|
+
else:
|
|
57
|
+
os.environ[uv_python_envkey] = old_uv_python_dir
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
class TestUVFakes:
|
|
61
|
+
def test_fake_get_uv_python_path_success(self, uv_pythondir):
|
|
62
|
+
# Test the subprocess is called correctly and returned correctly
|
|
63
|
+
with mock.patch("subprocess.run") as run_mock:
|
|
64
|
+
run_mock.return_value.stdout = f"{uv_pythondir}\n"
|
|
65
|
+
|
|
66
|
+
pydir = get_uv_python_path()
|
|
67
|
+
|
|
68
|
+
run_mock.assert_called_once_with(
|
|
69
|
+
["uv", "python", "dir"],
|
|
70
|
+
check=True,
|
|
71
|
+
text=True,
|
|
72
|
+
capture_output=True,
|
|
73
|
+
)
|
|
74
|
+
|
|
75
|
+
assert pydir == uv_pythondir
|
|
76
|
+
|
|
77
|
+
def test_fake_get_uv_python_path_failure(self, uv_pythondir):
|
|
78
|
+
# Test the subprocess is called correctly and returned correctly
|
|
79
|
+
with mock.patch("subprocess.run") as run_mock:
|
|
80
|
+
run_mock.side_effect = subprocess.CalledProcessError(-1, "uv python dir")
|
|
81
|
+
|
|
82
|
+
pydir = get_uv_python_path()
|
|
83
|
+
|
|
84
|
+
run_mock.assert_called_once_with(
|
|
85
|
+
["uv", "python", "dir"],
|
|
86
|
+
check=True,
|
|
87
|
+
text=True,
|
|
88
|
+
capture_output=True,
|
|
89
|
+
)
|
|
90
|
+
|
|
91
|
+
assert pydir is None
|
|
92
|
+
|
|
93
|
+
def test_fake_get_uv_python_path_notfound(self, uv_pythondir):
|
|
94
|
+
# Test the subprocess is called correctly and returned correctly
|
|
95
|
+
with mock.patch("subprocess.run") as run_mock:
|
|
96
|
+
run_mock.side_effect = FileNotFoundError("[Errno 2] No such file or directory: 'uv'")
|
|
97
|
+
|
|
98
|
+
pydir = get_uv_python_path()
|
|
99
|
+
|
|
100
|
+
run_mock.assert_called_once_with(
|
|
101
|
+
["uv", "python", "dir"],
|
|
102
|
+
check=True,
|
|
103
|
+
text=True,
|
|
104
|
+
capture_output=True,
|
|
105
|
+
)
|
|
106
|
+
|
|
107
|
+
assert pydir is None
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
@pytest.mark.skipif(get_uv_python_path() is None, reason=UV_REASON)
|
|
111
|
+
class TestUVReal:
|
|
112
|
+
def test_real_get_uv_python_path(self, uv_pythondir):
|
|
113
|
+
# This checks the UV python path has actually been set
|
|
114
|
+
uv_path = get_uv_python_path()
|
|
115
|
+
assert uv_path == uv_pythondir
|
|
116
|
+
|
|
117
|
+
def test_tempdir_empty(self, uv_pythondir):
|
|
118
|
+
# Tempdir should not have any python installs initially
|
|
119
|
+
pythons = list(get_uv_pythons())
|
|
120
|
+
assert pythons == []
|
|
121
|
+
|
|
122
|
+
@pytest.mark.uv_python
|
|
123
|
+
def test_finds_installed_python(self, uv_pythondir):
|
|
124
|
+
# Install python 3.12.6 in the tempdir
|
|
125
|
+
subprocess.run(
|
|
126
|
+
["uv", "python", "install", "3.12.6"],
|
|
127
|
+
check=True,
|
|
128
|
+
)
|
|
129
|
+
|
|
130
|
+
pythons = list(get_uv_pythons())
|
|
131
|
+
assert len(pythons) == 1
|
|
132
|
+
assert pythons[0].version_str == "3.12.6"
|
|
133
|
+
assert pythons[0].implementation == "cpython"
|
|
134
|
+
|
|
135
|
+
@pytest.mark.uv_python
|
|
136
|
+
def test_finds_installed_pypy(self, uv_pythondir):
|
|
137
|
+
subprocess.run(
|
|
138
|
+
["uv", "python", "install", "pypy3.10"],
|
|
139
|
+
check=True,
|
|
140
|
+
)
|
|
141
|
+
|
|
142
|
+
pythons = list(get_uv_pythons())
|
|
143
|
+
assert len(pythons) == 1
|
|
144
|
+
assert pythons[0].version >= (3, 10, 14)
|
|
145
|
+
assert pythons[0].implementation == "pypy"
|
|
146
|
+
assert pythons[0].implementation_version >= (7, 3, 17)
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{ducktools_pythonfinder-0.5.4 → ducktools_pythonfinder-0.6.0}/scripts/print_python_versions.py
RENAMED
|
File without changes
|
|
File without changes
|
{ducktools_pythonfinder-0.5.4 → ducktools_pythonfinder-0.6.0}/src/ducktools/pythonfinder/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{ducktools_pythonfinder-0.5.4 → ducktools_pythonfinder-0.6.0}/tests/sources/python_versions.txt
RENAMED
|
File without changes
|
|
File without changes
|
{ducktools_pythonfinder-0.5.4 → ducktools_pythonfinder-0.6.0}/tests/sources/release_file.json
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|