python-discovery 1.0.0__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 (49) hide show
  1. python_discovery-1.0.0/.github/dependabot.yml +6 -0
  2. python_discovery-1.0.0/.github/release.yml +5 -0
  3. python_discovery-1.0.0/.github/workflows/check.yaml +54 -0
  4. python_discovery-1.0.0/.github/workflows/release.yaml +48 -0
  5. python_discovery-1.0.0/.gitignore +5 -0
  6. python_discovery-1.0.0/.pre-commit-config.yaml +37 -0
  7. python_discovery-1.0.0/.readthedocs.yaml +8 -0
  8. python_discovery-1.0.0/.readthedocs.yml +8 -0
  9. python_discovery-1.0.0/CODE_OF_CONDUCT.md +60 -0
  10. python_discovery-1.0.0/LICENSE +18 -0
  11. python_discovery-1.0.0/PKG-INFO +71 -0
  12. python_discovery-1.0.0/README.md +8 -0
  13. python_discovery-1.0.0/docs/_static/custom.css +4 -0
  14. python_discovery-1.0.0/docs/_static/logo.svg +11 -0
  15. python_discovery-1.0.0/docs/conf.py +48 -0
  16. python_discovery-1.0.0/docs/explanation.rst +153 -0
  17. python_discovery-1.0.0/docs/how-to/standalone-usage.rst +143 -0
  18. python_discovery-1.0.0/docs/index.rst +48 -0
  19. python_discovery-1.0.0/docs/reference/api.rst +7 -0
  20. python_discovery-1.0.0/docs/tutorial/getting-started.rst +180 -0
  21. python_discovery-1.0.0/pyproject.toml +181 -0
  22. python_discovery-1.0.0/src/python_discovery/__init__.py +22 -0
  23. python_discovery-1.0.0/src/python_discovery/_cache.py +153 -0
  24. python_discovery-1.0.0/src/python_discovery/_cached_py_info.py +259 -0
  25. python_discovery-1.0.0/src/python_discovery/_compat.py +29 -0
  26. python_discovery-1.0.0/src/python_discovery/_discovery.py +308 -0
  27. python_discovery-1.0.0/src/python_discovery/_py_info.py +726 -0
  28. python_discovery-1.0.0/src/python_discovery/_py_spec.py +235 -0
  29. python_discovery-1.0.0/src/python_discovery/_specifier.py +264 -0
  30. python_discovery-1.0.0/src/python_discovery/_windows/__init__.py +13 -0
  31. python_discovery-1.0.0/src/python_discovery/_windows/_pep514.py +222 -0
  32. python_discovery-1.0.0/src/python_discovery/_windows/_propose.py +53 -0
  33. python_discovery-1.0.0/src/python_discovery/py.typed +0 -0
  34. python_discovery-1.0.0/tests/conftest.py +29 -0
  35. python_discovery-1.0.0/tests/py_info/test_py_info.py +455 -0
  36. python_discovery-1.0.0/tests/py_info/test_py_info_exe_based_of.py +82 -0
  37. python_discovery-1.0.0/tests/test_cache.py +115 -0
  38. python_discovery-1.0.0/tests/test_cached_py_info.py +216 -0
  39. python_discovery-1.0.0/tests/test_discovery.py +411 -0
  40. python_discovery-1.0.0/tests/test_discovery_extra.py +241 -0
  41. python_discovery-1.0.0/tests/test_py_info_extra.py +512 -0
  42. python_discovery-1.0.0/tests/test_py_spec.py +237 -0
  43. python_discovery-1.0.0/tests/test_py_spec_extra.py +130 -0
  44. python_discovery-1.0.0/tests/test_specifier.py +299 -0
  45. python_discovery-1.0.0/tests/windows/conftest.py +175 -0
  46. python_discovery-1.0.0/tests/windows/test_windows.py +35 -0
  47. python_discovery-1.0.0/tests/windows/test_windows_pep514.py +156 -0
  48. python_discovery-1.0.0/tests/windows/winreg_mock_values.py +170 -0
  49. python_discovery-1.0.0/tox.toml +78 -0
