Packages¶
All our packages should be distributed on PyPI.
Use the Pypackage Cookiecutter template, and add the repository to pre-commit.ci:
uv tool install cookiecutter
cookiecutter gh:open-contracting/software-development-handbook --directory cookiecutter-pypackage
uv venv -p 3.9
uv pip install -e .[test]
uv tool install pre-commit
pre-commit install
See also
Package-rated content in Directory layout, Testing and Linting
Metadata¶
If the package is distributed on PyPI, use this template for the pyproject.toml
file, adding options like entry-points
as needed, and removing the Jinja syntax if not using the Cookiecutter template:
[build-system]
requires = ["setuptools>=61.2"]
build-backend = "setuptools.build_meta"
[project]
name = "{{ cookiecutter.package_name }}"
version = "0.0.1"
authors = [{name = "Open Contracting Partnership", email = "data@open-contracting.org"}]
description = "{{ cookiecutter.short_description }}"
readme = "README.rst"
license = {text = "BSD"}
urls = {Homepage = "https://github.com/open-contracting/{{ cookiecutter.repository_name }}"}
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.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 %}
]
dependencies = []
[project.optional-dependencies]
test = [
"coverage",
"pytest",
]
[tool.setuptools.packages.find]
exclude = [
"tests",
"tests.*",
]
[tool.ruff]
line-length = 119
target-version = "py39"
[tool.ruff.lint]
select = ["ALL"]
ignore = [
"ANN", "C901", "COM812", "D203", "D212", "D415", "EM", "ISC001", "PERF203", "PLR091", "Q000",
]
[tool.ruff.lint.flake8-builtins]
builtins-ignorelist = ["copyright"]
[tool.ruff.lint.flake8-unused-arguments]
ignore-variadic-names = true
[tool.ruff.lint.per-file-ignores]
"docs/conf.py" = ["D100", "INP001"]
"tests/*" = [
"ARG001", "D", "FBT003", "INP001", "PLR2004", "S", "TRY003",
]
If the package isn’t distributed on PyPI, use this template pyproject.toml
:
[project]
name = "NAME"
version = "0.0.0"
[tool.setuptools.packages.find]
exclude = ["tests", "tests.*"]
Reference: Packaging and distributing projects
Requirements¶
Use
dependencies
andoptional-dependencies
in thepyproject.toml
fileDo not use a
requirements.txt
fileSort requirements alphabetically
See also
Classifiers¶
"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.
Documentation¶
The template reads the documentation from a README.rst
file. To convert a README.md
file, install pandoc
and run:
pandoc --from=markdown --to=rst --output=README.rst README.md
See also
Publish releases¶
Continuous integration¶
To publish tagged releases to PyPI, create a .github/workflows/pypi.yml
file:
name: Publish to PyPI
on: push
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: '3.10'
- run: pip install --upgrade build
- run: python -m build --sdist --wheel
- uses: actions/upload-artifact@v4
with:
name: python-package-distributions
path: dist/
test:
needs: build
permissions:
id-token: write
runs-on: ubuntu-latest
steps:
- uses: actions/download-artifact@v4
with:
name: python-package-distributions
path: dist/
- uses: pypa/gh-action-pypi-publish@release/v1
with:
repository-url: https://test.pypi.org/legacy/
skip-existing: true
publish:
if: startsWith(github.ref, 'refs/tags/')
needs: test
permissions:
id-token: write
runs-on: ubuntu-latest
steps:
- uses: actions/download-artifact@v4
with:
name: python-package-distributions
path: dist/
- 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 opencontracting Test PyPI user.
After publishing the first release to PyPI, add additional owners.
Release process¶
Ensure that you are on an up-to-date
main
branch:git checkout main git pull --rebase
Ensure that the package is ready for release:
All tests pass on continuous integration
The version number is correct in
pyproject.toml
anddocs/conf.py
(if present)The changelog is up-to-date and dated
Tag the release, replacing
x.y.z
twice:git tag -a x.y.z -m 'x.y.z release.'
Push the release:
git push --follow-tags
Announce on the discussion group if relevant
Reference: Publishing package distribution releases using GitHub Actions CI/CD workflows