Coverage for src/somesy/package_json/writer.py: 93%

72 statements  

« prev     ^ index     » next       coverage.py v7.3.2, created at 2024-04-30 09:42 +0000

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

2import logging 

3from collections import OrderedDict 

4from pathlib import Path 

5from typing import Dict, List, Optional, Union 

6 

7from rich.pretty import pretty_repr 

8 

9from somesy.core.models import Person, ProjectMetadata 

10from somesy.core.writer import FieldKeyMapping, IgnoreKey, ProjectMetadataWriter 

11from somesy.json_wrapper import json 

12from somesy.package_json.models import PackageJsonConfig 

13 

14logger = logging.getLogger("somesy") 

15 

16 

17class PackageJSON(ProjectMetadataWriter): 

18 """package.json parser and saver.""" 

19 

20 def __init__( 

21 self, 

22 path: Path, 

23 ): 

24 """package.json parser. 

25 

26 See [somesy.core.writer.ProjectMetadataWriter.__init__][]. 

27 """ 

28 mappings: FieldKeyMapping = { 

29 "authors": ["author"], 

30 "documentation": IgnoreKey(), 

31 } 

32 super().__init__(path, create_if_not_exists=False, direct_mappings=mappings) 

33 

34 @property 

35 def authors(self): 

36 """Return the only author of the package.json file as list.""" 

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

38 

39 @authors.setter 

40 def authors(self, authors: List[Person]) -> None: 

41 """Set the authors of the project.""" 

42 authors = self._from_person(authors[0]) 

43 self._set_property(self._get_key("authors"), authors) 

44 

45 @property 

46 def contributors(self): 

47 """Return the contributors of the package.json file.""" 

48 return self._get_property(self._get_key("contributors")) 

49 

50 @contributors.setter 

51 def contributors(self, contributors: List[Person]) -> None: 

52 """Set the contributors of the project.""" 

53 contributors = [self._from_person(c) for c in contributors] 

54 self._set_property(self._get_key("contributors"), contributors) 

55 

56 def _load(self) -> None: 

57 """Load package.json file.""" 

58 with self.path.open() as f: 

59 self._data = json.load(f, object_pairs_hook=OrderedDict) 

60 

61 def _validate(self) -> None: 

62 """Validate package.json content using pydantic class.""" 

63 config = dict(self._get_property([])) 

64 logger.debug( 

65 f"Validating config using {PackageJsonConfig.__name__}: {pretty_repr(config)}" 

66 ) 

67 PackageJsonConfig(**config) 

68 

69 def save(self, path: Optional[Path] = None) -> None: 

70 """Save the package.json file.""" 

71 path = path or self.path 

72 logger.debug(f"Saving package.json to {path}") 

73 

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

75 # package.json indentation is 2 spaces 

76 json.dump(self._data, f) 

77 

78 @staticmethod 

79 def _from_person(person: Person): 

80 """Convert project metadata person object to package.json dict for person format.""" 

81 person_dict = {"name": person.full_name} 

82 if person.email: 

83 person_dict["email"] = person.email 

84 if person.orcid: 

85 person_dict["url"] = str(person.orcid) 

86 return person_dict 

87 

88 @staticmethod 

89 def _to_person(person) -> Person: 

90 """Convert package.json dict or str for person format to project metadata person object.""" 

91 if isinstance(person, str): 

92 # parse from package.json format 

93 person = PackageJsonConfig.convert_author(person).model_dump( 

94 exclude_none=True 

95 ) 

96 

97 names = list(map(lambda s: s.strip(), person["name"].split())) 

98 person_obj = { 

99 "given-names": " ".join(names[:-1]), 

100 "family-names": names[-1], 

101 } 

102 if "email" in person: 

103 person_obj["email"] = person["email"].strip() 

104 if "url" in person: 

105 person_obj["orcid"] = person["url"].strip() 

106 return Person(**person_obj) 

107 

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

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

110 

111 Use existing sync function from ProjectMetadataWriter but update repository and contributors. 

112 """ 

113 super().sync(metadata) 

114 self.contributors = self._sync_person_list(self.contributors, metadata.people) 

115 

116 @property 

117 def repository(self) -> Optional[Union[str, Dict]]: 

118 """Return the repository url of the project.""" 

119 if repo := super().repository: 

120 if isinstance(repo, str): 

121 return repo 

122 else: 

123 return repo.get("url") 

124 else: 

125 return None 

126 

127 @repository.setter 

128 def repository(self, value: Optional[Union[str, Dict]]) -> None: 

129 """Set the repository url of the project.""" 

130 self._set_property(self._get_key("repository"), dict(type="git", url=value))