uv init vs poetry init for CLI tools
Choosing between uv for Python CLI Dependency Management and Poetry dictates your CLI's bootstrapping speed, lockfile strategy, and script entry point syntax. This guide compares initialization workflows, focusing on Python 3.10+ standards and production-ready pyproject.toml configurations.
Core Initialization Differences
uv init generates a PEP 621-compliant pyproject.toml instantly. It leverages Rust-based resolution for near-zero latency. poetry init runs an interactive wizard that defaults to legacy Poetry metadata formats.
For internal DevOps scripts, the non-interactive uv init --app flag is preferred for CI/CD pipelines. Both tools integrate into broader Project Setup & Dependency Management strategies. They differ significantly in lockfile generation. uv produces a uv.lock alongside requirements.txt compatibility. Poetry enforces a strict poetry.lock.
Configuring CLI Entry Points
The primary friction point is script registration. uv uses standard [project.scripts]. Poetry requires [tool.poetry.scripts]. Misconfiguration triggers ModuleNotFoundError or console_scripts resolution failures at runtime.
Always verify the module path matches your package structure before deployment. Incorrect paths will silently fail during local development but crash in production environments.
Minimal Reproducible Configuration
Below are exact pyproject.toml snippets for a Python 3.10+ CLI tool. Ensure your cli.py contains a def main(): entry point. Always run uv sync or poetry install immediately after initialization to validate environment isolation.
uv init (PEP 621 compliant)
[project]
name = "my-cli-tool"
version = "0.1.0"
description = "Internal CLI utility"
requires-python = ">=3.10"
dependencies = ["click>=8.1.0"]
[project.scripts]
mycli = "mycli.cli:main"
[build-system]
requires = ["hatchling"]
build-backend = "hatchling.build"
poetry init (Legacy format)
[tool.poetry]
name = "my-cli-tool"
version = "0.1.0"
description = "Internal CLI utility"
authors = ["Dev <dev@example.com>"]
requires-python = ">=3.10"
[tool.poetry.dependencies]
python = "^3.10"
click = "^8.1.0"
[tool.poetry.scripts]
mycli = "mycli.cli:main"
[build-system]
requires = ["poetry-core"]
build-backend = "poetry.core.masonry.api"
CLI Entry Point (cli.py)
import click
@click.command()
@click.option("--verbose", is_flag=True)
def main(verbose: bool):
"""Production-ready CLI entry point."""
click.echo("CLI initialized successfully.")
if __name__ == "__main__":
main()
Execute the following terminal commands to bootstrap and validate the environment:
# uv workflow
uv init --app my-cli-tool
cd my-cli-tool
uv add "click>=8.1.0"
uv sync
uv run mycli --verbose
# poetry workflow
poetry init --no-interaction --name my-cli-tool
poetry add click
poetry install
poetry run mycli --verbose
Debugging Common Initialization Errors
Error: poetry add fails with RuntimeError: Poetry could not find a pyproject.toml file.Fix: Run poetry init manually. Alternatively, migrate to PEP 621 using uv init which auto-generates compliant metadata.
Error: uv run cannot locate CLI command.
Fix: Verify [project.scripts] matches the exact module path (e.g., mycli = "mycli.cli:main"). Always clear .venv and re-run uv sync or poetry install after metadata changes.
FAQ
Q: Does uv init support interactive dependency selection like poetry init?
A: No. uv init prioritizes speed and CI/CD compatibility. Add dependencies post-init via uv add <package>.
Q: Can I migrate a Poetry project to uv without breaking CLI scripts?
A: Yes. Convert [tool.poetry.scripts] to [project.scripts], run uv init in the directory, and execute uv sync to generate a compatible lockfile.