Coverage for src/somesy/pyproject/models.py: 84%

105 statements  

« prev     ^ index     » next       coverage.py v7.2.7, created at 2023-08-10 14:33 +0000

1"""Pyproject models.""" 

2from enum import Enum 

3from pathlib import Path 

4from typing import Dict, List, Optional, Set, Union 

5 

6from packaging.version import parse as parse_version 

7from pydantic import ( 

8 BaseModel, 

9 EmailStr, 

10 Field, 

11 HttpUrl, 

12 ValidationError, 

13 root_validator, 

14 validator, 

15) 

16from typing_extensions import Annotated 

17 

18from somesy.core.models import LicenseEnum 

19 

20 

21class PoetryConfig(BaseModel): 

22 """Poetry configuration model.""" 

23 

24 name: Annotated[ 

25 str, 

26 Field(regex=r"^[A-Za-z0-9]+([_-][A-Za-z0-9]+)*$", description="Package name"), 

27 ] 

28 version: Annotated[ 

29 str, 

30 Field( 

31 regex=r"^\d+(\.\d+)*((a|b|rc)\d+)?(post\d+)?(dev\d+)?$", 

32 description="Package version", 

33 ), 

34 ] 

35 description: Annotated[str, Field(description="Package description")] 

36 license: Annotated[ 

37 Optional[Union[LicenseEnum, List[LicenseEnum]]], 

38 Field(description="An SPDX license identifier."), 

39 ] 

40 authors: Annotated[Set[str], Field(description="Package authors")] 

41 maintainers: Annotated[Optional[Set[str]], Field(description="Package maintainers")] 

42 readme: Annotated[ 

43 Optional[Union[Path, List[Path]]], Field(description="Package readme file(s)") 

44 ] 

45 homepage: Annotated[Optional[HttpUrl], Field(description="Package homepage")] 

46 repository: Annotated[Optional[HttpUrl], Field(description="Package repository")] 

47 documentation: Annotated[ 

48 Optional[HttpUrl], Field(description="Package documentation page") 

49 ] 

50 keywords: Annotated[ 

51 Optional[Set[str]], Field(description="Keywords that describe the package") 

52 ] 

53 classifiers: Annotated[Optional[List[str]], Field(description="pypi classifiers")] 

54 urls: Annotated[Optional[Dict[str, HttpUrl]], Field(description="Package URLs")] 

55 

56 @validator("version") 

57 def validate_version(cls, v): 

58 """Validate version using PEP 440.""" 

59 try: 

60 _ = parse_version(v) 

61 except ValueError: 

62 raise ValidationError("Invalid version") 

63 return v 

64 

65 @validator("authors", "maintainers") 

66 def validate_email_format(cls, v): 

67 """Validate email format.""" 

68 for author in v: 

69 if ( 

70 not isinstance(author, str) 

71 or " " not in author 

72 or not EmailStr.validate(author.split(" ")[-1][1:-1]) 

73 ): 

74 raise ValidationError("Invalid email format") 

75 return v 

76 

77 @validator("readme") 

78 def validate_readme(cls, v): 

79 """Validate readme file(s) by checking whether files exist.""" 

80 if type(v) is list: 

81 if any(not e.is_file() for e in v): 

82 raise ValidationError("Some file(s) do not exist") 

83 else: 

84 if not v.is_file(): 

85 raise ValidationError("File does not exist") 

86 

87 class Config: 

88 """Pydantic configuration.""" 

89 

90 use_enum_values = True 

91 

92 

93class ContentTypeEnum(Enum): 

94 """Content type enum for setuptools field file.""" 

95 

96 plain = "text/plain" 

97 rst = "text/x-rst" 

98 markdown = "text/markdown" 

99 

100 

101class File(BaseModel): 

102 """File model for setuptools.""" 

103 

104 file: Path 

105 content_type: Optional[ContentTypeEnum] = Field(alias="content-type") 

106 

107 

108class License(BaseModel): 

109 """License model for setuptools.""" 

110 

111 file: Optional[Path] 

112 text: Optional[LicenseEnum] 

113 

114 class Config: 

115 """Pydantic configuration.""" 

116 

117 validate_assignment = True 

118 

119 @root_validator(pre=True) 

120 def validate_xor(cls, values): 

121 """Validate that only one of file or text is set.""" 

122 if sum([bool(v) for v in values.values()]) != 1: 

123 raise ValueError("Either file or text must be set.") 

124 return values 

125 

126 

127class STPerson(BaseModel): 

128 """Person model for setuptools.""" 

129 

130 name: Annotated[str, Field(min_length=1)] 

131 email: Annotated[str, Field(min_length=1)] 

132 

133 

134class URLs(BaseModel): 

135 """URLs model for setuptools.""" 

136 

137 homepage: Optional[HttpUrl] = None 

138 repository: Optional[HttpUrl] = None 

139 documentation: Optional[HttpUrl] = None 

140 changelog: Optional[HttpUrl] = None 

141 

142 

143class SetuptoolsConfig(BaseModel): 

144 """Setuptools input model. Required fields are name, version, description, and requires_python.""" 

145 

146 name: Annotated[str, Field(regex=r"^[A-Za-z0-9]+([_-][A-Za-z0-9]+)*$")] 

147 version: Annotated[ 

148 str, Field(regex=r"^\d+(\.\d+)*((a|b|rc)\d+)?(post\d+)?(dev\d+)?$") 

149 ] 

150 description: str 

151 readme: Optional[Union[Path, List[Path], File]] = None 

152 license: Optional[Union[LicenseEnum, List[LicenseEnum]]] = Field( 

153 None, description="An SPDX license identifier." 

154 ) 

155 authors: Optional[List[STPerson]] 

156 maintainers: Optional[List[STPerson]] 

157 keywords: Optional[Set[str]] 

158 classifiers: Optional[List[str]] 

159 urls: Optional[URLs] 

160 

161 @validator("version") 

162 def validate_version(cls, v): 

163 """Validate version using PEP 440.""" 

164 try: 

165 _ = parse_version(v) 

166 except ValueError: 

167 raise ValidationError("Invalid version") 

168 return v 

169 

170 @validator("readme") 

171 def validate_readme(cls, v): 

172 """Validate readme file(s) by checking whether files exist.""" 

173 if type(v) is list: 

174 if any(not e.is_file() for e in v): 

175 raise ValidationError("Some file(s) do not exist") 

176 elif type(v) is File: 

177 if not Path(v.file).is_file(): 

178 raise ValidationError("File does not exist") 

179 else: 

180 if not v.is_file(): 

181 raise ValidationError("File does not exist") 

182 

183 @validator("authors", "maintainers") 

184 def validate_email_format(cls, v): 

185 """Validate email format.""" 

186 for person in v: 

187 if person.email: 

188 if not EmailStr.validate(person.email): 

189 raise ValidationError("Invalid email format") 

190 return v 

191 

192 class Config: 

193 """Pydantic configuration.""" 

194 

195 use_enum_values = True