Choosing & Migrating

This content was generated with the assistance of AI and should be verified against the official documentation before being relied on in production.

The reference material consulted while preparing these pages is listed in the Python Reference bibliography.

pip and Poetry are not two answers to the same question. pip installs distributions into an environment; everything else in a workflow comes from separate tools and standard files. Poetry manages the whole project: metadata, dependency resolution, a lock file, virtual environments, building and publishing. This page compares them and shows how to move a project from one to the other. It was written against pip 26.2.2 and Poetry 2.5.1, and the migration commands were run on CPython 3.14.6 on 2026-10-22.

Two different scopes

pip’s own documentation says it plainly: "pip is not a workflow management tool". Creating environments, running tasks, managing the Python interpreter and managing "the project" are outside its scope, and there is deliberately no pip build for sdists. pip gives you installation, a resolver, requirements files, editable installs and, recently, standard lock files and dependency groups.

The Packaging User Guide lists Poetry among the workflow tools, alongside Flit, Hatch, nox, PDM, Pipenv and tox: environment managers that also run tasks, build distributions and upload them, and in some cases lock applications.

Feature by feature

Concern pip 26.2.2 (+ build, twine, venv) Poetry 2.5.1

Resolver

Backtracking resolver (since 20.3) for the requirements you give it, against this environment’s Python and platform.

Its own PubGrub-based solver, which resolves for the whole requires-python range and every platform at once.

Lock file

pip freeze or hashed requirements files; pip lock writes pylock.toml for one platform (experimental, 25.1).

poetry.lock, cross-platform; exports to requirements.txt, constraints.txt and pylock.toml with the export plugin.

Environments

None: create them with python -m venv; --python targets another one.

Creates and selects a venv per project (poetry env use, virtualenvs.* settings).

Dependency groups

--group reads PEP 735 [dependency-groups] (25.1).

PEP 735 groups (2.2) and [tool.poetry.group.*], with --with, --without, --only.

Adding a dependency

Edit pyproject.toml or a requirements file by hand, then install.

poetry add edits pyproject.toml, re-locks and installs.

Building

pip wheel builds wheels only; use python -m build for sdists and wheels with any backend.

poetry build with poetry-core.

Publishing

twine upload, with Trusted Publishing support since twine 6.1.

