Coverage for changes_metadata_manager / export_zenodo_csv.py: 100%
56 statements
« prev ^ index » next coverage.py v7.12.0, created at 2026-07-17 16:43 +0000
« prev ^ index » next coverage.py v7.12.0, created at 2026-07-17 16:43 +0000
1# SPDX-FileCopyrightText: 2025-2026 Arcangelo Massari <arcangelo.massari@unibo.it>
2#
3# SPDX-License-Identifier: ISC
5import argparse
6import csv
7import json
8import os
9import tempfile
10import time
11from pathlib import Path
13from rich.progress import (
14 BarColumn,
15 MofNCompleteColumn,
16 Progress,
17 SpinnerColumn,
18 TextColumn,
19)
21from changes_metadata_manager.zenodo_api import fetch_latest_published_record
22from changes_metadata_manager.zenodo_metadata import LICENSE_URI_TO_ZENODO
24CSV_FIELDNAMES = [
25 "Numero su DMP",
26 "Caso di studio",
27 "Autore/i",
28 "Tipo",
29 "Titolo",
30 "Data pubblicazione",
31 "DOI",
32 "URL",
33 "Repository",
34 "Licenza",
35 "Note",
36]
37REQUEST_DELAY = 0.5
40def _format_creators(metadata: dict) -> str:
41 creators: list[str] = []
42 for creator in metadata["creators"]:
43 person = creator["person_or_org"]
44 orcid = next(
45 identifier["identifier"]
46 for identifier in person["identifiers"]
47 if identifier["scheme"] == "orcid"
48 )
49 creators.append(
50 f"{person['family_name']}, {person['given_name']} [orcid:{orcid}]"
51 )
52 return "; ".join(creators)
55def _format_licenses(metadata: dict) -> str:
56 licenses: list[str] = []
57 for right in metadata["rights"]:
58 short_name = LICENSE_URI_TO_ZENODO[right["link"]]
59 context = right["title"]["en"].rsplit(" (", maxsplit=1)[1].removesuffix(")")
60 licenses.append(f"{short_name} ({context})")
61 return "; ".join(licenses)
64def _build_row(record: dict) -> dict[str, str]:
65 metadata = record["metadata"]
66 return {
67 "Numero su DMP": "",
68 "Caso di studio": "Aldrovandi",
69 "Autore/i": _format_creators(metadata),
70 "Tipo": metadata["resource_type"]["title"]["en"],
71 "Titolo": metadata["title"],
72 "Data pubblicazione": metadata["publication_date"],
73 "DOI": record["pids"]["doi"]["identifier"],
74 "URL": record["links"]["self_html"],
75 "Repository": metadata["publisher"],
76 "Licenza": _format_licenses(metadata),
77 "Note": "",
78 }
81def _write_csv(path: Path, rows: list[dict[str, str]]) -> None:
82 descriptor, temporary_path = tempfile.mkstemp(dir=path.parent, suffix=".tmp")
83 with os.fdopen(descriptor, "w", newline="") as file:
84 writer = csv.DictWriter(file, fieldnames=CSV_FIELDNAMES)
85 writer.writeheader()
86 writer.writerows(rows)
87 os.replace(temporary_path, path)
90def export_zenodo_csv(drafts_path: Path, output_path: Path | None = None) -> Path:
91 with open(drafts_path) as file:
92 drafts = json.load(file)
94 records = [draft for draft in drafts if draft["draft_id"]]
95 rows: list[dict[str, str]] = []
96 with Progress(
97 SpinnerColumn(),
98 TextColumn("[progress.description]{task.description}"),
99 BarColumn(),
100 MofNCompleteColumn(),
101 ) as progress:
102 task = progress.add_task("Fetching published records", total=len(records))
103 for draft in records:
104 record_id = str(draft["draft_id"])
105 progress.update(task, description=f"Fetching record {record_id}")
106 record = fetch_latest_published_record(
107 draft["zenodo_url"].rstrip("/"),
108 record_id,
109 draft["access_token"],
110 draft["user_agent"],
111 )
112 rows.append(_build_row(record))
113 progress.advance(task)
114 time.sleep(REQUEST_DELAY)
116 if output_path is None:
117 destination = drafts_path.parent / "doi_table.csv"
118 else:
119 destination = output_path
120 _write_csv(destination, rows)
121 print(f"DOI table written to {destination}")
122 return destination
125def main() -> None: # pragma: no cover
126 parser = argparse.ArgumentParser(
127 description="Export published Zenodo records to the DMP CSV format"
128 )
129 parser.add_argument("drafts_json", type=Path, help="Path to drafts.json")
130 parser.add_argument("--output", type=Path, help="Output CSV path")
131 args = parser.parse_args()
132 export_zenodo_csv(args.drafts_json, args.output)
135if __name__ == "__main__": # pragma: no cover
136 main()