Search code examples
pythonsetuptoolssetup.pypython-packaging

single-sourcing package version for setup.cfg Python projects


For traditional Python projects with a setup.py, there are various ways of ensuring that the version string does not have to be repeated throughout the code base. See PyPA's guide on "Single-sourcing the package version" for a list of recommendations.

Many are trying to move away from setup.py to setup.cfg (probably under the influence of PEP517 and PEP518; setup.py was mostly used declaratively anyway, and when there was logic in setup.py, it was probably for the worse.) This means that most the suggestions won't work anymore since setup.cfg cannot contain "code".

How can I single-source the package version for Python projects that use setup.cfg?


Solution

  • There are a couple of ways to do this (see below for the project structure used in these examples):

    1.

    setup.cfg

    [metadata]
    version = 1.2.3.dev4
    

    src/my_top_level_package/__init__.py

    import importlib.metadata
    __version__ = importlib.metadata.version('MyProject')
    

    2.

    setup.cfg

    [metadata]
    version = file: VERSION.txt
    

    VERSION.txt

    1.2.3.dev4
    

    src/my_top_level_package/__init__.py

    import importlib.metadata
    __version__ = importlib.metadata.version('MyProject')
    

    3.

    setup.cfg

    [metadata]
    version = attr: my_top_level_package.__version__
    

    src/my_top_level_package/__init__.py

    __version__ = '1.2.3.dev4'
    

    And more...

    There are probably other ways to do this, by playing with different combinatons.


    References:


    Structure assumed in the previous examples is as follows...

    MyProject
    ├── setup.cfg
    ├── setup.py
    └── src
        └── my_top_level_package
            └── __init__.py
    

    setup.py

    #!/usr/bin/env python3
    
    import setuptools
    
    if __name__ == '__main__':
        setuptools.setup(
            # see 'setup.cfg'
        )
    

    setup.cfg

    [metadata]
    name = MyProject
    # See above for the value of 'version = ...'
    
    [options]
    package_dir =
        = src
    packages = find:
    
    [options.packages.find]
    where = src
    
    $ cd path/to/MyProject
    $ python3 setup.py --version
    1.2.3.dev4
    $ python3 -m pip install .
    # ...
    $ python3 -c 'import my_top_level_package; print(my_top_level_package.__version__)'
    1.2.3.dev4
    $ python3 -V
    Python 3.6.9
    $ python3 -m pip list
    Package       Version   
    ------------- ----------
    MyProject     1.2.3.dev4
    pip           20.0.2    
    pkg-resources 0.0.0     
    setuptools    45.2.0    
    wheel         0.34.2    
    zipp          3.0.0