python-discovery 1.4.4__tar.gz → 1.5.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.
Files changed (62) hide show
  1. {python_discovery-1.4.4 → python_discovery-1.5.3}/.github/dependabot.yaml +6 -1
  2. {python_discovery-1.4.4 → python_discovery-1.5.3}/.github/workflows/check.yaml +27 -5
  3. {python_discovery-1.4.4 → python_discovery-1.5.3}/.github/workflows/prepare-release.yaml +2 -11
  4. {python_discovery-1.4.4 → python_discovery-1.5.3}/.github/workflows/release.yaml +3 -7
  5. {python_discovery-1.4.4 → python_discovery-1.5.3}/.pre-commit-config.yaml +17 -10
  6. {python_discovery-1.4.4 → python_discovery-1.5.3}/PKG-INFO +6 -16
  7. {python_discovery-1.4.4 → python_discovery-1.5.3}/README.md +3 -0
  8. {python_discovery-1.4.4 → python_discovery-1.5.3}/docs/changelog.rst +43 -0
  9. {python_discovery-1.4.4 → python_discovery-1.5.3}/docs/conf.py +5 -1
  10. {python_discovery-1.4.4 → python_discovery-1.5.3}/docs/explanation.rst +79 -34
  11. {python_discovery-1.4.4 → python_discovery-1.5.3}/pyproject.toml +63 -50
  12. {python_discovery-1.4.4 → python_discovery-1.5.3}/src/python_discovery/_cache.py +5 -5
  13. {python_discovery-1.4.4 → python_discovery-1.5.3}/src/python_discovery/_cached_py_info.py +69 -33
  14. {python_discovery-1.4.4 → python_discovery-1.5.3}/src/python_discovery/_discovery.py +149 -22
  15. {python_discovery-1.4.4 → python_discovery-1.5.3}/src/python_discovery/_py_info.py +60 -304
  16. python_discovery-1.5.3/src/python_discovery/_py_info_collect.py +370 -0
  17. {python_discovery-1.4.4 → python_discovery-1.5.3}/src/python_discovery/_py_spec.py +3 -3
  18. {python_discovery-1.4.4 → python_discovery-1.5.3}/src/python_discovery/_specifier.py +9 -6
  19. {python_discovery-1.4.4 → python_discovery-1.5.3}/src/python_discovery/_windows/_pep514.py +11 -11
  20. python_discovery-1.5.3/tasks/old_target_check.py +45 -0
  21. python_discovery-1.5.3/tasks/old_target_check.sh +8 -0
  22. {python_discovery-1.4.4 → python_discovery-1.5.3}/tasks/release.py +35 -35
  23. {python_discovery-1.4.4 → python_discovery-1.5.3}/tests/py_info/test_py_info.py +17 -21
  24. {python_discovery-1.4.4 → python_discovery-1.5.3}/tests/test_cached_py_info.py +97 -1
  25. {python_discovery-1.4.4 → python_discovery-1.5.3}/tests/test_discovery.py +134 -62
  26. python_discovery-1.5.3/tests/test_iter_interpreters.py +423 -0
  27. python_discovery-1.5.3/tests/test_py_info_collect.py +333 -0
  28. {python_discovery-1.4.4 → python_discovery-1.5.3}/tests/test_py_info_extra.py +4 -222
  29. {python_discovery-1.4.4 → python_discovery-1.5.3}/tests/test_specifier.py +5 -5
  30. {python_discovery-1.4.4 → python_discovery-1.5.3}/tests/windows/conftest.py +1 -1
  31. {python_discovery-1.4.4 → python_discovery-1.5.3}/tox.toml +4 -4
  32. python_discovery-1.4.4/tests/test_iter_interpreters.py +0 -218
  33. {python_discovery-1.4.4 → python_discovery-1.5.3}/.github/FUNDING.yaml +0 -0
  34. {python_discovery-1.4.4 → python_discovery-1.5.3}/.github/SECURITY.md +0 -0
  35. {python_discovery-1.4.4 → python_discovery-1.5.3}/.github/release.yaml +0 -0
  36. {python_discovery-1.4.4 → python_discovery-1.5.3}/.gitignore +0 -0
  37. {python_discovery-1.4.4 → python_discovery-1.5.3}/.readthedocs.yaml +0 -0
  38. {python_discovery-1.4.4 → python_discovery-1.5.3}/.readthedocs.yml +0 -0
  39. {python_discovery-1.4.4 → python_discovery-1.5.3}/CODE_OF_CONDUCT.md +0 -0
  40. {python_discovery-1.4.4 → python_discovery-1.5.3}/LICENSE +0 -0
  41. {python_discovery-1.4.4 → python_discovery-1.5.3}/docs/_static/custom.css +0 -0
  42. {python_discovery-1.4.4 → python_discovery-1.5.3}/docs/_static/logo.svg +0 -0
  43. {python_discovery-1.4.4 → python_discovery-1.5.3}/docs/changelog/template.jinja2 +0 -0
  44. {python_discovery-1.4.4 → python_discovery-1.5.3}/docs/how-to/standalone-usage.rst +0 -0
  45. {python_discovery-1.4.4 → python_discovery-1.5.3}/docs/index.rst +0 -0
  46. {python_discovery-1.4.4 → python_discovery-1.5.3}/docs/reference/api.rst +0 -0
  47. {python_discovery-1.4.4 → python_discovery-1.5.3}/docs/reference/environment-variables.rst +0 -0
  48. {python_discovery-1.4.4 → python_discovery-1.5.3}/docs/tutorial/getting-started.rst +0 -0
  49. {python_discovery-1.4.4 → python_discovery-1.5.3}/src/python_discovery/__init__.py +0 -0
  50. {python_discovery-1.4.4 → python_discovery-1.5.3}/src/python_discovery/_compat.py +0 -0
  51. {python_discovery-1.4.4 → python_discovery-1.5.3}/src/python_discovery/_windows/__init__.py +0 -0
  52. {python_discovery-1.4.4 → python_discovery-1.5.3}/src/python_discovery/_windows/_propose.py +0 -0
  53. {python_discovery-1.4.4 → python_discovery-1.5.3}/src/python_discovery/py.typed +0 -0
  54. {python_discovery-1.4.4 → python_discovery-1.5.3}/tests/conftest.py +0 -0
  55. {python_discovery-1.4.4 → python_discovery-1.5.3}/tests/py_info/test_py_info_exe_based_of.py +0 -0
  56. {python_discovery-1.4.4 → python_discovery-1.5.3}/tests/test_cache.py +0 -0
  57. {python_discovery-1.4.4 → python_discovery-1.5.3}/tests/test_discovery_extra.py +0 -0
  58. {python_discovery-1.4.4 → python_discovery-1.5.3}/tests/test_py_spec.py +0 -0
  59. {python_discovery-1.4.4 → python_discovery-1.5.3}/tests/test_py_spec_extra.py +0 -0
  60. {python_discovery-1.4.4 → python_discovery-1.5.3}/tests/windows/test_windows.py +0 -0
  61. {python_discovery-1.4.4 → python_discovery-1.5.3}/tests/windows/test_windows_pep514.py +0 -0
  62. {python_discovery-1.4.4 → python_discovery-1.5.3}/tests/windows/winreg_mock_values.py +0 -0
