DEV Community

Cover image for Mastering Python Project Management with uv: Part 2 – Deep Dives and Advanced Use
Thomas Bury
Thomas Bury

Posted on • Edited on

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

Welcome back! If you haven’t checked out Part 1, now’s the time to do so. We covered how uv simplifies managing dependencies, creating virtual environments, Python versions, and inline metadata. Now, let's go deeper into the magic of uv and explore its advanced features that can take Python development to the next level.

🛠 Updated on 23rd Feb 2025

This article has been revised based on valuable feedback from readers, thank you all!


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
Enter fullscreen mode Exit fullscreen mode

This command modifies your pyproject.toml to include:

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

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
Enter fullscreen mode Exit fullscreen mode

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>
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

You can then activate your virtual environment as you normally would:

$ .venv/Scripts/activate
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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/
Enter fullscreen mode Exit fullscreen mode

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 }
]
Enter fullscreen mode Exit fullscreen mode

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

[[index]]
url = "https://example.com/simple"
default = true
Enter fullscreen mode Exit fullscreen mode

Precedence Order:

  1. uv.toml overrides pyproject.toml if both exist in the same directory.
  2. Project-level settings override user-level settings, which override system-level settings.
  3. Environment variables take priority over all configuration files.
  4. 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 Type Description Example pyproject.toml Config
Standard Python Project Uses setuptools for traditional package builds. Ideal for legacy and widely adopted setups. [build-system] requires = ["setuptools", "wheel"]
PEP 517 Build Backend Defines a custom backend like hatchling, poetry, or flit. Recommended for modern Python packaging. [build-system] requires = ["hatchling"]
Workspace (Multi-Package) A single repository containing multiple projects. Useful for monorepos and modular architectures. [tool.uv] workspace = true
Non-Build Projects Scripts or tool configurations that do not require packaging. Used for CLI utilities and personal projects. No [build-system] section needed

🔹 Best Practice:

For new projects, use hatchling or flit instead of setuptools, as they align with modern Python packaging standards.


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! 🐍✨


Enter fullscreen mode Exit fullscreen mode

Top comments (0)