Coverage for src/somesy/package_json/writer.py: 92%
106 statements
« prev ^ index » next coverage.py v7.6.0, created at 2024-07-29 07:42 +0000
« prev ^ index » next coverage.py v7.6.0, created at 2024-07-29 07:42 +0000
1"""package.json parser and saver."""
3import logging
4from collections import OrderedDict
5from pathlib import Path
6from typing import Dict, List, Optional, Union
8from rich.pretty import pretty_repr
10from somesy.core.models import Person, ProjectMetadata
11from somesy.core.writer import FieldKeyMapping, IgnoreKey, ProjectMetadataWriter
12from somesy.json_wrapper import json
13from somesy.package_json.models import PackageJsonConfig
15logger = logging.getLogger("somesy")
18class PackageJSON(ProjectMetadataWriter):
19 """package.json parser and saver."""
21 def __init__(
22 self,
23 path: Path,
24 ):
25 """package.json parser.
27 See [somesy.core.writer.ProjectMetadataWriter.__init__][].
28 """
29 mappings: FieldKeyMapping = {
30 "authors": ["author"],
31 "documentation": IgnoreKey(),
32 }
33 super().__init__(path, create_if_not_exists=False, direct_mappings=mappings)
35 @property
36 def authors(self):
37 """Return the only author of the package.json file as list."""
38 # check if the author has the correct format
39 if isinstance(author := self._get_property(self._get_key("authors")), str):
40 author = PackageJsonConfig.convert_author(author)
41 if author is None:
42 return []
44 return [self._get_property(self._get_key("authors"))]
46 @authors.setter
47 def authors(self, authors: List[Person]) -> None:
48 """Set the authors of the project."""
49 authors = self._from_person(authors[0])
50 self._set_property(self._get_key("authors"), authors)
52 @property
53 def maintainers(self):
54 """Return the maintainers of the package.json file."""
55 # check if the maintainer has the correct format
56 maintainers = self._get_property(self._get_key("maintainers"))
57 # return empty list if maintainers is None
58 if maintainers is None:
59 return []
61 maintainers_valid = []
63 for maintainer in maintainers:
64 if isinstance(maintainer, str):
65 maintainer = PackageJsonConfig.convert_author(maintainer)
66 if maintainer is None:
67 continue
68 maintainers_valid.append(maintainer)
69 return maintainers_valid
71 @maintainers.setter
72 def maintainers(self, maintainers: List[Person]) -> None:
73 """Set the maintainers of the project."""
74 maintainers = [self._from_person(m) for m in maintainers]
75 self._set_property(self._get_key("maintainers"), maintainers)
77 @property
78 def contributors(self):
79 """Return the contributors of the package.json file."""
80 # check if the contributor has the correct format
81 contributors = self._get_property(self._get_key("contributors"))
82 # return empty list if contributors is None
83 if contributors is None:
84 return []
86 contributors_valid = []
88 for contributor in contributors:
89 if isinstance(contributor, str):
90 contributor = PackageJsonConfig.convert_author(contributor)
91 if contributor is None:
92 continue
93 contributors_valid.append(contributor)
94 return contributors_valid
96 @contributors.setter
97 def contributors(self, contributors: List[Person]) -> None:
98 """Set the contributors of the project."""
99 contributors = [self._from_person(c) for c in contributors]
100 self._set_property(self._get_key("contributors"), contributors)
102 def _load(self) -> None:
103 """Load package.json file."""
104 with self.path.open() as f:
105 self._data = json.load(f, object_pairs_hook=OrderedDict)
107 def _validate(self) -> None:
108 """Validate package.json content using pydantic class."""
109 config = dict(self._get_property([]))
110 logger.debug(
111 f"Validating config using {PackageJsonConfig.__name__}: {pretty_repr(config)}"
112 )
113 PackageJsonConfig(**config)
115 def save(self, path: Optional[Path] = None) -> None:
116 """Save the package.json file."""
117 path = path or self.path
118 logger.debug(f"Saving package.json to {path}")
120 with path.open("w") as f:
121 # package.json indentation is 2 spaces
122 json.dump(self._data, f)
124 @staticmethod
125 def _from_person(person: Person):
126 """Convert project metadata person object to package.json dict for person format."""
127 person_dict = {"name": person.full_name}
128 if person.email:
129 person_dict["email"] = person.email
130 if person.orcid:
131 person_dict["url"] = str(person.orcid)
132 return person_dict
134 @staticmethod
135 def _to_person(person) -> Person:
136 """Convert package.json dict or str for person format to project metadata person object."""
137 if isinstance(person, str):
138 # parse from package.json format
139 person = PackageJsonConfig.convert_author(person)
141 if person is None:
142 return None
144 person = person.model_dump(exclude_none=True)
146 names = list(map(lambda s: s.strip(), person["name"].split()))
147 person_obj = {
148 "given-names": " ".join(names[:-1]),
149 "family-names": names[-1],
150 }
151 if "email" in person:
152 person_obj["email"] = person["email"].strip()
153 if "url" in person:
154 person_obj["orcid"] = person["url"].strip()
155 return Person(**person_obj)
157 def sync(self, metadata: ProjectMetadata) -> None:
158 """Sync package.json with project metadata.
160 Use existing sync function from ProjectMetadataWriter but update repository and contributors.
161 """
162 super().sync(metadata)
163 self.contributors = self._sync_person_list(self.contributors, metadata.people)
165 @property
166 def repository(self) -> Optional[Union[str, Dict]]:
167 """Return the repository url of the project."""
168 if repo := super().repository:
169 if isinstance(repo, str):
170 return repo
171 else:
172 return repo.get("url")
173 else:
174 return None
176 @repository.setter
177 def repository(self, value: Optional[Union[str, Dict]]) -> None:
178 """Set the repository url of the project."""
179 self._set_property(self._get_key("repository"), dict(type="git", url=value))