From 09a7b80f286fbbc79a71feb898a4fd5950258db5 Mon Sep 17 00:00:00 2001 From: cvanelteren Date: Tue, 14 Jul 2026 13:39:15 +1000 Subject: [PATCH] Give Zenodo native metadata via .zenodo.json instead of the lossy CFF conversion The Zenodo integration was not merely disabled: the repository is enabled on Zenodo and the `release` webhook has been live since 2026-06-18, yet v2.4.0 (published 2026-07-04, a real non-draft release) shows as **failed** on Zenodo with no error detail, and the archive has been stuck at v1.57.1 since June 2025. That last successful archive is the final release before `CITATION.cff` was added on 2025-06-25, and nothing has archived since -- Zenodo derives its deposit from that file, and the conversion is lossy in at least two ways that can fail validation: CFF is required to spell the license with its SPDX identifier (`MIT`), whereas Zenodo's vocabulary only knows the lowercase `mit` (`GET /api/vocabularies/licenses/mit` -> 200, `.../MIT` -> 404), and the file also carries a `doi` plus a hand-pinned `version` that Zenodo is supposed to decide for itself. Rather than guess which field Zenodo chokes on, ship a `.zenodo.json`, which Zenodo reads in preference to `CITATION.cff` and ignores the CFF entirely: it pins no DOI and no version, so Zenodo mints the DOI and takes the version from the release tag, and it names the license in Zenodo's own vocabulary. `CITATION.cff` stays as the human- and GitHub-facing citation metadata, where SPDX `MIT` is correct. NOTE: This is the leading hypothesis, not a verified fix -- Zenodo reports the failure with no diagnostics, so it must be confirmed against Zenodo Sandbox before the next real release. --- .zenodo.json | 30 ++++++++++++++++++++++++ ultraplot/tests/test_release_metadata.py | 25 ++++++++++++++++---- 2 files changed, 51 insertions(+), 4 deletions(-) create mode 100644 .zenodo.json diff --git a/.zenodo.json b/.zenodo.json new file mode 100644 index 000000000..968c9c3f5 --- /dev/null +++ b/.zenodo.json @@ -0,0 +1,30 @@ +{ + "upload_type": "software", + "title": "UltraPlot", + "description": "A succinct matplotlib wrapper for making beautiful, publication-quality graphics.", + "creators": [ + { + "name": "van Elteren, Casper", + "orcid": "0000-0001-9862-8936" + }, + { + "name": "Becker, Matthew R.", + "orcid": "0000-0001-7774-2246" + } + ], + "license": "mit", + "access_right": "open", + "keywords": [ + "plotting", + "matplotlib", + "scientific visualization", + "wrapper" + ], + "related_identifiers": [ + { + "identifier": "https://github.com/Ultraplot/UltraPlot", + "relation": "isSupplementTo", + "scheme": "url" + } + ] +} diff --git a/ultraplot/tests/test_release_metadata.py b/ultraplot/tests/test_release_metadata.py index 66842ab5e..bf9a2762c 100644 --- a/ultraplot/tests/test_release_metadata.py +++ b/ultraplot/tests/test_release_metadata.py @@ -1,5 +1,6 @@ from __future__ import annotations +import json import re from pathlib import Path @@ -38,12 +39,28 @@ def test_citation_does_not_pin_a_version(): assert "date-released" not in citation -def test_zenodo_json_is_not_committed(): +def test_zenodo_json_is_the_metadata_zenodo_reads(): """ - Zenodo reads .zenodo.json in preference to CITATION.cff, so committing one - would reintroduce a second, competing copy of the release metadata. + Zenodo reads .zenodo.json in preference to CITATION.cff and ignores the CFF + entirely when one exists. We ship it because the CFF -> Zenodo conversion is + lossy: CFF must spell the license with its SPDX id ("MIT"), while Zenodo's + vocabulary only knows the lowercase "mit", and a CFF-derived deposit carried a + 'doi' and a hand-pinned 'version' that Zenodo should be deciding itself. + Releases stopped archiving the day CITATION.cff landed; this file is the + controlled surface that removes the guesswork. """ - assert not (ROOT / ".zenodo.json").exists() + zenodo = json.loads((ROOT / ".zenodo.json").read_text(encoding="utf-8")) + + # Zenodo mints the DOI and takes the version from the release tag. Pinning + # either here is what we are getting away from. + assert "doi" not in zenodo + assert "version" not in zenodo + + # Must match Zenodo's license vocabulary, which is lowercase (GET + # /api/vocabularies/licenses/mit -> 200, .../MIT -> 404). + assert zenodo["license"] == zenodo["license"].lower() + assert zenodo["upload_type"] == "software" + assert zenodo["creators"][0]["orcid"] == "0000-0001-9862-8936" def test_readme_citation_section_uses_repository_metadata():