pyproject.toml & Project Metadata
|
This content was generated with the assistance of AI and should be verified against the official documentation before being relied on in production. The reference material consulted while preparing these pages is listed in the Python Reference bibliography. |
Since Poetry 2.0 a Poetry project describes itself with the standard [project] table, and keeps a [tool.poetry] table only for
what the standard cannot express. This page goes through both, as Poetry 2.5.1 and poetry-core 2.5.0 read them; the commands ran on
CPython 3.14.6 on 2026-10-22. The standard keys themselves, and the core metadata each becomes, are on
pyproject.toml; this page adds what is specific to Poetry.
The [project] section as Poetry reads it
Poetry reads every PEP 621 key. The points that differ from other backends:
| Key | Poetry specifics |
|---|---|
|
Required in package mode. Optional with |
|
Required whenever |
|
One line. |
|
An SPDX expression string (poetry-core supports PEP 639 since 2.2). The table form |
|
Globs; a pattern that matches nothing fails |
|
One file. For several files concatenated, see |
|
The Python range of the project, written into the metadata. To lock with an upper bound ( |
|
Tables with |
|
As in the standard. With static |
|
Console and GUI scripts and plugin entry points. Run |
|
PEP 508 strings, the source of the metadata. |
A dynamic version with a base version:
[project]
name = "birdtally"
dynamic = ["version"]
[tool.poetry]
version = "0.1.0" # base version
poetry build -f wheel --local-version birdwatch.1
- Building wheel
- Built birdtally-0.1.0+birdwatch.1-py3-none-any.whl
A local version is fine for an internal index, but PyPI refuses it; see Versioning & Release Management.
The [tool.poetry] section
package-mode
package-mode = false turns a project into a dependency-only application: no build, no publish, name and version optional, and
poetry install installs only the dependencies. See
Basic Usage.
Source: Poetry: package-mode.
packages
Poetry auto-detects one package or module whose name is the normalized project name with - replaced by _, either next to
pyproject.toml (flat layout) or under src/ (src layout). Anything else needs packages, and as soon as you set it auto-detection
is off, so list the main package too:
[tool.poetry]
packages = [
{ include = "birdtally", from = "src" }, # the main package, from src/
{ include = "birdtally_plugins", from = "lib", to = "birdtally/plugins" }, # installed under another path
{ include = "birdtally_fixtures", format = "sdist" }, # only in the sdist
]
-
includeis a package, module or glob (extra_package/**/*.py); subpackages are found automatically. -
fromis the directory that contains it. -
to(1.8) is the destination path inside the installed distribution. -
formatrestricts it tosdistorwheel(or a list).
The .gitignore caveat. In a Git (or other VCS) checkout, files matched by the ignore rules are excluded from the built
distributions even when their directory is listed in packages`. Generated code that you keep out of Git (a `_version.py, a
compiled parser) therefore silently goes missing; add it back with include and an explicit format.
Source: Poetry: packages.
include and exclude
[tool.poetry]
include = [
{ path = "CHANGELOG.md", format = "sdist" },
{ path = "src/birdtally/data/*.csv", format = ["sdist", "wheel"] },
]
exclude = ["src/birdtally/_scratch.py"]
-
includehas priority overexclude. -
Without
format,includeapplies only to the sdist (since Poetry 2.0; before, it applied to both), whileexcludeapplies to both. Files meant for the installed package (data files) needformat = ["sdist", "wheel"]. -
Top-level paths in a wheel are unpacked straight into
site-packages, so never shipCHANGELOG.md,LICENSE,testsordocsat the top level of a wheel; licence files belong inlicense-files. -
excludeis seeded with the VCS ignore rules; anincludeentry with an explicitformatcan override them.
Source: Poetry: exclude and include.
readme and classifiers
[tool.poetry] readme accepts a list of files that are concatenated into the long description (readme = ["README.md",
"docs/usage.md"]); use it with dynamic = ["readme"]. [tool.poetry] classifiers with dynamic = ["classifiers"] makes poetry-core
add Programming Language :: Python :: 3 and one 3.x classifier per minor version allowed by requires-python; with a static
[project] classifiers list nothing is added. Both behaviours are shown on
pyproject.toml.
scripts of type file
Console scripts belong in [project.scripts]. [tool.poetry.scripts] remains for scripts of type = "file": a file from the project
copied into bin/ (Scripts\ on Windows) at install time:
[tool.poetry.scripts]
birdtally-import = { reference = "scripts/birdtally-import.sh", type = "file" }
Source: Poetry: tool.poetry scripts.
requires-poetry, requires-plugins and build-constraints
[tool.poetry]
requires-poetry = ">=2.3"
[tool.poetry.requires-plugins]
poetry-plugin-export = ">=1.10"
[tool.poetry.build-constraints]
some-package = { setuptools = "<78" }
-
requires-poetry(2.0) makes every command fail fast on an unsuitable Poetry. Withrequires-poetry = ">=3.0":This project requires Poetry >=3.0, but you are using Poetry 2.5.1 -
requires-plugins(2.0) lists project plugins that Poetry installs into the project’s.poetry/pluginsdirectory when they are missing; see Plugins. -
build-constraints(2.3) constrains the build environment of a dependency that Poetry has to build from source, for example an sdist that requires an unboundedsetuptoolsit is not compatible with. The syntax is that of[tool.poetry.dependencies].
Dependencies and groups in [tool.poetry]
[tool.poetry.dependencies] and [tool.poetry.group.<name>.dependencies] still exist. Next to [project] they enrich the
standard dependencies for locking (a source, develop = true, allow-prereleases) and can hold Poetry-only syntax; see
Dependency Specification and
Managing Dependencies & Groups.
Deprecated [tool.poetry] fields
Deprecated [tool.poetry] field |
Use instead |
|---|---|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
a group ( |
tool.poetry.readme and tool.poetry.classifiers are not deprecated, but they need the key in project.dynamic when [project]
exists.
Source: Poetry: The tool.poetry section, extras, plugins, urls, Poetry 2.0.0 announcement: Supporting the project section.
poetry check
poetry check validates pyproject.toml (and, with --lock, that poetry.lock is consistent with it) and lists every deprecated
field. Run on a Poetry 1.x style file for birdtally:
poetry check
Warning: [tool.poetry.name] is deprecated. Use [project.name] instead.
Warning: [tool.poetry.version] is set but 'version' is not in [project.dynamic]. If it is static use [project.version]. If it is dynamic, add 'version' to [project.dynamic].
If you want to set the version dynamically via `poetry build --local-version` or you are using a plugin, which sets the version dynamically, you should define the version in [tool.poetry] and add 'version' to [project.dynamic].
Warning: [tool.poetry.description] is deprecated. Use [project.description] instead.
Warning: [tool.poetry.readme] is set but 'readme' is not in [project.dynamic]. If it is static use [project.readme]. If it is dynamic, add 'readme' to [project.dynamic].
If you want to define multiple readmes, you should define them in [tool.poetry] and add 'readme' to [project.dynamic].
Warning: [tool.poetry.license] is deprecated. Use [project.license] instead.
Warning: [tool.poetry.authors] is deprecated. Use [project.authors] instead.
Warning: [tool.poetry.keywords] is deprecated. Use [project.keywords] instead.
Warning: [tool.poetry.homepage] is deprecated. Use [project.urls] instead.
Warning: [tool.poetry.extras] is deprecated. Use [project.optional-dependencies] instead.
Warning: Defining console scripts in [tool.poetry.scripts] is deprecated. Use [project.scripts] instead. ([tool.poetry.scripts] should only be used for scripts of type 'file').
The exit code is 0: warnings do not fail the check. poetry check --strict turns warnings into errors (exit code 1), which is what to
run in CI. The full migration of that file is on
Migrating to Poetry 2.
Source: Poetry CLI: check.
[build-system] with poetry-core
[build-system]
requires = ["poetry-core>=2.0.0,<3.0.0"]
build-backend = "poetry.core.masonry.api"
This is what poetry new and poetry init write. Any PEP 517 frontend (pip, build, uv) can then build the project without Poetry.
The documentation’s own snippet still shows poetry-core>=1.0.0, which would allow a poetry-core 1.x that cannot read [project];
keep the 2.0 lower bound. A pyproject.toml that references poetry itself as the backend must be changed to poetry-core. The
Poetry 2.1 to 2.5 release notes announce that a future release will default to setuptools when [build-system] is missing,
following PEP 517, so always write the table.
Source: Poetry: Poetry and PEP 517.
The complete birdtally file
The Poetry flavour of the running scenario, which poetry check accepts with All set! and from which the published metadata on
pyproject.toml is built:
[build-system]
requires = ["poetry-core>=2.0.0,<3.0.0"]
build-backend = "poetry.core.masonry.api"
[project]
name = "birdtally"
version = "0.1.0"
description = "Tally bird-survey sightings from CSV files."
readme = "README.md"
requires-python = ">=3.10"
license = "MIT"
license-files = ["LICENSE"]
authors = [{ name = "The birdtally authors" }]
keywords = ["birds", "survey", "csv"]
classifiers = [
"Development Status :: 3 - Alpha",
"Typing :: Typed",
]
dependencies = []
[project.optional-dependencies]
pretty = ["rich>=13"]
[project.scripts]
birdtally = "birdtally.cli:main"
[project.urls]
Homepage = "https://example.org/birdtally"
Changelog = "https://example.org/birdtally/changelog"
[tool.poetry]
packages = [{ include = "birdtally", from = "src" }]
include = [{ path = "CHANGELOG.md", format = "sdist" }]
[dependency-groups]
test = ["pytest>=8"]
lint = ["ruff>=0.6"]
docs = ["mkdocs>=1.6"]
dev = [{ include-group = "test" }, { include-group = "lint" }]