Open-Source Wikis

/

uv

/

How to contribute

/

Patterns and conventions

astral-sh/uv

Patterns and conventions

Conventions that recur throughout uv. Most are documented in AGENTS.md and STYLE.md; this page collects the ones that show up in code reviews most often.

Rust style

Imports

Use top-level imports rather than local imports or fully-qualified paths. The exception is when the type name would conflict with a local one, in which case rename via use ... as:

use std::io;
use std::path::{Path, PathBuf};

use anyhow::{Result, anyhow};
use tracing::{debug, instrument};

use uv_cache::Cache;
use uv_resolver::{Resolver, ResolverEnvironment};

Naming

  • Don't shorten variable names. Use version not ver, requires_python not rp, interpreter not int. Names should make a reader who is unfamiliar with the code understand what's happening.
  • Use [TypeName] references in doc comments so they render as Rustdoc links.

Error handling

uv treats user-visible errors as a first-class concern.

  • Library crates use thiserror to define typed error enums. Errors should have specific variants per failure mode and ideally carry the offending value (path, version, URL, …) so the error message can render it.
  • The top-level binary (crates/uv/) uses anyhow for orchestration where the error source no longer matters.
  • Fancy error reports are produced via miette with the fancy-no-backtrace feature.
  • Always prefer if let and let-chains (if let X = y && condition) over deeply nested pattern matches.
  • Avoid panic!, unreachable!, .unwrap(), and unsafe outside of well-justified performance paths. When unsafe is required, write a SAFETY: comment in the project's usual style explaining why the invariant holds.
  • Prefer #[expect(rule = "...")] over #[allow(...)] when a clippy rule must be disabled. This way the suppression breaks if the underlying issue is fixed.

Lints

The workspace enables clippy::pedantic with a small allowlist (see [workspace.lints.clippy] in Cargo.toml). Notably:

  • print_stdout and print_stderr are denied in library crates. The user-facing CLI output uses the Printer abstraction in crates/uv/src/printer.rs (with structured anstream-aware streams).
  • dbg_macro, exit, get_unwrap, rc_buffer, and rc_mutex are warnings.
  • use_self is required.
  • unsafe_code and unreachable_pub are workspace-wide warnings.

The project's stance is: clippy warnings on main are bugs, not technical debt.

Concurrency

  • tokio for I/O. HTTP, filesystem reads/writes, and process spawns are async. The resolver in particular uses tokio channels and oneshots to coordinate with prefetchers.
  • rayon for CPU-bound work. Wheel installation and bytecode compilation parallelize over rayon. The initialize_rayon_once helper in uv-configuration ensures the global pool is configured once.
  • The resolver uses std::thread::scope to drive PubGrub on an OS thread while async prefetchers feed it metadata via channels (see crates/uv-resolver/src/resolver/mod.rs).

Crate boundaries

uv enforces a strict crate hierarchy because Cargo forbids circular dependencies. A few conventions help:

  • Trait-based abstractions. When a resolver needs to call into install/build code, it does so via a trait defined in uv-types (BuildContext, InstalledPackagesProvider, SourceBuildTrait, …). The implementations live in concrete crates that depend on uv-types.
  • *-types crates for shared data models. uv-distribution-types, uv-pypi-types, and uv-git-types hold pure data structures that downstream crates can use without pulling in the full implementation.
  • The dependency graph can be visualized with cargo depgraph --dedup-transitive-deps --workspace-only | dot -Tpng > graph.png (cargo-depgraph + graphviz needed).

Logging

  • tracing everywhere. The tracing::{debug, trace, info, warn} macros are imported at the top of most files.
  • #[instrument(skip_all, ...)] on hot paths. Span fields (fields(num_wheels = ...)) show up in the durations export.
  • No println! in libraries. Use tracing or, in the binary crate, the Printer abstraction.

User-facing output

STYLE.md codifies the conventions for messages and docs. Highlights:

  • Stylize uv as uv — never Uv (capitalized) or UV (uppercased). The exception is environment variables (UV_PYTHON, UV_CACHE_DIR).
  • Backticks for paths, packages, and commands.
  • Cyan for paths and important literals; green for commands; red for errors; yellow for warnings; cyan for hints. The CLI must remain interpretable when colors are disabled (NO_COLOR is respected).
  • Hints follow errors, separated by a blank line, prefixed with hint:.
  • warn_user and warn_user_once are the right way to surface actionable warnings; they show up without --verbose. tracing::warn! is reserved for diagnostic warnings the user might never see.
  • All logging goes to stderr. Stdout is reserved for "data" the user might pipe.
  • Lockfile (one word), pre-release (hyphenated), requires_python in code, requires-python in docs/CLI.

Testing

Conventions covered in detail in Testing, but the must-knows:

  • Prefer integration tests under crates/uv/tests/it/. Snapshot via uv_snapshot! and insta. Copy the style of nearby tests.
  • Add a test for every behavior change. When fixing a bug, the test should fail before the fix and pass after.
  • Run focused tests, not the whole suite, during iteration. Full runs are slow.

Async patterns

  • Arc<dyn Reporter> for progress. Most long-running APIs (resolver, installer, preparer) accept an optional Arc<dyn Reporter> for progress callbacks. This keeps the business logic free of UI concerns and lets the CLI (or a test) plug in different reporters.
  • Sender/Receiver channels for resolver coordination. The resolver and its prefetcher live on different threads/futures and exchange MetadataResponse and VersionsResponse values via tokio channels.

On-disk formats

  • TOML for everything user-visible. pyproject.toml, uv.toml, uv.lock, uv-receipt.toml. Use toml for read-only and toml_edit for mutate-and-preserve-style edits (see crates/uv-workspace/src/pyproject_mut.rs).
  • rkyv for cache files. Registry responses, metadata, and even some Version / VersionSpecifier representations are stored as rkyv archives so they can be memory-mapped on read.
  • Atomic writes. uv_fs::write_atomic is the standard way to write to a path that other uv processes might be reading.

Dependency hygiene

  • Don't cargo update in bulk. Use cargo update --precise for targeted lockfile changes. Routine bumps are handled by Renovate.
  • Audit new dependencies. uv has a small, deliberately curated set. Adding a new dependency needs prior discussion.
  • cargo shear runs in CI to flag unused dependencies.

Documentation style

The user docs in docs/ use mkdocs. The STYLE.md distinguishes three doc types:

  • Guides assume no prior uv knowledge, second person, imperative voice, link to concepts.
  • Concepts explain behavior in detail, third person, no imperative voice.
  • Reference is generated from code, third person.

Rust doc comments target the in-repo cargo doc reader (uv-internal API is not "stable") so they can be terse. Use [TypeName] references for navigation.

Built by Factory AutoWiki from public repository content. It is a generated preview for codebase exploration, not source-maintained documentation.

Patterns and conventions – uv wiki | Factory