diff --git a/.github/ISSUE_TEMPLATE/release-checklist.md b/.github/ISSUE_TEMPLATE/release-checklist.md new file mode 100644 index 00000000..919dfec8 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/release-checklist.md @@ -0,0 +1,40 @@ +--- +name: Release checklist +about: Checklist for cutting a new pycmor release +title: "Release vX.Y.Z" +labels: release +assignees: '' +--- + +## Pre-release + +- [ ] All planned PRs merged +- [ ] `CHANGELOG` / release notes up to date +- [ ] `version` in `CITATION.cff` and `codemeta.json` matches the new version +- [ ] `date-released` in `CITATION.cff` updated + +## Release + +- [ ] Tag pushed / GitHub Release created +- [ ] Zenodo DOI auto-generated (check https://zenodo.org — linked via `.zenodo.json` or GitHub integration) +- [ ] Update `doi` in `CITATION.cff` and `identifier[doi]` in `codemeta.json` with the new Zenodo DOI + +## Post-release: Software Heritage SWHID + +The GitHub Actions workflow triggers an SWH archival request automatically. +After ~1 hour, run the helper script to fetch the new SWHID and update the files: + +```bash +python scripts/update_swhid.py +# Follow the output — it will print the SWHID and patch CITATION.cff + codemeta.json +``` + +Then open a PR with the updated files. + +- [ ] `identifiers[swh]` in `CITATION.cff` updated with new SWHID +- [ ] `identifier[swh]` in `codemeta.json` updated with new SWHID +- [ ] SWHID also updated in the Helmholtz RSD: https://helmholtz.software/software/pycmor/edit/software-heritage + +## Done + +- [ ] Release announcement (if applicable) diff --git a/.github/workflows/software-heritage.yml b/.github/workflows/software-heritage.yml new file mode 100644 index 00000000..5669f6a8 --- /dev/null +++ b/.github/workflows/software-heritage.yml @@ -0,0 +1,34 @@ +name: Archive on Software Heritage + +on: + release: + types: [published] + +jobs: + save-to-swh: + runs-on: ubuntu-latest + steps: + - name: Request Software Heritage archival + run: | + ORIGIN="https://github.com/esm-tools/pycmor" + RESPONSE=$(curl -s -o /dev/null -w "%{http_code}" -X POST \ + "https://archive.softwareheritage.org/api/1/origin/save/git/url/${ORIGIN}/") + echo "SWH save request HTTP status: $RESPONSE" + + - name: Post release notes reminder + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + TAG: ${{ github.event.release.tag_name }} + run: | + ORIGIN="https://github.com/esm-tools/pycmor" + SWH_URL="https://archive.softwareheritage.org/browse/origin/directory/?origin_url=${ORIGIN}" + gh release edit "$TAG" \ + --notes "$(gh release view "$TAG" --json body -q .body) + +--- +**Software Heritage archival** was requested automatically for this release. +Once processed (usually within 1 hour), retrieve the snapshot SWHID at: +${SWH_URL} + +Then update \`CITATION.cff\` (identifiers.swh) and \`codemeta.json\` (identifier[swh]) with the new SWHID. +You can use the helper script: \`python scripts/update_swhid.py\`" || true diff --git a/CITATION.cff b/CITATION.cff index 3cdf8e58..f575fb84 100644 --- a/CITATION.cff +++ b/CITATION.cff @@ -1,5 +1,15 @@ cff-version: 1.2.0 -message: "If you use ESM-Tools software, please cite using the metadata below." +message: "If you use pycmor, please cite it using the metadata below." +type: software +title: "pycmor" +abstract: >- + pycmor (py-cmor) is a Python package that simplifies the conversion of climate + model output into Climate Model Output Rewriter (CMOR) compliant formats, + supporting both CMIP6 and CMIP7 standards. It provides a modular, plugin-based + pipeline architecture for processing NetCDF data from diverse Earth system + models, enabling compliance with WCRP Controlled Vocabularies and CF + conventions. Developed at the Alfred Wegener Institute Helmholtz Centre for + Polar and Marine Research. authors: - family-names: "Gierz" given-names: "Paul" @@ -17,8 +27,30 @@ authors: given-names: "Jan" orcid: "https://orcid.org/0000-0001-9515-3322" affiliation: "Alfred-Wegener-Institut Helmholtz-Zentrum für Polar- und Meeresforschung" - -title: "pycmor" -doi: 10.5281/zenodo.15530131 +version: "1.0.2" date-released: 2025-05-27 -url: "https://github.com/esm-tools/pycmor" +license: MIT +repository-code: "https://github.com/esm-tools/pycmor" +url: "https://pymor.readthedocs.io" +doi: 10.5281/zenodo.15530131 +identifiers: + - type: doi + value: "10.5281/zenodo.15530131" + description: "Zenodo DOI for pycmor v1.0.2" + - type: swh + value: "swh:1:snp:e4d5216be639a8e2dd2be23be1e78981de3fc67d" + description: "Software Heritage snapshot (HEAD, 2026-06-16)" +keywords: + - CMOR + - CMORization + - CMIP6 + - CMIP7 + - climate + - climate model output + - NetCDF + - CF-conventions + - earth system model + - WCRP + - research software + - Python + - AWI diff --git a/codemeta.json b/codemeta.json new file mode 100644 index 00000000..eadf1e6f --- /dev/null +++ b/codemeta.json @@ -0,0 +1,100 @@ +{ + "@context": "https://doi.org/10.5063/schema/codemeta-2.0", + "@type": "SoftwareSourceCode", + "name": "pycmor", + "description": "pycmor (py-cmor) is a Python package that simplifies the conversion of climate model output into Climate Model Output Rewriter (CMOR) compliant formats, supporting both CMIP6 and CMIP7 standards. It provides a modular, plugin-based pipeline architecture for processing NetCDF data from diverse Earth system models, enabling compliance with WCRP Controlled Vocabularies and CF conventions.", + "identifier": [ + { + "@type": "PropertyValue", + "propertyID": "doi", + "value": "https://doi.org/10.5281/zenodo.15530131" + }, + { + "@type": "PropertyValue", + "propertyID": "swh", + "value": "swh:1:snp:e4d5216be639a8e2dd2be23be1e78981de3fc67d" + } + ], + "version": "1.0.2", + "datePublished": "2025-05-27", + "license": "https://spdx.org/licenses/MIT", + "codeRepository": "https://github.com/esm-tools/pycmor", + "issueTracker": "https://github.com/esm-tools/pycmor/issues", + "url": "https://pymor.readthedocs.io", + "downloadUrl": "https://pypi.org/project/py-cmor/", + "programmingLanguage": { + "@type": "ComputerLanguage", + "name": "Python", + "url": "https://www.python.org" + }, + "operatingSystem": ["Linux", "macOS", "Windows"], + "softwareRequirements": "Python >= 3.9", + "developmentStatus": "active", + "keywords": [ + "CMOR", + "CMORization", + "CMIP6", + "CMIP7", + "climate", + "climate model output", + "NetCDF", + "CF-conventions", + "earth system model", + "WCRP", + "research software", + "AWI" + ], + "author": [ + { + "@type": "Person", + "givenName": "Paul", + "familyName": "Gierz", + "@id": "https://orcid.org/0000-0002-4512-087X", + "affiliation": { + "@type": "Organization", + "name": "Alfred-Wegener-Institut Helmholtz-Zentrum für Polar- und Meeresforschung", + "url": "https://www.awi.de" + } + }, + { + "@type": "Person", + "givenName": "Miguel", + "familyName": "Andrés-Martínez", + "@id": "https://orcid.org/0000-0002-1525-5546", + "affiliation": { + "@type": "Organization", + "name": "Alfred-Wegener-Institut Helmholtz-Zentrum für Polar- und Meeresforschung", + "url": "https://www.awi.de" + } + }, + { + "@type": "Person", + "givenName": "Pavan Kumar", + "familyName": "Siligam", + "@id": "https://orcid.org/0009-0003-8054-7021", + "affiliation": { + "@type": "Organization", + "name": "Alfred-Wegener-Institut Helmholtz-Zentrum für Polar- und Meeresforschung", + "url": "https://www.awi.de" + } + }, + { + "@type": "Person", + "givenName": "Jan", + "familyName": "Streffing", + "@id": "https://orcid.org/0000-0001-9515-3322", + "affiliation": { + "@type": "Organization", + "name": "Alfred-Wegener-Institut Helmholtz-Zentrum für Polar- und Meeresforschung", + "url": "https://www.awi.de" + } + } + ], + "funder": { + "@type": "Organization", + "name": "Alfred-Wegener-Institut Helmholtz-Zentrum für Polar- und Meeresforschung", + "url": "https://www.awi.de" + }, + "contIntegration": "https://github.com/esm-tools/pycmor/actions", + "readme": "https://github.com/esm-tools/pycmor/blob/main/README.rst" +} diff --git a/scripts/update_swhid.py b/scripts/update_swhid.py new file mode 100644 index 00000000..bd676c5f --- /dev/null +++ b/scripts/update_swhid.py @@ -0,0 +1,99 @@ +#!/usr/bin/env python3 +""" +Fetch the latest Software Heritage snapshot SWHID for pycmor and update +CITATION.cff and codemeta.json in-place. + +Usage: + python scripts/update_swhid.py + +Run this after a release, once the SWH archival is complete (~1 hour after tag push). +""" +import json +import re +import sys +import urllib.request +from pathlib import Path + +ORIGIN = "https://github.com/esm-tools/pycmor" +SWH_API = "https://archive.softwareheritage.org/api/1" +REPO_ROOT = Path(__file__).parent.parent + + +def get_latest_swhid() -> str: + url = f"{SWH_API}/origin/{ORIGIN}/visit/latest/?require_snapshot=true" + try: + with urllib.request.urlopen(url, timeout=15) as r: + data = json.load(r) + except Exception as e: + sys.exit(f"Failed to fetch latest SWH visit: {e}\n" + f"Is the repository archived? Check: " + f"https://archive.softwareheritage.org/browse/origin/?origin_url={ORIGIN}") + + snapshot = data.get("snapshot") + if not snapshot: + sys.exit("Latest SWH visit has no snapshot yet — archival may still be in progress. " + "Try again in a few minutes.") + + return f"swh:1:snp:{snapshot}" + + +def update_citation_cff(swhid: str) -> None: + path = REPO_ROOT / "CITATION.cff" + content = path.read_text() + + # Replace existing swh identifier value + pattern = r'(- type: swh\s+value: ")[^"]+"' + replacement = rf'\g<1>{swhid}"' + new_content, count = re.subn(pattern, replacement, content) + + if count == 0: + print("WARNING: could not find '- type: swh' block in CITATION.cff — " + "please update identifiers.swh manually.") + print(f" SWHID to add: {swhid}") + return + + path.write_text(new_content) + print(f"Updated CITATION.cff: identifiers.swh = {swhid}") + + +def update_codemeta(swhid: str) -> None: + path = REPO_ROOT / "codemeta.json" + data = json.loads(path.read_text()) + + identifiers = data.get("identifier", []) + if isinstance(identifiers, str): + identifiers = [{"@type": "PropertyValue", "propertyID": "doi", "value": identifiers}] + + updated = False + for entry in identifiers: + if isinstance(entry, dict) and entry.get("propertyID") == "swh": + entry["value"] = swhid + updated = True + break + + if not updated: + identifiers.append({"@type": "PropertyValue", "propertyID": "swh", "value": swhid}) + + data["identifier"] = identifiers + path.write_text(json.dumps(data, indent=2, ensure_ascii=False) + "\n") + print(f"Updated codemeta.json: identifier[swh] = {swhid}") + + +def main() -> None: + print(f"Fetching latest Software Heritage snapshot for {ORIGIN} ...") + swhid = get_latest_swhid() + print(f"SWHID: {swhid}\n") + + update_citation_cff(swhid) + update_codemeta(swhid) + + print("\nDone. Next steps:") + print(" 1. git diff # review the changes") + print(" 2. git add CITATION.cff codemeta.json && git commit -m 'chore: update SWHID for release'") + print(" 3. Open a PR or push directly to main") + print(f" 4. Update the Helmholtz RSD: " + f"https://helmholtz.software/software/pycmor/edit/software-heritage") + + +if __name__ == "__main__": + main()