Mastering Python Project Management with uv: Part 2 — Deep Dives and Advanced Use

Thomas Bury
5 min readOct 5, 2024

--

🛠 Updated on 23rd Feb 2025
This article has been revised based on valuable feedback from readers, thank you all!

Welcome back! If you haven’t checked out Part 1, we explored how uv simplifies managing dependencies, creating virtual environments, handling Python versions, and utilizing inline metadata. In this installment, we'll delve deeper into uv's advanced features, aligning with the latest PEP standards and uv documentation, to elevate your Python development experience.— -

1. Managing Dependencies with pyproject.toml

In Part 1, we introduced adding dependencies using uv. Let's expand on how uv integrates seamlessly with pyproject.toml, the standardized configuration file for Python projects.

Adding Dependencies with uv add

When you add a dependency using uv add, it updates your pyproject.toml file accordingly:

uv add fastapi

This command modifies your pyproject.toml to include:

[project]
name = "your_project_name"
version = "0.1.0"
dependencies = [
"fastapi>=0.68,<1.0",
]

This makes your dependencies explicit and your project easily shareable with others. Whenever you or someone else clones your project, simply running uv will install the same packages listed.

Version Constraints: >= vs. ==

It’s advisable to use version ranges (e.g., >=0.68,<1.0) rather than strict pinning (==0.68). This approach allows for compatibility with newer, non-breaking versions, ensuring your project remains up-to-date without unexpected disruptions. Strict pinning can lead to dependency conflicts and hinder the integration of security patches or performance improvements.

Optional Dependencies and Dependency Groups

uv supports the standardized optional-dependencies and dependency-groups as per the latest PEPs:

[project.optional-dependencies]
dev = ["pytest>=6.0", "black"]

[tool.uv.dependency-groups.docs]
dependencies = ["sphinx>=4.0"]
optional = true

To install these groups, use:

uv add --group dev <DEV_PACKAGE_01> <DEV_PACKAGE_02>
uv add --group docs <DOCS_PACKAGE_01> <DOCS_PACKAGE_02> <DOCS_PACKAGE_03>

for development dependencies and documentation dependencies

— -

2. Locking Dependencies with the uv.lock File

Whenever you add or update dependencies using uv, it doesn’t just modify your pyproject.toml file. uv also creates a uv.lock file. Why is this important?

- Precise Versioning: The uv.lock file locks in the exact versions of all dependencies and their transitive dependencies (packages that your dependencies rely on).
- Reproducible Environments: Whether it’s you coming back to a project after a break or a colleague cloning your repo, running uv will install the exact versions specified in the `uv.lock` file.

The result? A consistent, reliable environment that eliminates the “it works on my machine” problem.

— -

3. Managing Tools: Global vs. Project-Specific

In Part 1, we discussed how uv makes it easy to install CLI tools. Now, let’s break down how uv distinguishes between global and project-specific tools.

Installing Global Tools
Installing a tool globally with uv is simple:

uv tool install black

This makes black available across all your projects but keeps it in its isolated virtual environment, avoiding system-wide conflicts.

Project-Specific Tools
If you need a tool for a specific project, add it directly as a dependency:

uv add --group dev ruff

This keeps the tool local to your project and listed in your pyproject.toml. Your other projects remain unaffected, allowing for isolated development environments.

Running Tools Ephemerally with uvx
For quick, one-off tool usage without permanently installing it, uvx is your friend:

uvx black my_script.py

This runs black within a temporary virtual environment and then cleans up afterward.

— -

4. Creating & Using Virtual Environments the Right Way

Making Virtual Environments Easy
In Part 1, we explained how `uv` defaults to using virtual environments for all package installations. Here’s a quick recap:

uv venv

This command creates a `.venv` directory in your project. If you want to use a custom directory or Python version:

uv venv my_venv - python 3.11

You can then activate your virtual environment as you normally would:
`$ .venv/Scripts/activate`

Automatic Environment Detection
Whenever you work on a project managed by `uv`, the tool will automatically detect and use the appropriate virtual environment without any additional setup. No need to worry about manually activating or deactivating environments.

— -

5. `uv` and Existing Environments

Already using another environment manager like conda? No problem. uv is built to play nicely with external virtual environments.

Automatic Environment Detection & Integration
When you use uv pip install or uv add, uv searches for existing virtual environments:
1. Activated environments (e.g., `VIRTUAL_ENV` or `CONDA_PREFIX`).
2. A `.venv` directory in your current project.

If `uv` doesn’t find a virtual environment, it will prompt you to create one to keep your environment clean and isolated.

— -

6. Using Alternative Package Indexes and Authentication

While uv defaults to the official Python Package Index (PyPI), it supports alternative indexes, which often require authentication.

Configuring Alternative Indexes

Set an alternative index URL:

export UV_INDEX=https://example.com/simple

Authentication Using Environment Variables

For indexes requiring authentication, provide credentials via environment variables. For instance, with Azure Artifacts:

export UV_INDEX=https://username:$ADO_PAT@pkgs.dev.azure.com/organization/project/_packaging/feedName/pypi/simple/

Replace $ADO_PAT with your Personal Access Token. Refer to the official uv documentation for detailed instructions on various services.

7. Permanent Configuration

To avoid setting environment variables repeatedly, configure uv persistently.

Within a project, add the index URL to pyproject.toml:

[tool.uv]
index = [
{ url = "https://example.com/simple", default = true }
]

Alternatively, use uv.toml (preferred for non-Python-specific configurations):

[[index]]
url = "https://example.com/simple"
default = true

Precedence Order:

  • uv.toml overrides pyproject.toml if both exist in the same directory.
  • Project-level settings override user-level settings, which override system-level settings.
  • Environment variables take priority over all configuration files.
  • Use uv --no-config to temporarily ignore all configurations.

8. Types of Projects and Build System Options

uv supports multiple project types, each with different build system configurations. Selecting the right configuration depends on your project's packaging and distribution needs.

Project Types Supported in uv:

Image by Author

What’s Next?

New to uv? Start with Part 1.
Want more details? Check out the official uv documentation.

💬 Have you tried uv yet? Share your experience and questions in the comments! 🐍✨

--

--

Thomas Bury
Thomas Bury

Written by Thomas Bury

Physicist by passion and training, Data Scientist and MLE for a living (it's fun too), interdisciplinary by conviction.

Responses (3)