Help us improve
Share bugs, ideas, or general feedback.
From migrate-to-uv
Migrate Python projects from Poetry, pipx, or pip/requirements.txt to uv. Converts pyproject.toml from Poetry format to PEP 621 standard, handles dependency groups, scripts, extras, build backends, and generates uv.lock. Use when user asks to: (1) migrate/convert from Poetry to uv, (2) replace pipx with uv tool, (3) modernize Python project packaging, (4) convert requirements.txt to uv, (5) switch to uv, or mentions "poetry to uv" or "migrate to uv".
npx claudepluginhub fprochazka/claude-code-plugins --plugin migrate-to-uvHow this skill is triggered — by the user, by Claude, or both
Slash command
/migrate-to-uv:migrate-to-uvThis skill is limited to the following tools:
The summary Claude sees in its skill listing — used to decide when to auto-load this skill
Convert Python projects from Poetry/pipx/pip to uv.
Guides uv for Python projects: dependency management with pyproject.toml, virtual environments, Python versions, PEP 723 scripts, tool installs, pip/poetry migrations, CI/CD, Docker.
Replaces pip, pip-tools, pipx, pyenv, virtualenv, and poetry for Python package and project management. Covers uv add, uv sync, uv run, uv init, build, publish, and workspace commands.
Manages Python projects with Astral's uv: dependencies via pyproject.toml, PEP 723 scripts, virtualenvs, Python versions, tool installs, pip/poetry migrations, CI/CD, Docker setup.
Share bugs, ideas, or general feedback.
Convert Python projects from Poetry/pipx/pip to uv.
IMPORTANT: Before starting any migration, assess the project for blockers. Read pyproject.toml, setup.py (if present), and the build configuration to check for the following. Report findings to the user before proceeding.
Look for signs of compiled extensions:
setup.py with ext_modules, cffi, cython, or Extension() callssetuptools, cffi, cython, pybind11, maturin, scikit-build-core.c, .cpp, .pyx, or .rs source files referenced in the buildIf found: uv_build does NOT support compiling C/C++/Rust extensions. The project must use a compatible build backend (setuptools, scikit-build-core, maturin, or hatchling with extension plugins). This is a non-trivial change — warn the user that the build backend swap may require significant effort and testing, especially for projects with complex setup.py logic.
Look for [[tool.poetry.source]] sections or references to private PyPI mirrors.
If found: uv handles authentication differently from Poetry. Poetry uses poetry config http-basic.<name> <user> <pass>, while uv uses environment variables:
UV_INDEX_<NAME>_USERNAME / UV_INDEX_<NAME>_PASSWORDUV_INDEX_URL with credentials embeddedThe migration tool won't convert auth config. The user needs to set up credentials separately for CI and local dev. See the "Private indexes" section below for format conversion.
Look for multiple pyproject.toml files, path dependencies between packages, or Poetry monorepo plugins (poetry-plugin-monorepo, monoranger).
If found: This is actually a good candidate for migration — uv workspaces handle monorepos much better than Poetry. But the migration is more involved than a single-package project. See the "Monorepo / Workspace Migration" section below.
Look for tox.ini or [tool.tox] sections.
If found: tox creates its own virtualenvs using pip by default. While tox can be configured to use uv, consider whether uv run --python 3.X pytest can replace tox environments entirely. This is a separate concern from the Poetry migration — tox can coexist with uv.
For most Poetry projects, use the migrate-to-uv tool:
uvx migrate-to-uv
What it does:
[tool.poetry] sections and converts to PEP 621 [project] format^, ~) to PEP 440 format (>=, <)[tool.poetry.group.*.dependencies] to [dependency-groups][tool.poetry.scripts] to [project.scripts][tool.poetry.extras] to [project.optional-dependencies][tool.uv.sources]poetry.lock when generating uv.lockpoetry.lock after successful conversionAfter running, verify and test:
rm -rf .venv
uv sync
uv run pytest
Note: The tool does NOT change the build backend. You may need to manually update [build-system] from poetry-core to hatchling (see step 6 below).
Use manual migration for complex projects or when automated tool fails.
Poetry → PEP 621:
# BEFORE: [tool.poetry]
[tool.poetry]
name = "myapp"
version = "1.0.0"
description = "My app"
authors = ["John Doe <john@example.com>"]
license = "MIT"
readme = "README.md"
# AFTER: [project]
[project]
name = "myapp"
version = "1.0.0"
description = "My app"
authors = [{name = "John Doe", email = "john@example.com"}]
license = {text = "MIT"}
readme = "README.md"
Version specifier conversions:
^1.2.3 → >=1.2.3,<2.0.0 (caret = compatible)~1.2.3 → >=1.2.3,<1.3.0 (tilde = patch only)python = "^3.10" → requires-python = ">=3.10,<4.0"# BEFORE
[tool.poetry.dependencies]
python = "^3.10"
requests = "^2.26.0"
pandas = {version = "^2.0", extras = ["excel"]}
# AFTER
[project]
requires-python = ">=3.10,<4.0"
dependencies = [
"requests>=2.26.0,<3.0.0",
"pandas[excel]>=2.0,<3.0",
]
Poetry groups → [dependency-groups] (PEP 735):
# BEFORE
[tool.poetry.group.dev.dependencies]
pytest = "^7.0"
ruff = "^0.1.0"
[tool.poetry.group.docs.dependencies]
sphinx = "^7.0"
# AFTER
[dependency-groups]
dev = [
"pytest>=7.0,<8.0",
"ruff>=0.1.0,<1.0",
]
docs = ["sphinx>=7.0,<8.0"]
# BEFORE
[tool.poetry.scripts]
myapp = "myapp.cli:main"
# AFTER
[project.scripts]
myapp = "myapp.cli:main"
# BEFORE
[tool.poetry.dependencies]
psycopg2 = {version = "^2.9", optional = true}
[tool.poetry.extras]
postgresql = ["psycopg2"]
# AFTER
[project.optional-dependencies]
postgresql = ["psycopg2>=2.9,<3.0"]
Choose based on project type:
# Option 1: hatchling (recommended for most projects)
[build-system]
requires = ["hatchling"]
build-backend = "hatchling.build"
[tool.hatch.build.targets.wheel]
packages = ["src/myapp"] # if using src layout
# Option 2: uv_build (fastest, pure Python only — NO C/C++/Rust extensions)
[build-system]
requires = ["uv_build>=0.6,<0.7"]
build-backend = "uv_build"
[tool.uv.build-backend]
module-name = "myapp" # use a plain string, NOT a list — list syntax breaks on older uv
module-root = "" # "" for flat layout, "src" for src layout
# Option 3: setuptools (required for C extensions with ext_modules)
[build-system]
requires = ["setuptools>=61", "cffi"] # add extension build deps
build-backend = "setuptools.build_meta"
# Option 4: scikit-build-core (for CMake-based C++ extensions)
[build-system]
requires = ["scikit-build-core"]
build-backend = "scikit_build_core.build"
# Option 5: maturin (for Rust extensions via PyO3)
[build-system]
requires = ["maturin>=1.0,<2.0"]
build-backend = "maturin"
WARNING: uv_build does NOT support compiling C/C++/Rust extensions. If the project has compiled extensions, you MUST use setuptools, scikit-build-core, or maturin as the build backend.
Do NOT use setuptools with license = {text = "MIT"} — it has a known bug. Use license = "MIT" string form instead when using setuptools.
Git dependencies:
# BEFORE
[tool.poetry.dependencies]
httpx = {git = "https://github.com/encode/httpx.git", tag = "0.27.0"}
# AFTER
[project]
dependencies = ["httpx"]
[tool.uv.sources]
httpx = {git = "https://github.com/encode/httpx", tag = "0.27.0"}
Path dependencies:
# BEFORE
[tool.poetry.dependencies]
mylib = {path = "../mylib", develop = true}
# AFTER
[project]
dependencies = ["mylib"]
[tool.uv.sources]
mylib = {path = "../mylib", editable = true}
Private indexes:
# BEFORE
[[tool.poetry.source]]
name = "private"
url = "https://pypi.company.com/simple"
# AFTER
[[tool.uv.index]]
name = "private"
url = "https://pypi.company.com/simple"
Private index authentication (Poetry uses poetry config http-basic, uv uses env vars):
# Option 1: Environment variables (recommended for CI)
export UV_INDEX_PRIVATE_USERNAME="user"
export UV_INDEX_PRIVATE_PASSWORD="token"
# Option 2: Credentials in URL (use with caution)
# [[tool.uv.index]]
# url = "https://user:token@pypi.company.com/simple"
# Option 3: keyring integration
uv --keyring-provider subprocess ...
[tool.uv]
package = true # if this is an installable package
rm poetry.lock
rm -rf .venv
uv lock
uv sync --all-extras --dev
uv run pytest
If the project has multiple packages with Poetry path dependencies, convert to a uv workspace.
Add a pyproject.toml at the repository root (or use an existing one):
[project]
name = "my-monorepo"
version = "0.0.0"
requires-python = ">=3.10"
[tool.uv.workspace]
members = ["packages/*", "services/*"]
[tool.uv]
package = false # the root is not an installable package
Run the migration (automated or manual) for each pyproject.toml individually. Convert Poetry path dependencies to uv sources:
# BEFORE (in services/service-a/pyproject.toml)
[tool.poetry.dependencies]
lib-core = {path = "../../packages/lib-core", develop = true}
# AFTER
[project]
dependencies = ["lib-core"]
[tool.uv.sources]
lib-core = {workspace = true}
# Remove per-package Poetry lockfiles
find . -name "poetry.lock" -delete
rm -rf .venv
# Generate single workspace lockfile at the root
uv lock
uv sync --all-packages
Key differences from Poetry monorepos:
uv.lock at workspace root instead of per-package lockfilesuv sync installs all workspace members in one environment{workspace = true} instead of relative pathspoetry-plugin-monorepo, monoranger)Replace pipx installations with uv:
# BEFORE
pipx install black
pipx install ruff
# AFTER
uv tool install black
uv tool install ruff
# For editable local installs (git pull updates automatically)
uv tool install -e .
# Generate pyproject.toml from requirements.txt
uv init
uv add $(cat requirements.txt | grep -v '^#' | grep -v '^$' | tr '\n' ' ')
# Or keep requirements.txt and use uv pip
uv pip install -r requirements.txt
| Poetry | uv |
|---|---|
poetry install | uv sync |
poetry install --with dev | uv sync --dev |
poetry add requests | uv add requests |
poetry add pytest --group dev | uv add pytest --dev |
poetry remove requests | uv remove requests |
poetry run pytest | uv run pytest |
poetry build | uv build |
poetry publish | uv publish |
poetry lock | uv lock |
poetry update | uv lock --upgrade |
| pipx | uv |
|---|---|
pipx install pkg | uv tool install pkg |
pipx run pkg | uvx pkg |
pipx list | uv tool list |
pipx upgrade pkg | uv tool upgrade pkg |
pipx uninstall pkg | uv tool uninstall pkg |
GitHub Actions:
# BEFORE
- run: pip install poetry
- run: poetry install
# AFTER
- uses: astral-sh/setup-uv@v5
- run: uv sync --frozen
Docker:
# BEFORE
RUN pip install poetry && poetry install --no-root
# AFTER
COPY --from=ghcr.io/astral-sh/uv:latest /uv /bin/uv
RUN --mount=type=cache,target=/root/.cache/uv uv sync --frozen
Update installation instructions:
## Installation
Clone the repository, then:
\`\`\`bash
uv tool install -e .
\`\`\`
Updates are automatic after `git pull`.
After migration:
poetry.lock.venv[tool.poetry] sections from pyproject.toml[build-system] with poetry-core