python-discovery 1.1.1__tar.gz → 1.1.3__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.1.1 → python_discovery-1.1.3}/.pre-commit-config.yaml +2 -2
- {python_discovery-1.1.1 → python_discovery-1.1.3}/PKG-INFO +38 -3
- python_discovery-1.1.3/README.md +43 -0
- {python_discovery-1.1.1 → python_discovery-1.1.3}/src/python_discovery/_cached_py_info.py +1 -0
- {python_discovery-1.1.1 → python_discovery-1.1.3}/src/python_discovery/_py_info.py +17 -5
- {python_discovery-1.1.1 → python_discovery-1.1.3}/tests/py_info/test_py_info.py +1 -1
- {python_discovery-1.1.1 → python_discovery-1.1.3}/tests/test_cached_py_info.py +2 -1
- {python_discovery-1.1.1 → python_discovery-1.1.3}/tests/test_py_info_extra.py +22 -18
- python_discovery-1.1.1/README.md +0 -8
- {python_discovery-1.1.1 → python_discovery-1.1.3}/.github/FUNDING.yaml +0 -0
- {python_discovery-1.1.1 → python_discovery-1.1.3}/.github/SECURITY.md +0 -0
- {python_discovery-1.1.1 → python_discovery-1.1.3}/.github/dependabot.yaml +0 -0
- {python_discovery-1.1.1 → python_discovery-1.1.3}/.github/release.yaml +0 -0
- {python_discovery-1.1.1 → python_discovery-1.1.3}/.github/workflows/check.yaml +0 -0
- {python_discovery-1.1.1 → python_discovery-1.1.3}/.github/workflows/release.yaml +0 -0
- {python_discovery-1.1.1 → python_discovery-1.1.3}/.gitignore +0 -0
- {python_discovery-1.1.1 → python_discovery-1.1.3}/.readthedocs.yaml +0 -0
- {python_discovery-1.1.1 → python_discovery-1.1.3}/.readthedocs.yml +0 -0
- {python_discovery-1.1.1 → python_discovery-1.1.3}/CODE_OF_CONDUCT.md +0 -0
- {python_discovery-1.1.1 → python_discovery-1.1.3}/LICENSE +0 -0
- {python_discovery-1.1.1 → python_discovery-1.1.3}/docs/_static/custom.css +0 -0
- {python_discovery-1.1.1 → python_discovery-1.1.3}/docs/_static/logo.svg +0 -0
- {python_discovery-1.1.1 → python_discovery-1.1.3}/docs/conf.py +0 -0
- {python_discovery-1.1.1 → python_discovery-1.1.3}/docs/explanation.rst +0 -0
- {python_discovery-1.1.1 → python_discovery-1.1.3}/docs/how-to/standalone-usage.rst +0 -0
- {python_discovery-1.1.1 → python_discovery-1.1.3}/docs/index.rst +0 -0
- {python_discovery-1.1.1 → python_discovery-1.1.3}/docs/reference/api.rst +0 -0
- {python_discovery-1.1.1 → python_discovery-1.1.3}/docs/tutorial/getting-started.rst +0 -0
- {python_discovery-1.1.1 → python_discovery-1.1.3}/pyproject.toml +0 -0
- {python_discovery-1.1.1 → python_discovery-1.1.3}/src/python_discovery/__init__.py +0 -0
- {python_discovery-1.1.1 → python_discovery-1.1.3}/src/python_discovery/_cache.py +0 -0
- {python_discovery-1.1.1 → python_discovery-1.1.3}/src/python_discovery/_compat.py +0 -0
- {python_discovery-1.1.1 → python_discovery-1.1.3}/src/python_discovery/_discovery.py +0 -0
- {python_discovery-1.1.1 → python_discovery-1.1.3}/src/python_discovery/_py_spec.py +0 -0
- {python_discovery-1.1.1 → python_discovery-1.1.3}/src/python_discovery/_specifier.py +0 -0
- {python_discovery-1.1.1 → python_discovery-1.1.3}/src/python_discovery/_windows/__init__.py +0 -0
- {python_discovery-1.1.1 → python_discovery-1.1.3}/src/python_discovery/_windows/_pep514.py +0 -0
- {python_discovery-1.1.1 → python_discovery-1.1.3}/src/python_discovery/_windows/_propose.py +0 -0
- {python_discovery-1.1.1 → python_discovery-1.1.3}/src/python_discovery/py.typed +0 -0
- {python_discovery-1.1.1 → python_discovery-1.1.3}/tests/conftest.py +0 -0
- {python_discovery-1.1.1 → python_discovery-1.1.3}/tests/py_info/test_py_info_exe_based_of.py +0 -0
- {python_discovery-1.1.1 → python_discovery-1.1.3}/tests/test_cache.py +0 -0
- {python_discovery-1.1.1 → python_discovery-1.1.3}/tests/test_discovery.py +0 -0
- {python_discovery-1.1.1 → python_discovery-1.1.3}/tests/test_discovery_extra.py +0 -0
- {python_discovery-1.1.1 → python_discovery-1.1.3}/tests/test_py_spec.py +0 -0
- {python_discovery-1.1.1 → python_discovery-1.1.3}/tests/test_py_spec_extra.py +0 -0
- {python_discovery-1.1.1 → python_discovery-1.1.3}/tests/test_specifier.py +0 -0
- {python_discovery-1.1.1 → python_discovery-1.1.3}/tests/windows/conftest.py +0 -0
- {python_discovery-1.1.1 → python_discovery-1.1.3}/tests/windows/test_windows.py +0 -0
- {python_discovery-1.1.1 → python_discovery-1.1.3}/tests/windows/test_windows_pep514.py +0 -0
- {python_discovery-1.1.1 → python_discovery-1.1.3}/tests/windows/winreg_mock_values.py +0 -0
- {python_discovery-1.1.1 → python_discovery-1.1.3}/tox.toml +0 -0
|
@@ -10,7 +10,7 @@ repos:
|
|
|
10
10
|
- id: check-github-workflows
|
|
11
11
|
args: ["--verbose"]
|
|
12
12
|
- repo: https://github.com/codespell-project/codespell
|
|
13
|
-
rev: v2.4.
|
|
13
|
+
rev: v2.4.2
|
|
14
14
|
hooks:
|
|
15
15
|
- id: codespell
|
|
16
16
|
additional_dependencies: ["tomli>=2.4"]
|
|
@@ -19,7 +19,7 @@ repos:
|
|
|
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.5"
|
|
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.1.
|
|
3
|
+
Version: 1.1.3
|
|
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
|
|
@@ -64,8 +64,43 @@ Description-Content-Type: text/markdown
|
|
|
64
64
|
# [`python-discovery`](https://python-discovery.readthedocs.io/en/latest/)
|
|
65
65
|
|
|
66
66
|
[](https://pypi.org/project/python-discovery/)
|
|
67
|
-
[](https://pypi.org/project/python-discovery/)
|
|
67
|
+
[](https://pypi.org/project/python-discovery/)
|
|
69
68
|
[](https://pepy.tech/project/python-discovery)
|
|
70
69
|
[](https://github.com/tox-dev/python-discovery/actions/workflows/check.yaml)
|
|
71
70
|
[](https://python-discovery.readthedocs.io/en/latest/?badge=latest)
|
|
71
|
+
|
|
72
|
+
## What is python-discovery?
|
|
73
|
+
|
|
74
|
+
`python-discovery` is a library for discovering Python interpreters installed on your machine. You may have multiple
|
|
75
|
+
Python versions from system packages, [pyenv](https://github.com/pyenv/pyenv), [mise](https://mise.jdx.dev/),
|
|
76
|
+
[asdf](https://asdf-vm.com/), [uv](https://docs.astral.sh/uv/), or the Windows registry (PEP 514). This library finds
|
|
77
|
+
the right one for you.
|
|
78
|
+
|
|
79
|
+
Give it a requirement like `python3.12` or `>=3.11,<3.13`, and it searches all known locations, verifies each candidate,
|
|
80
|
+
and returns detailed metadata about the match. Results are cached to disk so repeated lookups are fast.
|
|
81
|
+
|
|
82
|
+
## Usage
|
|
83
|
+
|
|
84
|
+
```python
|
|
85
|
+
from pathlib import Path
|
|
86
|
+
|
|
87
|
+
from python_discovery import DiskCache, get_interpreter
|
|
88
|
+
|
|
89
|
+
cache = DiskCache(root=Path("~/.cache/python-discovery").expanduser())
|
|
90
|
+
result = get_interpreter("python3.12", cache=cache)
|
|
91
|
+
if result is not None:
|
|
92
|
+
print(result.executable) # /usr/bin/python3.12
|
|
93
|
+
print(result.implementation) # CPython
|
|
94
|
+
print(result.version_info[:3]) # (3, 12, 1)
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
The `get_interpreter()` function accepts various specification formats:
|
|
98
|
+
|
|
99
|
+
- Absolute path: `/usr/bin/python3.12`
|
|
100
|
+
- Version: `3.12`
|
|
101
|
+
- Implementation prefix: `cpython3.12`
|
|
102
|
+
- PEP 440 specifier: `>=3.10`, `>=3.11,<3.13`
|
|
103
|
+
|
|
104
|
+
## Documentation
|
|
105
|
+
|
|
106
|
+
Full documentation is available at [python-discovery.readthedocs.io](https://python-discovery.readthedocs.io/en/latest/)
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
# [`python-discovery`](https://python-discovery.readthedocs.io/en/latest/)
|
|
2
|
+
|
|
3
|
+
[](https://pypi.org/project/python-discovery/)
|
|
4
|
+
[](https://pypi.org/project/python-discovery/)
|
|
5
|
+
[](https://pepy.tech/project/python-discovery)
|
|
6
|
+
[](https://github.com/tox-dev/python-discovery/actions/workflows/check.yaml)
|
|
7
|
+
[](https://python-discovery.readthedocs.io/en/latest/?badge=latest)
|
|
8
|
+
|
|
9
|
+
## What is python-discovery?
|
|
10
|
+
|
|
11
|
+
`python-discovery` is a library for discovering Python interpreters installed on your machine. You may have multiple
|
|
12
|
+
Python versions from system packages, [pyenv](https://github.com/pyenv/pyenv), [mise](https://mise.jdx.dev/),
|
|
13
|
+
[asdf](https://asdf-vm.com/), [uv](https://docs.astral.sh/uv/), or the Windows registry (PEP 514). This library finds
|
|
14
|
+
the right one for you.
|
|
15
|
+
|
|
16
|
+
Give it a requirement like `python3.12` or `>=3.11,<3.13`, and it searches all known locations, verifies each candidate,
|
|
17
|
+
and returns detailed metadata about the match. Results are cached to disk so repeated lookups are fast.
|
|
18
|
+
|
|
19
|
+
## Usage
|
|
20
|
+
|
|
21
|
+
```python
|
|
22
|
+
from pathlib import Path
|
|
23
|
+
|
|
24
|
+
from python_discovery import DiskCache, get_interpreter
|
|
25
|
+
|
|
26
|
+
cache = DiskCache(root=Path("~/.cache/python-discovery").expanduser())
|
|
27
|
+
result = get_interpreter("python3.12", cache=cache)
|
|
28
|
+
if result is not None:
|
|
29
|
+
print(result.executable) # /usr/bin/python3.12
|
|
30
|
+
print(result.implementation) # CPython
|
|
31
|
+
print(result.version_info[:3]) # (3, 12, 1)
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
The `get_interpreter()` function accepts various specification formats:
|
|
35
|
+
|
|
36
|
+
- Absolute path: `/usr/bin/python3.12`
|
|
37
|
+
- Version: `3.12`
|
|
38
|
+
- Implementation prefix: `cpython3.12`
|
|
39
|
+
- PEP 440 specifier: `>=3.10`, `>=3.11,<3.13`
|
|
40
|
+
|
|
41
|
+
## Documentation
|
|
42
|
+
|
|
43
|
+
Full documentation is available at [python-discovery.readthedocs.io](https://python-discovery.readthedocs.io/en/latest/)
|
|
@@ -210,6 +210,7 @@ def _run_subprocess(
|
|
|
210
210
|
code = process.returncode
|
|
211
211
|
except TimeoutExpired:
|
|
212
212
|
process.kill()
|
|
213
|
+
process.communicate()
|
|
213
214
|
out, err, code = "", "timed out", -1
|
|
214
215
|
except OSError as os_error:
|
|
215
216
|
out, err, code = "", os_error.strerror, os_error.errno
|
|
@@ -469,12 +469,24 @@ class PythonInfo: # noqa: PLR0904
|
|
|
469
469
|
if spec.version_specifier is None: # pragma: no cover
|
|
470
470
|
return True
|
|
471
471
|
version_info = self.version_info
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
472
|
+
for specifier in spec.version_specifier:
|
|
473
|
+
assert specifier.version is not None # noqa: S101
|
|
474
|
+
numeric_version = specifier.version_str
|
|
475
|
+
for prefix in ("rc", "b", "a"):
|
|
476
|
+
if prefix in numeric_version:
|
|
477
|
+
numeric_version = numeric_version.split(prefix)[0]
|
|
478
|
+
break
|
|
479
|
+
precision = numeric_version.count(".") + 1
|
|
480
|
+
release = ".".join(str(c) for c in [version_info.major, version_info.minor, version_info.micro][:precision])
|
|
481
|
+
if (
|
|
482
|
+
version_info.releaselevel != "final"
|
|
483
|
+
and (precision == 3 or specifier.version.pre_type is not None) # noqa: PLR2004
|
|
484
|
+
and (suffix := {"alpha": "a", "beta": "b", "candidate": "rc"}.get(version_info.releaselevel))
|
|
485
|
+
):
|
|
476
486
|
release = f"{release}{suffix}{version_info.serial}"
|
|
477
|
-
|
|
487
|
+
if not specifier.contains(release):
|
|
488
|
+
return False
|
|
489
|
+
return True
|
|
478
490
|
|
|
479
491
|
_current_system = None
|
|
480
492
|
_current = None
|
|
@@ -338,7 +338,7 @@ def test_py_info_machine_property() -> None:
|
|
|
338
338
|
assert machine is not None
|
|
339
339
|
assert isinstance(machine, str)
|
|
340
340
|
assert len(machine) > 0
|
|
341
|
-
known_isas = {"arm64", "i686", "ppc64", "ppc64le", "riscv64", "s390x", "x86", "x86_64"}
|
|
341
|
+
known_isas = {"arm64", "i686", "loongarch64", "ppc64", "ppc64le", "riscv64", "s390x", "x86", "x86_64"}
|
|
342
342
|
assert machine in known_isas, f"unexpected machine value: {machine}"
|
|
343
343
|
|
|
344
344
|
|
|
@@ -113,13 +113,14 @@ def test_run_subprocess_with_cookies(mocker: MockerFixture) -> None:
|
|
|
113
113
|
|
|
114
114
|
def test_run_subprocess_timeout(mocker: MockerFixture) -> None:
|
|
115
115
|
mock_process = MagicMock()
|
|
116
|
-
mock_process.communicate.side_effect = TimeoutExpired(cmd="python", timeout=30)
|
|
116
|
+
mock_process.communicate.side_effect = [TimeoutExpired(cmd="python", timeout=30), ("", "")]
|
|
117
117
|
mocker.patch("python_discovery._cached_py_info.Popen", return_value=mock_process)
|
|
118
118
|
failure, result = _run_subprocess(PythonInfo, sys.executable, dict(os.environ))
|
|
119
119
|
assert failure is not None
|
|
120
120
|
assert "timed out" in str(failure)
|
|
121
121
|
assert result is None
|
|
122
122
|
mock_process.kill.assert_called_once()
|
|
123
|
+
assert mock_process.communicate.call_count == 2
|
|
123
124
|
|
|
124
125
|
|
|
125
126
|
def test_run_subprocess_nonzero_exit(mocker: MockerFixture) -> None:
|
|
@@ -276,25 +276,29 @@ def test_satisfies_version_specifier_fails() -> None:
|
|
|
276
276
|
assert CURRENT.satisfies(spec, impl_must_match=False) is False
|
|
277
277
|
|
|
278
278
|
|
|
279
|
-
|
|
279
|
+
@pytest.mark.parametrize(
|
|
280
|
+
("version_info", "spec_str", "expected"),
|
|
281
|
+
[
|
|
282
|
+
pytest.param(VersionInfo(3, 14, 0, "alpha", 1), ">=3.14.0a1", True, id="alpha_match_exact"),
|
|
283
|
+
pytest.param(VersionInfo(3, 14, 0, "beta", 1), ">=3.14.0b1", True, id="beta_match_exact"),
|
|
284
|
+
pytest.param(VersionInfo(3, 14, 0, "candidate", 1), ">=3.14.0rc1", True, id="rc_match_exact"),
|
|
285
|
+
pytest.param(VersionInfo(3, 15, 0, "alpha", 6), ">=3.15", True, id="prerelease_match_major_minor"),
|
|
286
|
+
pytest.param(VersionInfo(3, 15, 0, "alpha", 6), ">=3.15.0", False, id="prerelease_not_match_full_precision"),
|
|
287
|
+
pytest.param(VersionInfo(3, 15, 0, "alpha", 5), "<3.15.0a6", True, id="earlier_prerelease_less_than"),
|
|
288
|
+
pytest.param(VersionInfo(3, 15, 0, "alpha", 6), "<3.15.0a6", False, id="prerelease_not_less_than_itself"),
|
|
289
|
+
pytest.param(VersionInfo(3, 15, 0, "alpha", 6), ">=3.15.0a6", True, id="prerelease_match_itself"),
|
|
290
|
+
pytest.param(VersionInfo(3, 15, 0, "alpha", 6), ">=3.15.0a7", False, id="prerelease_not_match_later"),
|
|
291
|
+
pytest.param(VersionInfo(3, 15, 0, "final", 0), ">=3.15.0a6", True, id="final_greater_than_prerelease"),
|
|
292
|
+
pytest.param(VersionInfo(3, 15, 0, "final", 0), "<3.15.0a6", False, id="final_not_less_than_prerelease"),
|
|
293
|
+
pytest.param(VersionInfo(3, 15, 0, "final", 0), ">=3.15", True, id="final_match_major_minor"),
|
|
294
|
+
pytest.param(VersionInfo(3, 15, 1, "alpha", 1), ">=3.15.0", True, id="later_micro_prerelease_match"),
|
|
295
|
+
],
|
|
296
|
+
)
|
|
297
|
+
def test_satisfies_version_specifier_prerelease(version_info: VersionInfo, spec_str: str, expected: bool) -> None:
|
|
280
298
|
info = copy.deepcopy(CURRENT)
|
|
281
|
-
info.version_info =
|
|
282
|
-
spec = PythonSpec.from_string_spec(
|
|
283
|
-
assert info.satisfies(spec, impl_must_match=False) is
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
def test_satisfies_prerelease_beta() -> None:
|
|
287
|
-
info = copy.deepcopy(CURRENT)
|
|
288
|
-
info.version_info = VersionInfo(3, 14, 0, "beta", 1)
|
|
289
|
-
spec = PythonSpec.from_string_spec(">=3.14.0b1")
|
|
290
|
-
assert info.satisfies(spec, impl_must_match=False) is True
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
def test_satisfies_prerelease_candidate() -> None:
|
|
294
|
-
info = copy.deepcopy(CURRENT)
|
|
295
|
-
info.version_info = VersionInfo(3, 14, 0, "candidate", 1)
|
|
296
|
-
spec = PythonSpec.from_string_spec(">=3.14.0rc1")
|
|
297
|
-
assert info.satisfies(spec, impl_must_match=False) is True
|
|
299
|
+
info.version_info = version_info
|
|
300
|
+
spec = PythonSpec.from_string_spec(spec_str)
|
|
301
|
+
assert info.satisfies(spec, impl_must_match=False) is expected
|
|
298
302
|
|
|
299
303
|
|
|
300
304
|
def test_satisfies_path_not_abs_basename_match() -> None:
|
python_discovery-1.1.1/README.md
DELETED
|
@@ -1,8 +0,0 @@
|
|
|
1
|
-
# [`python-discovery`](https://python-discovery.readthedocs.io/en/latest/)
|
|
2
|
-
|
|
3
|
-
[](https://pypi.org/project/python-discovery/)
|
|
4
|
-
[](https://pypi.org/project/python-discovery/)
|
|
6
|
-
[](https://pepy.tech/project/python-discovery)
|
|
7
|
-
[](https://github.com/tox-dev/python-discovery/actions/workflows/check.yaml)
|
|
8
|
-
[](https://python-discovery.readthedocs.io/en/latest/?badge=latest)
|
|
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
|
|
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.1.1 → python_discovery-1.1.3}/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
|