ducktools-pythonfinder 0.6.3__tar.gz → 0.6.5__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.6.3/src/ducktools_pythonfinder.egg-info → ducktools_pythonfinder-0.6.5}/PKG-INFO +1 -1
- {ducktools_pythonfinder-0.6.3 → ducktools_pythonfinder-0.6.5}/src/ducktools/pythonfinder/__main__.py +7 -6
- ducktools_pythonfinder-0.6.5/src/ducktools/pythonfinder/_version.py +2 -0
- {ducktools_pythonfinder-0.6.3 → ducktools_pythonfinder-0.6.5}/src/ducktools/pythonfinder/details_script.py +6 -1
- {ducktools_pythonfinder-0.6.3 → ducktools_pythonfinder-0.6.5}/src/ducktools/pythonfinder/linux/pyenv_search.py +18 -5
- {ducktools_pythonfinder-0.6.3 → ducktools_pythonfinder-0.6.5}/src/ducktools/pythonfinder/shared.py +30 -18
- {ducktools_pythonfinder-0.6.3 → ducktools_pythonfinder-0.6.5}/src/ducktools/pythonfinder/win32/pyenv_search.py +6 -2
- {ducktools_pythonfinder-0.6.3 → ducktools_pythonfinder-0.6.5}/src/ducktools/pythonfinder/win32/registry_search.py +12 -6
- {ducktools_pythonfinder-0.6.3 → ducktools_pythonfinder-0.6.5/src/ducktools_pythonfinder.egg-info}/PKG-INFO +1 -1
- {ducktools_pythonfinder-0.6.3 → ducktools_pythonfinder-0.6.5}/tests/test_pyenv.py +4 -4
- {ducktools_pythonfinder-0.6.3 → ducktools_pythonfinder-0.6.5}/tests/test_uv_finder.py +12 -1
- ducktools_pythonfinder-0.6.3/src/ducktools/pythonfinder/_version.py +0 -2
- {ducktools_pythonfinder-0.6.3 → ducktools_pythonfinder-0.6.5}/.gitignore +0 -0
- {ducktools_pythonfinder-0.6.3 → ducktools_pythonfinder-0.6.5}/LICENSE +0 -0
- {ducktools_pythonfinder-0.6.3 → ducktools_pythonfinder-0.6.5}/MANIFEST.in +0 -0
- {ducktools_pythonfinder-0.6.3 → ducktools_pythonfinder-0.6.5}/README.md +0 -0
- {ducktools_pythonfinder-0.6.3 → ducktools_pythonfinder-0.6.5}/pyproject.toml +0 -0
- {ducktools_pythonfinder-0.6.3 → ducktools_pythonfinder-0.6.5}/scripts/build_zipapp.py +0 -0
- {ducktools_pythonfinder-0.6.3 → ducktools_pythonfinder-0.6.5}/scripts/detail_this_python.py +0 -0
- {ducktools_pythonfinder-0.6.3 → ducktools_pythonfinder-0.6.5}/scripts/print_python_versions.py +0 -0
- {ducktools_pythonfinder-0.6.3 → ducktools_pythonfinder-0.6.5}/setup.cfg +0 -0
- {ducktools_pythonfinder-0.6.3 → ducktools_pythonfinder-0.6.5}/src/ducktools/pythonfinder/__init__.py +0 -0
- {ducktools_pythonfinder-0.6.3 → ducktools_pythonfinder-0.6.5}/src/ducktools/pythonfinder/darwin/__init__.py +0 -0
- {ducktools_pythonfinder-0.6.3 → ducktools_pythonfinder-0.6.5}/src/ducktools/pythonfinder/linux/__init__.py +0 -0
- {ducktools_pythonfinder-0.6.3 → ducktools_pythonfinder-0.6.5}/src/ducktools/pythonfinder/pythonorg_search.py +0 -0
- {ducktools_pythonfinder-0.6.3 → ducktools_pythonfinder-0.6.5}/src/ducktools/pythonfinder/win32/__init__.py +0 -0
- {ducktools_pythonfinder-0.6.3 → ducktools_pythonfinder-0.6.5}/src/ducktools_pythonfinder.egg-info/SOURCES.txt +0 -0
- {ducktools_pythonfinder-0.6.3 → ducktools_pythonfinder-0.6.5}/src/ducktools_pythonfinder.egg-info/dependency_links.txt +0 -0
- {ducktools_pythonfinder-0.6.3 → ducktools_pythonfinder-0.6.5}/src/ducktools_pythonfinder.egg-info/entry_points.txt +0 -0
- {ducktools_pythonfinder-0.6.3 → ducktools_pythonfinder-0.6.5}/src/ducktools_pythonfinder.egg-info/requires.txt +0 -0
- {ducktools_pythonfinder-0.6.3 → ducktools_pythonfinder-0.6.5}/src/ducktools_pythonfinder.egg-info/top_level.txt +0 -0
- {ducktools_pythonfinder-0.6.3 → ducktools_pythonfinder-0.6.5}/tests/conftest.py +0 -0
- {ducktools_pythonfinder-0.6.3 → ducktools_pythonfinder-0.6.5}/tests/sources/python_versions.txt +0 -0
- {ducktools_pythonfinder-0.6.3 → ducktools_pythonfinder-0.6.5}/tests/sources/release.json +0 -0
- {ducktools_pythonfinder-0.6.3 → ducktools_pythonfinder-0.6.5}/tests/sources/release_file.json +0 -0
- {ducktools_pythonfinder-0.6.3 → ducktools_pythonfinder-0.6.5}/tests/test_details_script.py +0 -0
- {ducktools_pythonfinder-0.6.3 → ducktools_pythonfinder-0.6.5}/tests/test_foldersearch.py +0 -0
- {ducktools_pythonfinder-0.6.3 → ducktools_pythonfinder-0.6.5}/tests/test_orgsearch.py +0 -0
- {ducktools_pythonfinder-0.6.3 → ducktools_pythonfinder-0.6.5}/tests/test_shared.py +0 -0
{ducktools_pythonfinder-0.6.3 → ducktools_pythonfinder-0.6.5}/src/ducktools/pythonfinder/__main__.py
RENAMED
|
@@ -157,12 +157,10 @@ def display_local_installs(
|
|
|
157
157
|
elif max_ver and install.version > max_ver:
|
|
158
158
|
continue
|
|
159
159
|
elif compatible:
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
break
|
|
165
|
-
if mismatch:
|
|
160
|
+
if install.version < compatible:
|
|
161
|
+
continue
|
|
162
|
+
version_parts = len(compatible) - 1
|
|
163
|
+
if install.version[:version_parts] > compatible[:-1]:
|
|
166
164
|
continue
|
|
167
165
|
|
|
168
166
|
version_str = install.version_str
|
|
@@ -181,6 +179,9 @@ def display_local_installs(
|
|
|
181
179
|
):
|
|
182
180
|
version_str = f"**{version_str}"
|
|
183
181
|
|
|
182
|
+
if install.metadata.get("freethreaded"):
|
|
183
|
+
version_str = f"{version_str}t"
|
|
184
|
+
|
|
184
185
|
if install.shadowed:
|
|
185
186
|
version_str = f"[{version_str}]"
|
|
186
187
|
|
|
@@ -26,7 +26,7 @@ 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>
|
|
29
|
+
FULL_PY_VER_RE = r"(?P<major>\d+)\.(?P<minor>\d+)\.?(?P<micro>\d*)-?(?P<releaselevel>a|b|c|rc)?(?P<serial>\d*)?"
|
|
30
30
|
|
|
31
31
|
|
|
32
32
|
def version_str_to_tuple(version):
|
|
@@ -80,6 +80,11 @@ def get_details():
|
|
|
80
80
|
metadata = {"{}_version".format(implementation): sys.implementation.version}
|
|
81
81
|
else:
|
|
82
82
|
metadata = {}
|
|
83
|
+
if sys.version_info >= (3, 13):
|
|
84
|
+
import sysconfig
|
|
85
|
+
freethreaded = bool(sysconfig.get_config_var("Py_GIL_DISABLED"))
|
|
86
|
+
metadata["freethreaded"] = freethreaded
|
|
87
|
+
|
|
83
88
|
|
|
84
89
|
install = dict(
|
|
85
90
|
version=list(sys.version_info),
|
|
@@ -33,7 +33,7 @@ from _collections_abc import Iterator
|
|
|
33
33
|
|
|
34
34
|
from ducktools.lazyimporter import LazyImporter, ModuleImport
|
|
35
35
|
|
|
36
|
-
from ..shared import PythonInstall, get_install_details
|
|
36
|
+
from ..shared import PythonInstall, get_install_details, FULL_PY_VER_RE, version_str_to_tuple
|
|
37
37
|
|
|
38
38
|
_laz = LazyImporter(
|
|
39
39
|
[
|
|
@@ -41,8 +41,7 @@ _laz = LazyImporter(
|
|
|
41
41
|
]
|
|
42
42
|
)
|
|
43
43
|
|
|
44
|
-
# pyenv folder
|
|
45
|
-
PYTHON_VER_RE = r"\d{1,2}\.\d{1,2}\.\d+[a-z]*\d*"
|
|
44
|
+
# pyenv folder name for pypy
|
|
46
45
|
PYPY_VER_RE = r"^pypy(?P<pyversion>\d{1,2}\.\d+)-(?P<pypyversion>[\d\.]*)$"
|
|
47
46
|
|
|
48
47
|
PYENV_VERSIONS_FOLDER = os.path.join(os.environ.get("PYENV_ROOT", ""), "versions")
|
|
@@ -64,7 +63,21 @@ def get_pyenv_pythons(
|
|
|
64
63
|
executable = os.path.join(p.path, "bin/python")
|
|
65
64
|
|
|
66
65
|
if os.path.exists(executable):
|
|
67
|
-
if
|
|
68
|
-
|
|
66
|
+
if p.name.endswith("t"):
|
|
67
|
+
freethreaded = True
|
|
68
|
+
version = p.name[:-1]
|
|
69
|
+
else:
|
|
70
|
+
freethreaded = False
|
|
71
|
+
version = p.name
|
|
72
|
+
if _laz.re.fullmatch(FULL_PY_VER_RE, version):
|
|
73
|
+
version_tuple = version_str_to_tuple(version)
|
|
74
|
+
metadata = {}
|
|
75
|
+
if version_tuple >= (3, 13):
|
|
76
|
+
metadata["freethreaded"] = freethreaded
|
|
77
|
+
yield PythonInstall(
|
|
78
|
+
version=version_tuple,
|
|
79
|
+
executable=executable,
|
|
80
|
+
metadata=metadata,
|
|
81
|
+
)
|
|
69
82
|
elif query_executables and (install := get_install_details(executable)):
|
|
70
83
|
yield install
|
{ducktools_pythonfinder-0.6.3 → ducktools_pythonfinder-0.6.5}/src/ducktools/pythonfinder/shared.py
RENAMED
|
@@ -46,7 +46,15 @@ _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>
|
|
49
|
+
FULL_PY_VER_RE = r"(?P<major>\d+)\.(?P<minor>\d+)\.?(?P<micro>\d*)-?(?P<releaselevel>a|b|c|rc)?(?P<serial>\d*)?"
|
|
50
|
+
|
|
51
|
+
UV_PYTHON_RE = (
|
|
52
|
+
r"(?P<implementation>[a-zA-Z]+)"
|
|
53
|
+
r"-(?P<version>\d+\.\d+\.\d*[a-zA-Z]*\d*)\+?(?P<extra>.*?)?"
|
|
54
|
+
r"-(?P<platform>\w*)"
|
|
55
|
+
r"-(?P<arch>\w*)"
|
|
56
|
+
r"-.*"
|
|
57
|
+
)
|
|
50
58
|
|
|
51
59
|
|
|
52
60
|
def version_str_to_tuple(version):
|
|
@@ -61,7 +69,7 @@ def version_str_to_tuple(version):
|
|
|
61
69
|
releaselevel = "alpha"
|
|
62
70
|
elif releaselevel == "b":
|
|
63
71
|
releaselevel = "beta"
|
|
64
|
-
elif releaselevel
|
|
72
|
+
elif releaselevel in {"c", "rc"}:
|
|
65
73
|
releaselevel = "candidate"
|
|
66
74
|
else:
|
|
67
75
|
releaselevel = "final"
|
|
@@ -323,25 +331,29 @@ def _implementation_from_uv_dir(
|
|
|
323
331
|
install: PythonInstall | None = None
|
|
324
332
|
|
|
325
333
|
if os.path.exists(python_path):
|
|
326
|
-
|
|
327
|
-
implementation, version, platform, arch
|
|
328
|
-
|
|
329
|
-
|
|
334
|
+
if match := _laz.re.fullmatch(UV_PYTHON_RE, direntry.name):
|
|
335
|
+
implementation, version, extra, platform, arch = match.groups()
|
|
336
|
+
metadata = {
|
|
337
|
+
"freethreaded": "freethreaded" in extra,
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
try:
|
|
341
|
+
if implementation in {"cpython"}:
|
|
342
|
+
install = PythonInstall.from_str(
|
|
343
|
+
version=version,
|
|
344
|
+
executable=python_path,
|
|
345
|
+
architecture="32bit" if arch in {"i686", "armv7"} else "64bit",
|
|
346
|
+
implementation=implementation,
|
|
347
|
+
metadata=metadata
|
|
348
|
+
)
|
|
349
|
+
except ValueError:
|
|
350
|
+
pass
|
|
351
|
+
|
|
352
|
+
if install is None:
|
|
353
|
+
# Directory name format has changed or this is an alternate implementation
|
|
330
354
|
# Slow backup - ask python itself
|
|
331
355
|
if query_executables:
|
|
332
356
|
install = get_install_details(python_path)
|
|
333
|
-
else:
|
|
334
|
-
if implementation in {"cpython"}:
|
|
335
|
-
install = PythonInstall.from_str(
|
|
336
|
-
version=version,
|
|
337
|
-
executable=python_path,
|
|
338
|
-
architecture="32bit" if arch in {"i686", "armv7"} else "64bit",
|
|
339
|
-
implementation=implementation,
|
|
340
|
-
)
|
|
341
|
-
else:
|
|
342
|
-
# Get additional alternate implementation details
|
|
343
|
-
if query_executables:
|
|
344
|
-
install = get_install_details(python_path)
|
|
345
357
|
|
|
346
358
|
return install
|
|
347
359
|
|
|
@@ -73,7 +73,9 @@ def get_pyenv_pythons(
|
|
|
73
73
|
arch = "32bit" if arch == "win32" else "64bit"
|
|
74
74
|
try:
|
|
75
75
|
yield PythonInstall.from_str(
|
|
76
|
-
version,
|
|
76
|
+
version=version,
|
|
77
|
+
executable=executable,
|
|
78
|
+
architecture=arch,
|
|
77
79
|
)
|
|
78
80
|
except ValueError:
|
|
79
81
|
pass
|
|
@@ -81,7 +83,9 @@ def get_pyenv_pythons(
|
|
|
81
83
|
version = split_version[0]
|
|
82
84
|
try:
|
|
83
85
|
yield PythonInstall.from_str(
|
|
84
|
-
version,
|
|
86
|
+
version=version,
|
|
87
|
+
executable=executable,
|
|
88
|
+
architecture="64bit",
|
|
85
89
|
)
|
|
86
90
|
except ValueError:
|
|
87
91
|
pass
|
|
@@ -31,7 +31,7 @@ Based on PEP 514 registry entries.
|
|
|
31
31
|
import winreg # noqa # pycharm seems to think winreg doesn't exist in python3.12
|
|
32
32
|
from _collections_abc import Iterator
|
|
33
33
|
|
|
34
|
-
from ..shared import PythonInstall
|
|
34
|
+
from ..shared import PythonInstall, version_str_to_tuple
|
|
35
35
|
|
|
36
36
|
exclude_companies = {
|
|
37
37
|
"PyLauncher", # pylauncher is special cased to be ignored
|
|
@@ -100,18 +100,24 @@ def get_registered_pythons() -> Iterator[PythonInstall]:
|
|
|
100
100
|
winreg.CloseKey(install_key)
|
|
101
101
|
|
|
102
102
|
python_version: str | None = metadata.get("Version")
|
|
103
|
-
# Pyenv puts architecture information in the Version value for some reason
|
|
104
|
-
if python_version:
|
|
105
|
-
python_version = python_version.split("-")[0]
|
|
106
103
|
|
|
107
104
|
architecture = metadata.get("SysArchitecture")
|
|
108
105
|
|
|
109
106
|
metadata["InWindowsRegistry"] = True
|
|
110
107
|
|
|
111
108
|
if python_path and python_version:
|
|
109
|
+
# Pyenv puts architecture information in the Version value for some reason
|
|
110
|
+
python_version = python_version.split("-")[0]
|
|
111
|
+
version_tuple = version_str_to_tuple(python_version)
|
|
112
|
+
|
|
113
|
+
if (
|
|
114
|
+
version_tuple >= (3, 13)
|
|
115
|
+
and "freethreaded" in metadata.get("DisplayName", "")
|
|
116
|
+
):
|
|
117
|
+
metadata["freethreaded"] = True
|
|
112
118
|
try:
|
|
113
|
-
yield PythonInstall
|
|
114
|
-
version=
|
|
119
|
+
yield PythonInstall(
|
|
120
|
+
version=version_tuple,
|
|
115
121
|
executable=python_path,
|
|
116
122
|
architecture=architecture,
|
|
117
123
|
metadata=metadata,
|
|
@@ -72,7 +72,7 @@ def test_mock_versions_folder():
|
|
|
72
72
|
|
|
73
73
|
python_versions = list(get_pyenv_pythons())
|
|
74
74
|
|
|
75
|
-
assert python_versions == [PythonInstall.from_str(out_ver, out_executable)]
|
|
75
|
+
assert python_versions == [PythonInstall.from_str(version=out_ver, executable=out_executable)]
|
|
76
76
|
|
|
77
77
|
|
|
78
78
|
@pytest.mark.skipif(sys.platform != "win32", reason="Test for Windows only")
|
|
@@ -89,7 +89,7 @@ def test_fs_versions_win(fs):
|
|
|
89
89
|
|
|
90
90
|
versions = list(get_pyenv_pythons(tmpdir))
|
|
91
91
|
|
|
92
|
-
assert versions == [PythonInstall.from_str("3.12.1", py_exe)]
|
|
92
|
+
assert versions == [PythonInstall.from_str(version="3.12.1", executable=py_exe)]
|
|
93
93
|
|
|
94
94
|
|
|
95
95
|
@pytest.mark.skipif(sys.platform != "win32", reason="Test for Windows only")
|
|
@@ -106,7 +106,7 @@ def test_32bit_version(fs):
|
|
|
106
106
|
|
|
107
107
|
versions = list(get_pyenv_pythons(tmpdir))
|
|
108
108
|
|
|
109
|
-
assert versions == [PythonInstall.from_str("3.12.1", py_exe, architecture="32bit")]
|
|
109
|
+
assert versions == [PythonInstall.from_str(version="3.12.1", executable=py_exe, architecture="32bit")]
|
|
110
110
|
|
|
111
111
|
|
|
112
112
|
@pytest.mark.skipif(sys.platform != "win32", reason="Test for Windows only")
|
|
@@ -155,7 +155,7 @@ def test_fs_versions_nix(fs):
|
|
|
155
155
|
|
|
156
156
|
versions = list(get_pyenv_pythons(tmpdir))
|
|
157
157
|
|
|
158
|
-
assert versions == [PythonInstall.from_str("3.12.1", py_exe)]
|
|
158
|
+
assert versions == [PythonInstall.from_str(version="3.12.1", executable=py_exe)]
|
|
159
159
|
|
|
160
160
|
|
|
161
161
|
@pytest.mark.skipif(sys.platform == "win32", reason="Test for non-Windows only")
|
|
@@ -22,6 +22,7 @@
|
|
|
22
22
|
# SOFTWARE.
|
|
23
23
|
|
|
24
24
|
import os
|
|
25
|
+
import re
|
|
25
26
|
import subprocess
|
|
26
27
|
|
|
27
28
|
from tempfile import TemporaryDirectory
|
|
@@ -32,8 +33,8 @@ import unittest.mock as mock
|
|
|
32
33
|
import pytest
|
|
33
34
|
|
|
34
35
|
from ducktools.pythonfinder.shared import (
|
|
36
|
+
UV_PYTHON_RE,
|
|
35
37
|
get_uv_python_path,
|
|
36
|
-
_implementation_from_uv_dir,
|
|
37
38
|
get_uv_pythons,
|
|
38
39
|
)
|
|
39
40
|
|
|
@@ -144,3 +145,13 @@ class TestUVReal:
|
|
|
144
145
|
assert pythons[0].version >= (3, 10, 14)
|
|
145
146
|
assert pythons[0].implementation == "pypy"
|
|
146
147
|
assert pythons[0].implementation_version >= (7, 3, 17)
|
|
148
|
+
|
|
149
|
+
|
|
150
|
+
def test_regex_matches():
|
|
151
|
+
example_312 = "cpython-3.12.7-windows-x86_64-none"
|
|
152
|
+
match = re.match(UV_PYTHON_RE, example_312)
|
|
153
|
+
assert match.groups() == ("cpython", "3.12.7", "", "windows", "x86_64")
|
|
154
|
+
|
|
155
|
+
example_313t = "cpython-3.13.0+freethreaded-windows-x86_64-none"
|
|
156
|
+
match = re.match(UV_PYTHON_RE, example_313t)
|
|
157
|
+
assert match.groups() == ("cpython", "3.13.0", "freethreaded", "windows", "x86_64")
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{ducktools_pythonfinder-0.6.3 → ducktools_pythonfinder-0.6.5}/scripts/print_python_versions.py
RENAMED
|
File without changes
|
|
File without changes
|
{ducktools_pythonfinder-0.6.3 → ducktools_pythonfinder-0.6.5}/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
|
{ducktools_pythonfinder-0.6.3 → ducktools_pythonfinder-0.6.5}/tests/sources/python_versions.txt
RENAMED
|
File without changes
|
|
File without changes
|
{ducktools_pythonfinder-0.6.3 → ducktools_pythonfinder-0.6.5}/tests/sources/release_file.json
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|