Python Environment and Project Management
The python community's offering of tooling for environment and project management provides semi-overlapping coverage of different use-cases and workflows. Choosing the "correct" tool depends strongly on the use-cases and workflows that one relies on for day-to-day development. This article aims to guide users in choosing which tool to use for common development tasks.
Library Summary
uv
while being relatively new to the scene has taken the python community by storm with a value proposition of 10-100x faster dependency resolution than pip
. Since then, Astral, the company backing both uv
and ruff
, has grown the library to cover many of the most common project management use-cases. It should be noted that Astral is a company which intends to keep its tools free and develop paid-for services on top of its open-source tooling.
hatch
is an official Python Packaging Authority (PyPA) project meaning it is always PEP-compliant. hatch
is designed for extensibility with plugins for customizing behavior. It can be easily configured to use uv
for environment creation and dependency resolution, meaning hatch
users can enjoy many of uv
s convenience features and optimizations while maintaining the higher degree of customization offered by hatch
.
poetry
's offering is almost purely limited to packaging and dependency management and does not contain many of the project management goodies that the other tools explored in this article offer. It is a hold-out of the previous generation of environment management tools, among the likes of pipenv
and pyenv
. poetry
is only PEP-621 compliant for versions later than 2.0.
Feature Comparison
Feature | uv | hatch | poetry |
---|---|---|---|
Handles python installation | yes | yes | no |
Environment scope | one env per project1 | one env per use-case | one env per project |
Dependency Lock | yes | no2 | yes |
Auto-updating dependencies | yes -- complete sync must be actively prevented by user | yes -- constraint check updates only when current env violates declared dependencies | no |
Command-line aliases | no3 | yes | no |
Inline-metadata scripting | yes -- can add and modify dependency blocks via CLI | yes | no |
Workspace support (shared environment across multiple projects) | yes | no | no |
ENVVAR configuration | no | yes | no |
Command Comparison
Create a new project
uv init --lib my-app
The --lib
arg is required for the src/
directory layout.
Install environment
If a lock is present:
uv sync --all-extras
Otherwise, to create a lock:
uv venv --python <version>
uv lock
uv sync --all-extras
List environments
N/A The default location of the created environment is the .venv
directory.
To configure an alternative path:
uv venv <path>
Remove environment
N/A. There is no way to programmatically removed an environment. One must manually delete .venv
directory.
Change a project's python version
First delete the .venv
directory:
uv venv --python <new_version>
uv sync --all-extras
Update a project's python version:
uv venv --refresh --python <new_version>
The env will be recreated on next uv run
call.
List dependencies
uv pip freeze
Or create a pipdeptree
visualization of the project's dependencies:
uv tree
Add a dependency
Update pyproject.toml
and then either let uv run
automatically update the environment before use or actively update the environment by running:
uv sync
Or add a standard dependency with the command-line:
uv add <package>
For local editable dependencies:
uv add --editable /path/to/pkg/
Add <package>
to an optional-dependencies
group:
uv add --optional <group_name> <package>
Add <package>
to a dependency-groups
group:
uv add --group <group_name> <package>
Remove a dependency
Update pyproject.toml
and then either let uv run
automatically update the environment before use or actively update the environment by running:
uv sync
Or remove a standard dependency with the command-line:
uv remove <package>
Remove <package>
from an optional-dependencies
group:
uv remove <package> --optional <group_name>
Remove <package>
from a dependency-groups
group:
uv remove --group <group_name> <package>
Update a dependency
N/A uv
automatically updates dependencies before running commands unless told otherwise.
Run a script
Project scripts declared in pyproject.toml
:
uv run <cmd> <args>
uv
allows one to use --
to pass arbitrary commands to uv run
, e.g.
uv run — flask run -p 3000
Using the uv tool
interface:
uv tool run <tool> <cmd>
Certain tools must not be installed into the local development environment, but are instead managed by uv
, e.g. uv tool run ruff --version
To learn more about the uv tool
interface check out the official documentation.
Format files
Assuming the project has ruff
as a dependency and ruff
is configured via the pyproject.toml
:
uv run ruff format <path>
Or without the declared dependency, using the uv tool
interface:
uv tool run ruff format <path>
Run tests
uv run pytest
Footnotes
- For non-workspace usage. ↩
- PEP-751 was accepted on 01.04.2025. Since
hatch
is a standards-based library, we can assume that lockfiles will be implemented inhatch
in the near future. ↩ - Astral, the company behind
uv
, has said that they are planning support for this feature since August 2024 ↩