Python Poetry: Modern Dependency Management Guide
Poetry is the modern standard for Python dependency management, replacing the fragmented combination of setup.py, requirements.txt, pip, and virtualenv with a single unified tool. It manages virtual environments automatically, resolves dependencies deterministically with a lock file, and handles packaging and publishing to PyPI — all from one pyproject.toml configuration file. Teams that adopt Poetry consistently report fewer "works on my machine" issues and faster onboarding for new developers.
Table of Contents
Installation and Project Setup
Install Poetry using the official installer rather than pip to avoid dependency conflicts with your system Python. Poetry manages its own isolated environment so it never pollutes the projects it manages. After installation, creating a new project or adding Poetry to an existing one takes only one command.
# Official installer (recommended — keeps Poetry isolated)
curl -sSL https://install.python-poetry.org | python3 -
# Or with pipx (also isolated)
pipx install poetry
# Verify installation
poetry --version
# Create a new project
poetry new my-project
# Creates:
# my-project/
# pyproject.toml
# README.md
# my_project/
# __init__.py
# tests/
# __init__.py
# Add Poetry to an existing project
cd existing-project
poetry init # interactive wizard
# Install all dependencies and create virtualenv
poetry install
# Install without dev dependencies (for production)
poetry install --only main
pyproject.toml Deep Dive
Poetry's entire configuration lives in pyproject.toml. This single file replaces setup.py, setup.cfg, requirements.txt, requirements-dev.txt and MANIFEST.in. The [tool.poetry] section handles package metadata, while [tool.poetry.dependencies] specifies runtime requirements with flexible version constraints.
[tool.poetry]
name = "my-awesome-lib"
version = "1.2.0"
description = "A library that does awesome things"
authors = ["Alice Smith "]
license = "MIT"
readme = "README.md"
homepage = "https://example.com"
repository = "https://github.com/alice/my-awesome-lib"
documentation = "https://docs.example.com"
keywords = ["python", "async", "utilities"]
classifiers = [
"Development Status :: 5 - Production/Stable",
"Intended Audience :: Developers",
"Programming Language :: Python :: 3.11",
"Programming Language :: Python :: 3.12",
]
packages = [{include = "my_awesome_lib", from = "src"}]
[tool.poetry.dependencies]
python = "^3.11" # requires 3.11.x or 3.12.x (not 4.0)
fastapi = "^0.115.0" # >=0.115.0, <0.116.0
pydantic = ">=2.0,<3.0" # explicit range
httpx = {version = "^0.27", extras = ["http2"]} # with extras
redis = {version = "^5.0", optional = true} # optional dependency
[tool.poetry.extras]
redis = ["redis"] # poetry install --extras redis
[build-system]
requires = ["poetry-core"]
build-backend = "poetry.core.masonry.api"
Adding and Managing Dependencies
Poetry's add and remove commands update both pyproject.toml and poetry.lock atomically. The lock file pins exact versions of every package — including transitive dependencies — so that poetry install on any machine reproduces the identical environment. Never edit the lock file manually; it is entirely managed by Poetry.
# Add a runtime dependency
poetry add fastapi
# Add with version constraint
poetry add "sqlalchemy>=2.0,<3.0"
# Add with extras
poetry add "pydantic[email]"
# Add a dev dependency (goes in [tool.poetry.group.dev.dependencies])
poetry add --group dev pytest pytest-asyncio black ruff
# Add a specific version
poetry add "django==4.2.13"
# Remove a package
poetry remove httpx
# Update a specific package
poetry update fastapi
# Update all packages within constraints
poetry update
# Show installed packages
poetry show
# Show dependency tree
poetry show --tree
# Check for outdated packages
poetry show --outdated
# Export to requirements.txt (useful for Docker)
poetry export -f requirements.txt --output requirements.txt --without-hashes
poetry export -f requirements.txt --only main --output requirements.prod.txt
Dependency Groups
Poetry supports named dependency groups for organizing packages by purpose. The default groups are main (runtime) and dev. You can define additional groups for testing, documentation, linting, type-checking, etc. Groups make it easy to install only what's needed in each environment — production gets main only, CI gets main + test, and developer machines get everything.
[tool.poetry.group.dev.dependencies]
black = "^24.0"
ruff = "^0.4.0"
mypy = "^1.10"
[tool.poetry.group.test.dependencies]
pytest = "^8.0"
pytest-asyncio = "^0.23"
pytest-cov = "^5.0"
httpx = "^0.27" # for TestClient
[tool.poetry.group.docs.dependencies]
mkdocs = "^1.6"
mkdocs-material = "^9.5"
# Install only main + test (no dev/docs)
poetry install --with test
# Install only main (production)
poetry install --only main
# Install without a specific group
poetry install --without docs
# Add to a named group
poetry add --group test factory-boy
# Run a command in the virtual environment
poetry run pytest
poetry run python -m my_awesome_lib
Virtual Environments
Poetry creates and manages virtual environments automatically. By default it places them in a cache directory (~/.cache/pypoetry/virtualenvs) keyed by project name and Python version. You can configure Poetry to create the virtualenv inside the project directory instead, which is preferred by many IDEs for automatic discovery.
# Configure in-project virtualenv (recommended for IDEs)
poetry config virtualenvs.in-project true
# Creates .venv/ in your project root
# Use a specific Python version
poetry env use python3.12
poetry env use /usr/bin/python3.11
# List environments for current project
poetry env list
# Show info about active environment
poetry env info
# Activate the virtual environment in your shell
poetry shell
# Or run a single command without activating:
poetry run python script.py
poetry run pytest --cov=src
# Remove an environment
poetry env remove python3.11
virtualenvs.in-project = true, VS Code automatically detects the .venv directory and selects the correct Python interpreter. Add .venv/ to your .gitignore.
Scripts and Entrypoints
Poetry can define console scripts that get installed as executables when someone installs your package. These replace the [console_scripts] section in setup.py. You can also define custom scripts for common development tasks — these become available via poetry run.
# Console scripts installed with the package
[tool.poetry.scripts]
my-cli = "my_awesome_lib.cli:main"
my-worker = "my_awesome_lib.worker:start"
# Custom dev task scripts
[tool.poetry.scripts]
format = "scripts:format_code"
lint = "scripts:lint_code"
test = "pytest:main"
# After poetry install, these are available in PATH (inside the venv)
my-cli --help
my-worker --concurrency 4
# Or via poetry run
poetry run my-cli --help
Building and Publishing to PyPI
Poetry handles the entire publication workflow: it builds both source distributions (sdist) and wheel files, then uploads them to PyPI or a private registry. Authentication is stored in Poetry's keyring — never commit credentials to source control.
# Build wheel and sdist
poetry build
# Creates: dist/my_awesome_lib-1.2.0.tar.gz
# dist/my_awesome_lib-1.2.0-py3-none-any.whl
# Configure PyPI token (stored in system keyring, not pyproject.toml)
poetry config pypi-token.pypi pypi-AgAAAA...your-token
# Publish to PyPI
poetry publish
# Build and publish in one step
poetry publish --build
# Publish to a private registry (e.g., Nexus, Artifactory)
poetry config repositories.company https://nexus.company.com/repository/pypi/
poetry config http-basic.company username password
poetry publish --repository company
CI/CD Integration
In CI environments, use poetry install --no-interaction and cache the virtual environment between runs by caching the Poetry virtualenvs directory. The lock file guarantees reproducibility — CI always installs the exact same versions that developers and previous CI runs used.
# .github/workflows/ci.yml
name: CI
on: [push, pull_request]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: "3.12"
- name: Install Poetry
uses: snok/install-poetry@v1
with:
version: "1.8.3"
virtualenvs-in-project: true
- name: Cache virtual environment
uses: actions/cache@v4
with:
path: .venv
key: venv-${{ runner.os }}-${{ hashFiles('poetry.lock') }}
- name: Install dependencies
run: poetry install --no-interaction --with test
- name: Run tests
run: poetry run pytest --cov=src --cov-report=xml
- name: Lint
run: |
poetry run ruff check .
poetry run black --check .