Python Poetry: Complete Guide to Modern Python Packaging
Python's packaging ecosystem has historically been fragmented: setup.py, setup.cfg, requirements.txt, Pipfile, and more. Poetry unifies dependency management, virtual environments, building, and publishing into a single tool driven by one file: pyproject.toml. This guide covers everything you need to go from zero to confidently shipping Python packages with Poetry.
1. What Is Poetry and Why Use It?
Poetry is an open-source tool (MIT license) that manages the full lifecycle of a Python project:
- Dependency resolution — a SAT-based solver that catches conflicts before they bite you at runtime.
- Lock file —
poetry.lockpins every transitive dependency for deterministic installs across machines. - Virtual environments — creates and manages them automatically, no manual
source .venv/bin/activaterequired. - Building & publishing — produces sdist and wheel packages and publishes them to PyPI or private indices.
- Single config file —
pyproject.tomlreplacessetup.py,setup.cfg,MANIFEST.in, andrequirements.txt.
2. Installation
Recommended: pipx (isolated install)
# Install pipx if you don't have it
python3 -m pip install --user pipx
python3 -m pipx ensurepath
# Install Poetry
pipx install poetry
# Verify
poetry --version
# Poetry (version 2.1.1)
Official installer
# Linux / macOS / WSL
curl -sSL https://install.python-poetry.org | python3 -
# Windows (PowerShell)
(Invoke-WebRequest -Uri https://install.python-poetry.org -UseBasicParsing).Content | python -
The installer places Poetry in $HOME/.local/bin (Linux/macOS) or %APPDATA%\Python\Scripts (Windows). Make sure the directory is on your PATH.
Shell completions
# Bash
poetry completions bash >> ~/.bash_completion
# Zsh
poetry completions zsh > ~/.zfunc/_poetry
# Fish
poetry completions fish > ~/.config/fish/completions/poetry.fish
Updating Poetry
# If installed via pipx
pipx upgrade poetry
# If installed via the official installer
poetry self update
3. Project Setup: poetry new vs poetry init
Starting a brand-new project
poetry new my-library
# Creates:
# my-library/
# pyproject.toml
# README.md
# my_library/
# __init__.py
# tests/
# __init__.py
# Use --src for src layout (recommended for libraries)
poetry new --src my-library
Adding Poetry to an existing project
cd existing-project
poetry init
The interactive wizard asks for project name, version, description, author, license, and initial dependencies. You can skip it with poetry init -n for sensible defaults.
4. pyproject.toml Deep Dive
Here is a fully annotated pyproject.toml:
[tool.poetry]
name = "my-library"
version = "0.3.1"
description = "A short description of your project"
authors = ["Jane Doe <jane@example.com>"]
license = "MIT"
readme = "README.md"
homepage = "https://github.com/jane/my-library"
repository = "https://github.com/jane/my-library"
documentation = "https://my-library.readthedocs.io"
keywords = ["utility", "tools"]
classifiers = [
"Development Status :: 4 - Beta",
"Intended Audience :: Developers",
]
packages = [{ include = "my_library", from = "src" }]
[tool.poetry.dependencies]
python = "^3.10"
requests = "^2.31"
pydantic = { version = "^2.5", extras = ["email"] }
[tool.poetry.group.dev.dependencies]
pytest = "^8.0"
ruff = "^0.3"
mypy = "^1.8"
[tool.poetry.group.docs.dependencies]
sphinx = "^7.2"
sphinx-rtd-theme = "^2.0"
[tool.poetry.scripts]
my-cli = "my_library.cli:main"
[tool.poetry.plugins."my_library.plugins"]
json = "my_library.plugins.json_plugin:JsonPlugin"
[build-system]
requires = ["poetry-core"]
build-backend = "poetry.core.masonry.api"
Version constraints explained
| Constraint | Meaning | Example range |
|---|---|---|
^2.31 | Compatible with (caret) | >=2.31.0, <3.0.0 |
~2.31 | Approximately (tilde) | >=2.31.0, <2.32.0 |
>=2.31,<3.0 | Explicit range | Exact bounds |
==2.31.1 | Exact pin | Only 2.31.1 |
* | Any version | All releases |
Extras
# Define extras your consumers can opt into
[tool.poetry.extras]
server = ["uvicorn", "gunicorn"]
all = ["uvicorn", "gunicorn", "redis"]
# Users install with:
# pip install my-library[server]
5. Dependency Management
Adding dependencies
# Add a runtime dependency
poetry add requests
# Add with version constraint
poetry add "django>=4.2,<5.0"
# Add from git
poetry add git+https://github.com/user/repo.git#main
# Add from a local path
poetry add ../shared-lib --editable
# Add with extras
poetry add "fastapi[standard]"
# Add to a specific group
poetry add pytest --group dev
poetry add sphinx --group docs
Removing dependencies
poetry remove requests
poetry remove pytest --group dev
Updating dependencies
# Update all within constraints
poetry update
# Update a single package
poetry update requests
# See what would change (dry run)
poetry update --dry-run
# Ignore version constraints and get the latest
poetry add requests@latest
Inspecting the dependency tree
# List installed packages
poetry show
# Tree view showing transitive deps
poetry show --tree
# Show info for a specific package
poetry show requests
# List outdated packages
poetry show --outdated
The lock file
# Regenerate the lock file without installing
poetry lock
# Install exactly what's in the lock file
poetry install
# Install without dev dependencies (production deploy)
poetry install --only main
# Validate lock file is consistent with pyproject.toml
poetry check
Tip: Always commit poetry.lock to version control for applications. For libraries, committing it is optional but helps CI reproducibility.
6. Virtual Environments
Poetry creates and manages virtual environments automatically.
Configuration
# Create the venv inside the project directory (.venv/)
poetry config virtualenvs.in-project true
# Check current config
poetry config --list
# See where the current venv lives
poetry env info
# See path only
poetry env info --path
Running commands in the environment
# Run a single command
poetry run python my_script.py
poetry run pytest
poetry run my-cli --help
# Spawn a shell inside the venv
poetry shell
Managing multiple Python versions
# Use a specific Python version (must be installed on the system)
poetry env use python3.12
poetry env use /usr/bin/python3.11
# List all envs for this project
poetry env list
# Remove an env
poetry env remove python3.11
Poetry works well alongside pyenv. Install the Python version with pyenv install 3.12.2, then point Poetry at it with poetry env use $(pyenv which python3.12).
7. Dependency Groups
Groups let you organize dependencies by purpose without polluting your production install:
[tool.poetry.group.dev.dependencies]
pytest = "^8.0"
pytest-cov = "^5.0"
ruff = "^0.3"
mypy = "^1.8"
[tool.poetry.group.docs.dependencies]
sphinx = "^7.2"
sphinx-autodoc-typehints = "^2.0"
[tool.poetry.group.test.dependencies]
pytest-xdist = "^3.5"
factory-boy = "^3.3"
Installing specific groups
# Install everything (main + all groups)
poetry install
# Install only main dependencies
poetry install --only main
# Install main + dev only
poetry install --with dev
# Skip docs group
poetry install --without docs
# Install only the test group (no main deps)
poetry install --only test
Optional groups
# Mark a group as optional (not installed by default)
[tool.poetry.group.docs]
optional = true
[tool.poetry.group.docs.dependencies]
sphinx = "^7.2"
8. Scripts and Plugins
CLI entry points
[tool.poetry.scripts]
my-cli = "my_library.cli:main"
my-gui = "my_library.gui:start_app"
After poetry install, running my-cli in the shell invokes my_library.cli.main().
Custom task runner with Poe the Poet
# Install the plugin
poetry self add poethepoet
# Add tasks to pyproject.toml
[tool.poe.tasks]
lint = "ruff check ."
format = "ruff format ."
test = "pytest -x --tb=short"
typecheck = "mypy src/"
check = ["lint", "typecheck", "test"]
# Run them
poetry poe lint
poetry poe check
Poetry plugins
# Install a plugin
poetry self add poetry-plugin-export
poetry self add poetry-dynamic-versioning
# List installed plugins
poetry self show plugins
# Remove a plugin
poetry self remove poetry-plugin-export
9. Publishing to PyPI
Building your package
# Build sdist and wheel
poetry build
# Output:
# dist/
# my_library-0.3.1.tar.gz
# my_library-0.3.1-py3-none-any.whl
Configuring PyPI credentials
# Store your PyPI token (recommended over username/password)
poetry config pypi-token.pypi pypi-AgEIcH...
# Or use environment variable
export POETRY_PYPI_TOKEN_PYPI=pypi-AgEIcH...
Publishing
# Build and publish in one step
poetry publish --build
# Dry run to check everything first
poetry publish --dry-run
# Publish to TestPyPI
poetry config repositories.testpypi https://test.pypi.org/legacy/
poetry config pypi-token.testpypi pypi-AgEIcH...
poetry publish -r testpypi --build
Private registries
# Add a private registry
poetry config repositories.private https://pypi.mycompany.com/simple/
poetry config http-basic.private username password
# Install from it
[tool.poetry.source]
name = "private"
url = "https://pypi.mycompany.com/simple/"
priority = "supplemental"
10. Poetry in CI/CD
GitHub Actions
# .github/workflows/test.yml
name: Tests
on: [push, pull_request]
jobs:
test:
runs-on: ubuntu-latest
strategy:
matrix:
python-version: ["3.10", "3.11", "3.12", "3.13"]
steps:
- uses: actions/checkout@v4
- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v5
with:
python-version: ${{ matrix.python-version }}
- name: Install Poetry
uses: snok/install-poetry@v1
with:
version: latest
virtualenvs-create: true
virtualenvs-in-project: true
- name: Cache dependencies
uses: actions/cache@v4
with:
path: .venv
key: venv-${{ runner.os }}-${{ matrix.python-version }}-${{ hashFiles('poetry.lock') }}
- name: Install dependencies
run: poetry install --no-interaction
- name: Run tests
run: poetry run pytest --cov=src/ --cov-report=xml
- name: Lint
run: poetry run ruff check .
GitLab CI
# .gitlab-ci.yml
image: python:3.12-slim
variables:
PIP_CACHE_DIR: "$CI_PROJECT_DIR/.pip-cache"
POETRY_VIRTUALENVS_IN_PROJECT: "true"
cache:
paths:
- .pip-cache/
- .venv/
before_script:
- pip install poetry
- poetry install --no-interaction
test:
script:
- poetry run pytest
lint:
script:
- poetry run ruff check .
Docker
FROM python:3.12-slim AS builder
ENV POETRY_NO_INTERACTION=1 \
POETRY_VIRTUALENVS_IN_PROJECT=1
WORKDIR /app
RUN pip install poetry
COPY pyproject.toml poetry.lock ./
RUN poetry install --only main --no-root
COPY . .
RUN poetry install --only main
# --- Runtime stage ---
FROM python:3.12-slim
WORKDIR /app
COPY --from=builder /app/.venv .venv
COPY --from=builder /app/src src
ENV PATH="/app/.venv/bin:$PATH"
CMD ["python", "-m", "my_library"]
11. Monorepo Support and Path Dependencies
Poetry supports path dependencies, making monorepos practical:
# Project structure
monorepo/
packages/
core/
pyproject.toml
api/
pyproject.toml
worker/
pyproject.toml
# packages/api/pyproject.toml
[tool.poetry.dependencies]
python = "^3.10"
core = { path = "../core", develop = true }
# packages/worker/pyproject.toml
[tool.poetry.dependencies]
python = "^3.10"
core = { path = "../core", develop = true }
The develop = true flag installs the dependency in editable mode, so changes to core are immediately visible in api and worker without reinstalling.
Workspace-level scripts
# Root-level Makefile for convenience
install:
cd packages/core && poetry install
cd packages/api && poetry install
cd packages/worker && poetry install
test:
cd packages/core && poetry run pytest
cd packages/api && poetry run pytest
cd packages/worker && poetry run pytest
12. Poetry vs pip vs pipenv vs PDM
| Feature | pip + venv | pipenv | Poetry | PDM |
|---|---|---|---|---|
| Lock file | Manual (pip freeze) | Pipfile.lock | poetry.lock | pdm.lock |
| Dependency resolver | Basic (pip 23+) | Good | Excellent (SAT) | Excellent |
| Virtual env management | Manual | Automatic | Automatic | Automatic |
| Build & publish | Needs setuptools/twine | No | Built-in | Built-in |
| pyproject.toml | Partial | No (Pipfile) | Full | Full (PEP 621) |
| Dependency groups | No | dev only | Unlimited | Unlimited |
| Scripts/tasks | No | Pipfile scripts | Entry points + plugins | Scripts |
| Monorepo support | Manual | No | Path deps | Path deps |
| PEP 621 compliance | N/A | No | Since v2.0 | Yes |
| Speed | Fast | Slow | Good | Fast |
Bottom line: Use pip + venv for simple scripts. Use Poetry for most serious projects—it has the best overall experience. Consider PDM if strict PEP 621 compliance matters. Avoid pipenv for new projects unless your team already uses it.
13. Common Issues and Troubleshooting
Slow dependency resolution
# Poetry caches metadata. If resolution is slow, clear the cache:
poetry cache clear pypi --all
# Use verbose mode to see what's happening:
poetry add somepackage -vvv
"SolverProblemError" conflict
# See which packages conflict
poetry show --tree
# Relax the constraint that's too tight
poetry add "problematic-package>=1.0,<3.0"
# As a last resort, regenerate the lock file
rm poetry.lock
poetry lock
Wrong Python version
# Check which Python Poetry is using
poetry env info
# Switch to the correct version
poetry env use python3.12
# If the env is broken, remove and recreate
poetry env remove python3.11
poetry install
"Package not found" from private registry
# Ensure the source is configured with correct priority
[tool.poetry.source]
name = "private"
url = "https://pypi.mycompany.com/simple/"
priority = "supplemental" # or "primary" if it should be checked first
# Verify credentials
poetry config http-basic.private --list
Poetry and Docker layer caching
# WRONG: Copies everything, busts cache on any code change
COPY . .
RUN poetry install
# RIGHT: Copy only dependency files first
COPY pyproject.toml poetry.lock ./
RUN poetry install --only main --no-root
COPY . .
RUN poetry install --only main
Exporting to requirements.txt
# Some tools or platforms require requirements.txt
poetry export -f requirements.txt --output requirements.txt
# Include dev dependencies
poetry export -f requirements.txt --with dev --output requirements-dev.txt
# Without hashes (some tools don't support them)
poetry export -f requirements.txt --without-hashes --output requirements.txt
Useful configuration options
# Store virtualenv in project directory
poetry config virtualenvs.in-project true
# Use a specific number of parallel installers
poetry config installer.max-workers 4
# Disable the new installer (if you hit bugs)
poetry config installer.modern-installation false
# View all config
poetry config --list
Quick Reference Cheat Sheet
| Task | Command |
|---|---|
| Create new project | poetry new my-project |
| Init in existing dir | poetry init |
| Add dependency | poetry add requests |
| Add dev dependency | poetry add pytest --group dev |
| Remove dependency | poetry remove requests |
| Update all | poetry update |
| Install from lock | poetry install |
| Production install | poetry install --only main |
| Run command | poetry run python app.py |
| Open shell | poetry shell |
| Show tree | poetry show --tree |
| Check outdated | poetry show --outdated |
| Build package | poetry build |
| Publish to PyPI | poetry publish --build |
| Export requirements | poetry export -f requirements.txt |
| Switch Python | poetry env use python3.12 |
| Clear cache | poetry cache clear pypi --all |
| Self update | poetry self update |
Frequently Asked Questions
What is Python Poetry and why should I use it?
Poetry is a modern Python dependency management and packaging tool. It handles virtual environments, dependency resolution, and package publishing through a single pyproject.toml file. Use it for deterministic builds with poetry.lock, better dependency resolution than pip, and streamlined publishing to PyPI.
Poetry vs pip vs pipenv: which should I use?
Use Poetry for most new projects—it combines dependency management, virtual environments, and packaging. Pip is simpler for basic needs. Pipenv focuses on application development but lacks Poetry's packaging features. Poetry has the best dependency resolver and produces clean pyproject.toml files.
How do I migrate from requirements.txt to Poetry?
Run poetry init in your project, then use poetry add with each dependency. For bulk migration: cat requirements.txt | xargs poetry add. Poetry will create pyproject.toml and poetry.lock. You can keep requirements.txt as a fallback with: poetry export -f requirements.txt > requirements.txt