Python Poetry: Complete Guide to Modern Python Packaging

Published February 12, 2026 · 20 min read

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.

What you'll learn: Installation, project setup, dependency management, virtual environments, dependency groups, scripts, publishing to PyPI, CI/CD integration, monorepo patterns, and a head-to-head comparison with pip, pipenv, and PDM.

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:

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

ConstraintMeaningExample range
^2.31Compatible with (caret)>=2.31.0, <3.0.0
~2.31Approximately (tilde)>=2.31.0, <2.32.0
>=2.31,<3.0Explicit rangeExact bounds
==2.31.1Exact pinOnly 2.31.1
*Any versionAll 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 fileManual (pip freeze)Pipfile.lockpoetry.lockpdm.lock
Dependency resolverBasic (pip 23+)GoodExcellent (SAT)Excellent
Virtual env managementManualAutomaticAutomaticAutomatic
Build & publishNeeds setuptools/twineNoBuilt-inBuilt-in
pyproject.tomlPartialNo (Pipfile)FullFull (PEP 621)
Dependency groupsNodev onlyUnlimitedUnlimited
Scripts/tasksNoPipfile scriptsEntry points + pluginsScripts
Monorepo supportManualNoPath depsPath deps
PEP 621 complianceN/ANoSince v2.0Yes
SpeedFastSlowGoodFast

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

TaskCommand
Create new projectpoetry new my-project
Init in existing dirpoetry init
Add dependencypoetry add requests
Add dev dependencypoetry add pytest --group dev
Remove dependencypoetry remove requests
Update allpoetry update
Install from lockpoetry install
Production installpoetry install --only main
Run commandpoetry run python app.py
Open shellpoetry shell
Show treepoetry show --tree
Check outdatedpoetry show --outdated
Build packagepoetry build
Publish to PyPIpoetry publish --build
Export requirementspoetry export -f requirements.txt
Switch Pythonpoetry env use python3.12
Clear cachepoetry cache clear pypi --all
Self updatepoetry 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