Python packaging has evolved a lot. The latest ("beta") uses one file, pyproject.toml
, to control the package.
A minimal pyproject.toml
might look like this:
[build-system]
requires = ["setuptools"]
build-backend = "setuptools.build_meta"
[project]
name = "cool_project"
version = "1.0.0"
The project section
The project section is the data about the Python project itself, including fields like name and version, which are required.
Other fields are often used, including:
- description: A one-line description.
- readme: Location of a README file.
- authors: Author names and e-mails.
- dependencies: Other packages used by this project.
The build-system section
Though it does not have to be first, the build-system usually goes at the top. This is because it is the most important one.
The build-backend key points to a module that knows how to build source distributions and wheels from the project. The requires field allows specifying build time dependencies.
Many projects are built with setuptools. There are some new alternatives, like flit and hatch.
Plugins
One benefit of the requires section in build-system is that it can be used to install plugins. The setuptools package, in particular, can use plugins to modify its behavior.
One thing that plugins can do is to set the version automatically. This is a popular need because version management can often be painful.
Segue
Before continuing, it is worth reflecting on the nature of "parodies". A parody of X is an instance of X which exaggerates some aspects, often to the point of humor.
For example, a "parody of a spy movie" is a spy movie, even as it riffs on the genre.
A parody of setuptools plugins
With this in mind, what would a parody of a setuptools plugin look like? By the rule above, it has to be a plugin.
The plugin, called onedotoh, sets the version to... 1.0.0. In order to be a plugin, it first has to be a package.
A package should have a pyproject.toml
:
[build-system]
requires = ["setuptools"]
build-backend = "setuptools.build_meta"
[project]
name = "onedotoh"
version = "1.0.0"
[project.entry-points."setuptools.finalize_distribution_options"]
setuptools_scm = "onedotoh:guess_version"
There is a new section: project.entry-points. This means that the function guess_version will be called as setuptools is ready to finalize the distribution options.
The code of guess_version is one line:
def guess_version(dist):
dist.metadata.version = "1.0.0"
Version to 1.0.0
Using onedotoh is subtle. One problem is writing the pyproject.toml project section to look like this:
[project]
name = "a_pyproject"
version = "0.1.2"
The version in the pyproject.toml will override the version from the plugin.
The obvious solution is to remove the version field:
[project]
name = "a_pyproject"
This fails in a different way. Without the version in the project section, the file is invalid. The name will not be used.
The right way to do it is as follows:
[project]
name = "a_pyproject"
dynamic = ["version"]
This approach explicitly declares the version field as dynamic.
A full example will look like this:
[build-system]
requires = [
"setuptools",
"onedotoh",
]
build-backend = "setuptools.build_meta"
[project]
name = "a_pyproject"
dynamic = ["version"]
Finally, the version is automatically set to 1.0.0.
Wrap up
The setuptools plugin can still be used with modern Python packaging, as long as relevant features are explicitly declared as "dynamic." This makes a field rife for further experimentation with automation.
For example, what if, in addition to guessing the version from external metadata, it would guess the name as well, using the git remote URL?
Comments are closed.