Dependency Management

Dependency management means declaring and locking the dependencies your project needs so installs stay reproducible across machines, continuous integration (CI), and deployments.

Virtual environment isolate dependencies and prevent version issues, but they don’t guarantee that two machines will install the same version of each dependency over time.

A solid dependency-management strategy should make installs predictable and repeatable across machines, continuous integration (CI), and deployment environments. That usually means capturing exact versions in a lock file and sticking to a consistent workflow.

Modern Python tooling increasingly treats pyproject.toml as a central configuration file for declaring project metadata and dependencies, with lock files used to record the resolved versions.

Here’s a summary of best practices that you can apply to dependency management:

  • Use virtual environments to isolate dependencies. Isolation keeps project requirements from colliding and makes upgrades safer.
  • Pin dependencies with a lock file for reproducible installs. Record the exact versions of every installed package, rather than relying on a loose list or top-level dependencies.
  • Use pyproject.toml as the single source of truth for your project. Declare dependencies and configure tooling in one place so the setup is quick to understand.
  • Choose a consistent dependency management workflow and stick to it. Select a strategy that suits your needs, document it, and use it consistently. Some common approaches include:
    • A modern tool like uv that can manage Python versions, virtual environments, and dependencies.
    • A traditional workflow built around venv and pip plus pip-tools.
    • A project manager, like Poetry, that integrates dependency management and packaging.
  • Install from the lock file in continuous integration (CI). Prefer workflows that fail if the lock file is out of date, so builds don’t silently drift.
  • Keep your dependency set as small as possible. Add third-party packages only when they clearly solve a problem, and remove unused packages regularly.

To see how these practices play out, check out a fragile setup that allows for silent dependency drift because it’s not recording resolved dependency versions:

🔴 Avoid this:

Shell
$ # Install dependencies without a lock file
$ pip install "requests>=2.28" "flask>=2.0"

$ # A teammate runs the same command a month later
$ pip install "requests>=2.28" "flask>=2.0"

This approach may look consistent, but it allows dependency resolution to drift over time. Even if the top-level requirements remain compatible, transitive dependencies can change underneath you, and two installs can end up with different dependency graphs.

Here’s a more robust approach that uses pyproject.toml plus a lock file that’s created and managed using the uv tool:

Favor this:

Shell
$ # Initialize a project (creates pyproject.toml)
$ uv init

$ # Add dependencies and resolve them into a lock file
$ uv add requests flask
$ uv lock

$ # Reproduce the environment in another machine from the lock file
$ uv sync

With this approach, your dependencies are declared in pyproject.toml and the resolved versions are recorded in a lock file. Now, your teammates or contributors can reproduce the working environment reliably, leading to more predictable results.

Tutorial

Managing Python Projects With uv: An All-in-One Solution

Learn how to create and manage your Python projects using uv, an extremely fast Python package and project manager written in Rust.

intermediate tools

For additional information on related topics, take a look at the following resources:


By Leodanis Pozo Ramos • Updated Jan. 8, 2026 • Reviewed by Martin Breuss