Coverage for src/somesy/rust/models.py: 85%
62 statements
« prev ^ index » next coverage.py v7.3.2, created at 2024-04-30 09:42 +0000
« prev ^ index » next coverage.py v7.3.2, created at 2024-04-30 09:42 +0000
1"""Pyproject models."""
2import re
3from pathlib import Path
4from typing import Dict, List, Optional, Set, Union
6from packaging.version import parse as parse_version
7from pydantic import BaseModel, Field, field_validator, model_validator
8from typing_extensions import Annotated
10from somesy.core.types import HttpUrlStr
13class RustConfig(BaseModel):
14 """Rust configuration model."""
16 model_config = dict(use_enum_values=True)
18 name: Annotated[
19 str,
20 Field(
21 pattern=r"^[A-Za-z0-9]+([_-][A-Za-z0-9]+)*$",
22 max_length=64,
23 description="Package name",
24 ),
25 ]
26 version: Annotated[
27 str,
28 Field(
29 pattern=r"^\d+(\.\d+)*((a|b|rc)\d+)?(post\d+)?(dev\d+)?$",
30 description="Package version",
31 ),
32 ]
33 description: Annotated[
34 Optional[str], Field(description="Package description")
35 ] = None
36 license: Annotated[
37 Optional[str],
38 Field(
39 description="A combination SPDX license identifiers with AND, OR and so on."
40 ),
41 ] = None
42 authors: Annotated[Set[str], Field(description="Package authors")]
43 maintainers: Annotated[
44 Optional[Set[str]], Field(description="Package maintainers")
45 ] = None
46 readme: Annotated[
47 Optional[Union[Path, List[Path]]], Field(description="Package readme file(s)")
48 ] = None
49 license_file: Annotated[
50 Optional[Path], Field(description="Package license file")
51 ] = None
52 homepage: Annotated[
53 Optional[HttpUrlStr], Field(description="Package homepage")
54 ] = None
55 repository: Annotated[
56 Optional[HttpUrlStr], Field(description="Package repository")
57 ] = None
58 documentation: Annotated[
59 Optional[HttpUrlStr], Field(description="Package documentation page")
60 ] = None
61 keywords: Annotated[
62 Optional[Set[str]], Field(description="Keywords that describe the package")
63 ] = None
64 classifiers: Annotated[
65 Optional[List[str]], Field(description="pypi classifiers")
66 ] = None
67 urls: Annotated[
68 Optional[Dict[str, HttpUrlStr]], Field(description="Package URLs")
69 ] = None
71 @model_validator(mode="before")
72 @classmethod
73 def license_or_file(cls, values):
74 """License and license file are mutually exclusive."""
75 if "license" in values and "license_file" in values:
76 raise ValueError("license and license_file are mutually exclusive")
77 return values
79 @field_validator("version")
80 @classmethod
81 def validate_version(cls, v):
82 """Validate version using PEP 440."""
83 try:
84 _ = parse_version(v)
85 except ValueError as err:
86 raise ValueError("Invalid version") from err
87 return v
89 @field_validator("readme", "license_file")
90 @classmethod
91 def validate_readme(cls, v):
92 """Validate readme file(s) by checking whether files exist."""
93 if isinstance(v, list):
94 if any(not e.is_file() for e in v):
95 raise ValueError("Some file(s) do not exist")
96 else:
97 if not v.is_file():
98 raise ValueError("File does not exist")
100 @field_validator("keywords")
101 @classmethod
102 def check_keywords_field(cls, v):
103 """Check the keywords field."""
104 if v is None:
105 return v
107 # Check if number of keywords is at most 5
108 if v is not None and len(v) > 5:
109 raise ValueError("A maximum of 5 keywords is allowed")
111 for keyword in v:
112 check_keyword(keyword)
114 return v
117def check_keyword(keyword: str):
118 """Check if keyword is valid."""
119 # Check if keyword is ASCII and has at most 20 characters
120 if not keyword.isascii() or len(keyword) > 20:
121 raise ValueError(
122 "Each keyword must be ASCII text and have at most 20 characters"
123 )
125 # Check if keyword starts with an alphanumeric character
126 if not re.match(r"^[a-zA-Z0-9]", keyword):
127 raise ValueError("Each keyword must start with an alphanumeric character")
129 # Check if keyword contains only allowed characters
130 if not re.match(r"^[a-zA-Z0-9_\-+]+$", keyword):
131 raise ValueError("Keywords can only contain letters, numbers, _, -, or +")