All our packages should be distributed on PyPI.

Use the Pypackage Cookiecutter template, and add the repository to

pip install cookiecutter
cookiecutter gh:open-contracting/software-development-handbook --directory cookiecutter-pypackage

See also

Package-rated content in Directory layout, Testing and Linting


If the package is distributed on PyPI, use this template for the setup.cfg file, adding options like entry_points and namespace_packages as needed, and removing the Jinja syntax if not using the Cookiecutter template:

max-line-length = 119
extend-ignore = E203

name = {{ cookiecutter.package_name }}
version = 0.0.1
author = Open Contracting Partnership
author_email =
license = BSD
description = {{ cookiecutter.short_description }}
url ={{ cookiecutter.repository_name }}
long_description = file: README.rst
long_description_content_type = text/x-rst
classifiers =
    License :: OSI Approved :: BSD License
{%- if cookiecutter.os_independent == "y" %}
    Operating System :: OS Independent
{%- else %}
    Operating System :: POSIX :: Linux
{%- endif %}
    Programming Language :: Python :: 3.8
    Programming Language :: Python :: 3.9
    Programming Language :: Python :: 3.10
    Programming Language :: Python :: 3.11
    Programming Language :: Python :: 3.12
    Programming Language :: Python :: Implementation :: CPython
{%- if cookiecutter.pypy == "y" %}
    Programming Language :: Python :: Implementation :: PyPy
{%- endif %}

packages = find:
install_requires =

exclude =

test =
docs =

If the package isn’t distributed on PyPI, use this template setup.cfg:

name = NAME
version = 0.0.0
license = BSD

packages = find:
install_requires =

Reference: Packaging and distributing projects


  • Use install_requires and extras_require in the setup.cfg file

  • Do not use a requirements.txt file

  • Sort requirements alphabetically


"Operating System :: OS Independent"

The package is tested on macOS, Windows and Ubuntu.

"Operating System :: POSIX :: Linux"

The package is tested on Ubuntu only.

"Programming Language :: Python :: Implementation :: PyPy"

The package is tested on PyPy.


The template reads the documentation from a README.rst file. To convert a file, install pandoc and run:

pandoc --from=markdown --to=rst --output=README.rst

Release process

One-time setup

To publish tagged releases to PyPI, create a .github/workflows/pypi.yml file:

name: Publish to PyPI
on: push
    runs-on: ubuntu-latest
      id-token: write
      - uses: actions/checkout@v4
      - uses: actions/setup-python@v5
          python-version: '3.10'
      - run: pip install --upgrade build
      - run: python -m build --sdist --wheel
      - name: Publish to TestPyPI
        uses: pypa/gh-action-pypi-publish@release/v1
          skip-existing: true
      - name: Publish to PyPI
        if: startsWith(github.ref, 'refs/tags')
        uses: pypa/gh-action-pypi-publish@release/v1

The open-contracting organization sets the PYPI_API_TOKEN organization secret to the API token of the opencontracting PyPI user, and TEST_PYPI_API_TOKEN to that of the TestPyPI user.

After publishing the first release to PyPI, add additional owners.

  1. Ensure that you are on an up-to-date main branch:

    git checkout main
    git pull --rebase
  2. Ensure that the package is ready for release:

    • All tests pass on continuous integration

    • The version number is correct in setup.cfg and docs/ (if present)

    • The changelog is up-to-date and dated

  3. Tag the release, replacing x.y.z twice:

    git tag -a x.y.z -m 'x.y.z release.'
  4. Push the release:

    git push --follow-tags
  5. Announce on the discussion group if relevant

Reference: Publishing package distribution releases using GitHub Actions CI/CD workflows