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
« 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
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
18from somesy.core.models import LicenseEnum
21class PoetryConfig(BaseModel):
22 """Poetry configuration model."""
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")]
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
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
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")
87 class Config:
88 """Pydantic configuration."""
90 use_enum_values = True
93class ContentTypeEnum(Enum):
94 """Content type enum for setuptools field file."""
96 plain = "text/plain"
97 rst = "text/x-rst"
98 markdown = "text/markdown"
101class File(BaseModel):
102 """File model for setuptools."""
104 file: Path
105 content_type: Optional[ContentTypeEnum] = Field(alias="content-type")
108class License(BaseModel):
109 """License model for setuptools."""
111 file: Optional[Path]
112 text: Optional[LicenseEnum]
114 class Config:
115 """Pydantic configuration."""
117 validate_assignment = True
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
127class STPerson(BaseModel):
128 """Person model for setuptools."""
130 name: Annotated[str, Field(min_length=1)]
131 email: Annotated[str, Field(min_length=1)]
134class URLs(BaseModel):
135 """URLs model for setuptools."""
137 homepage: Optional[HttpUrl] = None
138 repository: Optional[HttpUrl] = None
139 documentation: Optional[HttpUrl] = None
140 changelog: Optional[HttpUrl] = None
143class SetuptoolsConfig(BaseModel):
144 """Setuptools input model. Required fields are name, version, description, and requires_python."""
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]
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
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")
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
192 class Config:
193 """Pydantic configuration."""
195 use_enum_values = True