python-discovery 1.4.4__tar.gz → 1.5.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.4.4 → python_discovery-1.5.1}/.github/workflows/check.yaml +4 -5
- {python_discovery-1.4.4 → python_discovery-1.5.1}/.github/workflows/prepare-release.yaml +2 -11
- {python_discovery-1.4.4 → python_discovery-1.5.1}/.github/workflows/release.yaml +3 -7
- {python_discovery-1.4.4 → python_discovery-1.5.1}/.pre-commit-config.yaml +16 -9
- {python_discovery-1.4.4 → python_discovery-1.5.1}/PKG-INFO +2 -15
- {python_discovery-1.4.4 → python_discovery-1.5.1}/docs/changelog.rst +26 -0
- {python_discovery-1.4.4 → python_discovery-1.5.1}/docs/conf.py +1 -1
- {python_discovery-1.4.4 → python_discovery-1.5.1}/docs/explanation.rst +67 -34
- {python_discovery-1.4.4 → python_discovery-1.5.1}/pyproject.toml +17 -17
- {python_discovery-1.4.4 → python_discovery-1.5.1}/src/python_discovery/_cache.py +5 -5
- {python_discovery-1.4.4 → python_discovery-1.5.1}/src/python_discovery/_cached_py_info.py +4 -4
- {python_discovery-1.4.4 → python_discovery-1.5.1}/src/python_discovery/_discovery.py +149 -22
- {python_discovery-1.4.4 → python_discovery-1.5.1}/src/python_discovery/_py_info.py +17 -14
- {python_discovery-1.4.4 → python_discovery-1.5.1}/src/python_discovery/_py_spec.py +3 -3
- {python_discovery-1.4.4 → python_discovery-1.5.1}/src/python_discovery/_specifier.py +9 -6
- {python_discovery-1.4.4 → python_discovery-1.5.1}/src/python_discovery/_windows/_pep514.py +11 -11
- {python_discovery-1.4.4 → python_discovery-1.5.1}/tasks/release.py +35 -35
- {python_discovery-1.4.4 → python_discovery-1.5.1}/tests/test_discovery.py +134 -62
- python_discovery-1.5.1/tests/test_iter_interpreters.py +423 -0
- {python_discovery-1.4.4 → python_discovery-1.5.1}/tests/test_py_info_extra.py +4 -1
- {python_discovery-1.4.4 → python_discovery-1.5.1}/tests/test_specifier.py +5 -5
- {python_discovery-1.4.4 → python_discovery-1.5.1}/tests/windows/conftest.py +1 -1
- {python_discovery-1.4.4 → python_discovery-1.5.1}/tox.toml +4 -4
- python_discovery-1.4.4/tests/test_iter_interpreters.py +0 -218
- {python_discovery-1.4.4 → python_discovery-1.5.1}/.github/FUNDING.yaml +0 -0
- {python_discovery-1.4.4 → python_discovery-1.5.1}/.github/SECURITY.md +0 -0
- {python_discovery-1.4.4 → python_discovery-1.5.1}/.github/dependabot.yaml +0 -0
- {python_discovery-1.4.4 → python_discovery-1.5.1}/.github/release.yaml +0 -0
- {python_discovery-1.4.4 → python_discovery-1.5.1}/.gitignore +0 -0
- {python_discovery-1.4.4 → python_discovery-1.5.1}/.readthedocs.yaml +0 -0
- {python_discovery-1.4.4 → python_discovery-1.5.1}/.readthedocs.yml +0 -0
- {python_discovery-1.4.4 → python_discovery-1.5.1}/CODE_OF_CONDUCT.md +0 -0
- {python_discovery-1.4.4 → python_discovery-1.5.1}/LICENSE +0 -0
- {python_discovery-1.4.4 → python_discovery-1.5.1}/README.md +0 -0
- {python_discovery-1.4.4 → python_discovery-1.5.1}/docs/_static/custom.css +0 -0
- {python_discovery-1.4.4 → python_discovery-1.5.1}/docs/_static/logo.svg +0 -0
- {python_discovery-1.4.4 → python_discovery-1.5.1}/docs/changelog/template.jinja2 +0 -0
- {python_discovery-1.4.4 → python_discovery-1.5.1}/docs/how-to/standalone-usage.rst +0 -0
- {python_discovery-1.4.4 → python_discovery-1.5.1}/docs/index.rst +0 -0
- {python_discovery-1.4.4 → python_discovery-1.5.1}/docs/reference/api.rst +0 -0
- {python_discovery-1.4.4 → python_discovery-1.5.1}/docs/reference/environment-variables.rst +0 -0
- {python_discovery-1.4.4 → python_discovery-1.5.1}/docs/tutorial/getting-started.rst +0 -0
- {python_discovery-1.4.4 → python_discovery-1.5.1}/src/python_discovery/__init__.py +0 -0
- {python_discovery-1.4.4 → python_discovery-1.5.1}/src/python_discovery/_compat.py +0 -0
- {python_discovery-1.4.4 → python_discovery-1.5.1}/src/python_discovery/_windows/__init__.py +0 -0
- {python_discovery-1.4.4 → python_discovery-1.5.1}/src/python_discovery/_windows/_propose.py +0 -0
- {python_discovery-1.4.4 → python_discovery-1.5.1}/src/python_discovery/py.typed +0 -0
- {python_discovery-1.4.4 → python_discovery-1.5.1}/tests/conftest.py +0 -0
- {python_discovery-1.4.4 → python_discovery-1.5.1}/tests/py_info/test_py_info.py +0 -0
- {python_discovery-1.4.4 → python_discovery-1.5.1}/tests/py_info/test_py_info_exe_based_of.py +0 -0
- {python_discovery-1.4.4 → python_discovery-1.5.1}/tests/test_cache.py +0 -0
- {python_discovery-1.4.4 → python_discovery-1.5.1}/tests/test_cached_py_info.py +0 -0
- {python_discovery-1.4.4 → python_discovery-1.5.1}/tests/test_discovery_extra.py +0 -0
- {python_discovery-1.4.4 → python_discovery-1.5.1}/tests/test_py_spec.py +0 -0
- {python_discovery-1.4.4 → python_discovery-1.5.1}/tests/test_py_spec_extra.py +0 -0
- {python_discovery-1.4.4 → python_discovery-1.5.1}/tests/windows/test_windows.py +0 -0
- {python_discovery-1.4.4 → python_discovery-1.5.1}/tests/windows/test_windows_pep514.py +0 -0
- {python_discovery-1.4.4 → python_discovery-1.5.1}/tests/windows/winreg_mock_values.py +0 -0
|
@@ -7,14 +7,11 @@ on:
|
|
|
7
7
|
pull_request:
|
|
8
8
|
schedule:
|
|
9
9
|
- cron: "0 8 * * *"
|
|
10
|
-
|
|
11
10
|
concurrency:
|
|
12
11
|
group: check-${{ github.ref }}
|
|
13
12
|
cancel-in-progress: true
|
|
14
|
-
|
|
15
13
|
permissions:
|
|
16
14
|
contents: read
|
|
17
|
-
|
|
18
15
|
jobs:
|
|
19
16
|
test:
|
|
20
17
|
runs-on: ubuntu-latest
|
|
@@ -22,6 +19,8 @@ jobs:
|
|
|
22
19
|
fail-fast: false
|
|
23
20
|
matrix:
|
|
24
21
|
env:
|
|
22
|
+
- "3.15"
|
|
23
|
+
- "3.15t"
|
|
25
24
|
- "3.14"
|
|
26
25
|
- "3.13"
|
|
27
26
|
- "3.12"
|
|
@@ -34,12 +33,12 @@ jobs:
|
|
|
34
33
|
- dev
|
|
35
34
|
- pkg_meta
|
|
36
35
|
steps:
|
|
37
|
-
- uses: actions/checkout@
|
|
36
|
+
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
|
38
37
|
with:
|
|
39
38
|
fetch-depth: 0
|
|
40
39
|
persist-credentials: false
|
|
41
40
|
- name: Install the latest version of uv
|
|
42
|
-
uses: astral-sh/setup-uv@
|
|
41
|
+
uses: astral-sh/setup-uv@c771a70e6277c0a99b617c7a806ffedaca235ff9 # v9.0.0
|
|
43
42
|
with:
|
|
44
43
|
enable-cache: false
|
|
45
44
|
cache-dependency-glob: "pyproject.toml"
|
|
@@ -12,13 +12,10 @@ on:
|
|
|
12
12
|
- minor
|
|
13
13
|
- patch
|
|
14
14
|
default: auto
|
|
15
|
-
|
|
16
15
|
permissions:
|
|
17
16
|
contents: read
|
|
18
|
-
|
|
19
17
|
env:
|
|
20
18
|
FORCE_COLOR: 1
|
|
21
|
-
|
|
22
19
|
jobs:
|
|
23
20
|
prepare-release:
|
|
24
21
|
runs-on: ubuntu-24.04
|
|
@@ -26,24 +23,21 @@ jobs:
|
|
|
26
23
|
permissions:
|
|
27
24
|
contents: write
|
|
28
25
|
steps:
|
|
29
|
-
- uses: actions/checkout@
|
|
26
|
+
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
|
30
27
|
with:
|
|
31
28
|
fetch-depth: 0
|
|
32
29
|
token: ${{ secrets.RELEASE_PAT }}
|
|
33
30
|
persist-credentials: true
|
|
34
|
-
|
|
35
31
|
- name: Install the latest version of uv
|
|
36
|
-
uses: astral-sh/setup-uv@
|
|
32
|
+
uses: astral-sh/setup-uv@c771a70e6277c0a99b617c7a806ffedaca235ff9 # v9.0.0
|
|
37
33
|
with:
|
|
38
34
|
enable-cache: true
|
|
39
35
|
cache-dependency-glob: "pyproject.toml"
|
|
40
36
|
github-token: ${{ secrets.GITHUB_TOKEN }}
|
|
41
|
-
|
|
42
37
|
- name: Configure git
|
|
43
38
|
run: |
|
|
44
39
|
git config user.name "${GITHUB_ACTOR}"
|
|
45
40
|
git config user.email "${GITHUB_ACTOR}@users.noreply.github.com"
|
|
46
|
-
|
|
47
41
|
- name: Calculate next version
|
|
48
42
|
id: version
|
|
49
43
|
env:
|
|
@@ -84,16 +78,13 @@ jobs:
|
|
|
84
78
|
|
|
85
79
|
echo "Next version: $next"
|
|
86
80
|
echo "version=$next" >> $GITHUB_OUTPUT
|
|
87
|
-
|
|
88
81
|
- name: Install tox
|
|
89
82
|
run: uv tool install --python-preference only-managed --python 3.14 tox@latest --with tox-uv
|
|
90
|
-
|
|
91
83
|
- name: Run release process
|
|
92
84
|
run: tox run -e release -- ${STEPS_VERSION_OUTPUTS_VERSION}
|
|
93
85
|
env:
|
|
94
86
|
GH_TOKEN: ${{ secrets.RELEASE_PAT }}
|
|
95
87
|
STEPS_VERSION_OUTPUTS_VERSION: ${{ steps.version.outputs.version }}
|
|
96
|
-
|
|
97
88
|
- name: Display completion message
|
|
98
89
|
run: echo "Release ${STEPS_VERSION_OUTPUTS_VERSION} prepared and pushed successfully!"
|
|
99
90
|
env:
|
|
@@ -2,23 +2,20 @@ name: Release to PyPI
|
|
|
2
2
|
on:
|
|
3
3
|
push:
|
|
4
4
|
tags: ["*"]
|
|
5
|
-
|
|
6
5
|
permissions:
|
|
7
6
|
contents: read
|
|
8
|
-
|
|
9
7
|
env:
|
|
10
8
|
dists-artifact-name: python-package-distributions
|
|
11
|
-
|
|
12
9
|
jobs:
|
|
13
10
|
build:
|
|
14
11
|
runs-on: ubuntu-latest
|
|
15
12
|
steps:
|
|
16
|
-
- uses: actions/checkout@
|
|
13
|
+
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
|
17
14
|
with:
|
|
18
15
|
fetch-depth: 0
|
|
19
16
|
persist-credentials: false
|
|
20
17
|
- name: Install the latest version of uv
|
|
21
|
-
uses: astral-sh/setup-uv@
|
|
18
|
+
uses: astral-sh/setup-uv@c771a70e6277c0a99b617c7a806ffedaca235ff9 # v9.0.0
|
|
22
19
|
with:
|
|
23
20
|
enable-cache: false
|
|
24
21
|
cache-dependency-glob: "pyproject.toml"
|
|
@@ -30,7 +27,6 @@ jobs:
|
|
|
30
27
|
with:
|
|
31
28
|
name: ${{ env.dists-artifact-name }}
|
|
32
29
|
path: dist/*
|
|
33
|
-
|
|
34
30
|
release:
|
|
35
31
|
needs:
|
|
36
32
|
- build
|
|
@@ -47,6 +43,6 @@ jobs:
|
|
|
47
43
|
name: ${{ env.dists-artifact-name }}
|
|
48
44
|
path: dist/
|
|
49
45
|
- name: Publish to PyPI
|
|
50
|
-
uses: pypa/gh-action-pypi-publish@
|
|
46
|
+
uses: pypa/gh-action-pypi-publish@ba38be9e461d3875417946c167d0b5f3d385a247 # v1.14.1
|
|
51
47
|
with:
|
|
52
48
|
attestations: true
|
|
@@ -10,29 +10,36 @@ 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.3
|
|
14
14
|
hooks:
|
|
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.26.0"
|
|
19
19
|
hooks:
|
|
20
20
|
- id: pyproject-fmt
|
|
21
21
|
- repo: https://github.com/astral-sh/ruff-pre-commit
|
|
22
|
-
rev: "v0.
|
|
22
|
+
rev: "v0.16.0"
|
|
23
23
|
hooks:
|
|
24
24
|
- id: ruff-format
|
|
25
25
|
- id: ruff
|
|
26
26
|
args: ["--fix", "--unsafe-fixes", "--exit-non-zero-on-fix"]
|
|
27
|
-
- repo: https://github.com/
|
|
28
|
-
rev: "
|
|
27
|
+
- repo: https://github.com/google/yamlfmt
|
|
28
|
+
rev: "v0.21.0"
|
|
29
29
|
hooks:
|
|
30
|
-
- id:
|
|
30
|
+
- id: yamlfmt
|
|
31
|
+
- repo: local
|
|
32
|
+
hooks:
|
|
33
|
+
- id: mdformat
|
|
34
|
+
name: mdformat
|
|
35
|
+
entry: mdformat --wrap 120
|
|
36
|
+
language: python
|
|
37
|
+
types: [markdown]
|
|
31
38
|
additional_dependencies:
|
|
32
|
-
-
|
|
33
|
-
-
|
|
39
|
+
- mdformat-gfm>=1
|
|
40
|
+
- mdformat-ruff>=0.1.3
|
|
34
41
|
- repo: https://github.com/zizmorcore/zizmor-pre-commit
|
|
35
|
-
rev: v1.
|
|
42
|
+
rev: v1.28.0
|
|
36
43
|
hooks:
|
|
37
44
|
- id: zizmor
|
|
38
45
|
- repo: meta
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: python-discovery
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.5.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
|
|
@@ -42,25 +42,12 @@ Classifier: Programming Language :: Python :: 3.11
|
|
|
42
42
|
Classifier: Programming Language :: Python :: 3.12
|
|
43
43
|
Classifier: Programming Language :: Python :: 3.13
|
|
44
44
|
Classifier: Programming Language :: Python :: 3.14
|
|
45
|
+
Classifier: Programming Language :: Python :: 3.15
|
|
45
46
|
Classifier: Programming Language :: Python :: Implementation :: CPython
|
|
46
47
|
Classifier: Topic :: Software Development :: Libraries
|
|
47
48
|
Classifier: Topic :: Utilities
|
|
48
49
|
Requires-Python: >=3.8
|
|
49
50
|
Requires-Dist: filelock>=3.15.4
|
|
50
|
-
Requires-Dist: platformdirs<5,>=4.3.6
|
|
51
|
-
Provides-Extra: docs
|
|
52
|
-
Requires-Dist: furo>=2025.12.19; extra == 'docs'
|
|
53
|
-
Requires-Dist: sphinx-autodoc-typehints>=3.6.3; extra == 'docs'
|
|
54
|
-
Requires-Dist: sphinx>=9.1; extra == 'docs'
|
|
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'
|
|
58
|
-
Provides-Extra: testing
|
|
59
|
-
Requires-Dist: covdefaults>=2.3; extra == 'testing'
|
|
60
|
-
Requires-Dist: coverage>=7.5.4; extra == 'testing'
|
|
61
|
-
Requires-Dist: pytest-mock>=3.14; extra == 'testing'
|
|
62
|
-
Requires-Dist: pytest>=8.3.5; extra == 'testing'
|
|
63
|
-
Requires-Dist: setuptools>=75.1; extra == 'testing'
|
|
64
51
|
Description-Content-Type: text/markdown
|
|
65
52
|
|
|
66
53
|
# [`python-discovery`](https://python-discovery.readthedocs.io/en/latest/)
|
|
@@ -6,6 +6,32 @@
|
|
|
6
6
|
|
|
7
7
|
.. towncrier release notes start
|
|
8
8
|
|
|
9
|
+
********************
|
|
10
|
+
v1.5.1 (2026-07-31)
|
|
11
|
+
********************
|
|
12
|
+
|
|
13
|
+
Bug fixes - 1.5.1
|
|
14
|
+
=================
|
|
15
|
+
- Locate uv's interpreter store the way uv does, using XDG on macOS (``~/.local/share/uv/python``) and roaming
|
|
16
|
+
``%APPDATA%\uv\python`` on Windows, neither of which ``platformdirs`` reported. The uv variables now come from the
|
|
17
|
+
``env`` mapping rather than ``os.environ`` - by :user:`gaborbernat`. (:issue:`109`)
|
|
18
|
+
- Pick uv-managed interpreters by install directory name instead of globbing the store. A bare ``3.8`` now means
|
|
19
|
+
CPython, the way uv resolves it, rather than whichever implementation the filesystem happened to list first, and
|
|
20
|
+
probing runs newest version first - by :user:`gaborbernat`. (:issue:`110`)
|
|
21
|
+
|
|
22
|
+
Packaging updates and notes for downstreams - 1.5.1
|
|
23
|
+
===================================================
|
|
24
|
+
- Drop the ``platformdirs`` runtime dependency; locating uv's store was the only thing it did - by
|
|
25
|
+
:user:`gaborbernat`. (:issue:`109`)
|
|
26
|
+
|
|
27
|
+
********************
|
|
28
|
+
v1.5.0 (2026-07-21)
|
|
29
|
+
********************
|
|
30
|
+
|
|
31
|
+
Packaging updates and notes for downstreams - 1.5.0
|
|
32
|
+
===================================================
|
|
33
|
+
- Declare support for Python 3.15 and run the test suite against it, currently in beta. (:issue:`104`)
|
|
34
|
+
|
|
9
35
|
********************
|
|
10
36
|
v1.4.4 (2026-07-08)
|
|
11
37
|
********************
|
|
@@ -11,7 +11,7 @@ company = "tox-dev"
|
|
|
11
11
|
name = "python-discovery"
|
|
12
12
|
version = ".".join(__version__.split(".")[:2])
|
|
13
13
|
release = __version__
|
|
14
|
-
copyright = f"2026-{datetime.now(tz=timezone.utc).year}, {company}" #
|
|
14
|
+
copyright = f"2026-{datetime.now(tz=timezone.utc).year}, {company}" # ruff:ignore[builtin-variable-shadowing]
|
|
15
15
|
|
|
16
16
|
extensions = [
|
|
17
17
|
"sphinx.ext.autodoc",
|
|
@@ -68,9 +68,33 @@ detects these shims and resolves them to the actual binary.
|
|
|
68
68
|
How uv-managed Pythons are discovered
|
|
69
69
|
---------------------------------------
|
|
70
70
|
|
|
71
|
-
`uv <https://docs.astral.sh/uv/>`_ installs Python interpreters under a single root directory
|
|
72
|
-
|
|
73
|
-
|
|
71
|
+
`uv <https://docs.astral.sh/uv/>`_ installs Python interpreters under a single root directory. ``UV_PYTHON_INSTALL_DIR``
|
|
72
|
+
overrides it; otherwise the store is the ``python`` bucket of uv's state directory, which uv derives as follows:
|
|
73
|
+
|
|
74
|
+
.. list-table::
|
|
75
|
+
:header-rows: 1
|
|
76
|
+
:widths: 20 40 40
|
|
77
|
+
|
|
78
|
+
* - Platform
|
|
79
|
+
- Legacy state directory
|
|
80
|
+
- Current state directory
|
|
81
|
+
* - Windows
|
|
82
|
+
- ``%APPDATA%\uv\data``
|
|
83
|
+
- ``%APPDATA%\uv`` (roaming, not local)
|
|
84
|
+
* - macOS
|
|
85
|
+
- ``~/Library/Application Support/uv``
|
|
86
|
+
- ``$XDG_DATA_HOME/uv`` or ``~/.local/share/uv``
|
|
87
|
+
* - Other
|
|
88
|
+
- same as current
|
|
89
|
+
- ``$XDG_DATA_HOME/uv`` or ``~/.local/share/uv``
|
|
90
|
+
|
|
91
|
+
uv follows the XDG convention on macOS too, and falls back to the legacy directory only when that directory already
|
|
92
|
+
exists. python-discovery searches both, in the same order, so a leftover legacy directory cannot hide the store uv
|
|
93
|
+
installs into. A relative ``XDG_DATA_HOME`` does not count, as the XDG specification requires, and Windows ignores it
|
|
94
|
+
outright. Every one of these variables comes from the ``env`` mapping passed to
|
|
95
|
+
:func:`~python_discovery.get_interpreter`, rather than from the process environment.
|
|
96
|
+
|
|
97
|
+
Each install lives in its own subdirectory, but the actual binary location varies by OS and implementation:
|
|
74
98
|
|
|
75
99
|
.. list-table::
|
|
76
100
|
:header-rows: 1
|
|
@@ -89,42 +113,51 @@ install lives in its own subdirectory, but the actual binary location varies by
|
|
|
89
113
|
- ``<root>/<key>/bin/graalpy``
|
|
90
114
|
- ``<root>/<key>/bin/graalpy.exe``
|
|
91
115
|
|
|
116
|
+
GraalPy keeps its ``bin/`` segment on Windows (an upstream choice in uv); PyPy and CPython do not. python-discovery
|
|
117
|
+
globs all of these patterns regardless of the host OS, because globs that do not match anything are essentially
|
|
118
|
+
free, and the cross-platform list is short. Symlinked aliases inside an install (``bin/python``,
|
|
119
|
+
``bin/python3``, ``bin/python3.14`` all pointing at the same real file) are deduplicated by resolved path before
|
|
120
|
+
the subprocess interrogation, so each install is interrogated once. python-discovery also follows the ``install``
|
|
121
|
+
subdirectory some distributions unpack into, as uv does.
|
|
122
|
+
|
|
123
|
+
Which uv installs get interrogated
|
|
124
|
+
------------------------------------
|
|
125
|
+
|
|
126
|
+
Interrogating an interpreter costs a subprocess, and a uv install directory announces what it holds in its own name:
|
|
127
|
+
``<implementation>-<version>[+<variant>]-<os>-<arch>-<libc>``, for example
|
|
128
|
+
``cpython-3.14.6+freethreaded-macos-aarch64-none``. python-discovery reads that name and skips the installs the spec
|
|
129
|
+
already rules out, then probes what is left in uv's preference order:
|
|
130
|
+
|
|
92
131
|
.. mermaid::
|
|
93
132
|
|
|
94
133
|
flowchart LR
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
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
|
|
134
|
+
Dirs[/"store subdirectories"/] --> Parse{"name is a uv key?"}
|
|
135
|
+
Parse -->|"no"| Last["probe last"]
|
|
136
|
+
Parse -->|"yes"| Filter["drop what the spec rules out:<br>implementation, version, free-threaded, debug"]
|
|
137
|
+
Filter --> Sort["CPython → PyPy → GraalPy,<br>newest version first,<br>default build before variants"]
|
|
138
|
+
Sort --> Interrogate(["subprocess interrogation"])
|
|
139
|
+
Last --> Interrogate
|
|
140
|
+
|
|
141
|
+
style Dirs fill:#4a90d9,stroke:#2a5f8f,color:#fff
|
|
142
|
+
style Parse fill:#d9904a,stroke:#8f5f2a,color:#fff
|
|
143
|
+
style Filter fill:#9f4ad9,stroke:#5f2a8f,color:#fff
|
|
144
|
+
style Sort fill:#c2873a,stroke:#7a4c1f,color:#fff
|
|
121
145
|
style Interrogate fill:#4a9f4a,stroke:#2a6f2a,color:#fff
|
|
122
146
|
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
147
|
+
python-discovery reads the variant suffix the same way :class:`~python_discovery.PythonSpec` does: ``3.14t`` wants
|
|
148
|
+
``+freethreaded``, ``3.14d`` wants ``+debug``. A spec that says nothing about a variant leaves it unconstrained, so
|
|
149
|
+
``3.14`` still reaches a ``+debug`` install, after the default build rather than instead of it.
|
|
150
|
+
|
|
151
|
+
A bare version spec such as ``3.8`` matches CPython only, which is what uv resolves it to and what the equivalent
|
|
152
|
+
``python3.8`` probe on ``PATH`` finds in practice. Ask for ``pypy3.8`` or enumerate with
|
|
153
|
+
:func:`~python_discovery.iter_interpreters` to reach the other implementations - PyPy ships a ``bin/python`` symlink,
|
|
154
|
+
so a store-wide glob would otherwise hand back PyPy for ``3.8`` whenever the filesystem listed it first.
|
|
155
|
+
|
|
156
|
+
Two names carry less information than they appear to. GraalPy keys named after the GraalVM release
|
|
157
|
+
(``graalpy-24.1.1``, from older uv versions) do not constrain the version, and uv's minor version links
|
|
158
|
+
(``cpython-3.15`` pointing at ``cpython-3.15.0b3``) move on the next patch release, so the concrete install ranks
|
|
159
|
+
ahead of the link. Anything that is not a uv key - a hand-made ``UV_PYTHON_INSTALL_DIR`` layout, say - stays in
|
|
160
|
+
the running, at the back of the queue.
|
|
128
161
|
|
|
129
162
|
Selecting one interpreter vs. enumerating all of them
|
|
130
163
|
-------------------------------------------------------
|
|
@@ -35,6 +35,7 @@ classifiers = [
|
|
|
35
35
|
"Programming Language :: Python :: 3.12",
|
|
36
36
|
"Programming Language :: Python :: 3.13",
|
|
37
37
|
"Programming Language :: Python :: 3.14",
|
|
38
|
+
"Programming Language :: Python :: 3.15",
|
|
38
39
|
"Programming Language :: Python :: Implementation :: CPython",
|
|
39
40
|
"Topic :: Software Development :: Libraries",
|
|
40
41
|
"Topic :: Utilities",
|
|
@@ -44,22 +45,6 @@ dynamic = [
|
|
|
44
45
|
]
|
|
45
46
|
dependencies = [
|
|
46
47
|
"filelock>=3.15.4",
|
|
47
|
-
"platformdirs<5,>=4.3.6",
|
|
48
|
-
]
|
|
49
|
-
optional-dependencies.docs = [
|
|
50
|
-
"furo>=2025.12.19",
|
|
51
|
-
"sphinx>=9.1",
|
|
52
|
-
"sphinx-autodoc-typehints>=3.6.3",
|
|
53
|
-
"sphinxcontrib-mermaid>=2",
|
|
54
|
-
"sphinxcontrib-towncrier>=0.4",
|
|
55
|
-
"towncrier>=25.8",
|
|
56
|
-
]
|
|
57
|
-
optional-dependencies.testing = [
|
|
58
|
-
"covdefaults>=2.3",
|
|
59
|
-
"coverage>=7.5.4",
|
|
60
|
-
"pytest>=8.3.5",
|
|
61
|
-
"pytest-mock>=3.14",
|
|
62
|
-
"setuptools>=75.1",
|
|
63
48
|
]
|
|
64
49
|
urls.Changelog = "https://github.com/tox-dev/python-discovery/releases"
|
|
65
50
|
urls.Documentation = "https://python-discovery.readthedocs.io"
|
|
@@ -68,6 +53,21 @@ urls.Source = "https://github.com/tox-dev/python-discovery"
|
|
|
68
53
|
urls.Tracker = "https://github.com/tox-dev/python-discovery/issues"
|
|
69
54
|
|
|
70
55
|
[dependency-groups]
|
|
56
|
+
test = [
|
|
57
|
+
"covdefaults>=2.3",
|
|
58
|
+
"coverage>=7.5.4",
|
|
59
|
+
"pytest>=8.3.5",
|
|
60
|
+
"pytest-mock>=3.14",
|
|
61
|
+
"setuptools>=75.1",
|
|
62
|
+
]
|
|
63
|
+
docs = [
|
|
64
|
+
"furo>=2025.12.19",
|
|
65
|
+
"sphinx>=9.1",
|
|
66
|
+
"sphinx-autodoc-typehints>=3.6.3",
|
|
67
|
+
"sphinxcontrib-mermaid>=2",
|
|
68
|
+
"sphinxcontrib-towncrier>=0.4",
|
|
69
|
+
"towncrier>=25.8",
|
|
70
|
+
]
|
|
71
71
|
release = [
|
|
72
72
|
"gitpython>=3.1.46",
|
|
73
73
|
"packaging>=26",
|
|
@@ -145,7 +145,7 @@ write-changes = true
|
|
|
145
145
|
count = true
|
|
146
146
|
|
|
147
147
|
[tool.pyproject-fmt]
|
|
148
|
-
max_supported_python = "3.
|
|
148
|
+
max_supported_python = "3.15"
|
|
149
149
|
|
|
150
150
|
[tool.ty]
|
|
151
151
|
src.exclude = [ "tests/windows/winreg_mock_values.py" ]
|
|
@@ -104,7 +104,7 @@ class DiskContentStore:
|
|
|
104
104
|
|
|
105
105
|
@contextmanager
|
|
106
106
|
def locked(self) -> Generator[None]:
|
|
107
|
-
from filelock import FileLock #
|
|
107
|
+
from filelock import FileLock # ruff:ignore[import-outside-top-level]
|
|
108
108
|
|
|
109
109
|
lock_path = self._folder / f"{self._key}.lock"
|
|
110
110
|
lock_path.parent.mkdir(parents=True, exist_ok=True)
|
|
@@ -148,10 +148,10 @@ class DiskCache:
|
|
|
148
148
|
class NoOpContentStore(ContentStore):
|
|
149
149
|
"""Content store that does nothing -- implements ContentStore protocol."""
|
|
150
150
|
|
|
151
|
-
def exists(self) -> bool: #
|
|
151
|
+
def exists(self) -> bool: # ruff:ignore[no-self-use]
|
|
152
152
|
return False
|
|
153
153
|
|
|
154
|
-
def read(self) -> dict | None: #
|
|
154
|
+
def read(self) -> dict | None: # ruff:ignore[no-self-use]
|
|
155
155
|
return None
|
|
156
156
|
|
|
157
157
|
def write(self, content: dict) -> None:
|
|
@@ -161,14 +161,14 @@ class NoOpContentStore(ContentStore):
|
|
|
161
161
|
pass
|
|
162
162
|
|
|
163
163
|
@contextmanager
|
|
164
|
-
def locked(self) -> Generator[None]: #
|
|
164
|
+
def locked(self) -> Generator[None]: # ruff:ignore[no-self-use]
|
|
165
165
|
yield
|
|
166
166
|
|
|
167
167
|
|
|
168
168
|
class NoOpCache(PyInfoCache):
|
|
169
169
|
"""Cache that does nothing -- implements PyInfoCache protocol."""
|
|
170
170
|
|
|
171
|
-
def py_info(self, path: Path) -> NoOpContentStore: #
|
|
171
|
+
def py_info(self, path: Path) -> NoOpContentStore: # ruff:ignore[unused-method-argument, no-self-use]
|
|
172
172
|
return NoOpContentStore()
|
|
173
173
|
|
|
174
174
|
def py_info_clear(self) -> None:
|
|
@@ -8,14 +8,14 @@ import logging
|
|
|
8
8
|
import os
|
|
9
9
|
import pkgutil
|
|
10
10
|
import secrets
|
|
11
|
-
import subprocess #
|
|
11
|
+
import subprocess # ruff:ignore[suspicious-subprocess-import]
|
|
12
12
|
import sys
|
|
13
13
|
import tempfile
|
|
14
14
|
from collections import OrderedDict
|
|
15
15
|
from contextlib import contextmanager
|
|
16
16
|
from pathlib import Path
|
|
17
17
|
from shlex import quote
|
|
18
|
-
from subprocess import Popen, TimeoutExpired #
|
|
18
|
+
from subprocess import Popen, TimeoutExpired # ruff:ignore[suspicious-subprocess-import]
|
|
19
19
|
from typing import TYPE_CHECKING, Final
|
|
20
20
|
|
|
21
21
|
from ._cache import NoOpCache
|
|
@@ -32,7 +32,7 @@ _CACHE[Path(sys.executable)] = PythonInfo()
|
|
|
32
32
|
_LOGGER: Final[logging.Logger] = logging.getLogger(__name__)
|
|
33
33
|
|
|
34
34
|
|
|
35
|
-
def from_exe( #
|
|
35
|
+
def from_exe( # ruff:ignore[too-many-arguments]
|
|
36
36
|
cls: type[PythonInfo],
|
|
37
37
|
cache: PyInfoCache | None,
|
|
38
38
|
exe: str,
|
|
@@ -197,7 +197,7 @@ def _run_subprocess(
|
|
|
197
197
|
env["PYTHONUTF8"] = "1"
|
|
198
198
|
_LOGGER.debug("get interpreter info via cmd: %s", LogCmd(cmd))
|
|
199
199
|
try:
|
|
200
|
-
process = Popen( #
|
|
200
|
+
process = Popen( # ruff:ignore[subprocess-without-shell-equals-true]
|
|
201
201
|
cmd,
|
|
202
202
|
universal_newlines=True,
|
|
203
203
|
stdin=subprocess.PIPE,
|