27

If I do sphinx-quickstart I get asked about the version of the project.

I want to avoid to have two places for the version of my project.

How to do this in the python packing world?

3
  • 1
    So, did it any of the answers work for you? If so, please accept the respective answer. If not, what's the issue? Commented Oct 7, 2014 at 16:55
  • Thank you for asking. I am not in a hurry with this topic. I want a canonical answer. I will ask on dist-sig: python.org/community/sigs/current/distutils-sig Commented Oct 8, 2014 at 7:24
  • 1
    @guetti's link to the "single sourcing the package version" section of the Python packaging docs seems to be broken - current link is packaging.python.org/en/latest/guides/… Commented Mar 27, 2022 at 11:23

7 Answers 7

34

The easiest (and probably cleanest) way is to define __version__ for the __init__.py of your top-level package, and then import that package and read the version in both setup.py and your Sphinx project's conf.py.

So lets say your project is called myproject.

Move your current version out of setup.py, and make it a variable in myproject/__init__.py instead:

myproject/__init__.py:

# import foo
# ...

__version__ = '1.5'

Import myproject in your project's setup.py, and replace the hardcoded version with myproject.__version__:

setup.py:

from setuptools import setup
from myproject import __version__


project = "myproject"

setup(
    name=project,
    version=__version__,
    # ...
)

In your Sphinx project's conf.py, do the same. So edit the generated conf.py along these lines:

docs/conf.py:

from myproject import __version__

# ...

# The short X.Y version.
version = __version__
# The full version, including alpha/beta/rc tags.
release = version

For an example of a library that does this pretty much exactly like this, have a look at the requests module (__init__.py | setup.py | conf.py).

This will take care of the auto-generated texts where the project version is used (like the links to the front page of the documentation). If you want to use your version in specific custom places, you can use the rst_epilog directive to dynamically insert configuration values defined in conf.py.

Sign up to request clarification or add additional context in comments.

5 Comments

In another SO question it has been pointed out that if you import some dependencies in myproject/__init__.py, and then do from myproject import __version__ in setup.py, a user won't be able to install the package without installing the dependencies first: stackoverflow.com/questions/2058802/…
We also had a problem with third-party imports and solved it by introducing a separate package containing only meta information. Have a look at this answer: stackoverflow.com/a/52381178/1600678
@SeppoEnarvi thank you for the link, it is important to know. However, it seems irrelevant here, because to generate automatic documentation, one has always to import the package (it is different from installing that with setup.py).
This doesn't work for me. It can't manage to find the package to import the version. It's very odd, because I can find the version if I just open a python shell and try to import it. I've tried adding the package to the path in all kinds of ways in conf.py, and still it's not working.
I can get it to find the version and complete a run if I do the import absolute path of .. thing. But then sphinx doesn't actually find my docstrings in the module! It's infuriating.
7

Maybe an even cleaner option is to actually build sphinx from the setup.py command as described in http://www.sphinx-doc.org/en/master/setuptools.html:

setup.py

# this is only necessary when not using setuptools/distribute
from sphinx.setup_command import BuildDoc
cmdclass = {'build_sphinx': BuildDoc}

name = 'My project'
version = '1.2'
release = '1.2.0'
setup(
    name=name,
    author='Bernard Montgomery',
    version=release,
    cmdclass=cmdclass,
    # these are optional and override conf.py settings
    command_options={
        'build_sphinx': {
            'project': ('setup.py', name),
            'version': ('setup.py', version),
            'release': ('setup.py', release),
            'source_dir': ('setup.py', 'doc')}},
)

Then build documentation using

$ python setup.py build_sphinx

Benefits:

  • Makes setup.py the single source of version number
  • Avoids having to make packages out of your project folders unnecessarily

3 Comments

You have to install Sphinx manually for the setup.py to work. So you can't use pip3 -e . anymore.
@Pierre.Sassoulas that's a very good point, thanks for pointing that out. However, this issue may disappear if using setup.cfg or a pyproject.toml, right? The Sphinx doc has a section about it.
From the link you provided: "Deprecated since version 5.0: This feature will be removed in v7.0."
6

Here is a straightforward solution, ironically from the setuptools_scm PyPI page:

# contents of docs/conf.py
from importlib.metadata import version
release = version('myproject')
# for example take major/minor
version = '.'.join(release.split('.')[:2])

Here is their explanation why it is discouraged to use their package from Sphinx:

The underlying reason is, that services like Read the Docs sometimes change the working directory for good reasons and using the installed metadata prevents using needless volatile data there.

1 Comment

Minor hint: According to the setuptools_scm docs one should import from importlib.metadata import version as get_version and us it like release: str = get_version("package-name").
6

Extract Information from pyproject.toml

If you use a pyproject.toml you could also parse it in the conf.py with tomli or use the equivalent tomllib when you are on python ^3.11.
Like this you can extract the information from the pyproject.toml and use it in your sphinx documentation configuration.

Here a short incomplete example using tomli, assuming conf.py
is located at <project-root>/docs/source/conf.py:

# conf.py

import tomli
with open("../../pyproject.toml", "rb") as f:
    toml = tomli.load(f)

# -- Project information -----------------------------------------------------

pyproject = toml["tool"]["poetry"]

project = pyproject["name"]
version = pyproject["version"]
release = pyproject["version"]

copyright = ...
author = ...

# and the rest of the configuration

1 Comment

Does not work with dynamic version (dynamic = ["version"]) but otherwise a good way to access variables in the pyproject.toml.
4

You could have a look at bumpversion module:

"A small command line tool to simplify releasing software by updating all version strings in your source code by the correct increment"

You may use a configuration file .bumpversion.cfg for complex multi-file operations.

Comments

3

Another way is integrating setuptools_scm in your project. This way you can

from setuptools_scm import get_version

version = get_version()

in your conf.py

1 Comment

This is the best and most up-to-date answer as of 2022.
0

This is a hatchling (instead of poetry) variant of jan's excellent answer, with a few extra things.

Getting the version

One extra thing is that it has the source of truth for the version in my_package/__about__.py, so I don't pull it from pyproject.toml.

My Sphinx conf.py contains something like.

# conf.py
import os
import sys

# ...

sys.path.insert(0, os.path.abspath("."))
sys.path.insert(0, os.path.abspath("../../src"))

from my_package import __about__  # noqa: E402

version = __about__.__version__

Note that the "../../src" path may be different depending on your project layout.

Metadata from a hatchling pyproject.toml

# conf.py

import tomllib

# ...

# Pull general sphinx project info from pyproject.toml
# Modified from https://stackoverflow.com/a/75396624/1304076
with open("../../pyproject.toml", "rb") as f:
    toml = tomllib.load(f)

pyproject = toml["project"]

project = pyproject["name"]
release = version
author = ",".join(
  [author["name"] for author in pyproject["authors"]]
)
copyright = f"2024 {author}"

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.