|$ curl https://forge-ai.dev/api/markdown?path=docs/python/poetry-uv
$cat docs/poetry-&-uv.md
updated Today·18-24 min read·published
Poetry & uv
Introduction
Modern Python packaging centers on pyproject.toml. pip + venv still works; Poetry pioneered lockfiles UX; uv is the ultra-fast installer/resolver from Astral.
pip vs Poetry vs uv
| Tool | Strength | Lockfile |
|---|---|---|
| pip + venv | Universal, simple | requirements.txt (manual) |
| pip-tools | Compile pins | requirements.txt |
| Poetry | Projects + publish UX | poetry.lock |
| uv | Speed + pip/venv/poetry compat | uv.lock |
| PDM / Hatch | PEP 621 focus | varies |
pyproject.toml
pyproject.toml
TOML
| 1 | [project] |
| 2 | name = "forge-demo" |
| 3 | version = "0.1.0" |
| 4 | description = "Demo" |
| 5 | readme = "README.md" |
| 6 | requires-python = ">=3.11" |
| 7 | dependencies = [ |
| 8 | "httpx>=0.27", |
| 9 | "pydantic>=2", |
| 10 | ] |
| 11 | |
| 12 | [project.optional-dependencies] |
| 13 | dev = ["pytest>=8", "ruff>=0.6", "mypy>=1.11"] |
| 14 | |
| 15 | [project.scripts] |
| 16 | forge-demo = "forge_demo.cli:main" |
| 17 | |
| 18 | [build-system] |
| 19 | requires = ["hatchling"] |
| 20 | build-backend = "hatchling.build" |
uv Workflows
uv.sh
Bash
| 1 | # install uv: curl -LsSf https://astral.sh/uv/install.sh | sh |
| 2 | uv init forge-demo && cd forge-demo |
| 3 | uv add httpx pydantic |
| 4 | uv add --dev pytest ruff mypy |
| 5 | uv sync |
| 6 | uv run pytest |
| 7 | uv lock |
| 8 | uv pip install -r requirements.txt # pip-compatible interface |
| 9 | uv venv && source .venv/bin/activate |
🔥
pro tip
uv sync creates .venv and installs from uv.lock — fast CI-friendly reproducible installs.
Poetry Workflows
poetry.sh
Bash
| 1 | poetry new forge-demo && cd forge-demo |
| 2 | poetry add httpx pydantic |
| 3 | poetry add --group dev pytest ruff mypy |
| 4 | poetry install |
| 5 | poetry run pytest |
| 6 | poetry build |
| 7 | poetry publish # needs credentials |
poetry-pyproject.toml
TOML
| 1 | [tool.poetry] |
| 2 | name = "forge-demo" |
| 3 | version = "0.1.0" |
| 4 | description = "" |
| 5 | authors = ["You <you@example.com>"] |
| 6 | packages = [{include = "forge_demo"}] |
| 7 | |
| 8 | [tool.poetry.dependencies] |
| 9 | python = "^3.11" |
| 10 | httpx = "^0.27" |
| 11 | |
| 12 | [tool.poetry.group.dev.dependencies] |
| 13 | pytest = "^8.0" |
| 14 | |
| 15 | [build-system] |
| 16 | requires = ["poetry-core"] |
| 17 | build-backend = "poetry.core.masonry.api" |
Lockfiles
Commit lockfiles for applications. Libraries may omit them or provide constraints — but apps should pin the world.
| File | Tool |
|---|---|
| poetry.lock | Poetry |
| uv.lock | uv |
| requirements.txt | pip / pip-tools / uv pip compile |
| Pipfile.lock | Pipenv (legacy-ish) |
Workspaces / Monorepos
uv-workspace.toml
TOML
| 1 | # uv workspaces (excerpt) |
| 2 | [tool.uv.workspace] |
| 3 | members = ["packages/*"] |
| 4 | |
| 5 | # Poetry: use plugins / multiple projects; or path deps: |
| 6 | # mylib = { path = "../mylib", develop = true } |
path-deps.sh
Bash
| 1 | # uv |
| 2 | uv add --editable ../mylib |
| 3 | # poetry |
| 4 | poetry add --editable ../mylib |
Still Using pip
pip.sh
Bash
| 1 | python -m venv .venv |
| 2 | source .venv/bin/activate |
| 3 | python -m pip install -U pip |
| 4 | pip install -e ".[dev]" |
| 5 | pip freeze > requirements.txt # crude; prefer uv pip compile / pip-tools |
⚠
warning
pip freeze captures transitive pins but loses the distinction between direct and transitive deps. Prefer lock tools.
CI Pattern
ci.sh
Bash
| 1 | uv sync --frozen |
| 2 | uv run ruff check . |
| 3 | uv run mypy src |
| 4 | uv run pytest -q |
Version Specifiers
versions.toml
TOML
| 1 | # PEP 440 examples in dependencies: |
| 2 | # "httpx>=0.27,<0.29" |
| 3 | # "pydantic~=2.8" # compatible release |
| 4 | # "django>=5.0.0" |
| Specifier | Meaning |
|---|---|
| >=1.2.3 | Lower bound |
| ~=1.2.3 | >=1.2.3,<1.3.0 compatible |
| ==1.2.3 | Exact pin (apps via lock) |
| ^1.2 (Poetry) | Semver caret |
Exporting Requirements
export.sh
Bash
| 1 | # poetry |
| 2 | poetry export -f requirements.txt --output requirements.txt --without-hashes |
| 3 | # uv |
| 4 | uv export --no-hashes -o requirements.txt |
| 5 | # useful for platforms that only accept requirements.txt |
Align with Ruff / Pytest
tools.toml
TOML
| 1 | [tool.ruff] |
| 2 | line-length = 88 |
| 3 | target-version = "py311" |
| 4 | |
| 5 | [tool.pytest.ini_options] |
| 6 | testpaths = ["tests"] |
| 7 | |
| 8 | [tool.mypy] |
| 9 | python_version = "3.11" |
| 10 | strict = true |
Which Should You Pick?
- Greenfield apps in 2026: uv is an excellent default
- Existing Poetry repos: stay on Poetry or migrate carefully to uv
- Library maintainers: PEP 621 pyproject + tox/nox; lock optional
- Simple scripts: uvx / pipx for tools; uv run for project scripts
Migrating Poetry → uv
migrate.sh
Bash
| 1 | # Rough path: |
| 2 | # 1. Ensure pyproject has [project] deps (Poetry 2 / export) |
| 3 | # 2. uv lock |
| 4 | # 3. uv sync |
| 5 | # 4. Replace poetry run with uv run in CI |
| 6 | # 5. Keep poetry.lock until uv.lock verified |
- Translate ^ constraints carefully
- Re-run tests under uv sync --frozen
- Update Dockerfile to astral-sh/uv image or install script
Docker Snippet
Dockerfile
Dockerfile
| 1 | # FROM python:3.12-slim |
| 2 | # COPY --from=ghcr.io/astral-sh/uv:latest /uv /usr/local/bin/uv |
| 3 | # WORKDIR /app |
| 4 | # COPY pyproject.toml uv.lock ./ |
| 5 | # RUN uv sync --frozen --no-dev |
| 6 | # COPY . . |
| 7 | # CMD ["uv", "run", "uvicorn", "app:app", "--host", "0.0.0.0"] |
Scripts & Entrypoints
scripts.toml
TOML
| 1 | [project.scripts] |
| 2 | forge = "forge_demo.cli:main" |
| 3 | |
| 4 | # then: uv run forge --help |
| 5 | # or after sync: .venv/bin/forge |
$Blueprint — Engineering Documentation·Section ID: PYTHON-POETRY-UV·Revision: 1.0
Community
Get help on Slack, Discord or VIP
Stuck on a guide? Join the community and ask.