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:50 +0000

1"""package.json parser and saver.""" 

2 

3import logging 

4from collections import OrderedDict 

5from pathlib import Path 

6from typing import Dict, List, Optional, Union 

7 

8from rich.pretty import pretty_repr 

9 

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 

14 

15logger = logging.getLogger("somesy") 

16 

17 

18class PackageJSON(ProjectMetadataWriter): 

19 """package.json parser and saver.""" 

20 

21 def __init__( 

22 self, 

23 path: Path, 

24 ): 

25 """package.json parser. 

26 

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) 

34 

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 [] 

43 

44 return [self._get_property(self._get_key("authors"))] 

45 

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) 

51 

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 [] 

60 

61 maintainers_valid = [] 

62 

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 

70 

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) 

76 

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 [] 

85 

86 contributors_valid = [] 

87 

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 

95 

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) 

101 

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) 

106 

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) 

114 

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}") 

119 

120 with path.open("w") as f: 

121 # package.json indentation is 2 spaces 

122 json.dump(self._data, f) 

123 

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 

133 

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) 

140 

141 if person is None: 

142 return None 

143 

144 person = person.model_dump(exclude_none=True) 

145 

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) 

156 

157 def sync(self, metadata: ProjectMetadata) -> None: 

158 """Sync package.json with project metadata. 

159 

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) 

164 

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 

175 

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))