Setuptools and pyproject.tomlΒΆ

In the Python software ecosystem packaging and distributing software have historically been a difficult topic. Nevertheless, the community has been served well by setuptools as the de facto standard for creating distributable Python software packages.

In recent years, however, other needs and opportunities fostered the emergence of some alternatives. During this packaging renaissance, PEP 621 was proposed (and accepted) to standardise package metadata/configuration done by developers, in a single file format shared by all the packaging alternatives. It makes use of a TOML file, pyproject.toml, which is, unfortunately, significantly different from setuptools own original declarative configuration file, setup.cfg, and therefore its adoption by setuptools requires mapping concepts between these two files.

PEP 621 covers most of the information expected from a setup.cfg file, but there are parameters specific to setuptools without an obvious equivalent. The setuptools docs covers the remaining options using the [tool.setuptools] TOML table. Based on this description, the automatic translation proposed by ini2toml works like the following:

  • Any field without an obvious equivalent in PEP 621 is stored in the [tool.setuptools] TOML table, regardless if it comes from the [metadata] or [options] sections in setup.cfg.

  • [options.*] sections in setup.cfg are translated to sub-tables of [tool.setuptools] in pyproject.toml. For example:

    [options.package_data] => [tool.setuptools.package-data]
  • Field and subtables in [tool.setuptools] have the _ character replaced by - in their keys, to follow the conventions set in PEP 517 and PEP 621.

  • setup.cfg directives (e.g. fields starting with attr: or file:) can be transformed into a (potentially inline) TOML table, for example:

    'file: description.rst' => {file = "description.rst"}

    Note, however, that these directives are not allowed to be used directly under the project table. Instead, ini2toml will rely on dynamic, as explained below. Also note that for some fields (e.g. readme), ini2toml might try to automatically convert the directive into values accepted by PEP 621 (for complex scenarios dynamic might still be used).

  • Instead of requiring a separated/dedicated section to specify parameters, the directives find: and find_namespace: just use a nested table: tool.setuptools.packages.find. Moreover, two quality of life improvements are added: the where option takes a list of strings (instead of a single directory) and the boolean namespaces option is added (namespaces = true is equivalent to find_namespace: and namespaces = false is equivalent to find:). For example:

    # setup.cfg
    package = find_namespace:
    where = src
    exclude =
    # pyproject.toml
    where = ["src"]
    exclude = ["tests"]
    namespaces = true
  • Fields set up to be dynamically resolved by setuptools via directives, that cannot be directly represented by following PEP 621 (or other complementary standards) (e.g. version = attr: module.attribute or classifiers = file: classifiers.txt), are listed as dynamic under the [project] table. The configurations for how setuptools fill those fields are stored under the [tool.setuptools.dynamic] table. For example:

    # setup.cfg
    version = attr: module.attribute
    classifiers = file: classifiers.txt
    entry_points = file: entry-points.txt
    # pyproject.toml
    dynamic = ["version", "classifiers", "entry-points", "scripts", "gui-scripts"]
    version = {attr = "module.attribute"}
    classifiers = {file = "classifiers.txt"}
    entry-points = {file = "entry-points.txt"}

    There is a special case for dynamic entry-points, scripts and gui-scripts: while these 3 fields should be listed under project.dynamic, only tool.setuptools.dynamic.entry-point is allowed. scripts and gui-scripts should be directly derived from entry-points file.

  • The options.scripts field is renamed to script-files and resides inside the tool.setuptools table. This is done to avoid confusion with the project.scripts field defined by PEP 621.

  • When not present in the original config file, include_package_data is explicitly added with the False value to the translated TOML. This happens because in setup.cfg the default value for inclue_package_data is False, but in pyproject.toml the default value is True. This change was mentioned by some members of the community as a nice quality of life improvement.

  • The metadata.license_files field in setup.cfg is not translated to project.license.file in pyproject.toml, even when a single file is given. The reason behind this choice is that project.license.file is meant to be used in a different way than metadata.license_files when generating core metadata (the first is read and expanded into the License core metadata field, the second is added as a path - relative to the project root - as the License-file core metadata field). This might change in the future if PEP 639 is accepted. Meanwhile, metadata.license_files is translated to tool.setuptools.license-files.