@@ -3,6 +3,11 @@ updates:
3
3
  - package-ecosystem: "github-actions"
4
4
  directory: "/"
5
5
  schedule:
6
- interval: "daily"
6
+ interval: "weekly"
7
+ day: "tuesday"
8
+ groups:
9
+ github-actions:
10
+ patterns:
11
+ - "*"
7
12
  cooldown:
8
13
  default-days: 7
@@ -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@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
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@fac544c07dec837d0ccb6301d7b5580bf5edae39 # v8.2.0
41
+ uses: astral-sh/setup-uv@20cfd1bf945f4377ade1205e4dbc17946fc9a30d # v10.0.1
43
42
  with:
44
43
  enable-cache: false
45
44
  cache-dependency-glob: "pyproject.toml"
@@ -56,3 +55,26 @@ jobs:
56
55
  env:
57
56
  PYTEST_ADDOPTS: "-vv --durations=20"
58
57
  DIFF_AGAINST: HEAD
58
+ old-target:
59
+ runs-on: ubuntu-latest
60
+ steps:
61
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
62
+ with:
63
+ fetch-depth: 0
64
+ persist-credentials: false
65
+ - name: Install the latest version of uv
66
+ uses: astral-sh/setup-uv@20cfd1bf945f4377ade1205e4dbc17946fc9a30d # v10.0.1
67
+ with:
68
+ enable-cache: false
69
+ cache-dependency-glob: "pyproject.toml"
70
+ github-token: ${{ secrets.GITHUB_TOKEN }}
71
+ - name: Build the wheel
72
+ run: uv build --wheel --out-dir dist .
73
+ - name: Discover Python 3.6
74
+ run: tasks/old_target_check.sh 3.6 discover
75
+ - name: Discover Python 3.7
76
+ run: tasks/old_target_check.sh 3.7 discover
77
+ - name: Reject Python 3.5
78
+ run: tasks/old_target_check.sh 3.5 rejected
79
+ - name: Reject Python 2.7
80
+ run: tasks/old_target_check.sh 2.7 rejected
@@ -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@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
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@fac544c07dec837d0ccb6301d7b5580bf5edae39 # v8.2.0
32
+ uses: astral-sh/setup-uv@20cfd1bf945f4377ade1205e4dbc17946fc9a30d # v10.0.1
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@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
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@fac544c07dec837d0ccb6301d7b5580bf5edae39 # v8.2.0
18
+ uses: astral-sh/setup-uv@20cfd1bf945f4377ade1205e4dbc17946fc9a30d # v10.0.1
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@cef221092ed1bacb1cc03d23a2d87d1d172e277b # v1.14.0
46
+ uses: pypa/gh-action-pypi-publish@dc37677b2e1c63e2034f94d8a5b11f265b73ba33 # v1.14.2
51
47
  with:
