Poetry — Python Package & Dependency Manager
Manages Python project dependencies, virtual environments, building, and publishing using Poetry. Covers dependency resolution strategies, lockfile management, workspace/multirepo setup, plugin architecture, and migration from pip/requirements.txt workflows.
TL;DR Checklist
When to Use
Use this skill when:
- Setting up a new Python project with structured dependency management and virtual environment isolation
- Managing complex dependency trees with version constraints across multiple packages
- Building and publishing Python packages to PyPI or private registries
- Configuring Poetry workspaces for monorepo or multi-package projects
- Migrating an existing pip/requirements.txt project to Poetry's pyproject.toml workflow
- Resolving dependency conflicts between packages with overlapping version requirements
When NOT to Use
Avoid this skill for:
- Simple one-off scripts where a virtualenv + pip is sufficient overhead — use
python -m venv instead
- Projects that already use
uv or pip-tools and have no migration pressure — don't swap tools mid-project
- Python environments managed externally (Docker containers, system packages, conda) — Poetry's venv integration conflicts with these
Core Workflow
Phase 1: Project Initialization
Create the project structure — Run poetry init interactively or use poetry new <package-name> for a library with src/ layout. The init command creates pyproject.toml, README.md, and optionally a tests directory.
Checkpoint: Verify pyproject.toml contains [tool.poetry], valid name, version, Python version constraint (e.g., requires-python = "^3.10"), and at least one dependency or group.
Configure the build backend — Poetry uses setuptools by default but supports hatchling, flit, or pdm-backend as alternatives. Set the backend explicitly in pyproject.toml:
[build-system]
requires = ["setuptools>=61.0", "wheel"]
build-backend = "setuptools.build_meta"
# Or for hatchling (recommended for new projects):
# requires = ["hatchling"]
# build-backend = "hatchling.build"
Phase 2: Dependency Management
Add production dependencies — Use poetry add <package> or poetry add <package>@<version-spec>. Poetry resolves the full dependency tree, picks compatible versions, and writes to both pyproject.toml and poetry.lock.
# Add latest compatible version
poetry add httpx
# Pin to major version only
poetry add "requests>=2.28,<3"
# Add with extras (e.g., psycopg2 binary)
poetry add "psycopg2-binary"
# Add from a specific git repository
poetry add "my-lib @ git+https://github.com/user/my-lib.git@main"
# Add from a local path dependency
poetry add "../shared-utils"
Configure dependency groups for non-production dependencies — Poetry supports named dependency groups (introduced in 1.2) to separate dev, test, docs, and optional features:
[tool.poetry]
name = "my-project"
version = "0.1.0"
requires-python = ">=3.10,<4.0"
[tool.poetry.dependencies]
python = "^3.10"
httpx = "^0.27"
pydantic = "^2.5"
sqlalchemy = "^2.0"
[tool.poetry.group.dev.dependencies]
pytest = "^8.0"
pytest-asyncio = "^0.23"
ruff = "^0.4"
mypy = "^1.8"
ipython = "^8.0"
[tool.poetry.group.test.dependencies]
pytest-cov = "^5.0"
hypothesis = "^6.90"
[tool.poetry.group.docs.dependencies]
mkdocs = "^1.5"
mkdocstrings = {extras = ["python"], version = "^0.25"}
# Optional dependencies (installable via extras)
[tool.poetry.extras]
database = ["sqlalchemy", "alembic"]
async = ["httpx", "aiosqlite"]
Install dependencies — poetry install installs all dependencies defined in pyproject.toml into the virtual environment and ensures poetry.lock is up to date. Use flags for selective installation:
# Install everything (default — production + all groups)
poetry install
# Production only, skip all dependency groups
poetry install --only main
# Dev group only
poetry install --only dev
# Specific groups
poetry install --with dev,test
# Sync to match lockfile exactly (removes packages not in lock)
poetry lock && poetry install
Phase 3: Development Workflow
Execute commands within the virtual environment — Poetry provides three mechanisms: poetry run for one-off execution, poetry shell for an interactive subshell, or poetry env use to select a specific Python interpreter.
# Run a single command in the project's venv
poetry run pytest tests/
poetry run ruff check .
poetry run mypy src/
# Enter an interactive shell with the venv activated
poetry shell
(my-project-xyz) $ pytest tests/
(my-project-xyz) $ exit
# Run a script defined in [tool.poetry.scripts]
poetry run my-cli --help
Configure project scripts — Define CLI entry points and convenience scripts in pyproject.toml:
[tool.poetry.scripts]
my-cli = "my_project.cli:main"
migrate-db = "my_project.db:migrate"
# Development convenience scripts (run via poetry run <name>)
[tool.poetry.group.dev.scripts]
test = "pytest tests/ --cov=src --cov-report=term-missing"
lint = ["ruff", "check", "."]
typecheck = "mypy src/"
docs-serve = "mkdocs serve"
Phase 4: Workspace (Monorepo) Setup
Configure Poetry workspaces for multi-package projects — Poetry 1.2+ supports workspaces natively. Define a root pyproject.toml that declares member packages:
[tool.poetry]
name = "my-workspace"
version = "0.1.0"
requires-python = ">=3.10"
[tool.poetry.dependencies]
python = "^3.10"
[tool.poetry.workspaces]
members = ["packages/*"]
Each member package has its own pyproject.toml with its own dependencies:
# packages/core/pyproject.toml
[tool.poetry]
name = "my-core"
version = "0.1.0"
requires-python = "^3.10"
[tool.poetry.dependencies]
python = "^3.10"
pydantic = "^2.5"
# packages/api/pyproject.toml
[tool.poetry]
name = "my-api"
version = "0.1.0"
requires-python = "^3.10"
[tool.poetry.dependencies]
python = "^3.10"
httpx = "^0.27"
my-core = { path = "../core", develop = true }
Checkpoint: Run poetry install from the workspace root — it must resolve all member packages and create a unified lockfile with no dependency conflicts between workspaces.
Phase 5: Building and Publishing
Build distribution packages — Generate wheel and/or sdist artifacts:
# Build both wheel and source distribution
poetry build
# Output in dist/ directory
# dist/my_project-0.1.0-py3-none-any.whl
# dist/my_project-0.1.0.tar.gz
# Build only wheel (faster, no source)
poetry build --wheel
# Build only source distribution
poetry build --sdist
Publish to PyPI or private registry — Configure repository credentials and publish:
# Publish to PyPI (requires PYPI_TOKEN env var or interactive login)
poetry publish
# Dry-run to validate before publishing
poetry build && twine check dist/*
# Publish to a custom/private repository
poetry config repositories.my-registry https://pypi.internal.company.com/simple/
poetry config http-basic.my-registry $USERNAME $PASSWORD
poetry publish -r my-registry
# Or use the upload command directly
poetry upload -r my-registry
Implementation Patterns
Pattern 1: Complete pyproject.toml for a Production API Service
[tool.poetry]
name = "user-service"
version = "1.2.0"
description = "REST API service for user management with async database access"
authors = ["Engineering Team <eng@company.com>"]
readme = "README.md"
license = "MIT"
packages = [{ include = "user_service", from = "src" }]
[tool.poetry.dependencies]
python = "^3.11"
httpx = {version = "^0.27", extras = ["brotli"]}
pydantic = {version = "^2.5", extras = ["dotenv"]}
sqlalchemy = "^2.0"
alembic = "^1.13"
structlog = "^24.1"
prometheus-client = "^0.20"
asyncpg = {version = "^0.29", optional = true}
[tool.poetry.group.dev.dependencies]
pytest = "^8.0"
pytest-asyncio = "^0.23"
pytest-cov = "^5.0"
hypothesis = "^6.90"
ruff = "^0.4"
mypy = "^1.8"
types-requests = "^2.31"
[tool.poetry.group.lint.dependencies]
ruff = "^0.4"
mypy = "^1.8"
pre-commit = "^3.5"
[tool.poetry.scripts]
user-service = "user_service.cli:main"
db-migrate = "user_service.db:migrate"
[tool.poetry.extras]
postgresql = ["asyncpg"]
[tool.ruff]
target-version = "py311"
line-length = 100
[tool.ruff.lint]
select = ["E", "F", "I", "UP", "B", "SIM", "RUF", "D"]
ignore = ["D100", "D104"]
[tool.pytest.ini_options]
asyncio_mode = "auto"
testpaths = ["tests"]
filterwarnings = [
"error::DeprecationWarning",
"error::PendingDeprecationWarning",
]
[tool.mypy]
python_version = "3.11"
strict = true
warn_return_any = true
warn_unused_configs = true
disallow_untyped_defs = true
[[tool.mypy.overrides]]
module = "tests.*"
disallow_untyped_defs = false
Pattern 2: Dependency Resolution Strategies (BAD vs. GOOD)
# ❌ BAD — Too loose version constraints lead to non-reproducible builds
[tool.poetry.dependencies]
python = "^3.8"
requests = "*" # Wildcard! Could pull anything including breaking changes
flask = ">=1.0" # No upper bound — could break with any future release
# ✅ GOOD — Semantic versioning constraints balance safety and flexibility
[tool.poetry.dependencies]
python = "^3.11" # >=3.11, <4.0 (compatible release)
httpx = ">=0.27,<1.0" # Allow minor/patch updates within major version
pydantic = "^2.5" # >=2.5, <3.0 — stable API guarantees within major version
sqlalchemy = "~2.0.23" # >=2.0.23, <2.1.0 — patch-level stability for critical dep
# ❌ BAD — Conflicting constraints between groups
[tool.poetry.group.api.dependencies]
httpx = "^0.25" # Locks to older minor version
[tool.poetry.group.test.dependencies]
pytest-httpx = "^0.28" # Depends on httpx >=0.28 — conflict!
# ✅ GOOD — Aligned version constraints across all groups
[tool.poetry.dependencies]
httpx = "^0.27" # Shared minimum across all groups
[tool.poetry.group.api.dependencies]
# Inherits httpx from main, no re-pinning needed
[tool.poetry.group.test.dependencies]
pytest-httpx = "^0.28" # Compatible with httpx ^0.27
Pattern 3: Poetry Plugin for Custom Build Hooks
Poetry's plugin system lets you inject custom behavior into the build and publish lifecycle. A common use case is running pre-publish validation or adding post-install hooks:
# poetry_plugin.py — Register as a Poetry plugin via entry_points in your pyproject.toml
from poetry.plugins.application_plugin import ApplicationPlugin
from cleo.events.console_events import COMMAND, POST_COMMAND
class ValidationPlugin(ApplicationPlugin):
"""Run dependency and config validation before every command."""
def activate(self, app):
"""Register event listeners when the plugin loads."""
app.command_dispatchers[COMMAND].listen(self.on_command)
app.command_dispatchers[POST_COMMAND].listen(self.on_post_command)
def on_command(self, event, name, dispatcher=None):
"""Run before any Poetry command executes."""
import os
from pathlib import Path
lock_path = Path("poetry.lock")
project_path = Path("pyproject.toml")
if (
lock_path.exists()
and project_path.exists()
and lock_path.stat().st_mtime < project_path.stat().st_mtime
):
print(
"\033[91mWarning: pyproject.toml was modified more recently than poetry.lock. "
"Run 'poetry lock' to update.\033[0m"
)
def on_post_command(self, event, name, dispatcher=None):
"""Run after every Poetry command completes."""
# Auto-regenerate documentation if docs dependencies changed
print("\033[94mDocumentation sources may have changed — consider 'poetry run mkdocs build'\033[0m")
Register the plugin via entry points in pyproject.toml:
[tool.poetry.plugins."poetry.application.plugin"]
validation = "poetry_plugin.ValidationPlugin"
Pattern 4: Migration from pip/requirements.txt to Poetry
# Step 1: Generate an initial poetry.lock from requirements.txt
poetry import requirements requirements.txt
# Step 2: This creates pyproject.toml with dependencies extracted from requirements.txt
# Review the generated file and adjust version constraints
# Step 3: Resolve the full dependency tree
poetry lock
# Step 4: Install everything into a fresh virtual environment
poetry install
# Step 5: Verify functionality — run your test suite
poetry run pytest -x --tb=short
# Step 6: If tests pass, remove requirements.txt (Poetry manages deps via pyproject.toml + lockfile)
rm requirements.txt requirements-dev.txt
# Step 7: Update CI/CD to use poetry install instead of pip install -r requirements.txt
Constraints
MUST DO
- Commit
poetry.lock to version control for deterministic, reproducible builds — never skip this
- Pin
requires-python to a specific minimum version in pyproject.toml (e.g., "^3.10")
- Use semantic version constraints (
^, ~) rather than wildcards (*) or exact pins (except in the lockfile)
- Separate dev/test dependencies from production dependencies using named groups (
[tool.poetry.group.dev.dependencies])
- Use
poetry run to execute commands — never manually activate the virtual environment in scripts or CI
- Validate published packages with
twine check dist/* before running poetry publish
- Run
poetry install --only main in production deployments to skip unnecessary dev dependencies and speed up install
MUST NOT DO
- Edit
poetry.lock manually — always regenerate with poetry lock, even for single dependency changes
- Mix Poetry-managed installs with
pip install in the same virtual environment — this corrupts the dependency tree
- Commit
pyproject.toml without a matching poetry.lock — future builds will be non-deterministic
- Use
* (wildcard) version constraints for production dependencies — this defeats reproducible builds
- Remove
poetry.lock to "fix" dependency conflicts — instead, adjust version constraints in pyproject.toml and re-resolve
- Install Poetry via
pip install poetry as the primary method — use the official installer (install.sh) or your package manager to avoid bootstrap issues
Output Template
When configuring or auditing a Poetry-managed project, produce:
- Dependency Audit — List all production and dev dependencies with resolved versions from poetry.lock, flagging any wildcard constraints or version conflicts
- pyproject.toml Review — Validate structure: presence of
[tool.poetry], requires-python, dependency groups, scripts, and build-system configuration
- Lockfile Freshness Check — Verify poetry.lock timestamp against pyproject.toml; report if manual edits may have drifted the lockfile from the manifest
- Workspace Analysis — For monorepo setups: confirm all workspace members are declared, cross-references use
path dependencies, and there are no inter-package version conflicts
- Publish Readiness — Checklist: twine validation passed, changelog updated, version bumped, PyPI token configured, extras defined for optional features
Related Skills
| Skill |
Purpose |
python-uv |
uv alternative by Astral — 10-100x faster package resolution with Rust backend |
package-ecosystem-navigator |
General package manager ecosystem comparison (npm, pypi, cargo, etc.) |
dependency-supply-chain-security |
Dependency security auditing, CVE scanning, and supply chain protections |
Live References
Authoritative documentation links for this skill's domain. The model follows markdown links at load time to resolve external references and inline content.
1---2name: poetry3description: Manages Python project dependencies, virtual environments, building, and publishing using Poetry — covering dependency resolution, lockfiles, workspaces, plugin system, and migration from pip.4license: MIT5---678910# Poetry — Python Package & Dependency Manager1112Manages Python project dependencies, virtual environments, building, and publishing using Poetry. Covers dependency resolution strategies, lockfile management, workspace/multirepo setup, plugin architecture, and migration from pip/requirements.txt workflows.1314## TL;DR Checklist1516- [ ] Use `pyproject.toml` with `[tool.poetry]` section — no `setup.py`, no `requirements.txt`17- [ ] Always commit `poetry.lock` for reproducible builds in production and CI18- [ ] Use `poetry add <pkg>` to install dependencies (never pip install directly in Poetry-managed venvs)19- [ ] Pin Python version with `requires-python` in pyproject.toml20- [ ] Use named dependency groups (`group.dev.dependencies`) for dev-only packages21- [ ] Use `poetry run` or `poetry shell` to execute commands within the virtual environment22- [ ] Run `poetry lock --no-update` after manual pyproject.toml edits (never edit poetry.lock manually)2324---2526## When to Use2728Use this skill when:2930- Setting up a new Python project with structured dependency management and virtual environment isolation31- Managing complex dependency trees with version constraints across multiple packages32- Building and publishing Python packages to PyPI or private registries33- Configuring Poetry workspaces for monorepo or multi-package projects34- Migrating an existing pip/requirements.txt project to Poetry's pyproject.toml workflow35- Resolving dependency conflicts between packages with overlapping version requirements3637## When NOT to Use3839Avoid this skill for:4041- Simple one-off scripts where a virtualenv + pip is sufficient overhead — use `python -m venv` instead42- Projects that already use `uv` or `pip-tools` and have no migration pressure — don't swap tools mid-project43- Python environments managed externally (Docker containers, system packages, conda) — Poetry's venv integration conflicts with these4445---4647## Core Workflow4849### Phase 1: Project Initialization50511. **Create the project structure** — Run `poetry init` interactively or use `poetry new <package-name>` for a library with src/ layout. The init command creates pyproject.toml, README.md, and optionally a tests directory.52 **Checkpoint:** Verify pyproject.toml contains `[tool.poetry]`, valid name, version, Python version constraint (e.g., `requires-python = "^3.10"`), and at least one dependency or group.53542. **Configure the build backend** — Poetry uses setuptools by default but supports hatchling, flit, or pdm-backend as alternatives. Set the backend explicitly in pyproject.toml:55 ```toml56 [build-system]57 requires = ["setuptools>=61.0", "wheel"]58 build-backend = "setuptools.build_meta"5960 # Or for hatchling (recommended for new projects):61 # requires = ["hatchling"]62 # build-backend = "hatchling.build"63 ```6465### Phase 2: Dependency Management66673. **Add production dependencies** — Use `poetry add <package>` or `poetry add <package>@<version-spec>`. Poetry resolves the full dependency tree, picks compatible versions, and writes to both pyproject.toml and poetry.lock.68 ```bash69 # Add latest compatible version70 poetry add httpx7172 # Pin to major version only73 poetry add "requests>=2.28,<3"7475 # Add with extras (e.g., psycopg2 binary)76 poetry add "psycopg2-binary"7778 # Add from a specific git repository79 poetry add "my-lib @ git+https://github.com/user/my-lib.git@main"8081 # Add from a local path dependency82 poetry add "../shared-utils"83 ```84854. **Configure dependency groups for non-production dependencies** — Poetry supports named dependency groups (introduced in 1.2) to separate dev, test, docs, and optional features:86 ```toml87 [tool.poetry]88 name = "my-project"89 version = "0.1.0"90 requires-python = ">=3.10,<4.0"9192 [tool.poetry.dependencies]93 python = "^3.10"94 httpx = "^0.27"95 pydantic = "^2.5"96 sqlalchemy = "^2.0"9798 [tool.poetry.group.dev.dependencies]99 pytest = "^8.0"100 pytest-asyncio = "^0.23"101 ruff = "^0.4"102 mypy = "^1.8"103 ipython = "^8.0"104105 [tool.poetry.group.test.dependencies]106 pytest-cov = "^5.0"107 hypothesis = "^6.90"108109 [tool.poetry.group.docs.dependencies]110 mkdocs = "^1.5"111 mkdocstrings = {extras = ["python"], version = "^0.25"}112113 # Optional dependencies (installable via extras)114 [tool.poetry.extras]115 database = ["sqlalchemy", "alembic"]116 async = ["httpx", "aiosqlite"]117 ```1181195. **Install dependencies** — `poetry install` installs all dependencies defined in pyproject.toml into the virtual environment and ensures poetry.lock is up to date. Use flags for selective installation:120 ```bash121 # Install everything (default — production + all groups)122 poetry install123124 # Production only, skip all dependency groups125 poetry install --only main126127 # Dev group only128 poetry install --only dev129130 # Specific groups131 poetry install --with dev,test132133 # Sync to match lockfile exactly (removes packages not in lock)134 poetry lock && poetry install135 ```136137### Phase 3: Development Workflow1381396. **Execute commands within the virtual environment** — Poetry provides three mechanisms: `poetry run` for one-off execution, `poetry shell` for an interactive subshell, or `poetry env use` to select a specific Python interpreter.140 ```bash141 # Run a single command in the project's venv142 poetry run pytest tests/143 poetry run ruff check .144 poetry run mypy src/145146 # Enter an interactive shell with the venv activated147 poetry shell148 (my-project-xyz) $ pytest tests/149 (my-project-xyz) $ exit150151 # Run a script defined in [tool.poetry.scripts]152 poetry run my-cli --help153 ```1541557. **Configure project scripts** — Define CLI entry points and convenience scripts in pyproject.toml:156 ```toml157 [tool.poetry.scripts]158 my-cli = "my_project.cli:main"159 migrate-db = "my_project.db:migrate"160161 # Development convenience scripts (run via poetry run <name>)162 [tool.poetry.group.dev.scripts]163 test = "pytest tests/ --cov=src --cov-report=term-missing"164 lint = ["ruff", "check", "."]165 typecheck = "mypy src/"166 docs-serve = "mkdocs serve"167 ```168169### Phase 4: Workspace (Monorepo) Setup1701718. **Configure Poetry workspaces for multi-package projects** — Poetry 1.2+ supports workspaces natively. Define a root pyproject.toml that declares member packages:172 ```toml173 [tool.poetry]174 name = "my-workspace"175 version = "0.1.0"176 requires-python = ">=3.10"177178 [tool.poetry.dependencies]179 python = "^3.10"180181 [tool.poetry.workspaces]182 members = ["packages/*"]183 ```184185 Each member package has its own pyproject.toml with its own dependencies:186 ```toml187 # packages/core/pyproject.toml188 [tool.poetry]189 name = "my-core"190 version = "0.1.0"191 requires-python = "^3.10"192193 [tool.poetry.dependencies]194 python = "^3.10"195 pydantic = "^2.5"196197 # packages/api/pyproject.toml198 [tool.poetry]199 name = "my-api"200 version = "0.1.0"201 requires-python = "^3.10"202203 [tool.poetry.dependencies]204 python = "^3.10"205 httpx = "^0.27"206 my-core = { path = "../core", develop = true }207 ```208209 **Checkpoint:** Run `poetry install` from the workspace root — it must resolve all member packages and create a unified lockfile with no dependency conflicts between workspaces.210211### Phase 5: Building and Publishing2122139. **Build distribution packages** — Generate wheel and/or sdist artifacts:214 ```bash215 # Build both wheel and source distribution216 poetry build217218 # Output in dist/ directory219 # dist/my_project-0.1.0-py3-none-any.whl220 # dist/my_project-0.1.0.tar.gz221222 # Build only wheel (faster, no source)223 poetry build --wheel224225 # Build only source distribution226 poetry build --sdist227 ```22822910. **Publish to PyPI or private registry** — Configure repository credentials and publish:230 ```bash231 # Publish to PyPI (requires PYPI_TOKEN env var or interactive login)232 poetry publish233234 # Dry-run to validate before publishing235 poetry build && twine check dist/*236237 # Publish to a custom/private repository238 poetry config repositories.my-registry https://pypi.internal.company.com/simple/239 poetry config http-basic.my-registry $USERNAME $PASSWORD240 poetry publish -r my-registry241242 # Or use the upload command directly243 poetry upload -r my-registry244 ```245246---247248## Implementation Patterns249250### Pattern 1: Complete pyproject.toml for a Production API Service251252```toml253[tool.poetry]254name = "user-service"255version = "1.2.0"256description = "REST API service for user management with async database access"257authors = ["Engineering Team <eng@company.com>"]258readme = "README.md"259license = "MIT"260packages = [{ include = "user_service", from = "src" }]261262[tool.poetry.dependencies]263python = "^3.11"264httpx = {version = "^0.27", extras = ["brotli"]}265pydantic = {version = "^2.5", extras = ["dotenv"]}266sqlalchemy = "^2.0"267alembic = "^1.13"268structlog = "^24.1"269prometheus-client = "^0.20"270asyncpg = {version = "^0.29", optional = true}271272[tool.poetry.group.dev.dependencies]273pytest = "^8.0"274pytest-asyncio = "^0.23"275pytest-cov = "^5.0"276hypothesis = "^6.90"277ruff = "^0.4"278mypy = "^1.8"279types-requests = "^2.31"280281[tool.poetry.group.lint.dependencies]282ruff = "^0.4"283mypy = "^1.8"284pre-commit = "^3.5"285286[tool.poetry.scripts]287user-service = "user_service.cli:main"288db-migrate = "user_service.db:migrate"289290[tool.poetry.extras]291postgresql = ["asyncpg"]292293[tool.ruff]294target-version = "py311"295line-length = 100296297[tool.ruff.lint]298select = ["E", "F", "I", "UP", "B", "SIM", "RUF", "D"]299ignore = ["D100", "D104"]300301[tool.pytest.ini_options]302asyncio_mode = "auto"303testpaths = ["tests"]304filterwarnings = [305 "error::DeprecationWarning",306 "error::PendingDeprecationWarning",307]308309[tool.mypy]310python_version = "3.11"311strict = true312warn_return_any = true313warn_unused_configs = true314disallow_untyped_defs = true315316[[tool.mypy.overrides]]317module = "tests.*"318disallow_untyped_defs = false319```320321### Pattern 2: Dependency Resolution Strategies (BAD vs. GOOD)322323```toml324# ❌ BAD — Too loose version constraints lead to non-reproducible builds325[tool.poetry.dependencies]326python = "^3.8"327requests = "*" # Wildcard! Could pull anything including breaking changes328flask = ">=1.0" # No upper bound — could break with any future release329330# ✅ GOOD — Semantic versioning constraints balance safety and flexibility331[tool.poetry.dependencies]332python = "^3.11" # >=3.11, <4.0 (compatible release)333httpx = ">=0.27,<1.0" # Allow minor/patch updates within major version334pydantic = "^2.5" # >=2.5, <3.0 — stable API guarantees within major version335sqlalchemy = "~2.0.23" # >=2.0.23, <2.1.0 — patch-level stability for critical dep336337# ❌ BAD — Conflicting constraints between groups338[tool.poetry.group.api.dependencies]339httpx = "^0.25" # Locks to older minor version340341[tool.poetry.group.test.dependencies]342pytest-httpx = "^0.28" # Depends on httpx >=0.28 — conflict!343344# ✅ GOOD — Aligned version constraints across all groups345[tool.poetry.dependencies]346httpx = "^0.27" # Shared minimum across all groups347348[tool.poetry.group.api.dependencies]349# Inherits httpx from main, no re-pinning needed350351[tool.poetry.group.test.dependencies]352pytest-httpx = "^0.28" # Compatible with httpx ^0.27353```354355### Pattern 3: Poetry Plugin for Custom Build Hooks356357Poetry's plugin system lets you inject custom behavior into the build and publish lifecycle. A common use case is running pre-publish validation or adding post-install hooks:358359```python360# poetry_plugin.py — Register as a Poetry plugin via entry_points in your pyproject.toml361from poetry.plugins.application_plugin import ApplicationPlugin362from cleo.events.console_events import COMMAND, POST_COMMAND363364365class ValidationPlugin(ApplicationPlugin):366 """Run dependency and config validation before every command."""367368 def activate(self, app):369 """Register event listeners when the plugin loads."""370 app.command_dispatchers[COMMAND].listen(self.on_command)371 app.command_dispatchers[POST_COMMAND].listen(self.on_post_command)372373 def on_command(self, event, name, dispatcher=None):374 """Run before any Poetry command executes."""375 import os376 from pathlib import Path377378 lock_path = Path("poetry.lock")379 project_path = Path("pyproject.toml")380381 if (382 lock_path.exists()383 and project_path.exists()384 and lock_path.stat().st_mtime < project_path.stat().st_mtime385 ):386 print(387 "\033[91mWarning: pyproject.toml was modified more recently than poetry.lock. "388 "Run 'poetry lock' to update.\033[0m"389 )390391 def on_post_command(self, event, name, dispatcher=None):392 """Run after every Poetry command completes."""393 # Auto-regenerate documentation if docs dependencies changed394 print("\033[94mDocumentation sources may have changed — consider 'poetry run mkdocs build'\033[0m")395```396397Register the plugin via entry points in pyproject.toml:398```toml399[tool.poetry.plugins."poetry.application.plugin"]400validation = "poetry_plugin.ValidationPlugin"401```402403### Pattern 4: Migration from pip/requirements.txt to Poetry404405```bash406# Step 1: Generate an initial poetry.lock from requirements.txt407poetry import requirements requirements.txt408409# Step 2: This creates pyproject.toml with dependencies extracted from requirements.txt410# Review the generated file and adjust version constraints411412# Step 3: Resolve the full dependency tree413poetry lock414415# Step 4: Install everything into a fresh virtual environment416poetry install417418# Step 5: Verify functionality — run your test suite419poetry run pytest -x --tb=short420421# Step 6: If tests pass, remove requirements.txt (Poetry manages deps via pyproject.toml + lockfile)422rm requirements.txt requirements-dev.txt423424# Step 7: Update CI/CD to use poetry install instead of pip install -r requirements.txt425```426427---428429## Constraints430431### MUST DO432- Commit `poetry.lock` to version control for deterministic, reproducible builds — never skip this433- Pin `requires-python` to a specific minimum version in pyproject.toml (e.g., `"^3.10"`)434- Use semantic version constraints (`^`, `~`) rather than wildcards (`*`) or exact pins (except in the lockfile)435- Separate dev/test dependencies from production dependencies using named groups (`[tool.poetry.group.dev.dependencies]`)436- Use `poetry run` to execute commands — never manually activate the virtual environment in scripts or CI437- Validate published packages with `twine check dist/*` before running `poetry publish`438- Run `poetry install --only main` in production deployments to skip unnecessary dev dependencies and speed up install439440### MUST NOT DO441- Edit `poetry.lock` manually — always regenerate with `poetry lock`, even for single dependency changes442- Mix Poetry-managed installs with `pip install` in the same virtual environment — this corrupts the dependency tree443- Commit `pyproject.toml` without a matching `poetry.lock` — future builds will be non-deterministic444- Use `*` (wildcard) version constraints for production dependencies — this defeats reproducible builds445- Remove `poetry.lock` to "fix" dependency conflicts — instead, adjust version constraints in `pyproject.toml` and re-resolve446- Install Poetry via `pip install poetry` as the primary method — use the official installer (`install.sh`) or your package manager to avoid bootstrap issues447448---449450## Output Template451452When configuring or auditing a Poetry-managed project, produce:4534541. **Dependency Audit** — List all production and dev dependencies with resolved versions from poetry.lock, flagging any wildcard constraints or version conflicts4552. **pyproject.toml Review** — Validate structure: presence of `[tool.poetry]`, `requires-python`, dependency groups, scripts, and build-system configuration4563. **Lockfile Freshness Check** — Verify poetry.lock timestamp against pyproject.toml; report if manual edits may have drifted the lockfile from the manifest4574. **Workspace Analysis** — For monorepo setups: confirm all workspace members are declared, cross-references use `path` dependencies, and there are no inter-package version conflicts4585. **Publish Readiness** — Checklist: twine validation passed, changelog updated, version bumped, PyPI token configured, extras defined for optional features459460---461462## Related Skills463464| Skill | Purpose |465|---|---|466| `python-uv` | uv alternative by Astral — 10-100x faster package resolution with Rust backend |467| `package-ecosystem-navigator` | General package manager ecosystem comparison (npm, pypi, cargo, etc.) |468| `dependency-supply-chain-security` | Dependency security auditing, CVE scanning, and supply chain protections |469470---471472## Live References473474> Authoritative documentation links for this skill's domain. The model follows markdown links at load time to resolve external references and inline content.475476- [Poetry Documentation](https://python-poetry.org/docs/)477- [Poetry pyproject.toml Reference](https://python-poetry.org/docs/pyproject/)478- [Poetry Workspaces (Monorepo Support)](https://python-poetry.org/docs/workspaces/)479- [Poetry Plugin Development](https://python-poetry.org/docs/plugins/)480- [PEP 621 — Storing Project Metadata in pyproject.toml](https://peps.python.org/pep-0621/)481- [Twine — PyPI Package Upload Tool](https://twine.readthedocs.io/)482- [Semantic Versioning (SemVer) Specification](https://semver.org/)