Poetry is a modern, all-in-one toolchain for Python project and dependency management. It moves beyond the traditional combination of pip and virtualenv by integrating dependency resolution, virtual environment management, and packaging into a single, cohesive command-line interface. Its core philosophy is centered around the pyproject.toml file (PEP 518), which serves as the single source of truth for a project’s metadata, dependencies, and build system requirements.

Core Concepts: pyproject.toml and poetry.lock

At the heart of any Poetry-managed project are two files: pyproject.toml and poetry.lock.

The pyproject.toml file is a declarative configuration file. You explicitly state the dependencies your project needs, but you often specify version constraints with a degree of flexibility (e.g., ^1.2.0). This file is intended for human interaction and defines the ranges of acceptable versions.

In contrast, the poetry.lock file is an automatically generated, machine-readable file. When Poetry resolves the dependency tree based on your pyproject.toml, it calculates the exact, specific versions of every package—including all transitive dependencies—that satisfy all constraints. It then “locks” these precise versions into the poetry.lock file. This ensures that every installation, whether on a development machine or a production server, is completely identical and reproducible. The lockfile is the guarantee against “but it worked on my machine” problems.

Initializing a New Project and Adding Dependencies

A new Poetry project is created with the new command, which scaffolds the basic directory structure and a pyproject.toml file.

poetry new my-awesome-project
cd my-awesome-project

You can also add Poetry to an existing project by running poetry init within the project directory. This command will interactively guide you through creating the pyproject.toml file.

Dependencies are managed using the add and remove commands. Poetry distinguishes between main dependencies, which are required for the project to run, and development dependencies, which are only needed for development tasks like testing or linting.

# Add a main dependency (e.g., web framework)
poetry add fastapi@^0.68.0

# Add a development dependency (e.g., testing framework)
poetry add --group dev pytest@^7.0.0

# View the current dependencies
poetry show --tree

# Remove a dependency
poetry remove pytest

The @^0.68.0 syntax is a version constraint. The caret (^) allows for non-breaking updates (e.g., it would allow 0.68.1, 0.69.0, but not 1.0.0). Other common specifiers include tilde (~) for patch-level updates and wildcards (*).

Installing Dependencies and the Lockfile Workflow

The poetry install command is the primary way to set up a project. Its behavior is crucial to understand:

  1. If a poetry.lock file exists: Poetry will install the exact versions of all dependencies listed in the lockfile. This is the behavior you want for CI/CD pipelines and production deployments to ensure consistency.
  2. If no poetry.lock file exists: Poetry will resolve the dependencies from pyproject.toml, create a new poetry.lock file, and then install the resolved versions. This happens the first time you run the command.

The standard workflow is:

  1. Run poetry install to get a working environment based on the current lockfile.
  2. To update dependencies, use poetry update. This command ignores the lockfile, re-resolves the dependency tree from pyproject.toml, updates the lockfile with the latest versions that meet your constraints, and then installs them.
  3. To update a specific package, use poetry update <package-name>. This is safer than a full update as it limits potential breakage.
# Reproducible, safe install for CI/production
poetry install --no-dev  # Omits dev dependencies

# Update all dependencies to their latest allowed versions
poetry update

# Update only the 'requests' package
poetry update requests

Managing Virtual Environments

Poetry automatically creates and manages an isolated virtual environment for each project. This prevents dependency conflicts between projects. You don’t need to activate the environment manually; you can prefix any command with poetry run to execute it within the project’s context.

# Run a script inside the poetry-managed environment
poetry run python my_script.py

# Start a shell within the virtual environment
poetry shell

# See the path to the current virtual environment
poetry env info

# List all virtual environments associated with the project
poetry env list

A common pitfall is not realizing Poetry creates these environments in a central cache directory by default (e.g., ~/Library/Caches/pypoetry on macOS). This can be changed by setting the virtualenvs.in-project configuration option to true, which tells Poetry to create the .venv directory inside your project folder, a pattern familiar to users of other tools.

# Configure poetry to create .venv inside the project directory
poetry config virtualenvs.in-project true

Best Practices and Common Pitfalls

  • Commit Both Files: Always commit both pyproject.toml and poetry.lock to your version control system. The TOML file declares your intent, and the lockfile ensures reproducible installations for all your collaborators and deployment targets.
  • Use Dependency Groups: Organize your development tools (e.g., pytest, black, mypy) into groups like [tool.poetry.group.dev.dependencies]. This allows you to install only the production necessities in deployment using poetry install --no-dev.
  • Be Specific with update: Prefer poetry update <package> over a blanket poetry update to avoid unexpected breaking changes from many packages updating at once.
  • Path Dependencies: For local development, you can declare a dependency as a path, which is useful for working on multiple interrelated packages simultaneously.
    [tool.poetry.dependencies]
    my-local-package = { path = "../my-local-package", develop = true }
    
  • Publishing Packages: Poetry simplifies building and publishing packages to PyPI. The poetry build command creates source and wheel distributions, and poetry publish uploads them. You must configure your PyPI API token first.