文件pyproject.toml
spec
- pyproject.toml 文件是用 TOML 编写的。当前指定了三个表,即
[build-system]、[project]和[tool]。 - 注意:只有
[build-system]表的requires字段是必需的
当 pyproject.toml 文件不存在时,构建工具应使用下面的示例配置文件作为其默认语义:
[build-system]
# Minimum requirements for the build system to execute.
requires = ["setuptools"]示例:
[project]
name = "mypackage"
version = "0.0.1"
dependencies = [
"requests",
'importlib-metadata; python_version<"3.8"',
][build-system] section
- declare which build backend you use and which other dependencies are needed to build your project.
setuptools示例:
[build-system]
requires = ["setuptools"]
build-backend = "setuptools.build_meta"
[build-system]
requires = ["setuptools>=61.0"]
build-backend = "setuptools.build_meta"hatchling示例:
[build-system]
requires = ["hatchling"]
build-backend = "hatchling.build"Flit 示例:
[build-system]
requires = ["flit_core>=3.4"]
build-backend = "flit_core.buildapi"PDM 示例:
[build-system]
requires = ["pdm-backend"]
build-backend = "pdm.backend"[project] section
- specify your project’s basic metadata, such as the dependencies, your name, etc.
Note: As of August 2024,
Poetryis a notable build backend that does not use the [project] table, it uses the [tool.poetry] table instead. Also, thesetuptoolsbuild backend supports both the [project] table, and the older format in setup.cfg or setup.py.
[project]
dependencies = [
"httpx",
"gidgethub[httpx]>4.0.0",
"django>2.1; os_name != 'nt'",
"django>2.0; os_name == 'nt'", # ; 分隔 依赖项 和 条件, 格式: <依赖项> ; <条件>
]收集示例:
"pywin32>=306; sys_platform == 'win32' or platform_system == 'Windows'"
"pngpaste; sys_platform == 'darwin' and python_version < '3.12'"示例:
[project]
name = "example_package_YOUR_USERNAME_HERE"
version = "0.0.1"
authors = [
{ name="Example Author", email="author@example.com" },
]
maintainers = [
{name = "Brett Cannon", email = "brett@example.com"}
]
description = "A small example package"
readme = "README.md"
# readme = {file = "README.txt", content-type = "text/markdown"}
# readme = {file = "README.rst", content-type = "text/x-rst"}
license = {file = "LICENSE"}
keywords = ["egg", "bacon", "sausage", "tomatoes", "Lobster Thermidor"]
requires-python = ">=3.8"
# A list of PyPI classifiers that apply to your project.
classifiers = [
# How mature is this project? Common values are
# 3 - Alpha
# 4 - Beta
# 5 - Production/Stable
"Development Status :: 4 - Beta",
# Indicate who your project is intended for
"Intended Audience :: Developers",
"Topic :: Software Development :: Build Tools",
# Specify the Python versions you support here.
"Programming Language :: Python :: 3",
"Programming Language :: Python :: 3.8",
"Programming Language :: Python :: 3.9",
"License :: OSI Approved :: MIT License",
"Operating System :: OS Independent",
# PyPI will always reject packages with classifiers beginning with Private ::
Private :: Do Not Upload classifier,
]dynamic metadata
- When a field is dynamic, it is the build backend’s responsibility to fill it. Consult your build backend’s documentation to learn how it does it
setuptools示例:
[project]
dynamic = ["version"]
[tool.setuptools.dynamic]
version = {attr = "package.__version__"}[project.urls] sub section
允许您列出任意数量的额外链接以在 PyPI 上显示。通常,这可能是源、文档、问题跟踪器等:
[project.urls]
Homepage = "https://github.com/pypa/sampleproject"
Issues = "https://github.com/pypa/sampleproject/issues"
# 如果key带空格需要加引号
"Official Website" = "https://example.com"[project.optional-dependencies] sub section
[project.optional-dependencies]
gui = ["PyQt5"]
cli = [
"rich",
"click",
]指定了gui参数,则会安装 PyQt5 依赖库:
pip install your-project-name[gui][project.scripts] sub section
To install a command as part of your package, declare it in the [project.scripts] table:
[project.scripts]
spam-cli = "spam:main_cli"
# after installing your project, a spam-cli command will be available.
# Executing this command will do the equivalent of `from spam import main_cli; main_cli()`[project.gui-scripts] sub section
windows 专用:
[project.gui-scripts]
spam-gui = "spam:main_gui"Advanced plugins
- Some packages can be extended through plugins.
- Examples include Pytest and Pygments.
- To create such a plugin, you need to declare it in a subtable of [project.entry-points] like this:
[project.entry-points."spam.magical"]
tomatoes = "spam:main_tomatoes"pipx 示例:
[project.entry-points."pipx.run"]
greetings = "greetings.cli:app"[tool] section
- tool-specific subtables, e.g., [tool.hatch], [tool.black], [tool.mypy].
与 pip / pipx / uv 的边界
- pyproject.toml vs pip:pip 通过
[build-system]表知道用哪个后端(setuptools/hatchling/flit/pdm-backend)来构建当前项目;pip install -e .等命令的行为完全由这个文件决定,pip 自己不存储项目元数据。 - pyproject.toml vs pipx:
[project.entry-points."pipx.run"]是专门给 pipx 用的入口点声明,让一个包可以被pipx run直接调用;这是二者唯一的直接关联点,其余部分 pipx 不关心。 - pyproject.toml vs uv:uv 不发明新格式,
uv add/uv remove直接操作标准[project.dependencies],uv 专属配置(workspace、自定义 index 等)落在独立的[tool.uv]表里,不影响其他工具读取同一个文件。
常用工作流
- 新项目起手:用构建后端工具生成骨架(如
uv init或hatch new),拿到一份带[build-system]+[project]的最小pyproject.toml,再逐步补[project.optional-dependencies]、[project.scripts]。 - 加依赖:优先用工具命令(
uv add xxx/poetry add xxx)自动写入文件,而不是手动改 TOML 再假设格式正确——工具会顺带处理版本约束语法。 - 声明可选功能:用
[project.optional-dependencies]分组(如gui、cli),用户按需pip install pkg[gui],避免所有依赖打包成一个巨大安装。 - 声明命令行入口:
[project.scripts]里写cmd = "module:func",pip install之后这个命令就出现在$PATH上,不用再手写 setup.py 里的 entry_points。
现代项目推荐组合
- 纯应用/工具项目:
pyproject.toml+uv([build-system]用 hatchling 或 setuptools 均可)——一个文件管构建、依赖、元数据。 - 需要发布到 PyPI 的库:
pyproject.toml里补全[project.urls]、classifiers、readme、license,用build生成分发包,twine上传。 - 遗留 setup.py 项目迁移:先把
[build-system]加上(哪怕只是requires = ["setuptools"]),再逐步把 setup.py 里的静态元数据挪进[project],setuptools 同时支持两者共存过渡。
踩坑点
[build-system].requires是唯一强制必填的字段,很多人误以为不写[project]表文件就无效——其实没有[project]表时构建后端会退回读setup.py/setup.cfg。Poetry不遵循 PEP 621 标准[project]表,而是用自己的[tool.poetry]表,从 Poetry 项目迁移到其他工具(uv/pdm)时不能直接复用依赖声明,需要转换格式。- classifiers 列表里的字符串必须严格加引号,直接写裸标识符(如示例中演示的错误写法)会导致 TOML 解析失败。