Python notes

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.toml file.
  • 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:

  1. Checks lockfile freshness
  2. Syncs the .venv
  3. Executes within the virtual environment

Variants:

  • --with for ephemeral deps
  • --no-sync to skip syncing
  • --frozen / --locked for 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) beside pyproject.toml.
    • Used for persistent environments during development.
    • .venv is:
      • Auto-created on uv run or uv sync.
      • Auto-updated as needed.
      • Excluded from git with an internal .gitignore.
    • Run project commands with uv run, or activate the .venv as with any virtual environment.
    • Avoid modifying .venv manually. Use:
      • uv add for project dependencies.
      • uv run --with or uvx for ad-hoc installs.
    • To disable environment management:
      [tool.uv]
      managed = false
  • 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 with uv lock.
    • Should be committed to version control.
    • Should not be edited manually.
    • Format is UV-specific and not compatible with other tools.
  • 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
    • Some UV-specific data is not expressible in pylock.toml, so UV continues to use uv.lock internally.

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.toml already exists in the target.

Project Types

  • Applications (--app, default)

    • Suitable for scripts, CLIs, web apps.
    • Structure includes:
      • pyproject.toml with metadata (no build system).
      • main.py (was hello.py prior to v0.6.0).
      • .python-version for interpreter pinning.
      • README.md.
    • Run using:
      uv run main.py
  • Packaged Applications (--package)

    • Uses src/ layout for installable projects.
    • Adds build system (hatchling by default).
    • Includes a script entry point under [project.scripts].
    • Example run:
      uv run example-pkg
  • Libraries (--lib)

    • Implies --package and uses src/ layout.
    • Intended for code reuse and PyPI publishing.
    • Adds py.typed for 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())"

Extension Module Projects

  • Use --build-backend with maturin or scikit-build-core
    • For Rust (via maturin) or C/C++/FORTRAN (via scikit-build-core).
    • Implies --package.
    • Includes files like:
      • Cargo.toml and src/lib.rs (Rust).
      • or CMakeLists.txt and main.cpp (C/C++).
    • Extension function is imported into Python and exposed via __init__.py.
    • Rebuilding requires:
      uv sync --reinstall

Minimal Projects (--bare)

  • Creates only a pyproject.toml with minimal fields.
  • Skips:
    • README, .python-version, source files, VCS setup.
  • Example:
    [project]
    name = "example"
    version = "0.1.0"
    requires-python = ">=3.12"
    dependencies = []
  • Can be combined with flags like --lib or --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:
    uv add httpx
    Result:
    [project]
    dependencies = ["httpx>=0.27.2"]
  • With constraints:
    uv add "httpx>=0.20"
  • From a Git repo:
    uv add "httpx @ git+https://github.com/encode/httpx"
    Adds:
    [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 ../foo

    With 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.sources usage:
    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 run to execute code within the .venv project 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 --with to 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 .ps1 scripts 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, .bat order:
    uv run --with nuitka==2.6.7 -- nuitka --version

Signal Handling

  • UV forwards SIGINT and SIGTERM on 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.
  • 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.lock into the .venv environment.

Automatic Lock and Sync

  • Performed automatically on commands like uv run, uv sync, and uv 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.
  • dev group 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

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 sync or uv run.

Exporting Lockfile

  • Export uv.lock to requirements.txt format:
    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 .venv location with UV_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_ENV by 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.gz or .zip
    • Includes raw source files and metadata
  • Binary Distribution (wheel):
    • .whl file
    • 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-constraint to 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.lock file 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 tqdm source 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-python value, 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.