@@ -0,0 +1,6 @@
1
+ version: 2
2
+ updates:
3
+ - package-ecosystem: "github-actions"
4
+ directory: "/"
5
+ schedule:
6
+ interval: "daily"
@@ -0,0 +1,5 @@
1
+ changelog:
2
+ exclude:
3
+ authors:
4
+ - dependabot
5
+ - pre-commit-ci
@@ -0,0 +1,54 @@
1
+ name: check
2
+ on:
3
+ workflow_dispatch:
4
+ push:
5
+ branches: ["main"]
6
+ tags-ignore: ["**"]
7
+ pull_request:
8
+ schedule:
9
+ - cron: "0 8 * * *"
10
+
11
+ concurrency:
12
+ group: check-${{ github.ref }}
13
+ cancel-in-progress: true
14
+
15
+ jobs:
16
+ test:
17
+ runs-on: ubuntu-latest
18
+ strategy:
19
+ fail-fast: false
20
+ matrix:
21
+ env:
22
+ - "3.14"
23
+ - "3.13"
24
+ - "3.12"
25
+ - "3.11"
26
+ - "3.10"
27
+ - "3.9"
28
+ - "3.8"
29
+ - type-3.8
30
+ - type-3.14
31
+ - dev
32
+ - pkg_meta
33
+ steps:
34
+ - uses: actions/checkout@v6
35
+ with:
36
+ fetch-depth: 0
37
+ - name: Install the latest version of uv
38
+ uses: astral-sh/setup-uv@v7
39
+ with:
40
+ enable-cache: true
41
+ cache-dependency-glob: "pyproject.toml"
42
+ github-token: ${{ secrets.GITHUB_TOKEN }}
43
+ - name: Install tox
44
+ run: uv tool install --python-preference only-managed --python 3.14 tox --with tox-uv
45
+ - name: Install Python
46
+ if: startsWith(matrix.env, '3.') && matrix.env != '3.14'
47
+ run: uv python install --python-preference only-managed ${{ matrix.env }}
48
+ - name: Setup test suite
49
+ run: tox run -vv --notest --skip-missing-interpreters false -e ${{ matrix.env }}
50
+ - name: Run test suite
51
+ run: tox run --skip-pkg-install -e ${{ matrix.env }}
52
+ env:
53
+ PYTEST_ADDOPTS: "-vv --durations=20"
54
+ DIFF_AGAINST: HEAD
@@ -0,0 +1,48 @@
1
+ name: Release to PyPI
2
+ on:
3
+ push:
4
+ tags: ["*"]
5
+
6
+ env:
7
+ dists-artifact-name: python-package-distributions
8
+
9
+ jobs:
10
+ build:
11
+ runs-on: ubuntu-latest
12
+ steps:
13
+ - uses: actions/checkout@v6
14
+ with:
15
+ fetch-depth: 0
16
+ - name: Install the latest version of uv
17
+ uses: astral-sh/setup-uv@v7
18
+ with:
19
+ enable-cache: true
20
+ cache-dependency-glob: "pyproject.toml"
21
+ github-token: ${{ secrets.GITHUB_TOKEN }}
22
+ - name: Build package
23
+ run: uv build --python 3.14 --python-preference only-managed --sdist --wheel . --out-dir dist
24
+ - name: Store the distribution packages
25
+ uses: actions/upload-artifact@v6
26
+ with:
27
+ name: ${{ env.dists-artifact-name }}
28
+ path: dist/*
29
+
30
+ release:
31
+ needs:
32
+ - build
33
+ runs-on: ubuntu-latest
34
+ environment:
35
+ name: release
36
+ url: https://pypi.org/project/python-discovery/${{ github.ref_name }}
37
+ permissions:
38
+ id-token: write
39
+ steps:
40
+ - name: Download all the dists
41
+ uses: actions/download-artifact@v7
42
+ with:
43
+ name: ${{ env.dists-artifact-name }}
44
+ path: dist/
45
+ - name: Publish to PyPI
46
+ uses: pypa/gh-action-pypi-publish@v1.13.0
47
+ with:
48
+ attestations: true
@@ -0,0 +1,5 @@
1
+ *.pyc
2
+ *.egg-info
3
+ dist/
4
+ .tox/
5
+ /src/python_discovery/_version.py
@@ -0,0 +1,37 @@
1
+ repos:
2
+ - repo: https://github.com/pre-commit/pre-commit-hooks
3
+ rev: v6.0.0
4
+ hooks:
5
+ - id: end-of-file-fixer
6
+ - id: trailing-whitespace
7
+ - repo: https://github.com/python-jsonschema/check-jsonschema
8
+ rev: 0.36.1
9
+ hooks:
10
+ - id: check-github-workflows
11
+ args: ["--verbose"]
12
+ - repo: https://github.com/codespell-project/codespell
13
+ rev: v2.4.1
14
+ hooks:
15
+ - id: codespell
16
+ additional_dependencies: ["tomli>=2.4"]
17
+ - repo: https://github.com/tox-dev/pyproject-fmt
18
+ rev: "v2.11.1"
19
+ hooks:
20
+ - id: pyproject-fmt
21
+ - repo: https://github.com/astral-sh/ruff-pre-commit
22
+ rev: "v0.14.14"
23
+ hooks:
24
+ - id: ruff-format
25
+ - id: ruff
26
+ args: ["--fix", "--unsafe-fixes", "--exit-non-zero-on-fix"]
27
+ - repo: https://github.com/rbubley/mirrors-prettier
28
+ rev: "v3.8.1"
29
+ hooks:
30
+ - id: prettier
31
+ additional_dependencies:
32
+ - prettier@3.8.1
33
+ - "@prettier/plugin-xml@3.4.2"
34
+ - repo: meta
35
+ hooks:
36
+ - id: check-hooks-apply
37
+ - id: check-useless-excludes
@@ -0,0 +1,8 @@
1
+ version: 2
2
+ build:
3
+ os: ubuntu-lts-latest
4
+ tools: {}
5
+ commands:
6
+ - curl -LsSf https://astral.sh/uv/install.sh | sh
7
+ - ~/.local/bin/uv tool install tox --with tox-uv -p 3.14 --managed-python
8
+ - ~/.local/bin/tox run -e docs --
@@ -0,0 +1,8 @@
1
+ version: 2
2
+ build:
3
+ os: ubuntu-22.04
4
+ tools:
5
+ python: "3.12"
6
+ commands:
7
+ - pip install tox
8
+ - tox r -e docs -- "${READTHEDOCS_OUTPUT}"/html
@@ -0,0 +1,60 @@
1
+ # Contributor Covenant Code of Conduct
2
+
3
+ ## Our Pledge
4
+
5
+ In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making
6
+ participation in our project and our community a harassment-free experience for everyone, regardless of age, body size,
7
+ disability, ethnicity, gender identity and expression, level of experience, nationality, personal appearance, race,
8
+ religion, or sexual identity and orientation.
9
+
10
+ ## Our Standards
11
+
12
+ Examples of behavior that contributes to creating a positive environment include:
13
+
14
+ - Using welcoming and inclusive language
15
+ - Being respectful of differing viewpoints and experiences
16
+ - Gracefully accepting constructive criticism
17
+ - Focusing on what is best for the community
18
+ - Showing empathy towards other community members
19
+
20
+ Examples of unacceptable behavior by participants include:
21
+
22
+ - The use of sexualized language or imagery and unwelcome sexual attention or advances
23
+ - Trolling, insulting/derogatory comments, and personal or political attacks
24
+ - Public or private harassment
25
+ - Publishing others' private information, such as a physical or electronic address, without explicit permission
26
+ - Other conduct which could reasonably be considered inappropriate in a professional setting
27
+
28
+ ## Our Responsibilities
29
+
30
+ Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take
31
+ appropriate and fair corrective action in response to any instances of unacceptable behavior.
32
+
33
+ Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits,
34
+ issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any
35
+ contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful.
36
+
37
+ ## Scope
38
+
39
+ This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the
40
+ project or its community. Examples of representing a project or community include using an official project e-mail
41
+ address, posting via an official social media account, or acting as an appointed representative at an online or offline
42
+ event. Representation of a project may be further defined and clarified by project maintainers.
43
+
44
+ ## Enforcement
45
+
46
+ Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at
47
+ tox-dev@python.org. The project team will review and investigate all complaints, and will respond in a way that it deems
48
+ appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter
49
+ of an incident. Further details of specific enforcement policies may be posted separately.
50
+
51
+ Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent
52
+ repercussions as determined by other members of the project's leadership.
53
+
54
+ ## Attribution
55
+
56
+ This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available at
57
+ [https://www.contributor-covenant.org/version/1/4/code-of-conduct.html][version]
58
+
59
+ [homepage]: https://www.contributor-covenant.org/
60
+ [version]: https://www.contributor-covenant.org/version/1/4/
@@ -0,0 +1,18 @@
1
+ Permission is hereby granted, free of charge, to any person obtaining a
2
+ copy of this software and associated documentation files (the
3
+ "Software"), to deal in the Software without restriction, including
4
+ without limitation the rights to use, copy, modify, merge, publish,
5
+ distribute, sublicense, and/or sell copies of the Software, and to
6
+ permit persons to whom the Software is furnished to do so, subject to
7
+ the following conditions:
8
+
9
+ The above copyright notice and this permission notice shall be included
10
+ in all copies or substantial portions of the Software.
11
+
12
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
13
+ OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
14
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
15
+ IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
16
+ CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
17
+ TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
18
+ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,71 @@
1
+ Metadata-Version: 2.4
2
+ Name: python-discovery
3
+ Version: 1.0.0
4
+ Summary: Python interpreter discovery
5
+ Project-URL: Changelog, https://github.com/tox-dev/python-discovery/releases
6
+ Project-URL: Documentation, https://python-discovery.readthedocs.io
7
+ Project-URL: Homepage, https://github.com/tox-dev/python-discovery
8
+ Project-URL: Source, https://github.com/tox-dev/python-discovery
9
+ Project-URL: Tracker, https://github.com/tox-dev/python-discovery/issues
10
+ Maintainer-email: Bernát Gábor <gaborjbernat@gmail.com>
11
+ License: Permission is hereby granted, free of charge, to any person obtaining a
12
+ copy of this software and associated documentation files (the
13
+ "Software"), to deal in the Software without restriction, including
14
+ without limitation the rights to use, copy, modify, merge, publish,
15
+ distribute, sublicense, and/or sell copies of the Software, and to
16
+ permit persons to whom the Software is furnished to do so, subject to
17
+ the following conditions:
18
+
19
+ The above copyright notice and this permission notice shall be included
20
+ in all copies or substantial portions of the Software.
21
+
22
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
23
+ OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
24
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
25
+ IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
26
+ CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
27
+ TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
28
+ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
29
+ License-File: LICENSE
30
+ Keywords: discovery,interpreter,python
31
+ Classifier: Development Status :: 5 - Production/Stable
32
+ Classifier: Intended Audience :: Developers
33
+ Classifier: License :: OSI Approved :: MIT License
34
+ Classifier: Operating System :: MacOS :: MacOS X
35
+ Classifier: Operating System :: Microsoft :: Windows
36
+ Classifier: Operating System :: POSIX
37
+ Classifier: Programming Language :: Python :: 3 :: Only
38
+ Classifier: Programming Language :: Python :: 3.8
39
+ Classifier: Programming Language :: Python :: 3.9
40
+ Classifier: Programming Language :: Python :: 3.10
41
+ Classifier: Programming Language :: Python :: 3.11
42
+ Classifier: Programming Language :: Python :: 3.12
43
+ Classifier: Programming Language :: Python :: 3.13
44
+ Classifier: Programming Language :: Python :: 3.14
45
+ Classifier: Programming Language :: Python :: Implementation :: CPython
46
+ Classifier: Topic :: Software Development :: Libraries
47
+ Classifier: Topic :: Utilities
48
+ Requires-Python: >=3.8
49
+ 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
+ Provides-Extra: testing
57
+ Requires-Dist: covdefaults>=2.3; extra == 'testing'
58
+ Requires-Dist: coverage>=7.5.4; extra == 'testing'
59
+ Requires-Dist: pytest-mock>=3.14; extra == 'testing'
60
+ Requires-Dist: pytest>=8.3.5; extra == 'testing'
61
+ Requires-Dist: setuptools>=75.1; extra == 'testing'
62
+ Description-Content-Type: text/markdown
63
+
64
+ # [`python-discovery`](https://python-discovery.readthedocs.io/en/latest/)
65
+
66
+ [![PyPI](https://img.shields.io/pypi/v/python-discovery?style=flat-square)](https://pypi.org/project/python-discovery/)
67
+ [![Supported Python
68
+ versions](https://img.shields.io/pypi/pyversions/python-discovery.svg)](https://pypi.org/project/python-discovery/)
69
+ [![Downloads](https://static.pepy.tech/badge/python-discovery/month)](https://pepy.tech/project/python-discovery)
70
+ [![check](https://github.com/tox-dev/python-discovery/actions/workflows/check.yml/badge.svg)](https://github.com/tox-dev/python-discovery/actions/workflows/check.yml)
71
+ [![Documentation Status](https://readthedocs.org/projects/python-discovery/badge/?version=latest)](https://python-discovery.readthedocs.io/en/latest/?badge=latest)
@@ -0,0 +1,8 @@
1
+ # [`python-discovery`](https://python-discovery.readthedocs.io/en/latest/)
2
+
3
+ [![PyPI](https://img.shields.io/pypi/v/python-discovery?style=flat-square)](https://pypi.org/project/python-discovery/)
4
+ [![Supported Python
5
+ versions](https://img.shields.io/pypi/pyversions/python-discovery.svg)](https://pypi.org/project/python-discovery/)
6
+ [![Downloads](https://static.pepy.tech/badge/python-discovery/month)](https://pepy.tech/project/python-discovery)
7
+ [![check](https://github.com/tox-dev/python-discovery/actions/workflows/check.yml/badge.svg)](https://github.com/tox-dev/python-discovery/actions/workflows/check.yml)
8
+ [![Documentation Status](https://readthedocs.org/projects/python-discovery/badge/?version=latest)](https://python-discovery.readthedocs.io/en/latest/?badge=latest)
@@ -0,0 +1,4 @@
1
+ .sidebar-logo img {
2
+ max-width: 100%;
3
+ width: 100%;
4
+ }
@@ -0,0 +1,11 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" width="128" height="128" viewBox="0 0 24 24" fill="none" stroke-linecap="round" stroke-linejoin="round">
2
+
3
+ <!-- Magnifying glass (Python blue) -->
4
+ <circle cx="10.5" cy="10.5" r="6.5" stroke="#3776AB" stroke-width="2"></circle>
5
+ <path d="M15.7 15.7L21 21" stroke="#3776AB" stroke-width="2"></path>
6
+
7
+ <!-- "<>" (Python yellow) -->
8
+ <path d="M9.3 8.9L7.7 10.5L9.3 12.1" stroke="#FFD43B" stroke-width="2"></path>
9
+ <path d="M11.7 8.9L13.3 10.5L11.7 12.1" stroke="#FFD43B" stroke-width="2"></path>
10
+
11
+ </svg>
@@ -0,0 +1,48 @@
1
+ """Sphinx configuration for python-discovery documentation."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from datetime import datetime, timezone
6
+
7
+ from python_discovery import __version__
8
+
9
+ company = "tox-dev"
10
+ name = "python-discovery"
11
+ version = ".".join(__version__.split(".")[:2])
12
+ release = __version__
13
+ copyright = f"2026-{datetime.now(tz=timezone.utc).year}, {company}" # noqa: A001
14
+
15
+ extensions = [
16
+ "sphinx.ext.autodoc",
17
+ "sphinx.ext.autosectionlabel",
18
+ "sphinx.ext.extlinks",
19
+ "sphinx.ext.intersphinx",
20
+ "sphinx_autodoc_typehints",
21
+ "sphinxcontrib.mermaid",
22
+ ]
23
+
24
+ intersphinx_mapping = {
25
+ "python": ("https://docs.python.org/3", None),
26
+ }
27
+
28
+ templates_path = []
29
+ source_suffix = ".rst"
30
+ exclude_patterns = ["_build"]
31
+
32
+ main_doc = "index"
33
+ pygments_style = "default"
34
+ always_document_param_types = True
35
+ project = name
36
+
37
+ html_theme = "furo"
38
+ html_title = project
39
+ html_last_updated_fmt = datetime.now(tz=timezone.utc).isoformat()
40
+ pygments_dark_style = "monokai"
41
+ html_show_sourcelink = False
42
+ html_static_path = ["_static"]
43
+ html_theme_options = {
44
+ "light_logo": "logo.svg",
45
+ "dark_logo": "logo.svg",
46
+ "sidebar_hide_name": True,
47
+ }
48
+ html_css_files = ["custom.css"]
@@ -0,0 +1,153 @@
1
+ How it works
2
+ ============
3
+
4
+ Where does python-discovery look?
5
+ -------------------------------
6
+
7
+ When you call :func:`~python_discovery.get_interpreter`, the library checks several locations in
8
+ order. It stops as soon as it finds an interpreter that matches your spec.
9
+
10
+ .. mermaid::
11
+
12
+ flowchart TD
13
+ Start["get_interpreter()"] --> AbsPath{"Is spec an<br>absolute path?"}
14
+ AbsPath -->|Yes| TryAbs["Use path directly"]
15
+ AbsPath -->|No| TryFirst["try_first_with paths"]
16
+ TryFirst --> RelPath{"Is spec a<br>relative path?"}
17
+ RelPath -->|Yes| TryRel["Resolve relative to cwd"]
18
+ RelPath -->|No| Current["Current interpreter"]
19
+ Current --> Win{"Windows?"}
20
+ Win -->|Yes| PEP514["PEP 514 registry"]
21
+ Win -->|No| PATH
22
+ PEP514 --> PATH["PATH search"]
23
+ PATH --> Shims["Version-manager shims<br>(pyenv / mise / asdf)"]
24
+ Shims --> UV["uv-managed Pythons"]
25
+
26
+ TryAbs --> Verify
27
+ TryRel --> Verify
28
+ UV --> Verify
29
+
30
+ Verify{{"Verify candidate<br>(subprocess call)"}}
31
+ Verify -->|Matches spec| Cache["Cache and return"]
32
+ Verify -->|No match| Next["Try next candidate"]
33
+
34
+ style Start fill:#4a90d9,stroke:#2a5f8f,color:#fff
35
+ style Verify fill:#d9904a,stroke:#8f5f2a,color:#fff
36
+ style Cache fill:#4a9f4a,stroke:#2a6f2a,color:#fff
37
+ style Next fill:#d94a4a,stroke:#8f2a2a,color:#fff
38
+
39
+ Each candidate is verified by running it as a subprocess and collecting its metadata (version,
40
+ architecture, platform, sysconfig values, etc.). This subprocess call is the expensive part, which
41
+ is why results are cached.
42
+
43
+ How version-manager shims are handled
44
+ -----------------------------------------
45
+
46
+ Version managers like `pyenv <https://github.com/pyenv/pyenv>`_ install thin wrapper scripts called
47
+ **shims** (e.g., ``~/.pyenv/shims/python3.12``) that redirect to the real interpreter. python-discovery
48
+ detects these shims and resolves them to the actual binary.
49
+
50
+ .. mermaid::
51
+
52
+ flowchart TD
53
+ Shim["Shim detected"] --> EnvVar{"PYENV_VERSION<br>set?"}
54
+ EnvVar -->|Yes| Use["Use that version"]
55
+ EnvVar -->|No| File{".python-version<br>file exists?"}
56
+ File -->|Yes| Use
57
+ File -->|No| Global{"pyenv global<br>version exists?"}
58
+ Global -->|Yes| Use
59
+ Global -->|No| Skip["Skip shim"]
60
+
61
+ style Shim fill:#4a90d9,stroke:#2a5f8f,color:#fff
62
+ style Use fill:#4a9f4a,stroke:#2a6f2a,color:#fff
63
+ style Skip fill:#d94a4a,stroke:#8f2a2a,color:#fff
64
+
65
+ `mise <https://mise.jdx.dev/>`_ and `asdf <https://asdf-vm.com/>`_ work similarly, using the
66
+ ``MISE_DATA_DIR`` and ``ASDF_DATA_DIR`` environment variables to locate their installations.
67
+
68
+ How caching works
69
+ -------------------
70
+
71
+ Querying an interpreter requires a subprocess call, which is slow. The cache avoids repeating this
72
+ work by storing the result as a JSON file keyed by the interpreter's path.
73
+
74
+ .. mermaid::
75
+
76
+ flowchart TD
77
+ Lookup["py_info(path)"] --> Exists{"Cache hit?"}
78
+ Exists -->|Yes| Read["Read JSON"]
79
+ Exists -->|No| Run["Run subprocess"]
80
+ Run --> Write["Write JSON<br>(with filelock)"]
81
+ Write --> Return["Return PythonInfo"]
82
+ Read --> Return
83
+
84
+ style Lookup fill:#4a90d9,stroke:#2a5f8f,color:#fff
85
+ style Return fill:#4a9f4a,stroke:#2a6f2a,color:#fff
86
+ style Run fill:#d9904a,stroke:#8f5f2a,color:#fff
87
+
88
+ The built-in :class:`~python_discovery.DiskCache` stores files under ``<root>/py_info/4/<sha256>.json``
89
+ with `filelock <https://py-filelock.readthedocs.io/>`_-based locking for safe concurrent access. You
90
+ can also pass ``cache=None`` to disable caching, or implement your own backend (see
91
+ :doc:`/how-to/standalone-usage`).
92
+
93
+ Spec format reference
94
+ -----------------------
95
+
96
+ A spec string follows the pattern ``[impl][version][t][-arch][-machine]``. Every part is optional.
97
+
98
+ .. mermaid::
99
+
100
+ flowchart TD
101
+ Spec["Spec string"] --> Impl["impl<br>(optional)"]
102
+ Impl --> Version["version<br>(optional)"]
103
+ Version --> T["t<br>(optional)"]
104
+ T --> Arch["-arch<br>(optional)"]
105
+ Arch --> Machine["-machine<br>(optional)"]
106
+
107
+ style Impl fill:#4a90d9,stroke:#2a5f8f,color:#fff
108
+ style Version fill:#4a9f4a,stroke:#2a6f2a,color:#fff
109
+ style T fill:#d9904a,stroke:#8f5f2a,color:#fff
110
+ style Arch fill:#d94a4a,stroke:#8f2a2a,color:#fff
111
+ style Machine fill:#904ad9,stroke:#5f2a8f,color:#fff
112
+
113
+ **Parts explained:**
114
+
115
+ - **impl** -- the Python implementation name. ``python`` and ``py`` both mean "any implementation"
116
+ (usually CPython). Use ``cpython``, ``pypy``, or ``graalpy`` to be explicit.
117
+ - **version** -- dotted version number (``3``, ``3.12``, or ``3.12.1``). You can also write
118
+ ``312`` as shorthand for ``3.12``.
119
+ - **t** -- appended directly after the version. Matches free-threaded (no-GIL) builds only.
120
+ - **-arch** -- ``-32`` or ``-64`` for 32-bit or 64-bit interpreters.
121
+ - **-machine** -- the CPU instruction set: ``-arm64``, ``-x86_64``, ``-aarch64``, ``-riscv64``, etc.
122
+
123
+ **Full examples:**
124
+
125
+ .. list-table::
126
+ :header-rows: 1
127
+ :widths: 30 70
128
+
129
+ * - Spec
130
+ - Meaning
131
+ * - ``3.12``
132
+ - Any Python 3.12
133
+ * - ``python3.12``
134
+ - CPython 3.12
135
+ * - ``cpython3.12``
136
+ - Explicitly CPython 3.12
137
+ * - ``pypy3.9``
138
+ - PyPy 3.9
139
+ * - ``python3.13t``
140
+ - Free-threaded (no-GIL) CPython 3.13
141
+ * - ``python3.12-64``
142
+ - 64-bit CPython 3.12
143
+ * - ``python3.12-64-arm64``
144
+ - 64-bit CPython 3.12 on ARM64
145
+ * - ``/usr/bin/python3``
146
+ - Absolute path, used directly (no search)
147
+ * - ``>=3.11,<3.13``
148
+ - :pep:`440` version specifier (any Python in range)
149
+ * - ``cpython>=3.11``
150
+ - :pep:`440` specifier restricted to CPython
151
+
152
+ :pep:`440` specifiers (``>=``, ``<=``, ``~=``, ``!=``, ``==``, ``===``) are supported. Multiple
153
+ specifiers can be comma-separated, for example ``>=3.11,<3.13``.