projects
uv concept: projects
🧠UV High-Level Concepts
UV in Layers
| Layer | Role | Example |
|---|---|---|
| Metadata | Declares project intent | pyproject.toml |
| Resolution | Determines precise versions | uv.lock, uv lock |
| Environment | Runs with correct deps | .venv, uv run |
| Execution | Invokes Python code safely | uv run main.py |
| Workspaces | Scales across packages | uv run --package foo |
| Distribution | Makes packages shareable | uv build |
1. The Core Abstraction: A Project
- A UV project is a Python package or app structured around a
pyproject.tomlfile. - UV helps create, manage, run, and distribute this project with minimal friction.
- Projects can be:
- Apps (scripts, web services),
- Libraries (for reuse/publishing),
- Extension modules (Rust, C/C++),
- Minimal/Bare (just metadata scaffolding).
2. Environment Management
- UV creates and manages a virtual environment (
.venv) co-located with the project. - This environment:
- Is automatically updated when running or syncing.
- Is tightly bound to the project’s declared dependencies.
- Can be turned off for manual control.
💡 Think of .venv as your project’s runtime box.
3. Dependency Management
UV’s dependency system revolves around:
- Declaration (
pyproject.toml) - Resolution (lockfiles)
- Installation (
.venv)
Key components:
| Concept | Purpose |
|---|---|
pyproject.toml |
Declares dependencies, metadata |
uv.lock |
UV’s lockfile — full, platform-specific |
pylock.toml |
Standard, tool-agnostic lockfile (PEP 751) |
dependency-groups |
Organize dev/test/lint dependencies |
💡 Declarative + Reproducible: What you write vs. what you actually get (locked).
4. Running Code
You run code with:
uv run ...
This does three things:
- Checks lockfile freshness
- Syncs the
.venv - Executes within the virtual environment
Variants:
--withfor ephemeral deps--no-syncto skip syncing--frozen/--lockedfor strict modes
💡 Think of uv run as: sync + exec.
5. Workspaces
A workspace is:
- A monorepo pattern.
- Multiple packages, each with
pyproject.toml, under one roof. - Shared
uv.lock, shared.venv, coordinated commands.
Benefits:
- Consistent dependencies across all packages
- Intra-package imports and editing supported
- Suitable for plugin systems, modular CLI tools, etc.
Downsides:
- No true sandboxing
- Dependency conflicts = trouble
💡 Think of this as Cargo-like monorepo support for Python.
6. Building and Publishing
Use uv build to create:
sdist— source distribution (for PyPI uploads)wheel— binary, installable
Tied to build-system section:
[build-system]
requires = ["hatchling"]
build-backend = "hatchling.build"
💡 Think of this as preparing to ship.
7. Configuration & Fine Control
Via tool.uv table and flags:
- Python version control
- Build isolation toggle
- Editable installs (on/off)
- Custom sources
- Conflict definitions
💡 Think of this as project policy.
UV Project Structure and Core Files
-
pyproject.toml- Defines project metadata (name, version, dependencies, Python requirements, build system, entry points).
- Required for UV to identify the root of a project.
- Minimal example:
[project] name = "example" version = "0.1.0" - Can be created using
uv init.
-
Project Environment
- UV creates a virtual environment (in
.venv) besidepyproject.toml. - Used for persistent environments during development.
.venvis:- Auto-created on
uv runoruv sync. - Auto-updated as needed.
- Excluded from git with an internal
.gitignore.
- Auto-created on
- Run project commands with
uv run, or activate the.venvas with any virtual environment. - Avoid modifying
.venvmanually. Use:uv addfor project dependencies.uv run --withoruvxfor ad-hoc installs.
- To disable environment management:
[tool.uv] managed = false
- UV creates a virtual environment (in
-
uv.lock- UV-specific universal lockfile stored next to
pyproject.toml. - Captures fully resolved, platform-specific dependency versions.
- Ensures reproducible installs across systems and developers.
- Automatically created/updated via
uv sync,uv run, or explicitly withuv lock. - Should be committed to version control.
- Should not be edited manually.
- Format is UV-specific and not compatible with other tools.
- UV-specific universal lockfile stored next to
-
pylock.toml- Standardized lockfile format (PEP 751), intended to replace
requirements.txt. - Tool-agnostic and human-readable.
- UV can generate and install from
pylock.toml:- Export from
uv.lock:uv export -o pylock.toml - Generate from input requirements:
uv pip compile -o pylock.toml -r requirements.in - Install from
pylock.toml:uv pip sync pylock.toml uv pip install -r pylock.toml
- Export from
- Some UV-specific data is not expressible in
pylock.toml, so UV continues to useuv.lockinternally.
- Standardized lockfile format (PEP 751), intended to replace
UV Project Creation Overview
Creating Projects with uv init
- Default Behavior:
- Creates a project in the current or specified directory.
- Exits with an error if
pyproject.tomlalready exists in the target.
Project Types
-
Applications (
--app, default)- Suitable for scripts, CLIs, web apps.
- Structure includes:
pyproject.tomlwith metadata (no build system).main.py(washello.pyprior to v0.6.0)..python-versionfor interpreter pinning.README.md.
- Run using:
uv run main.py
-
Packaged Applications (
--package)- Uses
src/layout for installable projects. - Adds build system (
hatchlingby default). - Includes a script entry point under
[project.scripts]. - Example run:
uv run example-pkg
- Uses
-
Libraries (
--lib)- Implies
--packageand usessrc/layout. - Intended for code reuse and PyPI publishing.
- Adds
py.typedfor type hint support. - Defines a basic API:
def hello() -> str: return "Hello from example-lib!" - Run example:
uv run python -c "import example_lib; print(example_lib.hello())"
- Implies
Extension Module Projects
- Use
--build-backendwithmaturinorscikit-build-core- For Rust (via
maturin) or C/C++/FORTRAN (viascikit-build-core). - Implies
--package. - Includes files like:
Cargo.tomlandsrc/lib.rs(Rust).- or
CMakeLists.txtandmain.cpp(C/C++).
- Extension function is imported into Python and exposed via
__init__.py. - Rebuilding requires:
uv sync --reinstall
- For Rust (via
Minimal Projects (--bare)
- Creates only a
pyproject.tomlwith minimal fields. - Skips:
- README,
.python-version, source files, VCS setup.
- README,
- Example:
[project] name = "example" version = "0.1.0" requires-python = ">=3.12" dependencies = [] - Can be combined with flags like
--libor--build-backend. - Additional metadata can still be set via flags like:
uv init example --bare --description "Hello world" --author-from git --vcs git --python-pin
Managing Dependencies in UV
Dependency Fields
project.dependencies: Core dependencies for running the project.project.optional-dependencies: Extras installed optionally (e.g.,pandas[excel]).[dependency-groups]: Dev-time dependencies grouped by use (e.g., test, lint).[tool.uv.sources]: Custom or alternate sources for packages (e.g., local paths, Git, indexes).
Adding Dependencies
- Basic usage:
Result:
uv add httpx[project] dependencies = ["httpx>=0.27.2"] - With constraints:
uv add "httpx>=0.20" - From a Git repo:
Adds:
uv add "httpx @ git+https://github.com/encode/httpx"[tool.uv.sources] httpx = { git = "https://github.com/encode/httpx" } - From
requirements.txt:uv add -r requirements.txt
Removing Dependencies
- Remove and update
pyproject.toml:uv remove httpx
Changing Dependencies
- Update constraint:
uv add "httpx>0.1.0" - Force upgrade:
uv add "httpx>0.1.0" --upgrade-package httpx
Platform-Specific Dependencies
- Use environment markers:
uv add "jax; sys_platform == 'linux'" uv add "numpy; python_version >= '3.11'"
Optional Dependencies
- Extras (e.g.,
pandas[plot]) declared in:[project.optional-dependencies] plot = ["matplotlib>=3.6.3"] - Add with:
uv add httpx --optional network
Development Dependencies
- Local-only, non-publishable:
uv add --dev pytest - Result:
[dependency-groups] dev = ["pytest >=8.1.1,<9"]
Dependency Groups
- Grouped dev dependencies:
uv add --group lint ruff - Custom default groups:
[tool.uv] default-groups = ["dev", "lint"] - Disable during execution:
uv run --no-default-groups
Custom Dependency Sources
-
Local path:
uv add ../fooWith editable:
uv add --editable ../foo -
Git:
uv add git+https://github.com/encode/httpx --tag 0.27.0 -
URL:
uv add "https://.../httpx-0.27.0.tar.gz" -
Index:
uv add torch --index pytorch=https://download.pytorch.org/whl/cpu -
Workspace member:
[tool.uv.sources] foo = { workspace = true } [tool.uv.workspace] members = ["packages/foo"]
Platform-Specific Sources
-
Git only on macOS:
[tool.uv.sources] httpx = { git = "...", marker = "sys_platform == 'darwin'" } -
Multiple sources with markers:
[tool.uv.sources] httpx = [ { git = "...", tag = "0.27.2", marker = "sys_platform == 'darwin'" }, { git = "...", tag = "0.24.1", marker = "sys_platform == 'linux'" }, ]
Disabling Sources
- Prevent
tool.uv.sourcesusage:uv lock --no-sources
Build Dependencies
-
Defined in:
[build-system] requires = ["setuptools>=42"] build-backend = "setuptools.build_meta" -
Custom build source:
[tool.uv.sources] setuptools = { path = "./packages/setuptools" }
Editable Installations
- Link source directly:
uv add --editable ../bar - Disable editable:
uv add --no-editable ../bar
Dependency Specifiers (PEP 508)
- Format:
name[extra,...] version_specifier; marker - Examples:
"foo >=1.2.3,<2,!=1.4.0" "pandas[excel,plot] ==2.2" "colorama >=0.4.6,<5; platform_system == 'Windows'"
Running Commands in UV Projects
Executing Commands in the Project Environment
- Use
uv runto execute code within the.venvproject environment:uv run python -c "import example" - Ensures the environment is up-to-date before execution.
Supported Command Types
- Commands from the environment (e.g., installed CLI tools):
uv run example-cli foo - Commands outside the environment that depend on it:
uv run bash scripts/foo.sh
Per-Invocation Dependencies
- Use
--withto override or add dependencies for a single command:uv run --with httpx==0.26.0 python -c "import httpx; print(httpx.__version__)" uv run --with httpx==0.25.0 python -c "import httpx; print(httpx.__version__)" - The specified dependency version takes precedence over project-defined requirements.
Running Inline-Metadata Scripts
- UV supports scripts with inline dependency metadata using a special format:
# /// script # dependencies = ["httpx"] # /// - Run with:
uv run example.py - Executes in an isolated environment containing only the declared dependencies.
Legacy Windows Script Support
- Supports legacy
.cmd,.bat, and.ps1scripts installed via setuptools:uv run --with nuitka==2.6.7 -- nuitka.cmd --version - File extension is optional; UV auto-detects and runs in
.ps1,.cmd,.batorder:uv run --with nuitka==2.6.7 -- nuitka --version
Signal Handling
- UV forwards
SIGINTandSIGTERMon Unix systems for proper termination. - On Ctrl+C:
- If the child process group differs from UV's, or Ctrl+C is pressed multiple times, UV forwards
SIGINT.
- If the child process group differs from UV's, or Ctrl+C is pressed multiple times, UV forwards
- On Windows:
- Ctrl+C is passed directly to the child process for clean exit.
- UV maintains control over the parent process to display error messages more clearly.
Locking and Syncing in UV
Core Concepts
- Locking: Resolves dependencies and writes the result to
uv.lock. - Syncing: Installs a subset of packages from
uv.lockinto the.venvenvironment.
Automatic Lock and Sync
- Performed automatically on commands like
uv run,uv sync, anduv tree. - Ensures the project environment is always aligned with the lockfile and
pyproject.toml.
Controlling Lock and Sync Behavior
- Disable lockfile update if out-of-date:
uv run --locked ... - Run without checking lockfile freshness:
uv run --frozen ... - Run without syncing the environment:
uv run --no-sync ...
Check Lockfile Freshness
- Detect if lockfile is out-of-sync with metadata:
uv lock --check
Explicit Locking and Syncing
- Create or update the lockfile manually:
uv lock - Manually sync the environment:
uv sync
Editable Installations
- By default, UV installs the project as editable when syncing.
- To disable:
uv sync --no-editable
Exact vs. Inexact Sync
- Default: removes packages not in the lockfile.
- To retain extraneous packages:
uv sync --inexact
Optional Dependencies (Extras)
- Not installed by default.
- Include selectively:
uv sync --extra foo - Include all extras:
uv sync --all-extras
Development Dependencies
- Read from
[dependency-groups], e.g.,dev. devgroup is included by default.- Control with:
- Exclude dev:
uv sync --no-dev - Only install dev:
uv sync --only-dev - Include specific group:
uv sync --group lint - Exclude group (overrides include):
uv sync --no-group lint
- Exclude dev:
Upgrading Packages
- Upgrade all:
uv lock --upgrade - Upgrade one package to latest:
uv lock --upgrade-package httpx - Upgrade one package to a specific version:
uv lock --upgrade-package httpx==0.27.0 - Flags can also be passed to
uv syncoruv run.
Exporting Lockfile
- Export
uv.locktorequirements.txtformat:uv export --format requirements-txt - Can then be used with:
uv pip install -r requirements.txt
Partial Installations
- Useful for layered Docker builds.
- Skip project install:
uv sync --no-install-project - Skip project and workspace:
uv sync --no-install-workspace - Skip specific package(s):
uv sync --no-install-package <package-name> - These flags still install dependencies of skipped targets but can lead to incomplete environments if misused.
Configuring Projects in UV
Python Version Requirement
- Declare supported Python versions using
requires-python:[project] requires-python = ">=3.12" - Affects allowed syntax and compatible dependency versions.
Entry Points
- Used to expose command-line tools, GUIs, or plugins.
Command-Line Interfaces (CLIs):
[project.scripts]
hello = "example:hello"
Run with:
uv run hello
Graphical Interfaces (GUIs):
[project.gui-scripts]
hello = "example:app"
- Same as CLI, but GUI-wrapped on Windows.
Plugin Discovery:
[project.entry-points.'example.plugins']
a = "example_plugin_a"
Plugins loaded with:
from importlib.metadata import entry_points
for plugin in entry_points(group='example.plugins'):
plugin.load()
Build Systems
- Declared with:
[build-system] requires = ["hatchling"] build-backend = "hatchling.build" - Required for packaging and entry points.
- Without this section, UV won’t install the project itself—only its dependencies.
Override install behavior:
[tool.uv]
package = true # Force build and install
# or
package = false # Skip install even if a build system is present
Project Environment Path
-
Control
.venvlocation withUV_PROJECT_ENVIRONMENT:- Relative path: resolved from workspace root.
- Absolute path: used directly.
export UV_PROJECT_ENVIRONMENT=.venv-alt -
Avoid using across multiple projects; useful for CI or Docker only.
-
Respect
VIRTUAL_ENVby opting in with--active.
Platform-Constrained Resolutions
Limit solved platforms:
[tool.uv]
environments = ["sys_platform == 'darwin'", "sys_platform == 'linux'"]
Require support for specific platforms:
[tool.uv]
required-environments = ["sys_platform == 'darwin' and platform_machine == 'x86_64'"]
Build Isolation
- UV builds packages in isolated environments by default.
- To disable for a specific package:
[tool.uv] no-build-isolation-package = ["cchardet"] - Ensure build dependencies are installed before syncing.
Install in stages:
uv sync --extra build
uv sync --extra build --extra compile
Use uv pip for resolution-stage build dependencies:
uv venv
uv pip install torch setuptools
uv sync
Or predeclare metadata:
[[tool.uv.dependency-metadata]]
name = "flash-attn"
version = "2.6.3"
requires-dist = ["torch", "einops"]
Editable Mode
- Default: project is installed in editable mode.
- For deployment:
uv sync --no-editable
Conflicting Dependencies
- Extras or groups must be compatible; otherwise, UV will fail to resolve.
Define conflicts explicitly:
[tool.uv]
conflicts = [
[{ extra = "extra1" }, { extra = "extra2" }]
]
For groups:
[tool.uv]
conflicts = [
[{ group = "group1" }, { group = "group2" }]
]
- You cannot install both conflicting sets together:
uv sync --extra extra1 --extra extra2 # This will fail
Building Distributions with UV
Purpose
- Build your project into distributable formats to share or upload (e.g., to PyPI).
Distribution Types
- Source Distribution (sdist):
- Typically
.tar.gzor.zip - Includes raw source files and metadata
- Typically
- Binary Distribution (wheel):
.whlfile- Includes pre-built, installable artifacts
Basic Usage
- Build both source and wheel formats:
uv build ls dist/ - Build a specific project:
uv build path/to/project
Limit Build Output
- Only source distribution:
uv build --sdist - Only wheel:
uv build --wheel - Explicitly build both:
uv build --sdist --wheel
Build Frontend Behavior
- UV acts as a frontend, invoking the build backend specified in
[build-system]. - The backend determines what files are included and how distributions are named.
Build Constraints
- Use
--build-constraintto restrict versions of build dependencies:constraints.txt: setuptools==68.2.2 --hash=sha256:b454a35605876da60632df1a60f736524eb73cc47bbc9f3f1ef1b644de74fd2a - Enforce hash-verified builds:
uv build --build-constraint constraints.txt --require-hashes
Using Workspaces in UV
What is a Workspace?
- A collection of Python packages (apps or libraries) managed together.
- Each member has its own
pyproject.toml. - The workspace shares a single
uv.lockfile to ensure consistent dependencies.
Getting Started
- Define a workspace in the root project’s
pyproject.toml:[tool.uv.workspace] members = ["packages/*"] exclude = ["packages/seeds"] - Each member directory must contain a
pyproject.toml.
Workspace Behavior
- Shared lockfile for all members.
- Editable installs between members.
- Commands default to the workspace root:
uv run # same as: uv run --package albatross uv run --package bird-feeder
Workspace Sources
- Declare intra-workspace dependencies using:
[tool.uv.sources] bird-feeder = { workspace = true } - Default sources apply to all members unless overridden locally.
- Example: override
tqdmsource in root, inherited by all:tqdm = { git = "https://github.com/tqdm/tqdm" }
Typical Workspace Layout
albatross/
├── packages/
│ ├── bird-feeder/
│ └── seeds/ (excluded)
├── pyproject.toml (workspace root)
├── uv.lock
└── src/albatross/
When to Use Workspaces
Use when:
- Managing multiple interrelated packages in a monorepo.
- Needing shared dependencies and consistent resolution.
- Splitting concerns (e.g., core lib vs. CLI).
- Working with plugin systems or native modules.
Avoid if:
- Members require conflicting dependencies.
- You need isolated environments per package.
Alternatives: Path Dependencies
Use if more flexibility or isolation is needed:
[tool.uv.sources]
bird-feeder = { path = "packages/bird-feeder" }
- Each package can be managed independently.
- No shared lockfile or
uv run --package.
Version Compatibility
- The workspace must support a single
requires-pythonvalue, derived as the intersection of all members’ constraints.
Limitations
- No true runtime dependency isolation—Python imports may cross member boundaries unintentionally.
- UV can't enforce that members use only their declared dependencies.
Workspaces are powerful for coordinated development, dependency consistency, and modular design within a single repository.