Skip to content

PEP 621 Metadata#

The project metadata are stored in the pyproject.toml. The specifications are defined by PEP 621, PEP 631 and PEP 639. Read the detailed specifications in the PEPs.

In the following part of this document, metadata should be written under [project] table if not given explicitly.

Multiline description#

You can split a long description onto multiple lines, thanks to TOML support for multiline strings. Just remember to escape new lines, so the final description appears on one line only in your package metadata. Indentation will be removed as well when escaping new lines:

1
2
3
4
5
6
description = """\
    Lorem ipsum dolor sit amet, consectetur adipiscing elit, \
    sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. \
    Ut enim ad minim veniam, quis nostrud exercitation ullamco \
    laboris nisi ut aliquip ex ea commodo consequat.\
"""

See TOML's specification on strings.

Package version#

1
2
[project]
version = "1.0.0"
1
2
3
4
5
6
[project]
...
dynamic = ["version"]

[tool.pdm]
version = { source = "file", path = "mypackage/__version__.py" }

The version will be read from the mypackage/__version__.py file searching for the pattern: __version__ = "{version}".

Read more information about other configurations in dynamic project version from the pdm-backend documentation.

Python version#

The required version of Python is specified as the string requires-python:

1
2
3
4
5
6
7
8
requires-python = ">=3.9"
classifiers = [
    "Programming Language :: Python :: 3",
    "Programming Language :: Python :: 3.9",
    "Programming Language :: Python :: 3.10",
    "Programming Language :: Python :: 3.11",
    ...
]

Note: As per PEP 621, PDM is not permitted to dynamically update the classifiers section like some other non-compliant tools. Thus, you should also include the appropriate trove classifiers as shown above if you plan on publishing your package on PyPI.

License#

The license is specified as the string license:

1
2
3
4
5
license = {text = "BSD-2-Clause"}
classifiers = [
    "License :: OSI Approved :: BSD License",
    ...
]

Note: As per PEP 621, PDM is not permitted to dynamically update the classifiers section like some other non-compliant tools. Thus, you should also include the appropriate trove classifiers as shown above if you plan on publishing your package on PyPI.

Dependency specification#

The project.dependencies is an array of dependency specification strings following the PEP 440 and PEP 508.

Examples:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
[project]
...
dependencies = [
    # Named requirement
    "requests",
    # Named requirement with version specifier
    "flask >= 1.1.0",
    # Requirement with environment marker
    "pywin32; sys_platform == 'win32'",
    # URL requirement
    "pip @ git+https://github.com/pypa/[email protected]"
]

Optional dependencies#

You can have some requirements optional, which is similar to setuptools' extras_require parameter.

1
2
3
4
5
6
7
[project.optional-dependencies]
socks = [ 'PySocks >= 1.5.6, != 1.5.7, < 2' ]
tests = [
  'ddt >= 1.2.2, < 2',
  'pytest < 6',
  'mock >= 1.0.1, < 4; python_version < "3.4"',
]

To install a group of optional dependencies:

1
pdm install -G socks

-G option can be given multiple times to include more than one group.

Context variables expansion#

Depending on which build backend you are using, PDM will expand some variables in the dependency strings.

Environment variables#

1
2
[project]
dependencies = ["flask @ https://${USERNAME}:${PASSWORD}/artifacts.io/Flask-1.1.2.tar.gz"]
1
2
[project]
dependencies = ["flask @ https://{env:USERNAME}:{env:PASSWORD}/artifacts.io/Flask-1.1.2.tar.gz"]

Find more usages here

Don't worry about credential leakage, the environment variables will be expanded when needed and kept untouched in the lock file.

Relative paths#

When you add a package from a relative path, PDM will automatically save it as a relative path for pdm-backend and hatchling.

For example, if you run pdm add ./my-package, it will result in the following line in pyproject.toml.

1
2
[project]
dependencies = ["my-package @ file:///${PROJECT_ROOT}/my-package"]
1
2
[project]
dependencies = ["my-package @ {root:uri}/my-package"]

By default, hatchling doesn't support direct references in the dependency string, you need to turn it on in pyproject.toml:

1
2
[tool.hatch.metadata]
allow-direct-references = true

The relative path will be expanded based on the project root when installing or locking.

Console scripts#

The following content:

1
2
[project.scripts]
mycli = "mycli.__main__:main"

will be translated to setuptools style:

1
2
3
4
5
entry_points = {
    'console_scripts': [
        'mycli=mycli.__main__:main'
    ]
}

Also, [project.gui-scripts] will be translated to gui_scripts entry points group in setuptools style.

Entry points#

Other types of entry points are given by [project.entry-points.<type>] section, with the same format of [project.scripts]:

1
2
[project.entry-points.pytest11]
myplugin = "mypackage.plugin:pytest_plugin"

If the entry point name contains dots or other special characters, wrap it in quotes:

1
2
[project.entry-points."flake8.extension"]
myplugin = "mypackage.plugin:flake8_plugin"