Skip to article frontmatterSkip to article content

How Dynamic Metadata are Defined

The secret to setting your version in a Python file

Defining a Static Version

pyproject.toml has certain fields that are required, which are described in the Python Packaging Guide:

pyproject.toml
[project]
name = "arrow-to-knee" # required!
version = "1.0.0"      # required!

As a Build front end, Hatch provides a nice CLI to find out the current package version:

$ hatch version
1.0.0

You can also use this sub-command to set the version:

$ hatch version 2.0.0
Old: 1.0.0
New: 2.0.0

By default, this will update the pyproject.toml:

pyproject.toml
[project]
name = "arrow-to-knee" # required!
version = "2.0.0"      # required!

What About the __version__ Name?

Some people prefer to define their version in a __version__ global variable in their Import package. The __version__ variable is a convention (not a standard) that some packages use for exposing this infmormation to other users of their package.

__version__ = "1.0.0"

Setting __verion__ lets you use the version number in your code, e.g. to print it out:

def show_package_info():
    print(f"This package has version {__version__}")

It’s possible to get the installed version another way, via the importlib.metadata package that we saw earlier:

import importlib.metadata
importlib.metadata.version("arrow-to-knee")

This is the standard way to get at installed Distribution package metadata, but it can be “slow” as it has to perform file-system operations.

How Do I Use __version__ with hatchling?

First, set the version to be dynamic and remove the version field:

pyproject.toml
[project]
dynamic = ["version"]
# version = "1.0.0"

Then tell hatchling about the version source:

pyproject.toml
[tool.hatch.version]
source = "regex"
path = "src/arrow_to_knee/__init__.py"

Now, we can use te hatch version command once again to set the version, and Hatch will update the __init__.py file instead of pyproject.toml.

How Do I Build a Dynamic README?

Many projects also use dynamic README files. The intention behind this is often one of reducing duplication whilst also distributing different README files for different platforms. For example, the kinds of information that package authors want to show to users on PyPI may differ from those that they want contributors to the GitHub repository to say (such as the inclusion/removal of Markdown badges). For example the awkward-array project uses this to hide the code-contributing guidelines from the PyPI users, and change out the logo.

To build a dynamic readme, first, add hatch-fancy-pypi-readme to your [build-system] table:

pyproject.toml
[build-system]
requires = ["hatchling", "hatch-fancy-pypi-readme"]
build-backend = "hatchling.build"

This is a particular hatchling plugin that knows how to build a README, and we need to define it here so that the package manager installs the plugin before trying to build your package. Then tell hatchling about the dynamic readme and remove the readme field:

pyproject.toml
[project]
dynamic = ["version", "readme"]

This will signal to build frontends that they will need to ask hatchling to compute the readme (and version), if they want to read it. Next, configure the fancy-pypi-readme tool with the knowledge that our README is Markdown:

pyproject.toml
[tool.hatch.metadata.hooks.fancy-pypi-readme]
content-type = "text/markdown"

Finally, define your readme fragments. These individual parts are concatenated together to form a contiguous block of text. Here, we’re interleaving a fragment taken from the README.md file (between some BEGIN and END markers that we’re pre-inserted into that file), a block of raw text, and another fragment from README.md. You can see how this might be quite powerful!

pyproject.toml
[[tool.hatch.metadata.hooks.fancy-pypi-readme.fragments]]
path = "README.md"
start-after = "<!-- BEGIN README 1 -->"
end-before = "<!-- END README 1 -->"

[[tool.hatch.metadata.hooks.fancy-pypi-readme.fragments]]
text = "This is a readme\n"

[[tool.hatch.metadata.hooks.fancy-pypi-readme.fragments]]
path = "README.md"
start-after = "<!-- BEGIN README 2 -->"
end-before = "<!-- END README 2 -->"