7

The pyproject.toml specification affords the ability to specify the project version, e.g.

[project]
name = "foo"
version = "0.0.1"

However, it is also a common Python idiom to put __version__ = "0.0.1" in foo/__init__.py so that users can query it.

Is there a standard way of extracting the version from the pyproject.toml and getting it into the foo/__init__.py?

DC Slagel
  • 528
  • 1
  • 7
  • 13
user14717
  • 4,757
  • 2
  • 44
  • 68

1 Answers1

11

There are two approaches you can take here.

  1. Keep version in pyproject.toml and get it from the package metadata in the source code. So, in your foo/__init__.py or wherever:

    from importlib.metadata import version
    __version__ = version(__package__)
    

    importlib.metadata.version is available since Python 3.8. For earlier Python versions, you can do similar with the importlib_metadata backport.

  2. Keep the version in the source code and instruct the build system to get it from there. For a setuptools build backend, it looks like this in pyproject.toml:

    [project]
    dynamic = ["version"]
    
    [tool.setuptools.dynamic]
    version = {attr = "foo.__version__"}
    

My recommendation is actually () ... neither! Don't keep a __version__ attribute in the source code at all. It's an outdated habit which we can do without these days. Version is already a required field in the package metadata, it's redundant to keep the same string as an attribute in the package/module namespace.

wim
  • 338,267
  • 99
  • 616
  • 750
  • What if a user wants to do something in their code which depends on the version in my code? How can they recover my code's version with a `foo.__version__`? – user14717 Nov 29 '22 at 04:11
  • 2
    Users would do it the same way as demonstrated in point 1: by calling `version("yourpackage")`. As you said in the question, this attribute is just a "common Python idiom" so it's not like `module.__version__` is reliable in general anyway (it might or might not be there). Getting the metadata version _is_ reliable because it's guaranteed to be there for an installed package (ref [_PEP 566 – Metadata for Python Software Packages 2.1_](https://peps.python.org/pep-0566/)). – wim Nov 29 '22 at 04:17
  • 2
    If you've already provided a `__version__` attribute in previous releases, and need to preserve backwards compatibility, take your pick of 1. or 2., but for new projects I think it's best not to put the `__version__` attribute in the first place. – wim Nov 29 '22 at 04:20
  • @wim Could you please elaborate a bit more on how the version is determined for the purpose of creating package metadata? I went through the [packaging tutorial](https://packaging.python.org/en/latest/tutorials/packaging-projects/) and from that, I thought that when a wheel is created by `python3 -m build`, version metadata is set according to the value in `pyproject.toml`. However, judging from your answer, I must have missed something important. If the version is not in `pyproject.toml`, how does the `build` script determine it? – Matej Jun 22 '23 at 16:42
  • 1
    @Matej Have a read PEP517, in particular the [`prepare_metadata_for_build_wheel`](https://peps.python.org/pep-0517/#prepare-metadata-for-build-wheel) hook specifications. `build` will invoke the PEP517 hooks, and the build backend (setuptools in this example) will collect the version number from somewhere (from the attribute `foo.__version__` in this example, but in general may be from somewhere else such as from the VCS). How setuptools itself will access a `__version__` attribute has changed over time, I think current releases attempt to do it using an ast parse (to avoid actually importing) – wim Jun 22 '23 at 18:42