pyproject.toml
is a configuration file used by packaging tools, as
well as other tools such as linters, type checkers, etc. There are
three possible TOML tables in this file.
- The
[build-system]
table is strongly recommended. It allows you to declare which :term:`build backend` you use and which other dependencies are needed to build your project. - The
[project]
table is the format that most build backends use to specify your project's basic metadata, such as the dependencies, your name, etc. - The
[tool]
table has tool-specific subtables, e.g.,[tool.hatch]
,[tool.black]
,[tool.mypy]
. We only touch upon this table here because its contents are defined by each tool. Consult the particular tool's documentation to know what it can contain.
Note
The [build-system]
table should always be present,
regardless of which build backend you use ([build-system]
defines the
build tool you use).
On the other hand, the [project]
table is understood by most build
backends, but some build backends use a different format.
As of August 2024, Poetry is a notable build backend that does not use
the [project]
table, it uses the [tool.poetry]
table instead.
Also, the setuptools build backend supports both the [project]
table,
and the older format in setup.cfg
or setup.py
.
For new projects, use the [project]
table, and keep setup.py
only if
some programmatic configuration is needed (such as building C extensions),
but the setup.cfg
and setup.py
formats are still valid. See
:ref:`setup-py-deprecated`.
The [build-system]
table contains a build-backend
key, which specifies
the build backend to be used. It also contains a requires
key, which is a
list of dependencies needed to build the project -- this is typically just the
build backend package, but it may also contain additional dependencies. You can
also constrain the versions, e.g., requires = ["setuptools >= 61.0"]
.
Usually, you'll just copy what your build backend's documentation suggests (after :ref:`choosing your build backend <choosing-build-backend>`). Here are the values for some common build backends:
.. tab:: Hatchling .. code-block:: toml [build-system] requires = ["hatchling"] build-backend = "hatchling.build"
.. tab:: setuptools .. code-block:: toml [build-system] requires = ["setuptools >= 61.0"] build-backend = "setuptools.build_meta"
.. tab:: Flit .. code-block:: toml [build-system] requires = ["flit_core >= 3.4"] build-backend = "flit_core.buildapi"
.. tab:: PDM .. code-block:: toml [build-system] requires = ["pdm-backend"] build-backend = "pdm.backend"
The rest of this guide is devoted to the [project]
table.
Most of the time, you will directly write the value of a [project]
field. For example: requires-python = ">= 3.8"
, or version =
"1.0"
.
However, in some cases, it is useful to let your build backend compute
the metadata for you. For example: many build backends can read the
version from a __version__
attribute in your code, a Git tag, or
similar. In such cases, you should mark the field as dynamic using, e.g.,
[project]
dynamic = ["version"]
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.
Put the name of your project on PyPI. This field is required and is the only field that cannot be marked as dynamic.
[project]
name = "spam-eggs"
The project name must consist of ASCII letters, digits, underscores "_
",
hyphens "-
" and periods ".
". It must not start or end with an
underscore, hyphen or period.
Comparison of project names is case insensitive and treats arbitrarily long runs
of underscores, hyphens, and/or periods as equal. For example, if you register
a project named cool-stuff
, users will be able to download it or declare a
dependency on it using any of the following spellings: Cool-Stuff
,
cool.stuff
, COOL_STUFF
, CoOl__-.-__sTuFF
.
Put the version of your project.
[project]
version = "2020.0.0"
Some more complicated version specifiers like 2020.0.0a1
(for an alpha
release) are possible; see the :ref:`specification <version-specifiers>`
for full details.
This field is required, although it is often marked as dynamic using
[project]
dynamic = ["version"]
This allows use cases such as filling the version from a __version__
attribute or a Git tag. Consult the :ref:`single-source-version`
discussion for more details.
If your project has dependencies, list them like this:
[project]
dependencies = [
"httpx",
"gidgethub[httpx]>4.0.0",
"django>2.1; os_name != 'nt'",
"django>2.0; os_name == 'nt'",
]
See :ref:`Dependency specifiers <dependency-specifiers>` for the full syntax you can use to constrain versions.
You may want to make some of your dependencies optional, if they are
only needed for a specific feature of your package. In that case, put
them in optional-dependencies
.
[project.optional-dependencies]
gui = ["PyQt5"]
cli = [
"rich",
"click",
]
Each of the keys defines a "packaging extra". In the example above, one
could use, e.g., pip install your-project-name[gui]
to install your
project with GUI support, adding the PyQt5 dependency.
This lets you declare the minimum version of Python that you support [1].
[project]
requires-python = ">= 3.8"
To install a command as part of your package, declare it in the
[project.scripts]
table.
[project.scripts]
spam-cli = "spam:main_cli"
In this example, 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()
.
On Windows, scripts packaged this way need a terminal, so if you launch
them from within a graphical application, they will make a terminal pop
up. To prevent this from happening, use the [project.gui-scripts]
table instead of [project.scripts]
.
[project.gui-scripts]
spam-gui = "spam:main_gui"
In that case, launching your script from the command line will give back control immediately, leaving the script to run in the background.
The difference between [project.scripts]
and
[project.gui-scripts]
is only relevant on Windows.
Both of these fields contain lists of people identified by a name and/or an email address.
[project]
authors = [
{name = "Pradyun Gedam", email = "pradyun@example.com"},
{name = "Tzu-Ping Chung", email = "tzu-ping@example.com"},
{name = "Another person"},
{email = "different.person@example.com"},
]
maintainers = [
{name = "Brett Cannon", email = "brett@example.com"}
]
This should be a one-line description of your project, to show as the "headline" of your project page on PyPI (example), and other places such as lists of search results (example).
[project]
description = "Lovely Spam! Wonderful Spam!"
This is a longer description of your project, to display on your project
page on PyPI. Typically, your project will have a README.md
or
README.rst
file and you just put its file name here.
[project]
readme = "README.md"
The README's format is auto-detected from the extension:
README.md
→ GitHub-flavored Markdown,README.rst
→ reStructuredText (without Sphinx extensions).
You can also specify the format explicitly, like this:
[project]
readme = {file = "README.txt", content-type = "text/markdown"}
# or
readme = {file = "README.txt", content-type = "text/x-rst"}
This can take two forms. You can put your license in a file, typically
LICENSE
or LICENSE.txt
, and link that file here:
[project]
license = {file = "LICENSE"}
or you can write the name of the license:
[project]
license = {text = "MIT License"}
If you are using a standard, well-known license, it is not necessary to use this
field. Instead, you should use one of the :ref:`classifiers` starting with License
::
. (As a general rule, it is a good idea to use a standard, well-known
license, both to avoid confusion and because some organizations avoid software
whose license is unapproved.)
This will help PyPI's search box to suggest your project when people search for these keywords.
[project]
keywords = ["egg", "bacon", "sausage", "tomatoes", "Lobster Thermidor"]
A list of PyPI classifiers that apply to your project. Check the full list of possibilities.
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",
# Pick your license as you wish (see also "license" above)
"License :: OSI Approved :: MIT License",
# Specify the Python versions you support here.
"Programming Language :: Python :: 3",
"Programming Language :: Python :: 3.6",
"Programming Language :: Python :: 3.7",
"Programming Language :: Python :: 3.8",
"Programming Language :: Python :: 3.9",
]
Although the list of classifiers is often used to declare what Python versions a project supports, this information is only used for searching and browsing projects on PyPI, not for installing projects. To actually restrict what Python versions a project can be installed on, use the :ref:`requires-python` argument.
To prevent a package from being uploaded to PyPI, use the special Private ::
Do Not Upload
classifier. PyPI will always reject packages with classifiers
beginning with Private ::
.
A list of URLs associated with your project, displayed on the left sidebar of your PyPI project page.
Note
See :ref:`well-known-labels` for a listing of labels that PyPI and other packaging tools are specifically aware of, and PyPI's project metadata docs for PyPI-specific URL processing.
[project.urls]
Homepage = "https://example.com"
Documentation = "https://readthedocs.org"
Repository = "https://github.com/me/spam.git"
Issues = "https://github.com/me/spam/issues"
Changelog = "https://github.com/me/spam/blob/master/CHANGELOG.md"
Note that if the label contains spaces, it needs to be quoted, e.g.,
Website = "https://example.com"
but
"Official Website" = "https://example.com"
.
Users are advised to use :ref:`well-known-labels` for their project URLs where appropriate, since consumers of metadata (like package indices) can specialize their presentation.
For example in the following metadata, neither MyHomepage
nor
"Download Link"
is a well-known label, so they will be rendered verbatim:
[project.urls]
MyHomepage = "https://example.com"
"Download Link" = "https://example.com/abc.tar.gz"
Whereas in this metadata HomePage
and DOWNLOAD
both have
well-known equivalents (homepage
and download
), and can be presented
with those semantics in mind (the project's home page and its external
download location, respectively).
[project.urls]
HomePage = "https://example.com"
DOWNLOAD = "https://example.com/abc.tar.gz"
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"
See the :ref:`Plugin guide <plugin-entry-points>` for more information.
[build-system]
requires = ["hatchling"]
build-backend = "hatchling.build"
[project]
name = "spam-eggs"
version = "2020.0.0"
dependencies = [
"httpx",
"gidgethub[httpx]>4.0.0",
"django>2.1; os_name != 'nt'",
"django>2.0; os_name == 'nt'",
]
requires-python = ">=3.8"
authors = [
{name = "Pradyun Gedam", email = "pradyun@example.com"},
{name = "Tzu-Ping Chung", email = "tzu-ping@example.com"},
{name = "Another person"},
{email = "different.person@example.com"},
]
maintainers = [
{name = "Brett Cannon", email = "brett@example.com"}
]
description = "Lovely Spam! Wonderful Spam!"
readme = "README.rst"
license = {file = "LICENSE.txt"}
keywords = ["egg", "bacon", "sausage", "tomatoes", "Lobster Thermidor"]
classifiers = [
"Development Status :: 4 - Beta",
"Programming Language :: Python"
]
[project.optional-dependencies]
gui = ["PyQt5"]
cli = [
"rich",
"click",
]
[project.urls]
Homepage = "https://example.com"
Documentation = "https://readthedocs.org"
Repository = "https://github.com/me/spam.git"
"Bug Tracker" = "https://github.com/me/spam/issues"
Changelog = "https://github.com/me/spam/blob/master/CHANGELOG.md"
[project.scripts]
spam-cli = "spam:main_cli"
[project.gui-scripts]
spam-gui = "spam:main_gui"
[project.entry-points."spam.magical"]
tomatoes = "spam:main_tomatoes"
[1] | Think twice before applying an upper bound
like requires-python = "<= 3.10" here. This blog post
contains some information regarding possible problems. |