This skill should be used when the user is configuring pyproject.toml, choosing a build backend, declaring dependencies, setting up dependency groups, configuring tool settings, defining entry points, or managing package versioning. Covers PEP 621 metadata, hatchling vs setuptools vs flit-core vs maturin, PEP 735 dependency groups, PEP 639 SPDX licenses, dynamic versioning with hatch-vcs, dependency version constraints, console scripts, and tool configuration consolidation.
التثبيت
التثبيت باستخدام Codex أو Claude انسخ هذا Prompt والصقه في Codex أو Claude أو مساعد آخر ليراجع صفحة Skill ويثبّتها لك.
This skill should be used when the user is configuring pyproject.toml, choosing a build backend, declaring dependencies, setting up dependency groups, configuring tool settings, defining entry points, or managing package versioning. Covers PEP 621 metadata, hatchling vs setuptools vs flit-core vs maturin, PEP 735 dependency groups, PEP 639 SPDX licenses, dynamic versioning with hatch-vcs, dependency version constraints, console scripts, and tool configuration consolidation.
version
1.0.0
Configure Everything in pyproject.toml
pyproject.toml is the single source of truth for a Python package: build system, metadata, dependencies, and tool configuration. As of pip 24.0, setup.py install is no longer supported. Every major package -- Pydantic, httpx, Flask, pytest, attrs, Ruff -- has migrated. There is no reason to use setup.py, setup.cfg, or requirements.txt as primary configuration for any new package.
Anatomy: The Four Sections
Every pyproject.toml contains four logical sections. Keep them in this order:
[build-system] -- declares the build backend (PEP 517/518)
Use hatchling for new pure-Python packages. It is PyPA-maintained, fast, extensible, and the default for hatch new and uv init --lib.
Your Package
Use This Backend
Declaration
Pure Python (new)
hatchling
requires = ["hatchling"]
Pure Python (very simple)
flit-core
requires = ["flit_core>=3.10,<4"]
Pure Python (legacy)
setuptools
requires = ["setuptools>=75.0"]
Rust extensions (PyO3)
maturin
requires = ["maturin>=1.7,<2.0"]
C/C++ with CMake
scikit-build-core
requires = ["scikit-build-core>=0.10"]
What top packages use:
Package
Backend
Reason
Pydantic, httpx, Black, attrs
hatchling
Modern, clean, PyPA-aligned
Flask, Jinja2, Werkzeug
flit-core
Simple, minimal needs
pytest
setuptools
Historical, complex build
Polars, Ruff, pydantic-core
maturin
Rust (PyO3) bindings
PEP 621: Project Metadata
Only name and version are strictly required (unless version is listed in dynamic). For any public package, always set these recommended fields:
Field
Purpose
Example
name
PyPI distribution name (kebab-case)
"my-library"
version
PEP 440 version (or list in dynamic)
"1.0.0"
description
Single-line summary
"A fast HTTP client"
readme
PyPI long description
"README.md"
license
PEP 639 SPDX expression
"MIT"
requires-python
Minimum supported Python
">=3.10"
authors
Author list
[{ name = "Jane Doe" }]
classifiers
Trove classifiers (include Typing :: Typed)
See Reference Configuration
dependencies
Runtime dependencies (lower bounds only)
["httpx>=0.27"]
Set [project.urls] with at minimum Repository and Changelog -- PyPI renders recognized keys (Documentation, Issues, Funding) with icons. See the Reference Configuration for a complete example.
PEP 639: SPDX License Expressions
Use a plain SPDX string, not the legacy table form. All modern build backends and PyPI support this since 2024.
Do
Do Not
license = "MIT"
license = { text = "MIT License" }
license = "Apache-2.0"
license = { file = "LICENSE" }
license = "MIT OR Apache-2.0"
license = "BSD" (ambiguous -- which BSD?)
PEP 735: Dependency Groups
Use [dependency-groups] for dev-only dependencies. These are not published to PyPI and cannot be installed by end users.
[project.optional-dependencies]# Published to PyPI -- user-facing feature variantspostgres = ["asyncpg>=0.29"]
[dependency-groups]# NOT published -- developer-onlytest = ["pytest>=8.0", "pytest-cov>=6.0", "coverage[toml]>=7.6"]
lint = ["ruff>=0.9", "mypy>=1.14", "pyright>=1.1"]
dev = [{ include-group = "test" }, { include-group = "lint" }, "pre-commit>=4.0"]
User-facing feature variants (postgres, redis, HTTP/2) go in [project.optional-dependencies]. Dev-only tools (test, lint, docs) go in [dependency-groups]. Groups support { include-group = "name" } for composition. Install with uv sync --group dev or pip install --dependency-group test.
Dynamic Versioning
Derive the version from git tags to maintain a single source of truth. No version strings to update manually.
Consolidate all tool settings into [tool.*] sections of pyproject.toml where the tool supports it. Do not create .flake8, mypy.ini, pytest.ini, or .coveragerc -- Ruff, mypy, pytest, and coverage all read from pyproject.toml natively. Ruff replaces flake8, isort, Black, and pyupgrade as a single tool.
Exceptions: Tools that do not support pyproject.toml configuration keep their own files. The most common exception is .pre-commit-config.yaml -- pre-commit is a polyglot tool managing hooks across languages and requires its own YAML format.
The Reference Configuration below shows all recommended [tool.*] sections.
Reference Configuration
A complete, production-ready pyproject.toml for a new pure-Python package: