1- How py-discovery works
2- ======================
1+ How it works
2+ ============
33
4- Discovery strategy
5- ------------------
4+ Where does py-discovery look?
5+ -------------------------------
66
7- The :class: `~py_discovery.Builtin ` class searches for Python interpreters in the following order.
7+ When you call :class: `~py_discovery.Builtin ` ``.run() ``, the library checks several locations in
8+ order. It stops as soon as it finds an interpreter that matches your spec.
89
910.. mermaid ::
1011
1112 flowchart TD
1213 Start["Builtin.run()"] --> AbsPath{"Is spec an<br>absolute path?"}
13- AbsPath -->|Yes| TryAbs["Try path directly"]
14+ AbsPath -->|Yes| TryAbs["Use path directly"]
1415 AbsPath -->|No| TryFirst["try_first_with paths"]
1516 TryFirst --> RelPath{"Is spec a<br>relative path?"}
1617 RelPath -->|Yes| TryRel["Resolve relative to cwd"]
@@ -27,22 +28,24 @@ The :class:`~py_discovery.Builtin` class searches for Python interpreters in the
2728 UV --> Verify
2829
2930 Verify{{"Verify candidate<br>(subprocess call)"}}
30- Verify -->|Satisfies spec| Cache["Cache and return"]
31+ Verify -->|Matches spec| Cache["Cache and return"]
3132 Verify -->|No match| Next["Try next candidate"]
3233
3334 style Start fill:#4a90d9,stroke:#2a5f8f,color:#fff
3435 style Verify fill:#d9904a,stroke:#8f5f2a,color:#fff
3536 style Cache fill:#4a9f4a,stroke:#2a6f2a,color:#fff
3637 style Next fill:#d94a4a,stroke:#8f2a2a,color:#fff
3738
38- Each candidate is verified by running it as a subprocess to collect its metadata (version, architecture, platform,
39- sysconfig paths, and more). Verified interpreters are cached to avoid repeating this subprocess call.
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.
4042
41- Shim resolution
42- ---------------
43+ How version-manager shims are handled
44+ -----------------------------------------
4345
44- When a spec like ``python3.12 `` resolves to a version-manager shim (e.g., ``~/.pyenv/shims/python3.12 ``),
45- py-discovery detects this and resolves it to the real binary.
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. py-discovery
48+ detects these shims and resolves them to the actual binary.
4649
4750.. mermaid ::
4851
@@ -59,17 +62,14 @@ py-discovery detects this and resolves it to the real binary.
5962 style Use fill:#4a9f4a,stroke:#2a6f2a,color:#fff
6063 style Skip fill:#d94a4a,stroke:#8f2a2a,color:#fff
6164
62- For `mise <https://mise.jdx.dev/ >`_ and `asdf <https://asdf-vm.com/ >`_, the ``MISE_DATA_DIR `` and ``ASDF_DATA_DIR ``
63- directories are searched. The shim directory is identified via the ``PYENV_ROOT ``, ``MISE_DATA_DIR ``, or
64- ``ASDF_DATA_DIR `` environment variables.
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.
6567
66- Cache design
67- ------------
68+ How caching works
69+ -------------------
6870
69- The :class: `~py_discovery.DiskCache ` stores interpreter metadata as JSON files under
70- ``<root>/py_info/4/<sha256>.json ``, where the hash is derived from the interpreter path. File locking via
71- `filelock <https://py-filelock.readthedocs.io/ >`_
72- ensures safe concurrent access.
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.
7373
7474.. mermaid ::
7575
@@ -85,16 +85,15 @@ ensures safe concurrent access.
8585 style Return fill:#4a9f4a,stroke:#2a6f2a,color:#fff
8686 style Run fill:#d9904a,stroke:#8f5f2a,color:#fff
8787
88- The cache layer uses a :class: `typing.Protocol ` (:class: `~py_discovery.PyInfoCache `), so any object with the right
89- method signatures works as a cache backend -- no inheritance required. Two built-in implementations are provided:
88+ The built-in :class: `~py_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 `).
9092
91- - :class: ` ~py_discovery.DiskCache ` -- persistent JSON + ` filelock < https://py-filelock.readthedocs.io/ >`_ storage.
92- - `` cache=None `` -- disables caching, useful for one-shot scripts or testing.
93+ Spec format reference
94+ -----------------------
9395
94- Spec format
95- -----------
96-
97- A spec string follows the pattern ``[impl][version][t][-arch][-machine] ``.
96+ A spec string follows the pattern ``[impl][version][t][-arch][-machine] ``. Every part is optional.
9897
9998.. mermaid ::
10099
@@ -111,36 +110,44 @@ A spec string follows the pattern ``[impl][version][t][-arch][-machine]``.
111110 style Arch fill:#d94a4a,stroke:#8f2a2a,color:#fff
112111 style Machine fill:#904ad9,stroke:#5f2a8f,color:#fff
113112
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+
114125.. list-table ::
115126 :header-rows: 1
116127 :widths: 30 70
117128
118129 * - Spec
119130 - Meaning
131+ * - ``3.12 ``
132+ - Any Python 3.12
120133 * - ``python3.12 ``
121- - CPython 3.12 (any architecture)
134+ - CPython 3.12
122135 * - ``cpython3.12 ``
123136 - Explicitly CPython 3.12
124137 * - ``pypy3.9 ``
125138 - PyPy 3.9
126- * - ``3.12 ``
127- - Any implementation, version 3.12
128- * - ``python3.12t ``
129- - Free-threaded (no-GIL) CPython 3.12
139+ * - ``python3.13t ``
140+ - Free-threaded (no-GIL) CPython 3.13
130141 * - ``python3.12-64 ``
131142 - 64-bit CPython 3.12
132143 * - ``python3.12-64-arm64 ``
133144 - 64-bit CPython 3.12 on ARM64
134145 * - ``/usr/bin/python3 ``
135- - Absolute path, used directly
146+ - Absolute path, used directly (no search)
136147 * - ``>=3.11,<3.13 ``
137- - :pep: `440 ` version specifier
148+ - :pep: `440 ` version specifier (any Python in range)
138149 * - ``cpython>=3.11 ``
139- - :pep: `440 ` specifier with implementation filter
140-
141- The ``impl `` prefix is optional; ``python `` and ``py `` are treated as "any implementation." The ``t `` suffix matches
142- free-threaded (no-GIL) builds. Architecture (``-32 `` or ``-64 ``) and ISA (``-arm64 ``, ``-x86_64 ``, and others) are
143- optional suffixes separated by dashes.
150+ - :pep: `440 ` specifier restricted to CPython
144151
145- :pep: `440 ` specifiers (``>= ``, ``<= ``, ``~= ``, ``!= ``, ``== ``, ``=== ``) are supported for flexible version matching.
146- Multiple specifiers can be comma-separated, for example ``>=3.11,<3.13 ``.
152+ :pep: `440 ` specifiers (``>= ``, ``<= ``, ``~= ``, ``!= ``, ``== ``, ``=== ``) are supported. Multiple
153+ specifiers can be comma-separated, for example ``>=3.11,<3.13 ``.
0 commit comments