This skill should be used when creating or editing pyproject.toml, managing Python dependencies, configuring build systems, setting up uv workspaces, or publishing Python packages.
This skill defines comprehensive conventions for Python project packaging, dependency management,
and distribution. These conventions prioritize modern packaging standards, reproducible builds, and
streamlined tooling using uv and pyproject.toml.
RULE: All project configuration must live in pyproject.toml. No legacy configuration files:
# CORRECT: Everything in pyproject.toml
[project]
name = "mypackage"
version = "0.1.0"
description = "A sample Python package"
authors = [{name = "Your Name", email = "[email protected]"}]
readme = "README.md"
requires-python = ">=3.11"
license = {text = "MIT"}
keywords = ["example", "package"]
classifiers = [
"Development Status :: 3 - Alpha",
"Intended Audience :: Developers",
"Programming Language :: Python :: 3",
"Programming Language :: Python :: 3.11",
"Programming Language :: Python :: 3.12",
]
dependencies = [
"httpx>=0.24.0,<1.0",
"pydantic>=2.0,<3.0",
]
[project.optional-dependencies]
dev = [
"ruff>=0.1.0",
"mypy>=1.7.0",
"pre-commit>=3.5.0",
]
test = [
"pytest>=7.4.0",
"pytest-cov>=4.1.0",
"pytest-asyncio>=0.21.0",
"factory-boy>=3.3.0",
]
docs = [
"mkdocs>=1.5.0",
"mkdocs-material>=9.4.0",
]
[project.scripts]
myapp = "mypackage.cli:main"
[project.urls]
Homepage = "https://github.com/username/mypackage"
Documentation = "https://mypackage.readthedocs.io"
Repository = "https://github.com/username/mypackage"
Issues = "https://github.com/username/mypackage/issues"
[build-system]
requires = ["hatchling"]
build-backend = "hatchling.build"
[tool.ruff]
line-length = 100
target-version = "py311"
[tool.mypy]
python_version = "3.11"
warn_return_any = true
warn_unused_configs = true
disallow_untyped_defs = true
[tool.pytest.ini_options]
testpaths = ["tests"]
python_files = ["test_*.py"]
python_functions = ["test_*"]
addopts = "-ra -q --strict-markers"
markers = [
"slow: marks tests as slow",
"integration: marks tests as integration tests",
]
[tool.coverage.run]
source = ["src"]
branch = true
[tool.coverage.report]
fail_under = 90
show_missing = true
Files to DELETE or never create:
# WRONG: These files should not exist
setup.py # Use pyproject.toml
setup.cfg # Use pyproject.toml
requirements.txt # Use pyproject.toml dependencies
dev-requirements.txt # Use [project.optional-dependencies]
mypy.ini # Use [tool.mypy] in pyproject.toml
.flake8 # Use [tool.ruff] in pyproject.toml
.isort.cfg # Use [tool.ruff.lint.isort] in pyproject.toml
pytest.ini # Use [tool.pytest.ini_options] in pyproject.toml
tox.ini # Use pyproject.toml or separate workflow
RULE: Always use src/ layout for packages:
# CORRECT: src layout
myproject/
├── src/
│ └── mypackage/
│ ├── __init__.py
│ ├── core.py
│ ├── models.py
│ └── utils.py
├── tests/
│ ├── __init__.py
│ ├── conftest.py
│ └── test_core.py
├── docs/
│ └── index.md
├── pyproject.toml
├── uv.lock
├── .python-version
└── README.md
# WRONG: Flat layout
myproject/
├── mypackage/ # Package at root level
│ ├── __init__.py
│ └── core.py
├── tests/
├── pyproject.toml
└── README.md
Why src/ layout:
Package structure:
# src/mypackage/__init__.py
"""MyPackage - A sample Python package."""
from __future__ import annotations
from mypackage.core import main_function
from mypackage.models import User, Post
__version__ = "0.1.0"
__all__ = ["main_function", "User", "Post"]
RULE: Use dependency groups in pyproject.toml with appropriate version constraints:
# CORRECT: Well-structured dependencies
[project]
name = "mypackage"
version = "0.1.0"
requires-python = ">=3.11"
# Core runtime dependencies
dependencies = [
"httpx>=0.24.0,<1.0", # Compatible version range
"pydantic>=2.0,<3.0",
"sqlalchemy>=2.0,<3.0",
"alembic>=1.12,<2.0",
]
[project.optional-dependencies]
# Development tools
dev = [
"ruff>=0.1.0",
"mypy>=1.7.0",
"pre-commit>=3.5.0",
"ipython>=8.17.0",
]
# Testing dependencies
test = [
"pytest>=7.4.0",
"pytest-cov>=4.1.0",
"pytest-asyncio>=0.21.0",
"pytest-mock>=3.12.0",
"factory-boy>=3.3.0",
"faker>=20.0.0",
]
# Documentation
docs = [
"mkdocs>=1.5.0",
"mkdocs-material>=9.4.0",
"mkdocstrings[python]>=0.24.0",
]
# Optional database backends
postgres = [
"psycopg[binary]>=3.1.0",
]
mysql = [
"mysqlclient>=2.2.0",
]
# All optional dependencies combined
all = [
"mypackage[postgres,mysql]",
]
Version pinning guidelines:
# CORRECT: Appropriate version constraints
dependencies = [
"httpx>=0.24.0,<1.0", # Major version constraint
"pydantic>=2.5.0,<3.0", # Minimum minor for required feature
"python-dateutil>=2.8.2", # Stable package, minimum version
]
# WRONG: Too restrictive or too loose
dependencies = [
"httpx==0.24.1", # Exact pin - prevents security updates
"pydantic>=2.0", # No upper bound - may break on v3
"requests", # No version constraint at all
]
When to use exact versions:
uv.lock) onlyRULE: Specify Python version in both pyproject.toml and .python-version:
# pyproject.toml
[project]
requires-python = ">=3.11"
[tool.ruff]
target-version = "py311"
[tool.mypy]
python_version = "3.11"
# .python-version (for uv/pyenv)
3.11
Multiple Python version support:
[project]
requires-python = ">=3.11,<4.0"
classifiers = [
"Programming Language :: Python :: 3",
"Programming Language :: Python :: 3.11",
"Programming Language :: Python :: 3.12",
]
Testing multiple versions (in CI):
# .github/workflows/test.yml