File: getting-started.rst

package info (click to toggle)
python-discovery 1.2.0-1
  • links: PTS, VCS
  • area: main
  • in suites: forky
  • size: 492 kB
  • sloc: python: 4,511; makefile: 9; sh: 8
file content (207 lines) | stat: -rw-r--r-- 6,565 bytes parent folder | download | duplicates (2)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
Getting started
===============

Installation
------------

.. code-block:: console

   pip install python-discovery

Core concepts
-------------

Before diving into code, here are the key ideas:

- **Interpreter** -- a Python executable on your system (e.g., ``/usr/bin/python3.12``).
- **Spec** -- a short string describing what you are looking for (e.g., ``python3.12``, ``pypy3.9``, ``>=3.11``).
- **Discovery** -- the process of searching your system for an interpreter that matches a spec.
- **Cache** -- a disk store that remembers previously discovered interpreters so the next lookup is instant.

Inspecting the current interpreter
------------------------------------

The simplest use case: get information about the Python that is running right now.

.. mermaid::

    flowchart TD
        Call["PythonInfo.current_system(cache)"] --> Info["PythonInfo"]
        Info --> Exe["executable: /usr/bin/python3.12"]
        Info --> Ver["version_info: (3, 12, 1)"]
        Info --> Impl["implementation: CPython"]
        Info --> Arch["architecture: 64"]

        style Call fill:#4a90d9,stroke:#2a5f8f,color:#fff
        style Info fill:#4a9f4a,stroke:#2a6f2a,color:#fff

.. code-block:: python

   from pathlib import Path

   from python_discovery import DiskCache, PythonInfo

   cache = DiskCache(root=Path("~/.cache/python-discovery").expanduser())
   info = PythonInfo.current_system(cache)

   print(info.executable)        # /usr/bin/python3.12
   print(info.version_info[:3])  # (3, 12, 1)
   print(info.implementation)    # CPython  (or PyPy, GraalPy, etc.)
   print(info.architecture)      # 64       (or 32)

The returned :class:`~python_discovery.PythonInfo` object contains everything the library knows about that interpreter:
paths, version numbers, sysconfig variables, platform details, and more.

Finding a different interpreter
--------------------------------

Usually you need a *specific* Python version, not the one currently running. Pass a **spec** string
to :func:`~python_discovery.get_interpreter` to search your system.

.. mermaid::

    flowchart TD
        Spec["Spec: python3.12"] --> Call["get_interpreter(spec, cache)"]
        Call --> Found{"Match found?"}
        Found -->|Yes| Info["PythonInfo with full metadata"]
        Found -->|No| Nil["None"]

        style Spec fill:#4a90d9,stroke:#2a5f8f,color:#fff
        style Info fill:#4a9f4a,stroke:#2a6f2a,color:#fff
        style Nil fill:#d94a4a,stroke:#8f2a2a,color:#fff

.. code-block:: python

   from pathlib import Path

   from python_discovery import DiskCache, get_interpreter

   cache = DiskCache(root=Path("~/.cache/python-discovery").expanduser())
   result = get_interpreter("python3.12", cache=cache)
   if result is not None:
       print(result.executable)

You can pass multiple specs as a list -- the library tries each one in order and returns the first match.

.. code-block:: python

   result = get_interpreter(["python3.12", "python3.11"], cache=cache)

Writing specs
-------------

A spec tells python-discovery what to look for. The simplest form is just a version number like ``3.12``.
You can add more constraints to narrow the search.

.. mermaid::

    flowchart TD
        Spec["Spec string"] --> Impl["impl<br>(optional)"]
        Impl --> Version["version<br>(optional)"]
        Version --> T["t<br>(optional)"]
        T --> Arch["-arch<br>(optional)"]
        Arch --> Machine["-machine<br>(optional)"]

        style Impl fill:#4a90d9,stroke:#2a5f8f,color:#fff
        style Version fill:#4a9f4a,stroke:#2a6f2a,color:#fff
        style T fill:#d9904a,stroke:#8f5f2a,color:#fff
        style Arch fill:#d94a4a,stroke:#8f2a2a,color:#fff
        style Machine fill:#904ad9,stroke:#5f2a8f,color:#fff

Common examples:

.. list-table::
   :header-rows: 1
   :widths: 30 70

   * - Spec
     - What it matches
   * - ``3.12``
     - Any Python 3.12 (CPython, PyPy, etc.)
   * - ``python3.12``
     - CPython 3.12 (``python`` means CPython)
   * - ``pypy3.9``
     - PyPy 3.9
   * - ``python3.13t``
     - Free-threaded (no-GIL) CPython 3.13
   * - ``python3.12-64``
     - 64-bit CPython 3.12
   * - ``python3.12-64-arm64``
     - 64-bit CPython 3.12 on ARM64 hardware
   * - ``/usr/bin/python3``
     - An absolute path, used directly without searching
   * - ``>=3.11,<3.13``
     - Any Python in the 3.11--3.12 range (:pep:`440` syntax)

See the :doc:`full spec reference </explanation>` for all options.

Parsing a spec
--------------

You can parse a spec string into its components without searching the system. This is useful for
inspecting what a spec means or for building tools on top of python-discovery.

.. mermaid::

    flowchart TD
        Input["cpython3.12t-64-arm64"] --> Parse["PythonSpec.from_string_spec()"]
        Parse --> Spec["PythonSpec"]
        Spec --> impl["implementation: cpython"]
        Spec --> ver["major: 3, minor: 12"]
        Spec --> ft["free_threaded: True"]
        Spec --> arch["architecture: 64"]
        Spec --> mach["machine: arm64"]

        style Input fill:#4a90d9,stroke:#2a5f8f,color:#fff
        style Spec fill:#4a9f4a,stroke:#2a6f2a,color:#fff

.. code-block:: python

   from python_discovery import PythonSpec

   spec = PythonSpec.from_string_spec("cpython3.12t-64-arm64")
   spec.implementation   # "cpython"
   spec.major            # 3
   spec.minor            # 12
   spec.free_threaded    # True
   spec.architecture     # 64
   spec.machine          # "arm64"

Skipping the cache
------------------

If you only need to discover once and do not want to write anything to disk, pass ``cache=None``.
Every call will run a subprocess to query the interpreter, so this is slower for repeated lookups.

.. code-block:: python

   from python_discovery import get_interpreter

   result = get_interpreter("python3.12")

Handling slow interpreter queries
----------------------------------

On some systems (especially Windows with antivirus or other tools), Python startup is slow. If discovery
times out, increase the timeout using the ``PY_DISCOVERY_TIMEOUT`` environment variable.

.. code-block:: python

   import os

   from python_discovery import get_interpreter

   # Allow up to 30 seconds per interpreter
   os.environ["PY_DISCOVERY_TIMEOUT"] = "30"
   result = get_interpreter("python3.12", cache=cache)

Or, pass it directly in a custom environment dict:

.. code-block:: python

   import os

   from python_discovery import get_interpreter

   env = {**os.environ, "PY_DISCOVERY_TIMEOUT": "30"}
   result = get_interpreter("python3.12", env=env, cache=cache)