python-discovery 1.3.2__tar.gz → 1.4.1__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.
- {python_discovery-1.3.2 → python_discovery-1.4.1}/.github/workflows/check.yaml +2 -2
- {python_discovery-1.3.2 → python_discovery-1.4.1}/.github/workflows/prepare-release.yaml +2 -2
- {python_discovery-1.3.2 → python_discovery-1.4.1}/.github/workflows/release.yaml +2 -2
- {python_discovery-1.3.2 → python_discovery-1.4.1}/.pre-commit-config.yaml +2 -2
- {python_discovery-1.3.2 → python_discovery-1.4.1}/PKG-INFO +1 -1
- {python_discovery-1.3.2 → python_discovery-1.4.1}/docs/changelog.rst +19 -0
- {python_discovery-1.3.2 → python_discovery-1.4.1}/src/python_discovery/_py_info.py +34 -11
- {python_discovery-1.3.2 → python_discovery-1.4.1}/tests/py_info/test_py_info.py +23 -0
- {python_discovery-1.3.2 → python_discovery-1.4.1}/tests/test_discovery.py +7 -8
- {python_discovery-1.3.2 → python_discovery-1.4.1}/tests/test_py_info_extra.py +104 -1
- {python_discovery-1.3.2 → python_discovery-1.4.1}/.github/FUNDING.yaml +0 -0
- {python_discovery-1.3.2 → python_discovery-1.4.1}/.github/SECURITY.md +0 -0
- {python_discovery-1.3.2 → python_discovery-1.4.1}/.github/dependabot.yaml +0 -0
- {python_discovery-1.3.2 → python_discovery-1.4.1}/.github/release.yaml +0 -0
- {python_discovery-1.3.2 → python_discovery-1.4.1}/.gitignore +0 -0
- {python_discovery-1.3.2 → python_discovery-1.4.1}/.readthedocs.yaml +0 -0
- {python_discovery-1.3.2 → python_discovery-1.4.1}/.readthedocs.yml +0 -0
- {python_discovery-1.3.2 → python_discovery-1.4.1}/CODE_OF_CONDUCT.md +0 -0
- {python_discovery-1.3.2 → python_discovery-1.4.1}/LICENSE +0 -0
- {python_discovery-1.3.2 → python_discovery-1.4.1}/README.md +0 -0
- {python_discovery-1.3.2 → python_discovery-1.4.1}/docs/_static/custom.css +0 -0
- {python_discovery-1.3.2 → python_discovery-1.4.1}/docs/_static/logo.svg +0 -0
- {python_discovery-1.3.2 → python_discovery-1.4.1}/docs/changelog/template.jinja2 +0 -0
- {python_discovery-1.3.2 → python_discovery-1.4.1}/docs/conf.py +0 -0
- {python_discovery-1.3.2 → python_discovery-1.4.1}/docs/explanation.rst +0 -0
- {python_discovery-1.3.2 → python_discovery-1.4.1}/docs/how-to/standalone-usage.rst +0 -0
- {python_discovery-1.3.2 → python_discovery-1.4.1}/docs/index.rst +0 -0
- {python_discovery-1.3.2 → python_discovery-1.4.1}/docs/reference/api.rst +0 -0
- {python_discovery-1.3.2 → python_discovery-1.4.1}/docs/reference/environment-variables.rst +0 -0
- {python_discovery-1.3.2 → python_discovery-1.4.1}/docs/tutorial/getting-started.rst +0 -0
- {python_discovery-1.3.2 → python_discovery-1.4.1}/pyproject.toml +2 -2
- {python_discovery-1.3.2 → python_discovery-1.4.1}/src/python_discovery/__init__.py +0 -0
- {python_discovery-1.3.2 → python_discovery-1.4.1}/src/python_discovery/_cache.py +0 -0
- {python_discovery-1.3.2 → python_discovery-1.4.1}/src/python_discovery/_cached_py_info.py +0 -0
- {python_discovery-1.3.2 → python_discovery-1.4.1}/src/python_discovery/_compat.py +0 -0
- {python_discovery-1.3.2 → python_discovery-1.4.1}/src/python_discovery/_discovery.py +0 -0
- {python_discovery-1.3.2 → python_discovery-1.4.1}/src/python_discovery/_py_spec.py +0 -0
- {python_discovery-1.3.2 → python_discovery-1.4.1}/src/python_discovery/_specifier.py +0 -0
- {python_discovery-1.3.2 → python_discovery-1.4.1}/src/python_discovery/_windows/__init__.py +0 -0
- {python_discovery-1.3.2 → python_discovery-1.4.1}/src/python_discovery/_windows/_pep514.py +0 -0
- {python_discovery-1.3.2 → python_discovery-1.4.1}/src/python_discovery/_windows/_propose.py +0 -0
- {python_discovery-1.3.2 → python_discovery-1.4.1}/src/python_discovery/py.typed +0 -0
- {python_discovery-1.3.2 → python_discovery-1.4.1}/tasks/release.py +0 -0
- {python_discovery-1.3.2 → python_discovery-1.4.1}/tests/conftest.py +0 -0
- {python_discovery-1.3.2 → python_discovery-1.4.1}/tests/py_info/test_py_info_exe_based_of.py +0 -0
- {python_discovery-1.3.2 → python_discovery-1.4.1}/tests/test_cache.py +0 -0
- {python_discovery-1.3.2 → python_discovery-1.4.1}/tests/test_cached_py_info.py +0 -0
- {python_discovery-1.3.2 → python_discovery-1.4.1}/tests/test_discovery_extra.py +0 -0
- {python_discovery-1.3.2 → python_discovery-1.4.1}/tests/test_iter_interpreters.py +0 -0
- {python_discovery-1.3.2 → python_discovery-1.4.1}/tests/test_py_spec.py +0 -0
- {python_discovery-1.3.2 → python_discovery-1.4.1}/tests/test_py_spec_extra.py +0 -0
- {python_discovery-1.3.2 → python_discovery-1.4.1}/tests/test_specifier.py +0 -0
- {python_discovery-1.3.2 → python_discovery-1.4.1}/tests/windows/conftest.py +0 -0
- {python_discovery-1.3.2 → python_discovery-1.4.1}/tests/windows/test_windows.py +0 -0
- {python_discovery-1.3.2 → python_discovery-1.4.1}/tests/windows/test_windows_pep514.py +0 -0
- {python_discovery-1.3.2 → python_discovery-1.4.1}/tests/windows/winreg_mock_values.py +0 -0
- {python_discovery-1.3.2 → python_discovery-1.4.1}/tox.toml +0 -0
|
@@ -34,12 +34,12 @@ jobs:
|
|
|
34
34
|
- dev
|
|
35
35
|
- pkg_meta
|
|
36
36
|
steps:
|
|
37
|
-
- uses: actions/checkout@
|
|
37
|
+
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6
|
|
38
38
|
with:
|
|
39
39
|
fetch-depth: 0
|
|
40
40
|
persist-credentials: false
|
|
41
41
|
- name: Install the latest version of uv
|
|
42
|
-
uses: astral-sh/setup-uv@
|
|
42
|
+
uses: astral-sh/setup-uv@fac544c07dec837d0ccb6301d7b5580bf5edae39 # v8.2.0
|
|
43
43
|
with:
|
|
44
44
|
enable-cache: false
|
|
45
45
|
cache-dependency-glob: "pyproject.toml"
|
|
@@ -26,14 +26,14 @@ jobs:
|
|
|
26
26
|
permissions:
|
|
27
27
|
contents: write
|
|
28
28
|
steps:
|
|
29
|
-
- uses: actions/checkout@
|
|
29
|
+
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6
|
|
30
30
|
with:
|
|
31
31
|
fetch-depth: 0
|
|
32
32
|
token: ${{ secrets.RELEASE_PAT }}
|
|
33
33
|
persist-credentials: true
|
|
34
34
|
|
|
35
35
|
- name: Install the latest version of uv
|
|
36
|
-
uses: astral-sh/setup-uv@
|
|
36
|
+
uses: astral-sh/setup-uv@fac544c07dec837d0ccb6301d7b5580bf5edae39 # v8.2.0
|
|
37
37
|
with:
|
|
38
38
|
enable-cache: true
|
|
39
39
|
cache-dependency-glob: "pyproject.toml"
|
|
@@ -13,12 +13,12 @@ jobs:
|
|
|
13
13
|
build:
|
|
14
14
|
runs-on: ubuntu-latest
|
|
15
15
|
steps:
|
|
16
|
-
- uses: actions/checkout@
|
|
16
|
+
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6
|
|
17
17
|
with:
|
|
18
18
|
fetch-depth: 0
|
|
19
19
|
persist-credentials: false
|
|
20
20
|
- name: Install the latest version of uv
|
|
21
|
-
uses: astral-sh/setup-uv@
|
|
21
|
+
uses: astral-sh/setup-uv@fac544c07dec837d0ccb6301d7b5580bf5edae39 # v8.2.0
|
|
22
22
|
with:
|
|
23
23
|
enable-cache: false
|
|
24
24
|
cache-dependency-glob: "pyproject.toml"
|
|
@@ -15,11 +15,11 @@ repos:
|
|
|
15
15
|
- id: codespell
|
|
16
16
|
additional_dependencies: ["tomli>=2.4"]
|
|
17
17
|
- repo: https://github.com/tox-dev/pyproject-fmt
|
|
18
|
-
rev: "v2.
|
|
18
|
+
rev: "v2.23.0"
|
|
19
19
|
hooks:
|
|
20
20
|
- id: pyproject-fmt
|
|
21
21
|
- repo: https://github.com/astral-sh/ruff-pre-commit
|
|
22
|
-
rev: "v0.15.
|
|
22
|
+
rev: "v0.15.16"
|
|
23
23
|
hooks:
|
|
24
24
|
- id: ruff-format
|
|
25
25
|
- id: ruff
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: python-discovery
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.4.1
|
|
4
4
|
Summary: Python interpreter discovery
|
|
5
5
|
Project-URL: Changelog, https://github.com/tox-dev/python-discovery/releases
|
|
6
6
|
Project-URL: Documentation, https://python-discovery.readthedocs.io
|
|
@@ -6,6 +6,25 @@
|
|
|
6
6
|
|
|
7
7
|
.. towncrier release notes start
|
|
8
8
|
|
|
9
|
+
********************
|
|
10
|
+
v1.4.1 (2026-06-11)
|
|
11
|
+
********************
|
|
12
|
+
|
|
13
|
+
Bug fixes - 1.4.1
|
|
14
|
+
=================
|
|
15
|
+
- Resolve executable-only symlinks when computing ``system_executable``, mirroring CPython's ``getpath.realpath``
|
|
16
|
+
(python/cpython#115237): a symlink to the interpreter binary now resolves to the real interpreter, while a fully
|
|
17
|
+
symlinked interpreter tree is kept as-is - by :user:`gaborbernat`. (:issue:`84`)
|
|
18
|
+
|
|
19
|
+
********************
|
|
20
|
+
v1.4.0 (2026-05-28)
|
|
21
|
+
********************
|
|
22
|
+
|
|
23
|
+
Features - 1.4.0
|
|
24
|
+
================
|
|
25
|
+
- Add ``debug_build`` attribute to :class:`PythonInfo` exposing whether the interpreter is a debug build
|
|
26
|
+
(``Py_DEBUG``) - by :user:`gaborbernat`. (:issue:`80`)
|
|
27
|
+
|
|
9
28
|
********************
|
|
10
29
|
v1.3.2 (2026-05-27)
|
|
11
30
|
********************
|
|
@@ -12,6 +12,7 @@ import sys
|
|
|
12
12
|
import sysconfig
|
|
13
13
|
import warnings
|
|
14
14
|
from collections import OrderedDict
|
|
15
|
+
from itertools import product
|
|
15
16
|
from string import digits
|
|
16
17
|
from typing import TYPE_CHECKING, ClassVar, Final, NamedTuple
|
|
17
18
|
|
|
@@ -73,6 +74,7 @@ class PythonInfo: # noqa: PLR0904
|
|
|
73
74
|
self.version = sys.version
|
|
74
75
|
self.os = os.name
|
|
75
76
|
self.free_threaded = sysconfig.get_config_var("Py_GIL_DISABLED") == 1
|
|
77
|
+
self.debug_build = bool(sysconfig.get_config_var("Py_DEBUG"))
|
|
76
78
|
|
|
77
79
|
def _init_prefixes(self) -> None:
|
|
78
80
|
def abs_path(value: str | None) -> str | None:
|
|
@@ -202,7 +204,7 @@ class PythonInfo: # noqa: PLR0904
|
|
|
202
204
|
# if we're not in a virtual environment, this is already a system python, so return the original executable
|
|
203
205
|
# note we must choose the original and not the pure executable as shim scripts might throw us off
|
|
204
206
|
if not (self.real_prefix or (self.base_prefix is not None and self.base_prefix != self.prefix)):
|
|
205
|
-
return self.original_executable
|
|
207
|
+
return self._resolve_executable_symlink(self.original_executable)
|
|
206
208
|
|
|
207
209
|
# if this is NOT a virtual environment, can't determine easily, bail out
|
|
208
210
|
if self.real_prefix is not None:
|
|
@@ -218,11 +220,34 @@ class PythonInfo: # noqa: PLR0904
|
|
|
218
220
|
|
|
219
221
|
# We're not in a venv and base_executable exists; use it directly
|
|
220
222
|
if os.path.exists(base_executable): # pragma: >=3.11 cover
|
|
221
|
-
return base_executable
|
|
223
|
+
return self._resolve_executable_symlink(base_executable)
|
|
222
224
|
|
|
223
225
|
# Try fallback for POSIX virtual environments
|
|
224
226
|
return self._try_posix_fallback_executable(base_executable) # pragma: >=3.11 cover
|
|
225
227
|
|
|
228
|
+
def _resolve_executable_symlink(self, path: str) -> str:
|
|
229
|
+
"""
|
|
230
|
+
Resolve symlinks of the executable itself, but never of its parent directories.
|
|
231
|
+
|
|
232
|
+
Mirrors CPython's ``getpath.realpath`` (and ``venv`` in python/cpython#115237): an executable-only symlink
|
|
233
|
+
resolves to the real interpreter so its home can be located, while a fully symlinked interpreter tree is
|
|
234
|
+
kept as-is.
|
|
235
|
+
"""
|
|
236
|
+
result = os.path.abspath(path)
|
|
237
|
+
if self.os != "posix": # CPython only does this where HAVE_READLINK
|
|
238
|
+
return result
|
|
239
|
+
real_path = os.path.realpath(result)
|
|
240
|
+
if not os.path.exists(real_path): # symlink loop or broken symlink
|
|
241
|
+
return result
|
|
242
|
+
while os.path.islink(result):
|
|
243
|
+
link = os.readlink(result)
|
|
244
|
+
candidate = link if os.path.isabs(link) else os.path.normpath(os.path.join(os.path.dirname(result), link))
|
|
245
|
+
# normpath through a symlinked directory may point at a different file - stop resolving there
|
|
246
|
+
if not (os.path.exists(candidate) and os.path.samefile(real_path, candidate)):
|
|
247
|
+
return result
|
|
248
|
+
result = candidate
|
|
249
|
+
return result
|
|
250
|
+
|
|
226
251
|
def _try_posix_fallback_executable(self, base_executable: str) -> str | None:
|
|
227
252
|
"""Find a versioned Python binary as fallback for POSIX virtual environments."""
|
|
228
253
|
major, minor = self.version_info.major, self.version_info.minor
|
|
@@ -404,10 +429,11 @@ class PythonInfo: # noqa: PLR0904
|
|
|
404
429
|
@property
|
|
405
430
|
def spec(self) -> str:
|
|
406
431
|
"""A specification string identifying this interpreter (e.g. ``CPython3.13.2-64-arm64``)."""
|
|
407
|
-
return "{}{}{}-{}-{}".format(
|
|
432
|
+
return "{}{}{}{}-{}-{}".format(
|
|
408
433
|
self.implementation,
|
|
409
434
|
".".join(str(i) for i in self.version_info),
|
|
410
435
|
"t" if self.free_threaded else "",
|
|
436
|
+
"d" if self.debug_build else "",
|
|
411
437
|
self.architecture,
|
|
412
438
|
self.machine,
|
|
413
439
|
)
|
|
@@ -731,17 +757,14 @@ class PythonInfo: # noqa: PLR0904
|
|
|
731
757
|
|
|
732
758
|
def _find_possible_exe_names(self) -> list[str]:
|
|
733
759
|
name_candidate = OrderedDict()
|
|
760
|
+
mods = ["", "t"] if self.free_threaded else [""]
|
|
761
|
+
debug_suffixes = ["_d", ""] if self.debug_build else [""]
|
|
762
|
+
archs = [f"-{self.architecture}", ""]
|
|
734
763
|
for name in self._possible_base():
|
|
735
764
|
for at in (3, 2, 1, 0):
|
|
736
765
|
version = ".".join(str(i) for i in self.version_info[:at])
|
|
737
|
-
mods
|
|
738
|
-
|
|
739
|
-
mods.append("t")
|
|
740
|
-
for mod in mods:
|
|
741
|
-
for arch in [f"-{self.architecture}", ""]:
|
|
742
|
-
for ext in EXTENSIONS:
|
|
743
|
-
candidate = f"{name}{version}{mod}{arch}{ext}"
|
|
744
|
-
name_candidate[candidate] = None
|
|
766
|
+
for mod, debug, arch, ext in product(mods, debug_suffixes, archs, EXTENSIONS):
|
|
767
|
+
name_candidate[f"{name}{version}{mod}{debug}{arch}{ext}"] = None
|
|
745
768
|
return list(name_candidate.keys())
|
|
746
769
|
|
|
747
770
|
def _possible_base(self) -> Generator[str, None, None]:
|
|
@@ -40,6 +40,7 @@ def test_current_as_json() -> None:
|
|
|
40
40
|
"serial": serial,
|
|
41
41
|
}
|
|
42
42
|
assert parsed["free_threaded"] is free_threaded
|
|
43
|
+
assert parsed["debug_build"] is bool(sysconfig.get_config_var("Py_DEBUG"))
|
|
43
44
|
|
|
44
45
|
|
|
45
46
|
def test_bad_exe_py_info_raise(tmp_path: Path, session_cache: DiskCache) -> None:
|
|
@@ -418,6 +419,28 @@ def test_py_info_satisfies_machine_cross_os_normalization(platform: str, spec_ma
|
|
|
418
419
|
assert info.satisfies(spec, impl_must_match=True) is True
|
|
419
420
|
|
|
420
421
|
|
|
422
|
+
@pytest.mark.parametrize("debug", [True, False], ids=["debug", "release"])
|
|
423
|
+
def test_py_info_debug_build_in_spec(*, debug: bool) -> None:
|
|
424
|
+
info = copy.deepcopy(CURRENT)
|
|
425
|
+
info.debug_build = debug
|
|
426
|
+
assert ("d-" in info.spec) is debug
|
|
427
|
+
|
|
428
|
+
|
|
429
|
+
@pytest.mark.parametrize("debug", [True, False], ids=["debug", "release"])
|
|
430
|
+
def test_py_info_debug_build_exe_names(*, debug: bool) -> None:
|
|
431
|
+
info = copy.deepcopy(CURRENT)
|
|
432
|
+
info.debug_build = debug
|
|
433
|
+
names = info._find_possible_exe_names()
|
|
434
|
+
assert any("_d" in n for n in names) is debug
|
|
435
|
+
|
|
436
|
+
|
|
437
|
+
def test_py_info_debug_build_json_round_trip() -> None:
|
|
438
|
+
info = copy.deepcopy(CURRENT)
|
|
439
|
+
info.debug_build = True
|
|
440
|
+
restored = PythonInfo.from_json(info.to_json())
|
|
441
|
+
assert restored.debug_build is True
|
|
442
|
+
|
|
443
|
+
|
|
421
444
|
def test_py_info_to_dict_includes_sysconfig_platform() -> None:
|
|
422
445
|
data = CURRENT.to_dict()
|
|
423
446
|
assert "sysconfig_platform" in data
|
|
@@ -431,16 +431,15 @@ def test_predicate_none_is_noop(session_cache: DiskCache) -> None:
|
|
|
431
431
|
def test_predicate_with_fallback_specs(session_cache: DiskCache) -> None:
|
|
432
432
|
current = PythonInfo.current_system(session_cache)
|
|
433
433
|
major, minor = current.version_info.major, current.version_info.minor
|
|
434
|
-
|
|
434
|
+
proposed: list[str] = []
|
|
435
435
|
|
|
436
436
|
def reject_first(info: PythonInfo) -> bool:
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
accepted_exe = str(info.executable)
|
|
440
|
-
return False
|
|
441
|
-
return True
|
|
437
|
+
proposed.append(str(info.executable))
|
|
438
|
+
return len(proposed) > 1
|
|
442
439
|
|
|
443
440
|
result = get_interpreter([f"{major}.{minor}", sys.executable], [], session_cache, predicate=reject_first)
|
|
444
|
-
|
|
441
|
+
# rejecting the first proposal must not abort discovery; a later proposal (same or fallback spec) is accepted -
|
|
442
|
+
# on hosts where every alias resolves to one interpreter the fallback re-proposes it, so executables may match
|
|
445
443
|
assert result is not None
|
|
446
|
-
assert
|
|
444
|
+
assert len(proposed) > 1
|
|
445
|
+
assert str(result.executable) == proposed[-1]
|
|
@@ -19,6 +19,8 @@ except ImportError: # pragma: no cover
|
|
|
19
19
|
tk = None # type: ignore[assignment]
|
|
20
20
|
|
|
21
21
|
if TYPE_CHECKING:
|
|
22
|
+
from collections.abc import Callable
|
|
23
|
+
|
|
22
24
|
from pytest_mock import MockerFixture
|
|
23
25
|
|
|
24
26
|
CURRENT = PythonInfo.current_system()
|
|
@@ -71,7 +73,9 @@ def test_fast_get_system_executable_not_venv() -> None:
|
|
|
71
73
|
info = PythonInfo()
|
|
72
74
|
info.real_prefix = None
|
|
73
75
|
info.base_prefix = info.prefix
|
|
74
|
-
|
|
76
|
+
result = info._fast_get_system_executable()
|
|
77
|
+
assert result is not None
|
|
78
|
+
assert Path(result).samefile(info.original_executable)
|
|
75
79
|
|
|
76
80
|
|
|
77
81
|
def test_fast_get_system_executable_real_prefix() -> None:
|
|
@@ -96,6 +100,105 @@ def test_fast_get_system_executable_same_as_current(mocker: MockerFixture) -> No
|
|
|
96
100
|
assert info._fast_get_system_executable() is None
|
|
97
101
|
|
|
98
102
|
|
|
103
|
+
@pytest.fixture
|
|
104
|
+
def posix_info() -> PythonInfo:
|
|
105
|
+
info = PythonInfo()
|
|
106
|
+
info.os = "posix"
|
|
107
|
+
return info
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
def test_resolve_executable_symlink_not_posix() -> None:
|
|
111
|
+
info = PythonInfo()
|
|
112
|
+
info.os = "nt"
|
|
113
|
+
assert info._resolve_executable_symlink("/some/python") == str(Path("/some/python").resolve())
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
def _layout_regular_file(tmp_path: Path) -> tuple[Path, Path]:
|
|
117
|
+
exe = tmp_path / "python"
|
|
118
|
+
exe.touch()
|
|
119
|
+
return exe, exe
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
def _layout_broken_symlink(tmp_path: Path) -> tuple[Path, Path]:
|
|
123
|
+
link = tmp_path / "python"
|
|
124
|
+
link.symlink_to(tmp_path / "missing")
|
|
125
|
+
return link, link
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
def _layout_absolute_symlink(tmp_path: Path) -> tuple[Path, Path]:
|
|
129
|
+
exe = tmp_path / "install" / "bin" / "python3.12"
|
|
130
|
+
exe.parent.mkdir(parents=True)
|
|
131
|
+
exe.touch()
|
|
132
|
+
link = tmp_path / "symdir" / "python3"
|
|
133
|
+
link.parent.mkdir()
|
|
134
|
+
link.symlink_to(exe)
|
|
135
|
+
return link, exe
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
def _layout_relative_chain(tmp_path: Path) -> tuple[Path, Path]:
|
|
139
|
+
exe = tmp_path / "python3.12"
|
|
140
|
+
exe.touch()
|
|
141
|
+
(tmp_path / "python3").symlink_to("python3.12")
|
|
142
|
+
link = tmp_path / "python"
|
|
143
|
+
link.symlink_to("python3")
|
|
144
|
+
return link, exe
|
|
145
|
+
|
|
146
|
+
|
|
147
|
+
def _layout_tree_symlink(tmp_path: Path) -> tuple[Path, Path]:
|
|
148
|
+
real_bin = tmp_path / "install" / "bin"
|
|
149
|
+
real_bin.mkdir(parents=True)
|
|
150
|
+
(real_bin / "python3").touch()
|
|
151
|
+
tree_link = tmp_path / "tree"
|
|
152
|
+
tree_link.symlink_to(tmp_path / "install")
|
|
153
|
+
via_tree = tree_link / "bin" / "python3"
|
|
154
|
+
return via_tree, via_tree
|
|
155
|
+
|
|
156
|
+
|
|
157
|
+
def _layout_normpath_mismatch(tmp_path: Path) -> tuple[Path, Path]:
|
|
158
|
+
real_dir = tmp_path / "deep" / "real"
|
|
159
|
+
real_dir.mkdir(parents=True)
|
|
160
|
+
(tmp_path / "deep" / "exe").touch()
|
|
161
|
+
(real_dir / "python").symlink_to("../exe")
|
|
162
|
+
dir_link = tmp_path / "link"
|
|
163
|
+
dir_link.symlink_to(real_dir)
|
|
164
|
+
via_link = dir_link / "python"
|
|
165
|
+
return via_link, via_link
|
|
166
|
+
|
|
167
|
+
|
|
168
|
+
@pytest.mark.skipif(sys.platform == "win32", reason="POSIX only")
|
|
169
|
+
@pytest.mark.parametrize(
|
|
170
|
+
"layout",
|
|
171
|
+
[
|
|
172
|
+
pytest.param(_layout_regular_file, id="regular-file"),
|
|
173
|
+
pytest.param(_layout_broken_symlink, id="broken-symlink"),
|
|
174
|
+
pytest.param(_layout_absolute_symlink, id="absolute-symlink"),
|
|
175
|
+
pytest.param(_layout_relative_chain, id="relative-chain"),
|
|
176
|
+
pytest.param(_layout_tree_symlink, id="tree-preserved"),
|
|
177
|
+
pytest.param(_layout_normpath_mismatch, id="normpath-mismatch"),
|
|
178
|
+
],
|
|
179
|
+
)
|
|
180
|
+
def test_resolve_executable_symlink(
|
|
181
|
+
tmp_path: Path,
|
|
182
|
+
posix_info: PythonInfo,
|
|
183
|
+
layout: Callable[[Path], tuple[Path, Path]],
|
|
184
|
+
) -> None:
|
|
185
|
+
path, expected = layout(tmp_path)
|
|
186
|
+
assert posix_info._resolve_executable_symlink(str(path)) == str(expected)
|
|
187
|
+
|
|
188
|
+
|
|
189
|
+
@pytest.mark.skipif(sys.platform == "win32", reason="POSIX only")
|
|
190
|
+
def test_from_exe_resolves_executable_only_symlink(tmp_path: Path, session_cache: DiskCache) -> None:
|
|
191
|
+
system_exe = CURRENT.system_executable
|
|
192
|
+
assert system_exe is not None
|
|
193
|
+
link = tmp_path / "python3"
|
|
194
|
+
link.symlink_to(system_exe)
|
|
195
|
+
info = PythonInfo.from_exe(str(link), session_cache, ignore_cache=True)
|
|
196
|
+
assert info is not None
|
|
197
|
+
assert info.system_executable is not None
|
|
198
|
+
assert Path(info.system_executable).samefile(system_exe)
|
|
199
|
+
assert not Path(info.system_executable).is_symlink()
|
|
200
|
+
|
|
201
|
+
|
|
99
202
|
def test_try_posix_fallback_not_posix() -> None:
|
|
100
203
|
info = PythonInfo()
|
|
101
204
|
info.os = "nt"
|
|
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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
@@ -199,11 +199,11 @@ html.skip_covered = false
|
|
|
199
199
|
|
|
200
200
|
[tool.towncrier]
|
|
201
201
|
name = "python-discovery"
|
|
202
|
-
filename = "docs/changelog.rst"
|
|
203
202
|
directory = "docs/changelog"
|
|
203
|
+
filename = "docs/changelog.rst"
|
|
204
|
+
template = "docs/changelog/template.jinja2"
|
|
204
205
|
title_format = false
|
|
205
206
|
issue_format = ":issue:`{issue}`"
|
|
206
|
-
template = "docs/changelog/template.jinja2"
|
|
207
207
|
type = [
|
|
208
208
|
{ directory = "breaking", name = "Backward incompatible changes", showcontent = true },
|
|
209
209
|
{ directory = "deprecation", name = "Deprecations (removal in next major release)", showcontent = true },
|
|
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
|
|
File without changes
|
{python_discovery-1.3.2 → python_discovery-1.4.1}/tests/py_info/test_py_info_exe_based_of.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
|
|
File without changes
|