Publishing a Python library version (GitHub → Zenodo, PyPI, conda)#

Recipe with some steps that work for kval used by ØF. There are definitely many ways to do it, but this works for me on Ubuntu. Unlikely to work out of the box on any system..

Example: Publishing kval#

Example: publishing kval. Last updated for kval 0.5.0 (September 2026).

Before you start (one-time setup)#

  • Accounts with upload rights on PyPI and anaconda.org (the npiocean channel), plus API tokens configured locally.

  • The GitHub repo is linked to Zenodo: Zenodo → Account → GitHub → repository switched on.

  • A separate conda environment for release tools, so they don’t clash with your working environment:

    mamba create -n release -c conda-forge python=3.12 build "twine>=7" grayskull conda-build anaconda-client
    

    (twine must be ≥ 7. Recent hatchling writes metadata version 2.5, which older twine rejects.)

1. Update version and release notes#

  • Version number in pyproject.toml and src/kval/__init__.py. They must match.

  • Release notes in README.md and in Latest release in docs/source/other/about.md.

  • Commit and push.

2. Build and test locally, before tagging#

The GitHub release triggers a permanent Zenodo DOI, so check that everything works first.

In your development environment (kval installed from the repo with pip install -e ".[dev]", which includes pytest):

pytest                     # full test suite

In the release environment:

rm -rf dist
python -m build            # should create kval-X.Y.Z.tar.gz and .whl in dist/

3. GitHub release (→ Zenodo)#

  • On the repo page: Releases → Draft a new release.

  • Tag: vX.Y.Z. Title: Version X.Y.Z. Paste the release notes into the text field.

  • Publish it as a normal release, not as a draft.

Zenodo picks the release up automatically and issues a DOI. This usually takes a few minutes. Check its status under Zenodo → Account → GitHub → the repository.

  • If it stays at “Received” for hours, do not re-release. Re-releasing can create a duplicate DOI, which you can’t delete yourself. Wait, and if it is still stuck, contact Zenodo support.

  • The steps below don’t depend on Zenodo, so carry on.

DOI badge: use the concept DOI. It always points to the latest version, so the README badge never needs updating. For kval:

[![DOI](https://zenodo.org/badge/DOI/10.5281/zenodo.10360162.svg)](https://doi.org/10.5281/zenodo.10360162)

(To find a concept DOI, open any record on Zenodo and look under Versions for “Cite all versions?”)

4. Publish to PyPI#

twine upload dist/*

The new version should appear at https://pypi.org/project/kval/ within a minute.

5. Publish to anaconda.org#

Go to your conda recipes folder and generate the recipe:

grayskull pypi kval

This takes under a minute and writes kval/meta.yaml. Then check and edit meta.yaml:

  • Version: it should be the new one. PyPI can take a little while to propagate. If it’s the old version, wait and rerun.

  • Version caps survived: e.g. ipykernel >=6.26.0,<7 and matplotlib-base >=3.10,<3.11. Grayskull renames matplotlib to matplotlib-base, which is fine.

  • ruamel: keep ruamel.yaml >=.... Delete any other spelling that grayskull adds (ruamel_yaml, ruamel-yaml); those refer to a different or non-existent package.

  • Add at the end:

    channels:
      - npiocean
      - conda-forge
    

Build:

conda build . -c npiocean -c conda-forge
  • It takes 1–2 minutes. Warnings about numpy variants, “number of parsed outputs” and conda-verify can be ignored.

  • The build ends with an import test and pip check. Both should pass.

Upload. The build prints the exact file path at the end, or you can get it with conda build . --output:

anaconda upload <path>/noarch/kval-X.Y.Z-py_0.conda

It should show up immediately at https://anaconda.org/npiocean/kval. Paste the README contents into the description there; it supports Markdown.

6. Test the conda install#

mamba env remove -n kval_test_env
mamba create -n kval_test_env python=3.12
mamba activate kval_test_env
mamba install -c npiocean -c conda-forge kval pytest
  • Run the test suite from the kval repo folder.

  • Open JupyterLab in this environment. Check that the notebook runs this environment’s Python, and that the interactive plots work when called several times:

    import sys, ipykernel; print(sys.executable, ipykernel.__version__)
    
      - conda-forge
    
    • Also, remove the line ruamel-yaml >=0.18.15 from meta.yaml if applicable.

      • Correct line is ruamel.yaml >=0.18.15 (some issue in translation here - removing the line above fixes it).

    • Build the package from meta.yaml: ´conda build . -c npiocean -c conda-forge´

      • Some initial warnings (numpy, Jinja) don’t seem to matter - ignore them.

      • Should run (1-2 min) without any errors or major warnings

    • Now cd to ~/miniforge3/envs/oyv/conda-bld/noarch where I just built the .cond file.

    • Upload to anaconda.org: anaconda upload NNN-py_0.conda

      • Should dimmediately be able to see it at https://anaconda.org/npiocean/kval

      • Good idea to replace the description there with the README.md contents. Supports markdown so just c/p.

    • Now test that things work:

      • mamba env remove -n kval_test_env

      • mamba create -n kval_test_env python=3.12

      • mamba activate kval_test_env

      • mamba install -c npiocean -c conda-forge kval

      • mamba install pytest

      • cd to the kval base directory and run the test suite (pytest)