# How the uv-python crate handles Python interpreter discovery and installation

> Learn how the uv-python crate discovers and installs Python interpreters using a three-stage pipeline for virtual environments PATH and system registries. Get automatic download of managed builds.

- Repository: [Astral/uv](https://github.com/astral-sh/uv)
- Tags: internals
- Published: 2026-03-01

---

**The uv-python crate locates Python interpreters through a three-stage pipeline—discovery across virtual environments, PATH, and system registries; probing via isolated Python scripts to extract metadata; and automatic downloading of managed builds when no suitable interpreter exists—implemented primarily in [`discovery.rs`](https://github.com/astral-sh/uv/blob/main/discovery.rs), [`interpreter.rs`](https://github.com/astral-sh/uv/blob/main/interpreter.rs), and [`installation.rs`](https://github.com/astral-sh/uv/blob/main/installation.rs).**

The `uv-python` crate is the core component of the [uv](https://github.com/astral-sh/uv) Python package manager responsible for **Python interpreter discovery** and installation. It implements a robust, cross-platform mechanism to locate existing interpreters across virtual environments, system paths, and Windows registries, while also providing automatic downloads of managed Python builds when local interpreters don't satisfy project requirements.

## How the uv-python crate discovers Python interpreters

Discovery begins in [`crates/uv-python/src/discovery.rs`](https://github.com/astral-sh/uv/blob/main/crates/uv-python/src/discovery.rs), where the crate builds a lazy iterator of candidate executables from multiple sources.

### Discovery preferences and sources

Two preference enums control the search strategy:

- **`PythonPreference`** (`OnlyManaged`, `Managed`, `System`, `OnlySystem`) determines whether to prefer uv-managed builds over system installations.
- **`EnvironmentPreference`** (`OnlyVirtual`, `ExplicitSystem`, `OnlySystem`, `Any`) controls whether to select virtual environments, system interpreters, or both.

These preferences filter the `PythonSource` enum variants, which include `ProvidedPath`, `ActiveEnvironment`, `CondaPrefix`, `SearchPath`, `Registry` (Windows), `MicrosoftStore`, `Managed`, and `ParentInterpreter`.

### The discovery iterator

The `python_executables` function (lines 522-570) constructs a lazy iterator yielding `(PythonSource, PathBuf)` pairs. It aggregates candidates from three helper functions:

1. **`python_executables_from_virtual_environments`** – Checks `VIRTUAL_ENV`, `CONDA_PREFIX`, and parent-directory `.venv` folders.
2. **`python_executables_from_installed`** – Searches managed uv installations, `PATH`, Windows registry, and Microsoft Store.
3. **`ParentInterpreter`** – Uses the `UV_INTERNAL__PARENT_INTERPRETER` environment variable when uv is run as a Python subprocess.

The iterator respects `PythonPreference` ordering (lines 888-910) to prioritize preferred sources.

### Search path probing

`python_executables_from_search_path` (lines 723-762) generates candidate executable names based on the requested `VersionRequest` (e.g., `python3.12`, `python3`, `pypy3`). It walks each directory in `PATH`, deduplicates via `same_file::Handle`, and resolves executables using `which::which_in_global`.

Minor-version fallbacks (e.g., `python3.12` → `python3`) are handled by `find_all_minor` (lines 664-728).

## Probing and caching interpreter metadata

Once discovery yields a candidate executable, the crate must verify it satisfies the request and extract detailed metadata. This happens in [`crates/uv-python/src/interpreter.rs`](https://github.com/astral-sh/uv/blob/main/crates/uv-python/src/interpreter.rs).

### The probing script

`Interpreter::query` (lines 70-106) delegates to `InterpreterInfo::query_cached`, which executes a small Python script located at [`crates/uv-python/python/get_interpreter_info.py`](https://github.com/astral-sh/uv/blob/main/crates/uv-python/python/get_interpreter_info.py).

The script runs in **isolated mode** (`python -I -B`) to prevent user site-packages from interfering (see the `Command` construction at lines 558-564). It outputs JSON containing:

- Platform tags and marker environment
- Installation schemes (`purelib`, `platlib`, `include`, etc.)
- Implementation details (CPython vs. PyPy)
- Standalone build flags (`gil_disabled`, `debug_enabled`)

### Caching strategy

`InterpreterInfo::query_cached` (lines 811-885) constructs a cache key from:

- Host architecture and OS (`ARCH`, `sys_info::os_type()`, `sys_info::os_release()`)
- Absolute path of the executable
- Canonical (symlink-resolved) path

Cached entries live in `CacheBucket::Interpreter` (line 1110). If the cached timestamp matches the file's current modification time, the cached `InterpreterInfo` is returned immediately, avoiding the overhead of spawning a Python subprocess.

### The Interpreter struct

The resulting `Interpreter` struct stores:

- **Platform** and **MarkerEnvironment** for dependency resolution
- **Scheme** mapping for installation paths
- **Flags** indicating managed status, standalone builds, and GIL configuration
- **Paths** including `sys_executable`, `sys_prefix`, and `stdlib` locations

Accessor methods like `python_version()`, `implementation_name()`, and `is_managed()` (lines 306-333) expose this data to the rest of the uv codebase.

## Installing and downloading managed Python builds

When discovery fails to locate a suitable interpreter, the crate can download and install a managed build. This logic resides in [`crates/uv-python/src/installation.rs`](https://github.com/astral-sh/uv/blob/main/crates/uv-python/src/installation.rs).

### Finding or downloading interpreters

`PythonInstallation::find` (lines 45-68) attempts to locate an existing interpreter:

```rust
pub fn find(
    request: &PythonRequest,
    environments: EnvironmentPreference,
    preference: PythonPreference,
    download_list: &ManagedPythonDownloadList,
    cache: &Cache,
    preview: Preview,
) -> Result<Self, Error>

```

It delegates to `find_python_installation` in [`discovery.rs`](https://github.com/astral-sh/uv/blob/main/discovery.rs), which chains through `python_interpreters` and `Interpreter::query`.

`PythonInstallation::find_best` (lines 91-112) is used by commands like `uv python install`. It builds a `ManagedPythonDownloadList` index and, when `preference.allows_managed()` and downloads are automatic, attempts to satisfy the request from the download index.

### The installation process

`PythonInstallation::find_or_download` (lines 131-166) implements the fallback logic:

1. Attempt `Self::find`. If it returns `MissingPython` or a recoverable error, continue.
2. Convert the request to a `PythonDownloadRequest`.
3. If downloads are enabled (`PythonDownloads::Automatic`), locate a matching `ManagedPythonDownload` from the index.
4. Call `Self::fetch` (lines 176-226), which:
   - Ensures the managed installations directory exists (`ManagedPythonInstallations::from_settings`)
   - Downloads and unpacks the build via `download.fetch_with_retry` from the `uv-download` crate
   - Registers the installation under `~/.local/share/uv/python` (or platform equivalent)
   - Creates symlinks for minor version access (e.g., `python3.12`)
   - Returns a `PythonInstallation` with `source: PythonSource::Managed`

If downloads are disabled or the client is offline, the function returns a detailed error hinting at the missing interpreter and how to enable automatic downloads.

## Practical usage examples

The following examples demonstrate how to use the `uv-python` crate programmatically.

### Finding an existing interpreter

```rust
use uv_python::{PythonRequest, EnvironmentPreference, PythonPreference, PythonInstallation};
use uv_cache::Cache;
use uv_preview::Preview;

// Initialize the cache (defaults to $XDG_CACHE_HOME/uv)
let cache = Cache::new().expect("failed to initialize cache");

// Request any Python version between 3.9 and 3.13
let request = PythonRequest::parse(">=3.9,<3.13");

// Restrict search to system interpreters only
let env_pref = EnvironmentPreference::OnlySystem;
let py_pref = PythonPreference::System;

// Execute discovery and probing
let installation = PythonInstallation::find(
    &request,
    env_pref,
    py_pref,
    &Default::default(),
    &cache,
    Preview::default(),
)?;

println!("Found interpreter: {}", installation.interpreter().sys_executable().display());

```

This example chains through `find_python_installation` → `python_interpreters` → `Interpreter::query`, utilizing the cache to avoid redundant Python subprocess calls.

### Downloading a missing interpreter

```rust
use uv_python::{
    PythonRequest, EnvironmentPreference, PythonPreference, PythonDownloads, PythonInstallation,
};
use uv_client::BaseClientBuilder;
use uv_cache::Cache;
use uv_preview::Preview;

let cache = Cache::new()?;

// Request CPython 3.12 specifically
let request = PythonRequest::parse("cpython@3.12");

// Allow managed builds and automatic downloads
let py_pref = PythonPreference::Managed;
let py_downloads = PythonDownloads::Automatic;
let client_builder = BaseClientBuilder::default();

let installation = PythonInstallation::find_or_download(
    Some(&request),
    EnvironmentPreference::OnlySystem,
    py_pref,
    py_downloads,
    &client_builder,
    &cache,
    None,  // no reporter
    None,  // default mirrors
    None,
    None,
    Preview::default(),
).await?;

println!("Installed interpreter at {}", installation.interpreter().sys_executable().display());

```

This triggers the fallback path: `find_or_download` → `Self::find` (fails) → `ManagedPythonDownloadList` → `Self::fetch` → download and registration in `~/.local/share/uv/python`.

### Querying interpreter metadata

Once you have a `PythonInstallation`, you can inspect the `Interpreter` struct:

```rust
let interpreter = installation.interpreter();
println!("Python {} ({})", interpreter.python_version(), interpreter.implementation_name());
println!("Platform: {}", interpreter.platform());
println!("Is virtualenv? {}", interpreter.is_virtualenv());
println!("Managed by uv? {}", interpreter.is_managed());

```

These accessor methods (defined in [`interpreter.rs`](https://github.com/astral-sh/uv/blob/main/interpreter.rs) lines 306-333) expose the platform tags, marker environment, installation schemes, and managed status determined during the probing phase.

## Key source files in the uv-python crate

| File | Primary responsibility |
|------|------------------------|
| [[`crates/uv-python/src/discovery.rs`](https://github.com/astral-sh/uv/blob/main/crates/uv-python/src/discovery.rs)](https://github.com/astral-sh/uv/blob/main/crates/uv-python/src/discovery.rs) | Enumerates candidate executables from virtual environments, PATH, Windows registry, and managed installations; implements `PythonPreference` and `EnvironmentPreference` filtering. |
| [[`crates/uv-python/src/interpreter.rs`](https://github.com/astral-sh/uv/blob/main/crates/uv-python/src/interpreter.rs)](https://github.com/astral-sh/uv/blob/main/crates/uv-python/src/interpreter.rs) | Executes [`get_interpreter_info.py`](https://github.com/astral-sh/uv/blob/main/get_interpreter_info.py) in isolated mode, caches `InterpreterInfo` results, and constructs the `Interpreter` struct with platform and marker data. |
| [[`crates/uv-python/src/installation.rs`](https://github.com/astral-sh/uv/blob/main/crates/uv-python/src/installation.rs)](https://github.com/astral-sh/uv/blob/main/crates/uv-python/src/installation.rs) | Public API façade providing `PythonInstallation::find`, `find_best`, and `find_or_download` for automatic managed-Python downloads. |
| [[`crates/uv-python/python/get_interpreter_info.py`](https://github.com/astral-sh/uv/blob/main/crates/uv-python/python/get_interpreter_info.py)](https://github.com/astral-sh/uv/blob/main/crates/uv-python/python/get_interpreter_info.py) | Python helper script that emits JSON metadata about the interpreter's platform, schemes, and markers. |
| [[`crates/uv-python/src/downloads.rs`](https://github.com/astral-sh/uv/blob/main/crates/uv-python/src/downloads.rs)](https://github.com/astral-sh/uv/blob/main/crates/uv-python/src/downloads.rs) | Defines `ManagedPythonDownload` and `ManagedPythonDownloadList` for indexing available managed Python builds. |
| [[`crates/uv-python/src/managed.rs`](https://github.com/astral-sh/uv/blob/main/crates/uv-python/src/managed.rs)](https://github.com/astral-sh/uv/blob/main/crates/uv-python/src/managed.rs) | Manages the layout of uv-managed Python installations under `~/.local/share/uv/python` and handles symlink registration. |
| [[`crates/uv-python/src/windows_registry.rs`](https://github.com/astral-sh/uv/blob/main/crates/uv-python/src/windows_registry.rs)](https://github.com/astral-sh/uv/blob/main/crates/uv-python/src/windows_registry.rs) | Windows-specific lookup for PEP 514-registered Python installations. |

## Summary

- **Discovery** – The `uv-python` crate implements a lazy iterator in [`discovery.rs`](https://github.com/astral-sh/uv/blob/main/discovery.rs) that scans virtual environments, `PATH`, Windows registries, and managed installations, filtered by `PythonPreference` and `EnvironmentPreference`.
- **Probing** – Each candidate executable is validated by `Interpreter::query`, which runs [`get_interpreter_info.py`](https://github.com/astral-sh/uv/blob/main/get_interpreter_info.py) in isolated mode and caches the resulting metadata in `CacheBucket::Interpreter` to avoid redundant subprocess calls.
- **Installation** – When discovery fails, `PythonInstallation::find_or_download` falls back to downloading a managed Python build from the uv index, installing it to `~/.local/share/uv/python`, and registering it for future use.

## Frequently Asked Questions

### How does the uv-python crate prioritize different Python sources?

The crate uses the `PythonPreference` enum to determine whether to prefer managed installations (`Managed`, `OnlyManaged`) over system interpreters (`System`, `OnlySystem`). Within each category, `python_executables` builds a lazy iterator that yields candidates in order: active virtual environments, conda prefixes, parent interpreters, managed uv installations, PATH entries, and finally Windows registry or Microsoft Store entries. The `EnvironmentPreference` enum further filters these by requiring virtual environments (`OnlyVirtual`) or allowing system interpreters (`Any`).

### What happens when uv cannot find a suitable Python interpreter?

If `PythonInstallation::find` returns `MissingPython` or a non-critical error, the high-level API attempts `PythonInstallation::find_or_download`. This converts the request into a `PythonDownloadRequest`, queries the `ManagedPythonDownloadList` index for a matching build, and—if downloads are enabled—calls `Self::fetch` to download, unpack, and register the interpreter under `~/.local/share/uv/python`. If downloads are disabled or the client is offline, uv returns a detailed error indicating the missing version and suggesting how to enable automatic downloads.

### How does uv cache interpreter metadata to improve performance?

After probing an executable with [`get_interpreter_info.py`](https://github.com/astral-sh/uv/blob/main/get_interpreter_info.py), `InterpreterInfo::query_cached` (lines 811-885) constructs a cache key from the host architecture, OS, absolute path, and canonical path of the executable. This entry is stored in `CacheBucket::Interpreter` (line 1110). Subsequent queries check the cache first; if the modification time of the executable matches the cached timestamp, uv returns the stored `InterpreterInfo` immediately, avoiding the cost of spawning a Python subprocess.

### What is the difference between managed and system Python interpreters in uv?

**Managed interpreters** are Python builds downloaded and maintained by uv itself, stored in `~/.local/share/uv/python` (or the platform equivalent), and registered with version-specific symlinks. They are tracked with `PythonSource::Managed` and updated via `ManagedPythonInstallations`. **System interpreters** are pre-existing installations found on the host machine—whether in `/usr/bin`, the Windows registry, conda environments, or virtual environments—discovered via `PATH` scanning or `PythonSource::SearchPath`. The `PythonPreference` setting allows users to force uv to use only managed builds, only system interpreters, or a prioritized mix of both.