ducktools-pythonfinder 0.6.2__tar.gz → 0.6.4__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.2/src/ducktools_pythonfinder.egg-info → ducktools_pythonfinder-0.6.4}/PKG-INFO +1 -1
- {ducktools_pythonfinder-0.6.2 → ducktools_pythonfinder-0.6.4}/src/ducktools/pythonfinder/__main__.py +3 -0
- ducktools_pythonfinder-0.6.4/src/ducktools/pythonfinder/_version.py +2 -0
- {ducktools_pythonfinder-0.6.2 → ducktools_pythonfinder-0.6.4}/src/ducktools/pythonfinder/details_script.py +6 -1
- {ducktools_pythonfinder-0.6.2 → ducktools_pythonfinder-0.6.4}/src/ducktools/pythonfinder/linux/pyenv_search.py +18 -5
- {ducktools_pythonfinder-0.6.2 → ducktools_pythonfinder-0.6.4}/src/ducktools/pythonfinder/shared.py +36 -19
- {ducktools_pythonfinder-0.6.2 → ducktools_pythonfinder-0.6.4}/src/ducktools/pythonfinder/win32/pyenv_search.py +6 -2
- {ducktools_pythonfinder-0.6.2 → ducktools_pythonfinder-0.6.4}/src/ducktools/pythonfinder/win32/registry_search.py +12 -6
- {ducktools_pythonfinder-0.6.2 → ducktools_pythonfinder-0.6.4/src/ducktools_pythonfinder.egg-info}/PKG-INFO +1 -1
- {ducktools_pythonfinder-0.6.2 → ducktools_pythonfinder-0.6.4}/tests/test_pyenv.py +4 -4
- {ducktools_pythonfinder-0.6.2 → ducktools_pythonfinder-0.6.4}/tests/test_uv_finder.py +12 -1
- ducktools_pythonfinder-0.6.2/src/ducktools/pythonfinder/_version.py +0 -2
- {ducktools_pythonfinder-0.6.2 → ducktools_pythonfinder-0.6.4}/.gitignore +0 -0
- {ducktools_pythonfinder-0.6.2 → ducktools_pythonfinder-0.6.4}/LICENSE +0 -0
- {ducktools_pythonfinder-0.6.2 → ducktools_pythonfinder-0.6.4}/MANIFEST.in +0 -0
- {ducktools_pythonfinder-0.6.2 → ducktools_pythonfinder-0.6.4}/README.md +0 -0
- {ducktools_pythonfinder-0.6.2 → ducktools_pythonfinder-0.6.4}/pyproject.toml +0 -0
- {ducktools_pythonfinder-0.6.2 → ducktools_pythonfinder-0.6.4}/scripts/build_zipapp.py +0 -0
- {ducktools_pythonfinder-0.6.2 → ducktools_pythonfinder-0.6.4}/scripts/detail_this_python.py +0 -0
- {ducktools_pythonfinder-0.6.2 → ducktools_pythonfinder-0.6.4}/scripts/print_python_versions.py +0 -0
- {ducktools_pythonfinder-0.6.2 → ducktools_pythonfinder-0.6.4}/setup.cfg +0 -0
- {ducktools_pythonfinder-0.6.2 → ducktools_pythonfinder-0.6.4}/src/ducktools/pythonfinder/__init__.py +0 -0
- {ducktools_pythonfinder-0.6.2 → ducktools_pythonfinder-0.6.4}/src/ducktools/pythonfinder/darwin/__init__.py +0 -0
- {ducktools_pythonfinder-0.6.2 → ducktools_pythonfinder-0.6.4}/src/ducktools/pythonfinder/linux/__init__.py +0 -0
- {ducktools_pythonfinder-0.6.2 → ducktools_pythonfinder-0.6.4}/src/ducktools/pythonfinder/pythonorg_search.py +0 -0
- {ducktools_pythonfinder-0.6.2 → ducktools_pythonfinder-0.6.4}/src/ducktools/pythonfinder/win32/__init__.py +0 -0
- {ducktools_pythonfinder-0.6.2 → ducktools_pythonfinder-0.6.4}/src/ducktools_pythonfinder.egg-info/SOURCES.txt +0 -0
- {ducktools_pythonfinder-0.6.2 → ducktools_pythonfinder-0.6.4}/src/ducktools_pythonfinder.egg-info/dependency_links.txt +0 -0
- {ducktools_pythonfinder-0.6.2 → ducktools_pythonfinder-0.6.4}/src/ducktools_pythonfinder.egg-info/entry_points.txt +0 -0
- {ducktools_pythonfinder-0.6.2 → ducktools_pythonfinder-0.6.4}/src/ducktools_pythonfinder.egg-info/requires.txt +0 -0
- {ducktools_pythonfinder-0.6.2 → ducktools_pythonfinder-0.6.4}/src/ducktools_pythonfinder.egg-info/top_level.txt +0 -0
- {ducktools_pythonfinder-0.6.2 → ducktools_pythonfinder-0.6.4}/tests/conftest.py +0 -0
- {ducktools_pythonfinder-0.6.2 → ducktools_pythonfinder-0.6.4}/tests/sources/python_versions.txt +0 -0
- {ducktools_pythonfinder-0.6.2 → ducktools_pythonfinder-0.6.4}/tests/sources/release.json +0 -0
- {ducktools_pythonfinder-0.6.2 → ducktools_pythonfinder-0.6.4}/tests/sources/release_file.json +0 -0
- {ducktools_pythonfinder-0.6.2 → ducktools_pythonfinder-0.6.4}/tests/test_details_script.py +0 -0
- {ducktools_pythonfinder-0.6.2 → ducktools_pythonfinder-0.6.4}/tests/test_foldersearch.py +0 -0
- {ducktools_pythonfinder-0.6.2 → ducktools_pythonfinder-0.6.4}/tests/test_orgsearch.py +0 -0
- {ducktools_pythonfinder-0.6.2 → ducktools_pythonfinder-0.6.4}/tests/test_shared.py +0 -0
|
@@ -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.2 → ducktools_pythonfinder-0.6.4}/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"
|
|
@@ -273,8 +281,13 @@ def get_folder_pythons(
|
|
|
273
281
|
|
|
274
282
|
with os.scandir(base_folder) as fld:
|
|
275
283
|
for file_path in fld:
|
|
284
|
+
try:
|
|
285
|
+
is_file = file_path.is_file()
|
|
286
|
+
except PermissionError:
|
|
287
|
+
continue
|
|
288
|
+
|
|
276
289
|
if (
|
|
277
|
-
|
|
290
|
+
is_file
|
|
278
291
|
and any(reg.fullmatch(file_path.name) for reg in regexes)
|
|
279
292
|
):
|
|
280
293
|
p = file_path.path
|
|
@@ -318,25 +331,29 @@ def _implementation_from_uv_dir(
|
|
|
318
331
|
install: PythonInstall | None = None
|
|
319
332
|
|
|
320
333
|
if os.path.exists(python_path):
|
|
321
|
-
|
|
322
|
-
implementation, version, platform, arch
|
|
323
|
-
|
|
324
|
-
|
|
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
|
|
325
354
|
# Slow backup - ask python itself
|
|
326
355
|
if query_executables:
|
|
327
356
|
install = get_install_details(python_path)
|
|
328
|
-
else:
|
|
329
|
-
if implementation in {"cpython"}:
|
|
330
|
-
install = PythonInstall.from_str(
|
|
331
|
-
version=version,
|
|
332
|
-
executable=python_path,
|
|
333
|
-
architecture="32bit" if arch in {"i686", "armv7"} else "64bit",
|
|
334
|
-
implementation=implementation,
|
|
335
|
-
)
|
|
336
|
-
else:
|
|
337
|
-
# Get additional alternate implementation details
|
|
338
|
-
if query_executables:
|
|
339
|
-
install = get_install_details(python_path)
|
|
340
357
|
|
|
341
358
|
return install
|
|
342
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.2 → ducktools_pythonfinder-0.6.4}/scripts/print_python_versions.py
RENAMED
|
File without changes
|
|
File without changes
|
{ducktools_pythonfinder-0.6.2 → ducktools_pythonfinder-0.6.4}/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.2 → ducktools_pythonfinder-0.6.4}/tests/sources/python_versions.txt
RENAMED
|
File without changes
|
|
File without changes
|
{ducktools_pythonfinder-0.6.2 → ducktools_pythonfinder-0.6.4}/tests/sources/release_file.json
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|