52
48
  attestations: true
@@ -5,34 +5,41 @@ 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.4
8
+ rev: 0.38.0
9
9
  hooks:
10
10
  - id: check-github-workflows
11
11
  args: ["--verbose"]
12
12
  - repo: https://github.com/codespell-project/codespell
13
- rev: v2.4.2
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.25.1"
18
+ rev: "v2.28.0"
19
19
  hooks:
20
20
  - id: pyproject-fmt
21
21
  - repo: https://github.com/astral-sh/ruff-pre-commit
22
- rev: "v0.15.20"
22
+ rev: "v0.16.3"
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/rbubley/mirrors-prettier
28
- rev: "v3.9.4"
27
+ - repo: https://github.com/google/yamlfmt
28
+ rev: "v0.21.0"
29
29
  hooks:
30
- - id: prettier
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
- - prettier@3.8.1
33
- - "@prettier/plugin-xml@3.4.2"
39
+ - mdformat-gfm>=1
40
+ - mdformat-ruff>=0.1.3
34
41
  - repo: https://github.com/zizmorcore/zizmor-pre-commit
35
- rev: v1.26.1
42
+ rev: v1.29.0
36
43
  hooks:
37
44
  - id: zizmor
38
45
  - repo: meta
@@ -1,6 +1,6 @@
1
- Metadata-Version: 2.4
1
+ Metadata-Version: 2.5
2
2
  Name: python-discovery
3
- Version: 1.4.4
3
+ Version: 1.5.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
@@ -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/)
@@ -81,6 +68,9 @@ the right one for you.
81
68
  Give it a requirement like `python3.12` or `>=3.11,<3.13`, and it searches all known locations, verifies each candidate,
82
69
  and returns detailed metadata about the match. Results are cached to disk so repeated lookups are fast.
83
70
 
71
+ The library runs on Python 3.8+ and discovers interpreters as old as Python 3.6; anything older is skipped with a
72
+ warning naming the version it found.
73
+
84
74
  ## Usage
85
75
 
86
76
  ```python
@@ -16,6 +16,9 @@ the right one for you.
16
16
  Give it a requirement like `python3.12` or `>=3.11,<3.13`, and it searches all known locations, verifies each candidate,
17
17
  and returns detailed metadata about the match. Results are cached to disk so repeated lookups are fast.
18
18
 
19
+ The library runs on Python 3.8+ and discovers interpreters as old as Python 3.6; anything older is skipped with a
20
+ warning naming the version it found.
21
+
19
22
  ## Usage
20
23
 
21
24
  ```python
@@ -6,6 +6,49 @@
6
6
 
7
7
  .. towncrier release notes start
8
8
 
