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