| name | python-tooling |
| description | Python project tooling: pyproject.toml as the single source of truth, package managers (pip, Poetry, uv, pipenv), virtual environments, lockfiles, ruff (lint + format), mypy (type checking), and console scripts / entry points. Stack-agnostic — referenced by every Python plugin in the marketplace.
Use this skill to:
- Read pyproject.toml (or requirements.txt / Pipfile) to learn which package manager and Python version the project uses.
- Install, add, and remove dependencies with the project's package manager.
- Run ruff for formatting and linting, mypy for type checking.
- Define console scripts and entry points for CLI applications.
Do NOT use this skill for:
- Language idioms — see python-foundation:python-conventions.
- Testing patterns — see python-foundation:pytest-testing.
- Framework-specific tooling (django management commands, flask CLI) — see framework plugin skills.
|
Python Tooling (stack-agnostic)
pyproject.toml is the single source of truth for a modern Python project. Read it before doing anything else — it tells you the Python version, the package manager, the linting config, and the test settings.
Detection — which package manager is active
Check for lockfiles in this order:
test -f poetry.lock && echo "Poetry"
test -f uv.lock && echo "uv"
test -f Pipfile.lock && echo "Pipenv"
test -f requirements.txt && echo "pip"
| Signal | Package manager | Install command |
|---|
poetry.lock | Poetry | poetry install |
uv.lock | uv | uv sync |
Pipfile.lock | Pipenv | pipenv install |
requirements.txt only | pip | pip install -r requirements.txt |
When only a pyproject.toml with a [build-system] section exists but no lockfile, check [build-system].requires to identify the build backend (setuptools, hatch, flit) and fall back to pip install -e ..
Always use the project's existing package manager. Do not mix managers (e.g., do not run pip install in a Poetry project).
pyproject.toml — canonical structure
A fully configured pyproject.toml for a PEP 621 project:
[project]
name = "myapp"
version = "0.1.0"
description = "Short project description"
requires-python = ">=3.10"
dependencies = [
"httpx>=0.27",
"pydantic>=2.6",
]
[project.optional-dependencies]
dev = [
"pytest>=8.0",
"pytest-cov>=5.0",
"ruff>=0.4",
"mypy>=1.10",
]
[project.scripts]
myapp = "myapp.cli:main"
[build-system]
requires = ["hatchling"]
build-backend = "hatchling.build"
[tool.ruff]
line-length = 88
target-version = "py310"
[tool.ruff.lint]
select = ["E", "F", "I", "UP", "B", "SIM"]
ignore = ["E501"]
[tool.mypy]
python_version = "3.10"
strict = true
ignore_missing_imports = true
[tool.pytest.ini_options]
testpaths = ["tests"]
addopts = "--strict-markers -q"
= []
=
=
=
For Poetry projects the structure differs slightly — dependencies live under [tool.poetry.dependencies] and scripts under [tool.poetry.scripts]. The [tool.ruff], [tool.mypy], and [tool.pytest.ini_options] sections are identical regardless of build backend.
ruff — lint and format
ruff replaces flake8, isort, pyupgrade, and black in a single fast tool. Configure it in pyproject.toml:
[tool.ruff]
line-length = 88
target-version = "py310"
[tool.ruff.lint]
select = ["E", "F", "I", "UP", "B", "SIM"]
ignore = ["E501"]
[tool.ruff.lint.isort]
known-first-party = ["myapp"]
Key commands:
ruff check .
ruff check --fix .
ruff format .
ruff format --check .
Run both in CI:
ruff format --check . && ruff check .
ruff's formatter is intentionally compatible with black's output — switching a black project to ruff format requires no manual changes.
mypy — type checking
Configure mypy in pyproject.toml:
[tool.mypy]
python_version = "3.10"
strict = true
ignore_missing_imports = true
strict = true enables a comprehensive set of checks including disallow_untyped_defs, warn_return_any, warn_unused_ignores, and others. Start with strict on new projects; on legacy projects enable checks incrementally.
Per-module overrides for third-party packages that ship without stubs:
[[tool.mypy.overrides]]
module = ["boto3.*", "botocore.*"]
ignore_missing_imports = true
Add a py.typed marker file to signal that your package ships inline type information:
touch src/myapp/py.typed
Then declare it in pyproject.toml:
[tool.hatch.build.targets.wheel]
include = ["src/myapp/py.typed"]
Run mypy:
mypy src/
mypy src/myapp/service.py
Virtual environments
Each package manager manages its own virtual environment:
python -m venv .venv
source .venv/bin/activate
.venv\Scripts\activate
pip install -e ".[dev]"
poetry install
poetry shell
poetry run pytest
uv sync
uv run pytest
pipenv install --dev
pipenv shell
pipenv run pytest
Never commit the virtual environment. Add to .gitignore:
.venv/
__pycache__/
*.pyc
*.pyo
.mypy_cache/
.ruff_cache/
.pytest_cache/
dist/
*.egg-info/
Common commands — pip / Poetry / uv
| Task | pip | Poetry | uv |
|---|
| Install all deps | pip install -r requirements.txt | poetry install | uv sync |
| Install dev deps | pip install -e ".[dev]" | poetry install --with dev | uv sync --extra dev |
| Add runtime dep | pip install X then freeze | poetry add X | uv add X |
| Add dev dep | pip install X then freeze | poetry add --group dev X | uv add --dev X |
| Remove dep | pip uninstall X then freeze | poetry remove X | uv remove X |
| Update dep | pip install -U X then freeze | poetry update X | uv lock --upgrade-package X && uv sync |
| Run script | python -m mymodule | poetry run python -m mymodule | uv run python -m mymodule |
| Run tool | ruff check . | poetry run ruff check . | uv run ruff check . |
| Show dep tree | pip list | poetry show --tree | uv tree |
For pip projects, always regenerate the lockfile after adding or changing a dependency:
pip freeze > requirements.txt
Consider splitting into requirements.txt (runtime) and requirements-dev.txt (dev/test) to keep production images lean.
Console scripts
Define CLI entry points in pyproject.toml so users can run your tool by name after installation:
[project.scripts]
myapp = "myapp.cli:main"
myapp-worker = "myapp.worker:run"
[tool.poetry.scripts]
myapp = "myapp.cli:main"
The value is "package.module:callable". The callable must accept no arguments (or parse sys.argv internally):
import argparse
import sys
def main() -> None:
parser = argparse.ArgumentParser(description="myapp CLI")
parser.add_argument("command", choices=["serve", "migrate"])
args = parser.parse_args()
match args.command:
case "serve":
from myapp.server import serve
serve()
case "migrate":
from myapp.db import migrate
migrate()
case _:
parser.print_help()
sys.exit(1)
After installation (pip install -e . / poetry install / uv sync), the script is available on $PATH:
myapp serve
myapp migrate