9
+ ********************
10
+ v1.5.3 (2026-08-24)
11
+ ********************
12
+
13
+ Bug fixes - 1.5.3
14
+ =================
15
+ - Restore discovery of Python 3.6 and 3.7 interpreters: the interrogation script had grown 3.8-only syntax and is now
16
+ kept to Python 3.6. Candidates below 3.6 log one warning naming the version found instead of two debug-level query
17
+ attempts, and the verdict is cached so they are queried only once; an absolute-path spec for one raises
18
+ ``RuntimeError`` with the same message - by :user:`gaborbernat`. (:issue:`116`)
19
+ - Prefer the version-matched system executable when CPython reports an existing generic executable for a copied POSIX
20
+ virtual environment. (:issue:`124`)
21
+
22
+ Improved documentation - 1.5.3
23
+ ==============================
24
+ - Document the version floors: runs on Python 3.8+, discovers interpreters down to 3.6 - by :user:`gaborbernat`. (:issue:`116`)
25
+
26
+ ********************
27
+ v1.5.1 (2026-07-31)
28
+ ********************
29
+
30
+ Bug fixes - 1.5.1
31
+ =================
32
+ - Locate uv's interpreter store the way uv does, using XDG on macOS (``~/.local/share/uv/python``) and roaming
33
+ ``%APPDATA%\uv\python`` on Windows, neither of which ``platformdirs`` reported. The uv variables now come from the
34
+ ``env`` mapping rather than ``os.environ`` - by :user:`gaborbernat`. (:issue:`109`)
35
+ - Pick uv-managed interpreters by install directory name instead of globbing the store. A bare ``3.8`` now means
36
+ CPython, the way uv resolves it, rather than whichever implementation the filesystem happened to list first, and
37
+ probing runs newest version first - by :user:`gaborbernat`. (:issue:`110`)
38
+
39
+ Packaging updates and notes for downstreams - 1.5.1
40
+ ===================================================
41
+ - Drop the ``platformdirs`` runtime dependency; locating uv's store was the only thing it did - by
42
+ :user:`gaborbernat`. (:issue:`109`)
43
+
44
+ ********************
45
+ v1.5.0 (2026-07-21)
46
+ ********************
47
+
48
+ Packaging updates and notes for downstreams - 1.5.0
49
+ ===================================================
50
+ - Declare support for Python 3.15 and run the test suite against it, currently in beta. (:issue:`104`)
51
+
9
52
  ********************
10
53
  v1.4.4 (2026-07-08)
11
54
  ********************
@@ -2,6 +2,7 @@
2
2
 
3
3
  from __future__ import annotations
4
4
 
5
+ import os
5
6
  from datetime import datetime, timezone
6
7
  from pathlib import Path
7
8
 
@@ -11,9 +12,10 @@ company = "tox-dev"
11
12
  name = "python-discovery"
12
13
  version = ".".join(__version__.split(".")[:2])
13
14
  release = __version__
14
- copyright = f"2026-{datetime.now(tz=timezone.utc).year}, {company}" # noqa: A001
15
+ copyright = f"2026-{datetime.now(tz=timezone.utc).year}, {company}" # ruff:ignore[builtin-variable-shadowing]
15
16
 
16
17
  extensions = [
18
+ "sphinx_llm.txt",
17
19
  "sphinx.ext.autodoc",
18
20
  "sphinx.ext.autosectionlabel",
19
21
  "sphinx.ext.extlinks",
@@ -58,3 +60,5 @@ html_theme_options = {
58
60
  "sidebar_hide_name": True,
59
61
  }
60
62
  html_css_files = ["custom.css"]
63
+
64
+ markdown_http_base = os.environ.get("READTHEDOCS_CANONICAL_URL", "")
@@ -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 (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:
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
- 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
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
- 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.
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
  -------------------------------------------------------
@@ -210,6 +243,18 @@ The timeout applies to each individual interpreter being queried. If you set a v
210
243
  legitimate interpreters may be skipped; if too high, the discovery process may take longer to fail
211
244
  when encountering problematic interpreters.
212
245
 
246
+ Supported Python versions
247
+ -------------------------
248
+
249
+ Two floors apply: python-discovery itself runs on Python 3.8+ (``requires-python``), while discovery
250
+ reaches down to Python 3.6 - old enough to cover RHEL 8's system Python.
251
+
252
+ python-discovery skips candidates below 3.6 with one warning naming the executable, the version
253
+ found, and the floor; the verdict is cached, so the interpreter is not queried again. Candidates too
254
+ old to run the version check at all (before Python 2.7) fail as a generic query error. An
255
+ absolute-path spec for a below-floor interpreter raises :class:`RuntimeError` with the same message.
256
+ The floor moves only in a major release.
257
+
213
258
  Spec format reference
214
259
  -----------------------
215
260