poetry publish; no native Trusted Publishing or attestations (python-poetry/poetry#7940 is still open).

Plugins

None.

Application plugins (poetry self add, [tool.poetry.requires-plugins]).

Python management

None.

poetry python install (experimental, 2.1) downloads standalone CPython builds.

Speed

A single install is fast, especially with PEP 658 metadata; there is no lock step.

poetry lock can take noticeably longer than an install, because it resolves for all platforms; installs from the lock are fast.

uv, PDM and Hatch are the other common choices. uv (third party, Astral) is a very fast resolver and installer with a pip-compatible interface plus its own project workflow and uv.lock; PDM is a standards-first workflow tool that writes PEP 621 metadata and pylock.toml; Hatch is the PyPA’s workflow tool around the hatchling backend, with environments and versioning. They are listed on the ecosystem page and are not documented in this section.

When to pick which

flowchart TD A[New or existing project] --> B{Do you want one tool for environments, locking, building and publishing?} B -->|Yes| C{Is a large ecosystem of plugins and long-term Poetry use in the team important?} C -->|Yes| P[Poetry] C -->|No, speed or standards matter most| U[A standards-first workflow tool: uv, PDM or Hatch] B -->|No| D{Is it a library published to an index?} D -->|Yes| L["pip + build + twine, backend of your choice (hatchling, setuptools, poetry-core)"] D -->|No, an application or service| E{Need a cross-platform lock?} E -->|No| R["pip with a hashed requirements file or pip lock"] E -->|Yes| P
  • pip with standard files suits libraries, small projects, CI images and environments that must not depend on one vendor’s tool. Everything is a standard (PEP 517/518/621/735/751), so any other tool can take over later.

  • Poetry suits applications and teams that want one command set, a cross-platform lock file and managed environments without assembling tools. Since 2.0 it writes standard [project] metadata, so the cost of leaving it later is small.

Migrating a requirements.txt project to Poetry

The pip flavour of birdtally-report has a hand-written, fully pinned requirements file:

# birdtally-report, pip flavour
birdtally[pretty]>=0.1.0
rich==15.0.0
markdown-it-py==4.2.0
mdurl==0.1.2
Pygments==2.21.0
  1. Create pyproject.toml with poetry init (non-interactively here; without --no-interaction it asks for each value):

    poetry init --no-interaction --name birdtally-report --author "The birdtally authors" --python ">=3.10"
    cat pyproject.toml
    [project]
    name = "birdtally-report"
    version = "0.1.0"
    description = ""
    authors = [
        {name = "The birdtally authors"}
    ]
    requires-python = ">=3.10"
    dependencies = [
    ]
    
    
    [build-system]
    requires = ["poetry-core>=2.0.0,<3.0.0"]
    build-backend = "poetry.core.masonry.api"

    Without --author, poetry init takes the author from your Git configuration. For an application that is never built, add [tool.poetry] package-mode = false (see Poetry: Basic Usage & Project Setup).

  2. Add the private source and the dependency that lives on it, with a source constraint:

    poetry source add --priority=explicit birdwatch http://localhost:8080/simple/
    poetry add --source birdwatch "birdtally[pretty]>=0.1.0" --lock
  3. Add the rest. The tempting one-liner is poetry add $(cat requirements.txt):

    poetry add $(grep -v '^#' requirements.txt | grep -v birdtally) --lock

    which leaves:

    dependencies = [
        "birdtally[pretty] (>=0.1.0)",
        "rich (==15.0.0)",
        "markdown-it-py (==4.2.0)",
        "mdurl (==0.1.2)",
        "pygments (==2.21.0)"
    ]

The caveats are visible in the result:

  • Transitive pins became direct dependencies. markdown-it-py, mdurl and pygments are not used by birdtally-report; they were only in the file because it was a freeze. Add only what the project imports, and let poetry.lock hold the rest.

  • Exact pins became the constraints. ==15.0.0 in [project] means poetry update can never move. Replace them with ranges and let the lock pin versions: poetry add "rich>=15".

  • Comments, -r, -c, --hash and option lines in a requirements file are not requirements, and $(cat …) passes them on as words. Filter them out as above, or feed the file line by line.

  • Indexes (--index-url, --extra-index-url lines) have to be re-created as [[tool.poetry.source]] entries.

Migrating from Poetry to pure standards

Since Poetry 2.0 most of a project is already standard. Leaving Poetry means:

  1. Metadata in [project]. If the project still uses the Poetry 1.x layout (name, version, authors and dependencies under [tool.poetry]), move them to [project] first. Poetry 2 reads both, and the Poetry FAQ shows the transition with either static dependencies or dynamic = ["dependencies"] to keep Poetry’s richer syntax. Caret and tilde constraints must become PEP 440 ranges; see the table in Versions & Dependency Specifiers.

  2. Groups in [dependency-groups]. [tool.poetry.group.test.dependencies] becomes test = ["pytest>=8"].

  3. A lock other tools can read. poetry export -f pylock.toml or -f requirements.txt turns poetry.lock into a standard or pip-format file.

  4. Optionally another backend. poetry-core keeps working with every frontend; switching to hatchling or setuptools needs only a new [build-system] table and the backend’s own equivalent of [tool.poetry] packages/include.

Keeping a project usable by both tools

The Poetry flavour of birdtally uses [project] metadata, PEP 735 groups and poetry-core as its backend, so a contributor without Poetry can work on it with pip alone:

python -m venv .venv
.venv/bin/python -m pip install . --group test
.venv/bin/python -m pytest -q
Building wheels for collected packages: birdtally
  Building wheel for birdtally (pyproject.toml): started
  Building wheel for birdtally (pyproject.toml): finished with status 'done'
Successfully built birdtally
Installing collected packages: pygments, pluggy, packaging, iniconfig, birdtally, pytest
Successfully installed birdtally-0.1.0 iniconfig-2.3.1 packaging-26.3 pluggy-1.7.0 pygments-2.21.0 pytest-9.1.1
…
2 passed in 0.02s

The rules that make this work:

  • keep all metadata and dependencies in [project] (no dynamic dependencies);

  • keep development tools in [dependency-groups], which both pip and Poetry read;

  • use only PEP 440 constraints in [project];

  • if you need a private source, put the source constraint in [tool.poetry.dependencies] and document the equivalent --index-url for pip users.

The pip user installs whatever the resolver picks today, not the versions in poetry.lock; export a lock if they need those.