# 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:
  ```bash
  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:

```markdown
[![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

```bash
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:

```bash
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:
  ```yaml
  channels:
    - npiocean
    - conda-forge
  ```

Build:

```bash
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`:

```bash
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

```bash
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:
  ```python
  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`)