python-discovery 1.2.2__tar.gz → 1.3.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.2.2 → python_discovery-1.3.1}/.github/workflows/check.yaml +1 -1
- python_discovery-1.3.1/.github/workflows/prepare-release.yaml +100 -0
- {python_discovery-1.2.2 → python_discovery-1.3.1}/.github/workflows/release.yaml +3 -3
- {python_discovery-1.2.2 → python_discovery-1.3.1}/.pre-commit-config.yaml +5 -5
- {python_discovery-1.2.2 → python_discovery-1.3.1}/PKG-INFO +5 -2
- {python_discovery-1.2.2 → python_discovery-1.3.1}/README.md +2 -1
- python_discovery-1.3.1/docs/changelog/template.jinja2 +38 -0
- python_discovery-1.3.1/docs/changelog.rst +125 -0
- {python_discovery-1.2.2 → python_discovery-1.3.1}/docs/conf.py +7 -1
- {python_discovery-1.2.2 → python_discovery-1.3.1}/docs/explanation.rst +107 -4
- {python_discovery-1.2.2 → python_discovery-1.3.1}/docs/how-to/standalone-usage.rst +54 -0
- {python_discovery-1.2.2 → python_discovery-1.3.1}/docs/index.rst +6 -0
- {python_discovery-1.2.2 → python_discovery-1.3.1}/docs/tutorial/getting-started.rst +45 -1
- {python_discovery-1.2.2 → python_discovery-1.3.1}/pyproject.toml +35 -0
- {python_discovery-1.2.2 → python_discovery-1.3.1}/src/python_discovery/__init__.py +4 -2
- {python_discovery-1.2.2 → python_discovery-1.3.1}/src/python_discovery/_discovery.py +99 -11
- {python_discovery-1.2.2 → python_discovery-1.3.1}/src/python_discovery/_py_info.py +2 -0
- {python_discovery-1.2.2 → python_discovery-1.3.1}/src/python_discovery/_py_spec.py +20 -4
- python_discovery-1.3.1/tasks/release.py +167 -0
- python_discovery-1.3.1/tests/test_iter_interpreters.py +218 -0
- {python_discovery-1.2.2 → python_discovery-1.3.1}/tests/test_py_info_extra.py +6 -0
- {python_discovery-1.2.2 → python_discovery-1.3.1}/tests/test_py_spec.py +13 -0
- {python_discovery-1.2.2 → python_discovery-1.3.1}/tox.toml +9 -2
- python_discovery-1.2.2/docs/changelog/59.bugfix.rst +0 -1
- {python_discovery-1.2.2 → python_discovery-1.3.1}/.github/FUNDING.yaml +0 -0
- {python_discovery-1.2.2 → python_discovery-1.3.1}/.github/SECURITY.md +0 -0
- {python_discovery-1.2.2 → python_discovery-1.3.1}/.github/dependabot.yaml +0 -0
- {python_discovery-1.2.2 → python_discovery-1.3.1}/.github/release.yaml +0 -0
- {python_discovery-1.2.2 → python_discovery-1.3.1}/.gitignore +0 -0
- {python_discovery-1.2.2 → python_discovery-1.3.1}/.readthedocs.yaml +0 -0
- {python_discovery-1.2.2 → python_discovery-1.3.1}/.readthedocs.yml +0 -0
- {python_discovery-1.2.2 → python_discovery-1.3.1}/CODE_OF_CONDUCT.md +0 -0
- {python_discovery-1.2.2 → python_discovery-1.3.1}/LICENSE +0 -0
- {python_discovery-1.2.2 → python_discovery-1.3.1}/docs/_static/custom.css +0 -0
- {python_discovery-1.2.2 → python_discovery-1.3.1}/docs/_static/logo.svg +0 -0
- {python_discovery-1.2.2 → python_discovery-1.3.1}/docs/reference/api.rst +0 -0
- {python_discovery-1.2.2 → python_discovery-1.3.1}/docs/reference/environment-variables.rst +0 -0
- {python_discovery-1.2.2 → python_discovery-1.3.1}/src/python_discovery/_cache.py +0 -0
- {python_discovery-1.2.2 → python_discovery-1.3.1}/src/python_discovery/_cached_py_info.py +0 -0
- {python_discovery-1.2.2 → python_discovery-1.3.1}/src/python_discovery/_compat.py +0 -0
- {python_discovery-1.2.2 → python_discovery-1.3.1}/src/python_discovery/_specifier.py +0 -0
- {python_discovery-1.2.2 → python_discovery-1.3.1}/src/python_discovery/_windows/__init__.py +0 -0
- {python_discovery-1.2.2 → python_discovery-1.3.1}/src/python_discovery/_windows/_pep514.py +0 -0
- {python_discovery-1.2.2 → python_discovery-1.3.1}/src/python_discovery/_windows/_propose.py +0 -0
- {python_discovery-1.2.2 → python_discovery-1.3.1}/src/python_discovery/py.typed +0 -0
- {python_discovery-1.2.2 → python_discovery-1.3.1}/tests/conftest.py +0 -0
- {python_discovery-1.2.2 → python_discovery-1.3.1}/tests/py_info/test_py_info.py +0 -0
- {python_discovery-1.2.2 → python_discovery-1.3.1}/tests/py_info/test_py_info_exe_based_of.py +0 -0
- {python_discovery-1.2.2 → python_discovery-1.3.1}/tests/test_cache.py +0 -0
- {python_discovery-1.2.2 → python_discovery-1.3.1}/tests/test_cached_py_info.py +0 -0
- {python_discovery-1.2.2 → python_discovery-1.3.1}/tests/test_discovery.py +0 -0
- {python_discovery-1.2.2 → python_discovery-1.3.1}/tests/test_discovery_extra.py +0 -0
- {python_discovery-1.2.2 → python_discovery-1.3.1}/tests/test_py_spec_extra.py +0 -0
- {python_discovery-1.2.2 → python_discovery-1.3.1}/tests/test_specifier.py +0 -0
- {python_discovery-1.2.2 → python_discovery-1.3.1}/tests/windows/conftest.py +0 -0
- {python_discovery-1.2.2 → python_discovery-1.3.1}/tests/windows/test_windows.py +0 -0
- {python_discovery-1.2.2 → python_discovery-1.3.1}/tests/windows/test_windows_pep514.py +0 -0
- {python_discovery-1.2.2 → python_discovery-1.3.1}/tests/windows/winreg_mock_values.py +0 -0
|
@@ -39,7 +39,7 @@ jobs:
|
|
|
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@08807647e7069bb48b6ef5acd8ec9567f424441b # v8.1.0
|
|
43
43
|
with:
|
|
44
44
|
enable-cache: false
|
|
45
45
|
cache-dependency-glob: "pyproject.toml"
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
name: Prepare Release
|
|
2
|
+
on:
|
|
3
|
+
workflow_dispatch:
|
|
4
|
+
inputs:
|
|
5
|
+
bump:
|
|
6
|
+
description: "Version bump type"
|
|
7
|
+
required: true
|
|
8
|
+
type: choice
|
|
9
|
+
options:
|
|
10
|
+
- auto
|
|
11
|
+
- major
|
|
12
|
+
- minor
|
|
13
|
+
- patch
|
|
14
|
+
default: auto
|
|
15
|
+
|
|
16
|
+
permissions:
|
|
17
|
+
contents: read
|
|
18
|
+
|
|
19
|
+
env:
|
|
20
|
+
FORCE_COLOR: 1
|
|
21
|
+
|
|
22
|
+
jobs:
|
|
23
|
+
prepare-release:
|
|
24
|
+
runs-on: ubuntu-24.04
|
|
25
|
+
environment: release-auth
|
|
26
|
+
permissions:
|
|
27
|
+
contents: write
|
|
28
|
+
steps:
|
|
29
|
+
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
|
|
30
|
+
with:
|
|
31
|
+
fetch-depth: 0
|
|
32
|
+
token: ${{ secrets.RELEASE_PAT }}
|
|
33
|
+
persist-credentials: true
|
|
34
|
+
|
|
35
|
+
- name: Install the latest version of uv
|
|
36
|
+
uses: astral-sh/setup-uv@08807647e7069bb48b6ef5acd8ec9567f424441b # v8.1.0
|
|
37
|
+
with:
|
|
38
|
+
enable-cache: true
|
|
39
|
+
cache-dependency-glob: "pyproject.toml"
|
|
40
|
+
github-token: ${{ secrets.GITHUB_TOKEN }}
|
|
41
|
+
|
|
42
|
+
- name: Configure git
|
|
43
|
+
run: |
|
|
44
|
+
git config user.name "${GITHUB_ACTOR}"
|
|
45
|
+
git config user.email "${GITHUB_ACTOR}@users.noreply.github.com"
|
|
46
|
+
|
|
47
|
+
- name: Calculate next version
|
|
48
|
+
id: version
|
|
49
|
+
env:
|
|
50
|
+
BUMP: ${{ inputs.bump }}
|
|
51
|
+
run: |
|
|
52
|
+
current=$(git describe --tags --abbrev=0 2>/dev/null || echo "0.0.0")
|
|
53
|
+
echo "Current version: $current"
|
|
54
|
+
|
|
55
|
+
IFS='.' read -r major minor patch <<< "$current"
|
|
56
|
+
|
|
57
|
+
bump_type="$BUMP"
|
|
58
|
+
if [ "$bump_type" = "auto" ]; then
|
|
59
|
+
feature_count=$(find docs/changelog -name "*.feature.rst" 2>/dev/null | wc -l)
|
|
60
|
+
breaking_count=$(find docs/changelog -name "*.breaking.rst" 2>/dev/null | wc -l)
|
|
61
|
+
if [ "$breaking_count" -gt 0 ]; then
|
|
62
|
+
bump_type="major"
|
|
63
|
+
echo "Auto-detected: major bump (found $breaking_count breaking changelog(s))"
|
|
64
|
+
elif [ "$feature_count" -gt 0 ]; then
|
|
65
|
+
bump_type="minor"
|
|
66
|
+
echo "Auto-detected: minor bump (found $feature_count feature changelog(s))"
|
|
67
|
+
else
|
|
68
|
+
bump_type="patch"
|
|
69
|
+
echo "Auto-detected: patch bump (no feature changelogs)"
|
|
70
|
+
fi
|
|
71
|
+
fi
|
|
72
|
+
|
|
73
|
+
case "$bump_type" in
|
|
74
|
+
major)
|
|
75
|
+
next="$((major + 1)).0.0"
|
|
76
|
+
;;
|
|
77
|
+
minor)
|
|
78
|
+
next="$major.$((minor + 1)).0"
|
|
79
|
+
;;
|
|
80
|
+
patch)
|
|
81
|
+
next="$major.$minor.$((patch + 1))"
|
|
82
|
+
;;
|
|
83
|
+
esac
|
|
84
|
+
|
|
85
|
+
echo "Next version: $next"
|
|
86
|
+
echo "version=$next" >> $GITHUB_OUTPUT
|
|
87
|
+
|
|
88
|
+
- name: Install tox
|
|
89
|
+
run: uv tool install --python-preference only-managed --python 3.14 tox@latest --with tox-uv
|
|
90
|
+
|
|
91
|
+
- name: Run release process
|
|
92
|
+
run: tox run -e release -- ${STEPS_VERSION_OUTPUTS_VERSION}
|
|
93
|
+
env:
|
|
94
|
+
GH_TOKEN: ${{ secrets.RELEASE_PAT }}
|
|
95
|
+
STEPS_VERSION_OUTPUTS_VERSION: ${{ steps.version.outputs.version }}
|
|
96
|
+
|
|
97
|
+
- name: Display completion message
|
|
98
|
+
run: echo "Release ${STEPS_VERSION_OUTPUTS_VERSION} prepared and pushed successfully!"
|
|
99
|
+
env:
|
|
100
|
+
STEPS_VERSION_OUTPUTS_VERSION: ${{ steps.version.outputs.version }}
|
|
@@ -18,7 +18,7 @@ jobs:
|
|
|
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@08807647e7069bb48b6ef5acd8ec9567f424441b # v8.1.0
|
|
22
22
|
with:
|
|
23
23
|
enable-cache: false
|
|
24
24
|
cache-dependency-glob: "pyproject.toml"
|
|
@@ -26,7 +26,7 @@ jobs:
|
|
|
26
26
|
- name: Build package
|
|
27
27
|
run: uv build --python 3.14 --python-preference only-managed --sdist --wheel . --out-dir dist
|
|
28
28
|
- name: Store the distribution packages
|
|
29
|
-
uses: actions/upload-artifact@
|
|
29
|
+
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7
|
|
30
30
|
with:
|
|
31
31
|
name: ${{ env.dists-artifact-name }}
|
|
32
32
|
path: dist/*
|
|
@@ -47,6 +47,6 @@ jobs:
|
|
|
47
47
|
name: ${{ env.dists-artifact-name }}
|
|
48
48
|
path: dist/
|
|
49
49
|
- name: Publish to PyPI
|
|
50
|
-
uses: pypa/gh-action-pypi-publish@
|
|
50
|
+
uses: pypa/gh-action-pypi-publish@cef221092ed1bacb1cc03d23a2d87d1d172e277b # v1.14.0
|
|
51
51
|
with:
|
|
52
52
|
attestations: true
|
|
@@ -5,7 +5,7 @@ repos:
|
|
|
5
5
|
- id: end-of-file-fixer
|
|
6
6
|
- id: trailing-whitespace
|
|
7
7
|
- repo: https://github.com/python-jsonschema/check-jsonschema
|
|
8
|
-
rev: 0.37.
|
|
8
|
+
rev: 0.37.2
|
|
9
9
|
hooks:
|
|
10
10
|
- id: check-github-workflows
|
|
11
11
|
args: ["--verbose"]
|
|
@@ -15,24 +15,24 @@ 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.21.
|
|
18
|
+
rev: "v2.21.2"
|
|
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.12"
|
|
23
23
|
hooks:
|
|
24
24
|
- id: ruff-format
|
|
25
25
|
- id: ruff
|
|
26
26
|
args: ["--fix", "--unsafe-fixes", "--exit-non-zero-on-fix"]
|
|
27
27
|
- repo: https://github.com/rbubley/mirrors-prettier
|
|
28
|
-
rev: "v3.8.
|
|
28
|
+
rev: "v3.8.3"
|
|
29
29
|
hooks:
|
|
30
30
|
- id: prettier
|
|
31
31
|
additional_dependencies:
|
|
32
32
|
- prettier@3.8.1
|
|
33
33
|
- "@prettier/plugin-xml@3.4.2"
|
|
34
34
|
- repo: https://github.com/zizmorcore/zizmor-pre-commit
|
|
35
|
-
rev: v1.
|
|
35
|
+
rev: v1.24.1
|
|
36
36
|
hooks:
|
|
37
37
|
- id: zizmor
|
|
38
38
|
- repo: meta
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: python-discovery
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.3.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
|
|
@@ -53,6 +53,8 @@ Requires-Dist: furo>=2025.12.19; extra == 'docs'
|
|
|
53
53
|
Requires-Dist: sphinx-autodoc-typehints>=3.6.3; extra == 'docs'
|
|
54
54
|
Requires-Dist: sphinx>=9.1; extra == 'docs'
|
|
55
55
|
Requires-Dist: sphinxcontrib-mermaid>=2; extra == 'docs'
|
|
56
|
+
Requires-Dist: sphinxcontrib-towncrier>=0.4; extra == 'docs'
|
|
57
|
+
Requires-Dist: towncrier>=25.8; extra == 'docs'
|
|
56
58
|
Provides-Extra: testing
|
|
57
59
|
Requires-Dist: covdefaults>=2.3; extra == 'testing'
|
|
58
60
|
Requires-Dist: coverage>=7.5.4; extra == 'testing'
|
|
@@ -99,7 +101,8 @@ The `get_interpreter()` function accepts various specification formats:
|
|
|
99
101
|
- Absolute path: `/usr/bin/python3.12`
|
|
100
102
|
- Version: `3.12`
|
|
101
103
|
- Implementation prefix: `cpython3.12`
|
|
102
|
-
-
|
|
104
|
+
- [Version specifier](https://packaging.python.org/en/latest/specifications/version-specifiers/): `>=3.10`,
|
|
105
|
+
`>=3.11,<3.13`
|
|
103
106
|
|
|
104
107
|
## Documentation
|
|
105
108
|
|
|
@@ -36,7 +36,8 @@ The `get_interpreter()` function accepts various specification formats:
|
|
|
36
36
|
- Absolute path: `/usr/bin/python3.12`
|
|
37
37
|
- Version: `3.12`
|
|
38
38
|
- Implementation prefix: `cpython3.12`
|
|
39
|
-
-
|
|
39
|
+
- [Version specifier](https://packaging.python.org/en/latest/specifications/version-specifiers/): `>=3.10`,
|
|
40
|
+
`>=3.11,<3.13`
|
|
40
41
|
|
|
41
42
|
## Documentation
|
|
42
43
|
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
{% set top_underline = "*" %}
|
|
2
|
+
{% set underline = "=" %}
|
|
3
|
+
{% if versiondata.name %}
|
|
4
|
+
{{ top_underline * ((versiondata.version + versiondata.date)|length + 5)}}
|
|
5
|
+
v{{ versiondata.version }} ({{ versiondata.date }})
|
|
6
|
+
{{ top_underline * ((versiondata.version + versiondata.date)|length + 5)}}
|
|
7
|
+
{% else %}
|
|
8
|
+
{{ top_underline * ((versiondata.version + versiondata.date)|length + 4)}}
|
|
9
|
+
{{ versiondata.version }} ({{ versiondata.date }})
|
|
10
|
+
{{ top_underline * ((versiondata.version + versiondata.date)|length + 4)}}
|
|
11
|
+
{% endif %}
|
|
12
|
+
|
|
13
|
+
{% for section, _ in sections.items() %}
|
|
14
|
+
{% if sections[section] %}
|
|
15
|
+
{% for category, val in definitions.items() if category in sections[section]%}
|
|
16
|
+
{{ definitions[category]['name'] }} - {{ versiondata.version }}
|
|
17
|
+
{{ underline * ((definitions[category]['name'] + versiondata.version)|length + 3)}}
|
|
18
|
+
{% if definitions[category]['showcontent'] %}
|
|
19
|
+
{% for text, values in sections[section][category].items() %}
|
|
20
|
+
- {{ text }} ({{ values|join(', ') }})
|
|
21
|
+
{% endfor %}
|
|
22
|
+
|
|
23
|
+
{% else %}
|
|
24
|
+
- {{ sections[section][category]['']|join(', ') }}
|
|
25
|
+
|
|
26
|
+
{% endif %}
|
|
27
|
+
{% if sections[section][category]|length == 0 %}
|
|
28
|
+
No significant changes.
|
|
29
|
+
|
|
30
|
+
{% else %}
|
|
31
|
+
{% endif %}
|
|
32
|
+
{% endfor %}
|
|
33
|
+
{% else %}
|
|
34
|
+
No significant changes.
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
{% endif %}
|
|
38
|
+
{% endfor %}
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
#################
|
|
2
|
+
Release History
|
|
3
|
+
#################
|
|
4
|
+
|
|
5
|
+
.. towncrier-draft-entries::
|
|
6
|
+
|
|
7
|
+
.. towncrier release notes start
|
|
8
|
+
|
|
9
|
+
********************
|
|
10
|
+
v1.3.1 (2026-05-12)
|
|
11
|
+
********************
|
|
12
|
+
|
|
13
|
+
Bug fixes - 1.3.1
|
|
14
|
+
=================
|
|
15
|
+
- export normalize_isa and deprecate KNOWN_ARCHITECTURES - by :user:`rahuldevikar`. (:issue:`59`)
|
|
16
|
+
- discover uv-managed Pythons on Windows. Previously the glob assumed Unix layout (``<root>/<key>/bin/python``) and
|
|
17
|
+
silently found nothing on Windows, where uv places ``python.exe`` directly under the install root - by
|
|
18
|
+
:user:`gaborbernat`. (:issue:`65`)
|
|
19
|
+
- Canonicalize GraalVM to match GraalPy Python interpreter in PythonSpec and PythonInfo. - by :user:`timfel`. (:issue:`73`)
|
|
20
|
+
|
|
21
|
+
********************
|
|
22
|
+
v1.3.0 (2026-05-05)
|
|
23
|
+
********************
|
|
24
|
+
|
|
25
|
+
Features - 1.3.0
|
|
26
|
+
================
|
|
27
|
+
|
|
28
|
+
- Add :func:`~python_discovery.iter_interpreters` for enumerating every discovered interpreter, with PATH and
|
|
29
|
+
UV-install support for non-CPython implementations listed in :data:`~python_discovery.KNOWN_IMPLEMENTATIONS`
|
|
30
|
+
(:pull:`71`)
|
|
31
|
+
|
|
32
|
+
********************
|
|
33
|
+
v1.2.2 (2026-04-06)
|
|
34
|
+
********************
|
|
35
|
+
|
|
36
|
+
Features - 1.2.2
|
|
37
|
+
================
|
|
38
|
+
|
|
39
|
+
- Export ``normalize_isa`` and deprecate ``KNOWN_ARCHITECTURES`` (:pull:`62`)
|
|
40
|
+
|
|
41
|
+
********************
|
|
42
|
+
v1.2.1 (2026-03-26)
|
|
43
|
+
********************
|
|
44
|
+
|
|
45
|
+
Features - 1.2.1
|
|
46
|
+
================
|
|
47
|
+
|
|
48
|
+
- Expose ``KNOWN_ARCHITECTURES`` as public API (:pull:`56`)
|
|
49
|
+
|
|
50
|
+
Contributor-facing changes - 1.2.1
|
|
51
|
+
==================================
|
|
52
|
+
|
|
53
|
+
- Add zizmor security auditing for workflows (:pull:`55`)
|
|
54
|
+
|
|
55
|
+
********************
|
|
56
|
+
v1.2.0 (2026-03-18)
|
|
57
|
+
********************
|
|
58
|
+
|
|
59
|
+
Features - 1.2.0
|
|
60
|
+
================
|
|
61
|
+
|
|
62
|
+
- Increase interpreter query timeout to 15s, with an override (:pull:`53`)
|
|
63
|
+
|
|
64
|
+
********************
|
|
65
|
+
v1.1.3 (2026-03-10)
|
|
66
|
+
********************
|
|
67
|
+
|
|
68
|
+
Bug fixes - 1.1.3
|
|
69
|
+
=================
|
|
70
|
+
|
|
71
|
+
- Add ``loongarch64`` to known ISAs (:pull:`50`)
|
|
72
|
+
|
|
73
|
+
********************
|
|
74
|
+
v1.1.2 (2026-03-09)
|
|
75
|
+
********************
|
|
76
|
+
|
|
77
|
+
Bug fixes - 1.1.2
|
|
78
|
+
=================
|
|
79
|
+
|
|
80
|
+
- Match prerelease versions against ``major.minor`` specs (:pull:`48`)
|
|
81
|
+
- Drain pipes after killing a timed-out interpreter probe (:pull:`49`)
|
|
82
|
+
|
|
83
|
+
Improved documentation - 1.1.2
|
|
84
|
+
==============================
|
|
85
|
+
|
|
86
|
+
- Add package description and usage to the README (:pull:`46`)
|
|
87
|
+
|
|
88
|
+
********************
|
|
89
|
+
v1.1.1 (2026-03-06)
|
|
90
|
+
********************
|
|
91
|
+
|
|
92
|
+
Bug fixes - 1.1.1
|
|
93
|
+
=================
|
|
94
|
+
|
|
95
|
+
- Add a timeout to interpreter probing (:pull:`42`)
|
|
96
|
+
- Add ``i686`` to known ISAs (:pull:`43`)
|
|
97
|
+
|
|
98
|
+
Contributor-facing changes - 1.1.1
|
|
99
|
+
==================================
|
|
100
|
+
|
|
101
|
+
- Add a security policy and workflow permissions hardening (:pull:`33`)
|
|
102
|
+
|
|
103
|
+
********************
|
|
104
|
+
v1.1.0 (2026-02-26)
|
|
105
|
+
********************
|
|
106
|
+
|
|
107
|
+
Features - 1.1.0
|
|
108
|
+
================
|
|
109
|
+
|
|
110
|
+
- Add a ``predicate`` parameter to :func:`~python_discovery.get_interpreter` (:pull:`31`)
|
|
111
|
+
|
|
112
|
+
Improved documentation - 1.1.0
|
|
113
|
+
==============================
|
|
114
|
+
|
|
115
|
+
- Fix the ReadTheDocs build (:pull:`29`)
|
|
116
|
+
- Add ``:param:`` descriptions to all public APIs (:pull:`32`)
|
|
117
|
+
|
|
118
|
+
********************
|
|
119
|
+
v1.0.0 (2026-02-25)
|
|
120
|
+
********************
|
|
121
|
+
|
|
122
|
+
Features - 1.0.0
|
|
123
|
+
================
|
|
124
|
+
|
|
125
|
+
- Initial release as a standalone package, extracted from ``virtualenv`` (:pull:`28`)
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
|
|
5
5
|
from datetime import datetime, timezone
|
|
6
|
+
from pathlib import Path
|
|
6
7
|
|
|
7
8
|
from python_discovery import __version__
|
|
8
9
|
|
|
@@ -19,8 +20,13 @@ extensions = [
|
|
|
19
20
|
"sphinx.ext.intersphinx",
|
|
20
21
|
"sphinx_autodoc_typehints",
|
|
21
22
|
"sphinxcontrib.mermaid",
|
|
23
|
+
"sphinxcontrib.towncrier.ext",
|
|
22
24
|
]
|
|
23
25
|
|
|
26
|
+
towncrier_draft_autoversion_mode = "draft"
|
|
27
|
+
towncrier_draft_include_empty = True
|
|
28
|
+
towncrier_draft_working_directory = Path(__file__).parent.parent
|
|
29
|
+
|
|
24
30
|
extlinks = {
|
|
25
31
|
"issue": ("https://github.com/tox-dev/python-discovery/issues/%s", "#%s"),
|
|
26
32
|
"pull": ("https://github.com/tox-dev/python-discovery/pull/%s", "PR #%s"),
|
|
@@ -33,7 +39,7 @@ intersphinx_mapping = {
|
|
|
33
39
|
|
|
34
40
|
templates_path = []
|
|
35
41
|
source_suffix = ".rst"
|
|
36
|
-
exclude_patterns = ["_build", "changelog
|
|
42
|
+
exclude_patterns = ["_build", "changelog/*"]
|
|
37
43
|
|
|
38
44
|
main_doc = "index"
|
|
39
45
|
pygments_style = "default"
|
|
@@ -65,6 +65,106 @@ detects these shims and resolves them to the actual binary.
|
|
|
65
65
|
`mise <https://mise.jdx.dev/>`_ and `asdf <https://asdf-vm.com/>`_ work similarly, using the
|
|
66
66
|
``MISE_DATA_DIR`` and ``ASDF_DATA_DIR`` environment variables to locate their installations.
|
|
67
67
|
|
|
68
|
+
How uv-managed Pythons are discovered
|
|
69
|
+
---------------------------------------
|
|
70
|
+
|
|
71
|
+
`uv <https://docs.astral.sh/uv/>`_ installs Python interpreters under a single root directory (configurable via
|
|
72
|
+
``UV_PYTHON_INSTALL_DIR``, otherwise defaulting under ``XDG_DATA_HOME`` or the platform user-data path). Each
|
|
73
|
+
install lives in its own subdirectory, but the actual binary location varies by OS and implementation:
|
|
74
|
+
|
|
75
|
+
.. list-table::
|
|
76
|
+
:header-rows: 1
|
|
77
|
+
:widths: 25 35 40
|
|
78
|
+
|
|
79
|
+
* - Implementation
|
|
80
|
+
- Unix layout
|
|
81
|
+
- Windows layout
|
|
82
|
+
* - CPython
|
|
83
|
+
- ``<root>/<key>/bin/python``
|
|
84
|
+
- ``<root>/<key>/python.exe``
|
|
85
|
+
* - PyPy
|
|
86
|
+
- ``<root>/<key>/bin/pypy*``
|
|
87
|
+
- ``<root>/<key>/pypy*.exe``
|
|
88
|
+
* - GraalPy
|
|
89
|
+
- ``<root>/<key>/bin/graalpy``
|
|
90
|
+
- ``<root>/<key>/bin/graalpy.exe``
|
|
91
|
+
|
|
92
|
+
.. mermaid::
|
|
93
|
+
|
|
94
|
+
flowchart LR
|
|
95
|
+
Call(["iter_interpreters(key)"]) --> Mode{"key is None?"}
|
|
96
|
+
Mode -->|"narrow"| N1["*/bin/python"]
|
|
97
|
+
Mode -->|"narrow"| N2["*/python.exe"]
|
|
98
|
+
Mode -->|"wide"| W1["*/bin/pypy*"]
|
|
99
|
+
Mode -->|"wide"| W2["*/bin/graalpy"]
|
|
100
|
+
Mode -->|"wide"| W3["*/pypy*.exe"]
|
|
101
|
+
Mode -->|"wide"| W4["*/bin/graalpy.exe"]
|
|
102
|
+
|
|
103
|
+
N1 --> Dedup[/"realpath dedup"/]
|
|
104
|
+
N2 --> Dedup
|
|
105
|
+
W1 --> Dedup
|
|
106
|
+
W2 --> Dedup
|
|
107
|
+
W3 --> Dedup
|
|
108
|
+
W4 --> Dedup
|
|
109
|
+
|
|
110
|
+
Dedup --> Interrogate(["subprocess interrogation"])
|
|
111
|
+
|
|
112
|
+
style Call fill:#4a90d9,stroke:#2a5f8f,color:#fff
|
|
113
|
+
style Mode fill:#d9904a,stroke:#8f5f2a,color:#fff
|
|
114
|
+
style N1 fill:#3a7fc2,stroke:#1f4d7a,color:#fff
|
|
115
|
+
style N2 fill:#3a7fc2,stroke:#1f4d7a,color:#fff
|
|
116
|
+
style W1 fill:#9f4ad9,stroke:#5f2a8f,color:#fff
|
|
117
|
+
style W2 fill:#9f4ad9,stroke:#5f2a8f,color:#fff
|
|
118
|
+
style W3 fill:#9f4ad9,stroke:#5f2a8f,color:#fff
|
|
119
|
+
style W4 fill:#9f4ad9,stroke:#5f2a8f,color:#fff
|
|
120
|
+
style Dedup fill:#c2873a,stroke:#7a4c1f,color:#fff
|
|
121
|
+
style Interrogate fill:#4a9f4a,stroke:#2a6f2a,color:#fff
|
|
122
|
+
|
|
123
|
+
GraalPy keeps its ``bin/`` segment on Windows (an upstream choice in uv); PyPy and CPython do not. python-discovery
|
|
124
|
+
globs all of these patterns regardless of the host OS, because globs that do not match anything are essentially
|
|
125
|
+
free, and the cross-platform list is short. Symlinked aliases inside an install (``bin/python``,
|
|
126
|
+
``bin/python3``, ``bin/python3.14`` all pointing at the same real file) are deduplicated by resolved path before
|
|
127
|
+
the subprocess interrogation, so each install is interrogated once.
|
|
128
|
+
|
|
129
|
+
Selecting one interpreter vs. enumerating all of them
|
|
130
|
+
-------------------------------------------------------
|
|
131
|
+
|
|
132
|
+
:func:`~python_discovery.get_interpreter` and :func:`~python_discovery.iter_interpreters` walk the same candidate
|
|
133
|
+
sources, but they answer different questions and behave differently in three ways.
|
|
134
|
+
|
|
135
|
+
.. mermaid::
|
|
136
|
+
|
|
137
|
+
flowchart LR
|
|
138
|
+
Sources["candidate sources<br>(try_first_with → current →<br>PEP 514 → PATH → uv)"]
|
|
139
|
+
Sources --> Get["get_interpreter()<br>first match wins, returns one"]
|
|
140
|
+
Sources --> Iter["iter_interpreters()<br>yields every match"]
|
|
141
|
+
|
|
142
|
+
style Get fill:#4a9f4a,stroke:#2a6f2a,color:#fff
|
|
143
|
+
style Iter fill:#4a90d9,stroke:#2a5f8f,color:#fff
|
|
144
|
+
|
|
145
|
+
**Implementation coverage on PATH.** :func:`~python_discovery.get_interpreter` matches only ``python*`` filenames on
|
|
146
|
+
PATH unless the spec names another implementation explicitly (``pypy3.12``, ``graalpy3.11``). This keeps backwards
|
|
147
|
+
compatibility with tools that have always read "no implementation in the spec" as "give me CPython."
|
|
148
|
+
:func:`~python_discovery.iter_interpreters` with no spec broadens the search to every name in
|
|
149
|
+
:data:`~python_discovery.KNOWN_IMPLEMENTATIONS` -- otherwise an "all interpreters" call would silently miss every
|
|
150
|
+
PyPy and GraalPy on the system. When you pass a spec to :func:`~python_discovery.iter_interpreters`, it falls back
|
|
151
|
+
to the same narrow regex as :func:`~python_discovery.get_interpreter`, so behavior is consistent across the two
|
|
152
|
+
APIs whenever a spec is given.
|
|
153
|
+
|
|
154
|
+
**Deduplication.** :func:`~python_discovery.get_interpreter` deduplicates per call so it does not interrogate the
|
|
155
|
+
same binary twice while searching, and stops as soon as a match is found. :func:`~python_discovery.iter_interpreters`
|
|
156
|
+
deduplicates by the resolved real path of each candidate's ``system_executable`` (falling back to ``executable``).
|
|
157
|
+
That means symlinked aliases like ``/bin/python3`` and ``/usr/bin/python3``, or a virtualenv whose ``python``
|
|
158
|
+
symlinks to its base interpreter, collapse to a single yield. The semantic is "one entry per distinct install,"
|
|
159
|
+
which is what callers building choosers or version-range pickers usually want.
|
|
160
|
+
|
|
161
|
+
**Iteration order.** Yields come back in *priority order*: ``try_first_with`` first, then the running interpreter,
|
|
162
|
+
then :pep:`514` entries on Windows, then PATH left-to-right, then UV-managed installs. This matches what
|
|
163
|
+
:func:`~python_discovery.get_interpreter` would have returned at each step. If your ordering differs (newest
|
|
164
|
+
version first, smallest install root, etc.), wrap the call in :func:`sorted` -- the API deliberately does not
|
|
165
|
+
include a ``sort_by`` parameter because keeping discovery order preserves the priority signal for callers who
|
|
166
|
+
want it.
|
|
167
|
+
|
|
68
168
|
How caching works
|
|
69
169
|
-------------------
|
|
70
170
|
|
|
@@ -165,9 +265,12 @@ A spec string follows the pattern ``[impl][version][t][-arch][-machine]``. Every
|
|
|
165
265
|
* - ``/usr/bin/python3``
|
|
166
266
|
- Absolute path, used directly (no search)
|
|
167
267
|
* - ``>=3.11,<3.13``
|
|
168
|
-
-
|
|
268
|
+
- `Version specifier <https://packaging.python.org/en/latest/specifications/version-specifiers/>`_
|
|
269
|
+
(any Python in range)
|
|
169
270
|
* - ``cpython>=3.11``
|
|
170
|
-
-
|
|
271
|
+
- `Version specifier <https://packaging.python.org/en/latest/specifications/version-specifiers/>`_
|
|
272
|
+
restricted to CPython
|
|
171
273
|
|
|
172
|
-
|
|
173
|
-
specifiers can be comma-separated,
|
|
274
|
+
`Version specifiers <https://packaging.python.org/en/latest/specifications/version-specifiers/>`_
|
|
275
|
+
(``>=``, ``<=``, ``~=``, ``!=``, ``==``, ``===``) are supported. Multiple specifiers can be comma-separated,
|
|
276
|
+
for example ``>=3.11,<3.13``.
|
|
@@ -62,6 +62,60 @@ the ``PY_DISCOVERY_TIMEOUT`` environment variable.
|
|
|
62
62
|
The timeout value should be a number in seconds. Each interpreter candidate is given this much time
|
|
63
63
|
to respond. If a timeout occurs, the candidate is skipped and the search continues with the next one.
|
|
64
64
|
|
|
65
|
+
List every interpreter on the system
|
|
66
|
+
--------------------------------------
|
|
67
|
+
|
|
68
|
+
Use :func:`~python_discovery.iter_interpreters` to enumerate every Python python-discovery can find. With no spec
|
|
69
|
+
it yields all known implementations (CPython, PyPy, GraalPy -- see
|
|
70
|
+
:data:`~python_discovery.KNOWN_IMPLEMENTATIONS`). Pass a spec to filter, exactly like
|
|
71
|
+
:func:`~python_discovery.get_interpreter`.
|
|
72
|
+
|
|
73
|
+
.. code-block:: python
|
|
74
|
+
|
|
75
|
+
from pathlib import Path
|
|
76
|
+
|
|
77
|
+
from python_discovery import DiskCache, iter_interpreters
|
|
78
|
+
|
|
79
|
+
cache = DiskCache(root=Path("~/.cache/python-discovery").expanduser())
|
|
80
|
+
|
|
81
|
+
# Every interpreter, no filter
|
|
82
|
+
for info in iter_interpreters(cache=cache):
|
|
83
|
+
print(info.executable, info.version_str, info.implementation)
|
|
84
|
+
|
|
85
|
+
# Every CPython 3.10 or newer, newest first
|
|
86
|
+
newest_first = sorted(
|
|
87
|
+
iter_interpreters("cpython>=3.10", cache=cache),
|
|
88
|
+
key=lambda info: info.version_info,
|
|
89
|
+
reverse=True,
|
|
90
|
+
)
|
|
91
|
+
|
|
92
|
+
Enumeration interrogates every candidate as a subprocess on a cold cache. Always pass a
|
|
93
|
+
:class:`~python_discovery.DiskCache` if you call this more than once.
|
|
94
|
+
|
|
95
|
+
Pick an interpreter from a range, preferring newer
|
|
96
|
+
----------------------------------------------------
|
|
97
|
+
|
|
98
|
+
A common need: search a version range and prefer newer interpreters when more than one matches. Sort the result of
|
|
99
|
+
:func:`~python_discovery.iter_interpreters` and take the first.
|
|
100
|
+
|
|
101
|
+
.. code-block:: python
|
|
102
|
+
|
|
103
|
+
from pathlib import Path
|
|
104
|
+
|
|
105
|
+
from python_discovery import DiskCache, iter_interpreters
|
|
106
|
+
|
|
107
|
+
cache = DiskCache(root=Path("~/.cache/python-discovery").expanduser())
|
|
108
|
+
|
|
109
|
+
matches = sorted(
|
|
110
|
+
iter_interpreters("cpython>=3.10,<3.15", cache=cache),
|
|
111
|
+
key=lambda info: info.version_info,
|
|
112
|
+
reverse=True,
|
|
113
|
+
)
|
|
114
|
+
info = matches[0] if matches else None
|
|
115
|
+
|
|
116
|
+
Use :func:`get_interpreter` instead when you only need the first PATH-priority hit; use the sort-and-take pattern
|
|
117
|
+
when *your* ordering differs from PATH order (newest version, smallest install size, preferred install root, etc.).
|
|
118
|
+
|
|
65
119
|
Read interpreter metadata
|
|
66
120
|
---------------------------
|
|
67
121
|
|
|
@@ -87,6 +87,49 @@ You can pass multiple specs as a list -- the library tries each one in order and
|
|
|
87
87
|
|
|
88
88
|
result = get_interpreter(["python3.12", "python3.11"], cache=cache)
|
|
89
89
|
|
|
90
|
+
Listing every interpreter
|
|
91
|
+
---------------------------
|
|
92
|
+
|
|
93
|
+
When you need *every* interpreter rather than just the first match -- for example, to show the user a chooser, or
|
|
94
|
+
to apply your own ranking -- use :func:`~python_discovery.iter_interpreters`. Pass no arguments to enumerate every
|
|
95
|
+
implementation python-discovery knows about, or pass a spec to filter.
|
|
96
|
+
|
|
97
|
+
.. mermaid::
|
|
98
|
+
|
|
99
|
+
flowchart TD
|
|
100
|
+
Call["iter_interpreters(spec, cache)"] --> Yield["yields PythonInfo"]
|
|
101
|
+
Yield --> A["1. try_first_with paths"]
|
|
102
|
+
Yield --> B["2. running interpreter"]
|
|
103
|
+
Yield --> C["3. PATH (left to right)"]
|
|
104
|
+
Yield --> D["4. uv-managed installs"]
|
|
105
|
+
|
|
106
|
+
style Call fill:#4a90d9,stroke:#2a5f8f,color:#fff
|
|
107
|
+
style Yield fill:#4a9f4a,stroke:#2a6f2a,color:#fff
|
|
108
|
+
|
|
109
|
+
.. code-block:: python
|
|
110
|
+
|
|
111
|
+
from pathlib import Path
|
|
112
|
+
|
|
113
|
+
from python_discovery import DiskCache, iter_interpreters
|
|
114
|
+
|
|
115
|
+
cache = DiskCache(root=Path("~/.cache/python-discovery").expanduser())
|
|
116
|
+
for info in iter_interpreters(cache=cache):
|
|
117
|
+
print(info.executable, info.version_str, info.implementation)
|
|
118
|
+
|
|
119
|
+
The result is an iterator, so :func:`list`, :func:`sorted`, generator expressions and early ``break`` all work as you
|
|
120
|
+
would expect. Symlinked aliases (``/bin/python3`` and ``/usr/bin/python3``, or a virtualenv and the base it points at)
|
|
121
|
+
collapse to a single entry, so you do not see the same install twice.
|
|
122
|
+
|
|
123
|
+
To prefer newer interpreters in a range, sort the result by ``version_info`` after filtering:
|
|
124
|
+
|
|
125
|
+
.. code-block:: python
|
|
126
|
+
|
|
127
|
+
newest_first = sorted(
|
|
128
|
+
iter_interpreters(">=3.10,<3.15", cache=cache),
|
|
129
|
+
key=lambda info: info.version_info,
|
|
130
|
+
reverse=True,
|
|
131
|
+
)
|
|
132
|
+
|
|
90
133
|
Writing specs
|
|
91
134
|
-------------
|
|
92
135
|
|
|
@@ -131,7 +174,8 @@ Common examples:
|
|
|
131
174
|
* - ``/usr/bin/python3``
|
|
132
175
|
- An absolute path, used directly without searching
|
|
133
176
|
* - ``>=3.11,<3.13``
|
|
134
|
-
- Any Python in the 3.11--3.12 range
|
|
177
|
+
- Any Python in the 3.11--3.12 range
|
|
178
|
+
(`version specifier <https://packaging.python.org/en/latest/specifications/version-specifiers/>`_ syntax)
|
|
135
179
|
|
|
136
180
|
See the :doc:`full spec reference </explanation>` for all options.
|
|
137
181
|
|