Coverage for src/somesy/codemeta/writer.py: 92%
85 statements
« prev ^ index » next coverage.py v7.6.0, created at 2024-07-29 07:50 +0000
« prev ^ index » next coverage.py v7.6.0, created at 2024-07-29 07:50 +0000
1"""codemeta.json creation module."""
3import logging
4from collections import OrderedDict
5from pathlib import Path
6from typing import Any, List, Optional
8from rich.pretty import pretty_repr
10from somesy.core.models import Person, ProjectMetadata
11from somesy.core.writer import FieldKeyMapping, ProjectMetadataWriter
12from somesy.json_wrapper import json
14logger = logging.getLogger("somesy")
17class CodeMeta(ProjectMetadataWriter):
18 """Codemeta.json parser and saver."""
20 def __init__(
21 self,
22 path: Path,
23 ):
24 """Codemeta.json parser.
26 See [somesy.core.writer.ProjectMetadataWriter.__init__][].
27 """
28 mappings: FieldKeyMapping = {
29 "repository": ["codeRepository"],
30 "homepage": ["softwareHelp"],
31 "documentation": ["buildInstructions"],
32 "keywords": ["keywords"],
33 "authors": ["author"],
34 "maintainers": ["maintainer"],
35 "contributors": ["contributor"],
36 }
37 # delete the file if it exists
38 if path.is_file():
39 logger.verbose("Deleting existing codemeta.json file.")
40 path.unlink()
41 super().__init__(path, create_if_not_exists=True, direct_mappings=mappings)
43 @property
44 def authors(self):
45 """Return the only author of the codemeta.json file as list."""
46 return self._get_property(self._get_key("publication_authors")) or []
48 @authors.setter
49 def authors(self, authors: List[Person]) -> None:
50 """Set the authors of the project."""
51 authors = [self._from_person(a) for a in authors]
52 self._set_property(self._get_key("authors"), authors)
54 @property
55 def contributors(self):
56 """Return the contributors of the codemeta.json file."""
57 return self._get_property(self._get_key("contributors"))
59 @contributors.setter
60 def contributors(self, contributors: List[Person]) -> None:
61 """Set the contributors of the project."""
62 contributors = [self._from_person(c) for c in contributors]
63 self._set_property(self._get_key("contributors"), contributors)
65 def _load(self) -> None:
66 """Load codemeta.json file."""
67 with self.path.open() as f:
68 self._data = json.load(f, object_pairs_hook=OrderedDict)
70 def _validate(self) -> None:
71 """Validate codemeta.json content using pydantic class."""
72 config = dict(self._get_property([]))
74 logger.debug(
75 f"No validation for codemeta.json files {CodeMeta.__name__}: {pretty_repr(config)}"
76 )
78 def _init_new_file(self) -> None:
79 data = {
80 "@context": [
81 "https://doi.org/10.5063/schema/codemeta-2.0",
82 "https://w3id.org/software-iodata",
83 "https://raw.githubusercontent.com/jantman/repostatus.org/master/badges/latest/ontology.jsonld",
84 "https://schema.org",
85 "https://w3id.org/software-types",
86 ],
87 "@type": "SoftwareSourceCode",
88 "author": [],
89 }
90 # dump to file
91 with self.path.open("w+") as f:
92 json.dump(data, f)
94 def save(self, path: Optional[Path] = None) -> None:
95 """Save the codemeta.json file."""
96 path = path or self.path
97 logger.debug(f"Saving codemeta.json to {path}")
99 # copy the _data
100 data = self._data.copy()
102 # set license
103 if "license" in data:
104 data["license"] = (f"https://spdx.org/licenses/{data['license']}",)
106 # if softwareHelp is set, set url to softwareHelp
107 if "softwareHelp" in data:
108 data["url"] = data["softwareHelp"]
110 with path.open("w") as f:
111 # codemeta.json indentation is 2 spaces
112 json.dump(data, f)
114 @staticmethod
115 def _from_person(person: Person):
116 """Convert project metadata person object to codemeta.json dict for person format."""
117 person_dict = {
118 "@type": "Person",
119 }
120 if person.given_names:
121 person_dict["givenName"] = person.given_names
122 if person.family_names:
123 person_dict["familyName"] = person.family_names
124 if person.email:
125 person_dict["email"] = person.email
126 if person.orcid:
127 person_dict["@id"] = str(person.orcid)
128 if person.address:
129 person_dict["address"] = person.address
130 if person.affiliation:
131 person_dict["affiliation"] = person.affiliation
132 return person_dict
134 @staticmethod
135 def _to_person(person) -> Person:
136 """Convert codemeta.json dict or str for person format to project metadata person object."""
137 person_obj = {}
138 if "givenName" in person:
139 person_obj["given_names"] = person["givenName"].strip()
140 if "familyName" in person:
141 person_obj["family_names"] = person["familyName"].strip()
142 if "email" in person:
143 person_obj["email"] = person["email"].strip()
144 if "@id" in person:
145 person_obj["orcid"] = person["@id"].strip()
146 if "address" in person:
147 person_obj["address"] = person["address"].strip()
149 return Person(**person_obj)
151 def _sync_person_list(self, old: List[Any], new: List[Person]) -> List[Any]:
152 """Override the _sync_person_list function from ProjectMetadataWriter.
154 This method wont care about existing persons in codemeta.json file.
156 Args:
157 old (List[Any]): _description_
158 new (List[Person]): _description_
160 Returns:
161 List[Any]: _description_
163 """
164 return new
166 def sync(self, metadata: ProjectMetadata) -> None:
167 """Sync codemeta.json with project metadata.
169 Use existing sync function from ProjectMetadataWriter but update repository and contributors.
170 """
171 super().sync(metadata)
172 self.contributors = self._sync_person_list(
173 self.contributors, metadata.contributors()
174 )