Skip to content
Fran Gonzalez
← Back to blog
(updated Jun 27, 2026)·Clanker·3 min read

Python's Build System with uv

The frontend/backend split, the wheel pipeline, and why uv sync builds your project.

Some matmuls wrote this slop, sorry. My goal with this content is to document some work I (a real human bean) do while poking the Clanker, and try to learn something along the way.

Python packaging has a frontend/backend split that JavaScript lacks. When you uv sync, Python builds and installs your project, not just its dependencies. This post explains the model. I applied it in Adding Dagger CI to a Polyglot Monorepo.

The Problem

uv sync does more than install dependencies. It installs dependencies, then builds and installs the project itself. Most package managers stop at dependencies. Python’s does not. The difference matters for Docker layer caching, and it matters the first time a build error says “unable to determine which files to ship inside the wheel.”

What Changed

The key concepts are the frontend/backend split, the wheel pipeline, and editable installs.

The frontend/backend split

Python packaging separates two roles defined in PEP 517.

The installer frontend is what you type commands into: pip, uv, poetry. It resolves dependencies, manages virtual environments, and coordinates the build.

The build backend produces the actual package. It reads your source tree and pyproject.toml, then builds a wheel. Common backends: hatchling, setuptools, uv_build.

The backend is declared in pyproject.toml:

[build-system]
requires = ["hatchling"]
build-backend = "hatchling.build"

When you run uv sync, uv resolves dependencies, then calls hatchling. Hatchling builds the wheel. uv installs it.

uv init originally defaulted to hatchling because it was a modern, standards-compliant backend with minimal dependencies. Since July 2025 it defaults to uv_build, uv’s own backend. Existing projects keep whatever they were initialized with.

The wheel pipeline

A .whl (wheel) is Python’s binary distribution format. It is a ZIP archive with the package files and a .dist-info metadata directory, structured so the installer extracts them directly into site-packages.

The flow:


pyproject.toml + src/


   build backend (build_wheel)


      .whl file


   installer extracts to site-packages/

The build backend reads the source. It always reads the source. Python packaging embeds the project build inside the dependency install.

Editable installs

When developing locally, you do not want to reinstall the project after every file change. PEP 660 defines the editable install: the build backend produces a wheel that, instead of copying files to site-packages, places a .pth file or proxy modules pointing back to your source directory.

uv sync installs the project as editable by default. The pipeline is the same: frontend calls build_editable() on the backend, but the output points to the source tree rather than duplicating it.

The build backend still reads the source. It has to, to determine which files belong in the wheel. The --no-install-project flag skips this step entirely, installing only third-party dependencies. That is what makes lockfile-first caching work for Python: install deps without the project, copy the source, then build the project in a separate layer.

References

This post was written with AI assistance.