Skip to article frontmatterSkip to article content
Site not loading correctly?

This may be due to an incorrect BASE_URL configuration. See the MyST Documentation for reference.

2.1 Python Project Management with uv

uv is a fast, modern tool for managing Python projects, designed to handle dependency management, packaging, and environment isolation in a unified way. Unlike traditional tools that require separate configurations, uv uses a single pyproject.toml file for project specifications, making it easier to manage dependencies, build projects, and ensure reproducible environments across different machines.

Benefits of Using uv Over Traditional Tools

Installing uv and Basic Setup

To install uv, run the following command in your terminal:

curl -LsSf https://astral.sh/uv/install.sh | sh

This installs uv as a standalone tool. You can verify the installation by running uv --version.

Initialising a New Project with uv

uv supports two main project types: applications and packages. Understanding the difference between them is key to choosing the right starting point.

Applications

An application is the default project type in uv — suitable for scripts, web servers, and command-line tools. Think of it as code you run, not code you import.

To create a new application project, run:

uv init my-app
cd my-app
Initialized project `my-app` at `/home/runner/work/msdp-book/home/ch41/my-app`

This command creates a new my-app directory with the following files and directories:

my-app/
├── .gitignore                 ] Git ignore file (starts with common Python ignores)
├── .git/                      ] Git repository (initialized by uv)
├── .python-version            ] Python version specification for uv
├── README.md                  ] Project documentation (starts empty)
├── main.py                    ] Default executable entry point
└── pyproject.toml             ] Project metadata and dependency manifest

To make the scaffold concrete, here is the default content of each file and why it matters.

  1. .python-version

This file contains the Python version that is pinned for the project. When you run uv sync or uv run, uv will automatically create a virtual environment with that Python version and install your dependencies there. This ensures that your project is isolated from the system Python and other projects, and that everyone working on the project uses the same Python version.

If we look inside the file, we see the Python version:

3.12
  1. README.md

This starts empty on purpose. You are expected to fill it with the project goal, setup steps, and run instructions.

  1. main.py

This is the default executable entry point. The main() function contains your application logic, and the if __name__ == "__main__": block runs it when the file is executed as a script.

def main():
    print("Hello from my-app!")


if __name__ == "__main__":
    main()
  1. pyproject.toml

This is the project’s metadata and dependency manifest. It is the central configuration file for your project, where you declare the project name, version, description, Python version requirements, and dependencies. The pyproject.toml file is used by uv to manage dependencies and build the project. The default content is:

If you open the pyproject.toml file, you will see the following content:

[project]
name = "my-app"
version = "0.1.0"
description = "Add your description here"
readme = "README.md"
requires-python = ">=3.12"
dependencies = []

At this point, the project is ready to run:

uv run main.py
warning: `VIRTUAL_ENV=/home/runner/work/msdp-book/msdp-book/.venv` does not match the project environment path `.venv` and will be ignored; use `--active` to target the active environment instead
Using CPython 3.12.3 interpreter at: /usr/bin/python3.12
Creating virtual environment at: .venv
Hello from my-app!

Before moving to larger project layouts, it is useful to clarify the terminology used in this chapter.

Project Structure

As your project grows, you may want to split your code into multiple reusable modules. This is when you need an import package: a directory of .py files with an __init__.py that makes them importable within your project. Two layouts are commonly used in the Python ecosystem.

Flat Layout

In the flat layout, the import package directory sits directly in the root of the project:

my-app/
├── pyproject.toml           ] Project metadata and build configuration
├── my_project/              ┐
│     ├── __init__.py        │ Package source code
│     ├── moduleA.py         │
│     └── moduleB.py         ┘
└── tests/                   ┐
      └── test_file1.py      | Package tests
      └── ....               ┘

This layout has a long history in the Python ecosystem for creating reusable import packages. Many of the most widely-used scientific Python packages use the flat layout — including NumPy, SciPy, pandas, xarray, scikit-learn, and Jupyter. Migrating these large, established codebases to a different layout would require considerable effort, so they continue to use the flat layout.

uv does not generate this structure directly. After running uv init, you would manually create the package directory and an __init__.py file, then add a build system to pyproject.toml to make the package installable.

One drawback of the flat layout is that when running tests from the project root, Python may inadvertently import the local source files directly rather than the installed version of the package. This can hide packaging problems that users would encounter when installing your package.

src Layout

The src layout is specifically designed for creating distribution packages — code you want to share and install via package managers like PyPI. The package directory is placed inside a src/ subdirectory. uv creates this structure automatically when you use the --package flag:

uv init --package my-package
cd my-package
Initialized project `my-package` at `/home/runner/work/msdp-book/home/ch41/my-package`

The resulting structure is:

my-package/
├── .python-version
├── README.md
├── pyproject.toml
└── src/
  └── my_package/
    └── __init__.py

Unlike the plain application template, this pyproject.toml includes a build system — this tells uv that the code is set up as a distribution package (i.e., ready to be packaged and distributed). When you run uv sync, uv builds and installs the distribution package into the virtual environment in editable (development) mode:

[project]
name = "my-package"
version = "0.1.0"
description = "Add your description here"
readme = "README.md"
requires-python = ">=3.12"
dependencies = []

[project.scripts]
my-package = "my_package:main"

[build-system]
requires = ["uv_build>=0.11.14,<0.12.0"]
build-backend = "uv_build"

The exact uv_build version range may differ depending on your installed uv version.

Compared with the application version, two sections are added:

The core [project] metadata fields are the same as in the application template.

With the src layout, the package directory is isolated from the project root. This, combined with the package being installed via uv sync, ensures you’re always importing the installed version during development. This approach catches packaging issues early, rather than after distribution to users.

The src layout is recommended by PyPA and pyOpenSci for new packages.

About cases and case conventions:

In the course, we will use:

Since valid Python identifiers cannot contain dashes (-), we should avoid using dashes in the import package names.

A valid identifier cannot start with a number, or contain any spaces and can only contain alphanumeric letters (a-z) and (0-9), or underscores (_).

Converting a Pre-existing Project to Use uv

Sometimes we don’t want to start from scratch but rather convert an existing project to use uv. To convert an existing project to uv, navigate to the project’s root directory and run:

uv init

This command will create a pyproject.toml file if one doesn’t exist, and you can then add dependencies using uv add <package>. If you have existing requirements files, you can import them with uv add -r requirements.txt. This allows you to migrate your project to uv’s modern dependency management while preserving your existing code.

References