Skip to content

Commit 2174a3d

Browse files
committed
Create pydantic models
1 parent 3c260b5 commit 2174a3d

File tree

4 files changed

+630
-9
lines changed

4 files changed

+630
-9
lines changed

iib/common/pydantic_models.py

Lines changed: 356 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,356 @@
1+
from typing import Any, Dict, List, Optional, Union
2+
from typing_extensions import Annotated
3+
4+
from pydantic import (
5+
AfterValidator,
6+
BaseModel,
7+
BeforeValidator,
8+
model_validator,
9+
SecretStr,
10+
)
11+
12+
from iib.exceptions import ValidationError
13+
from iib.common.pydantic_utils import (
14+
DISTRIBUTION_SCOPE_LITERAL,
15+
GRAPH_MODE_LITERAL,
16+
binary_image_check,
17+
distribution_scope_lower,
18+
get_unique_bundles,
19+
get_unique_deprecation_list_items,
20+
image_format_check,
21+
images_format_check,
22+
length_validator,
23+
from_index_add_arches,
24+
validate_graph_mode_index_image,
25+
validate_overwrite_params,
26+
)
27+
28+
UnionPydanticRequestType = Union[
29+
'AddPydanticModel',
30+
'CreateEmptyIndexPydanticModel',
31+
'FbcOperationsPydanticModel',
32+
'MergeIndexImagePydanticModel',
33+
'RecursiveRelatedBundlesPydanticModel',
34+
'RegenerateBundlePydanticModel',
35+
'RmPydanticModel',
36+
]
37+
38+
39+
class PydanticModel(BaseModel):
40+
41+
@classmethod
42+
def _get_all_keys_to_check_in_db(cls):
43+
raise NotImplementedError("Not implemented")
44+
45+
def get_keys_to_check_in_db(self):
46+
"""Filter keys, which need to be checked in db. Return only a keys that are set to values."""
47+
return [
48+
k for k in self._get_all_keys_to_check_in_db() if getattr(self, k, None)
49+
]
50+
51+
52+
class AddPydanticModel(PydanticModel):
53+
"""Datastructure of the request to /builds/add API point."""
54+
55+
add_arches: Optional[List[str]] = None
56+
binary_image: Annotated[
57+
Optional[str],
58+
AfterValidator(length_validator),
59+
AfterValidator(binary_image_check),
60+
] = None
61+
build_tags: Optional[List[str]] = []
62+
bundles: Annotated[
63+
List[str],
64+
AfterValidator(length_validator),
65+
AfterValidator(get_unique_bundles),
66+
AfterValidator(images_format_check),
67+
]
68+
cnr_token: Optional[SecretStr] = None # deprecated
69+
check_related_images: Optional[bool] = False
70+
deprecation_list: Annotated[
71+
Optional[List[str]],
72+
AfterValidator(get_unique_deprecation_list_items),
73+
AfterValidator(images_format_check),
74+
] = [] # deprecated
75+
distribution_scope: Annotated[
76+
Optional[DISTRIBUTION_SCOPE_LITERAL], BeforeValidator(distribution_scope_lower),
77+
] = None
78+
force_backport: Optional[bool] = False # deprecated
79+
from_index: Annotated[str, AfterValidator(image_format_check)]
80+
graph_update_mode: Optional[GRAPH_MODE_LITERAL] = None
81+
organization: Optional[str] = None # deprecated
82+
overwrite_from_index: Optional[bool] = False
83+
overwrite_from_index_token: Optional[SecretStr] = None
84+
85+
_from_index_add_arches_check = model_validator(mode='after')(from_index_add_arches)
86+
87+
# TODO remove this comment -> Validator from RequestIndexImageMixin class
88+
@model_validator(mode='after')
89+
def verify_overwrite_from_index_token(self) -> 'AddPydanticModel':
90+
"""Check the 'overwrite_from_index' parameter in combination with 'overwrite_from_index_token' parameter."""
91+
validate_overwrite_params(self.overwrite_from_index, self.overwrite_from_index_token)
92+
return self
93+
94+
# TODO remove this comment -> Validator from RequestAdd class
95+
@model_validator(mode='after')
96+
def verify_graph_update_mode_with_index_image(self) -> 'AddPydanticModel':
97+
"""Validate graph mode and check if index image is allowed to use different graph mode."""
98+
validate_graph_mode_index_image(self.graph_update_mode, self.from_index)
99+
return self
100+
101+
# TODO remove this comment -> Validator from RequestAdd class
102+
@model_validator(mode='after')
103+
def from_index_needed_if_no_bundles(self) -> 'AddPydanticModel':
104+
"""
105+
Check if no bundles and `from_index is specified
106+
107+
if no bundles and no from index then an empty index will be created which is a no-op
108+
"""
109+
if not (self.bundles or self.from_index):
110+
raise ValidationError('"from_index" must be specified if no bundles are specified')
111+
return self
112+
113+
# TODO remove this comment -> Validator from RequestADD class
114+
@model_validator(mode='after')
115+
def bundles_needed_with_check_related_images(self) -> 'AddPydanticModel':
116+
"""Verify that `check_related_images` is specified when bundles are specified"""
117+
if self.check_related_images and not self.bundles:
118+
raise ValidationError(
119+
'"check_related_images" must be specified only when bundles are specified'
120+
)
121+
return self
122+
123+
def get_json_for_request(self):
124+
"""Return json with the parameters we store in the db."""
125+
return self.model_dump(
126+
exclude=[
127+
"add_arches",
128+
"build_tags",
129+
"cnr_token",
130+
"force_backport",
131+
"overwrite_from_index",
132+
"overwrite_from_index_token",
133+
],
134+
exclude_defaults=True,
135+
)
136+
137+
138+
def _get_all_keys_to_check_in_db(self):
139+
return ["binary_image", "bundles", "deprecation_list", "from_index"]
140+
141+
142+
class RmPydanticModel(PydanticModel):
143+
"""Datastructure of the request to /builds/rm API point."""
144+
145+
add_arches: Optional[List[str]] = None
146+
binary_image: Annotated[
147+
Optional[str],
148+
AfterValidator(binary_image_check),
149+
] = None
150+
build_tags: Optional[List[str]] = []
151+
distribution_scope: Annotated[
152+
Optional[DISTRIBUTION_SCOPE_LITERAL], BeforeValidator(distribution_scope_lower),
153+
] = None
154+
from_index: Annotated[str, AfterValidator(image_format_check)]
155+
operators: Annotated[List[str], AfterValidator(length_validator)]
156+
overwrite_from_index: Optional[bool] = False
157+
overwrite_from_index_token: Optional[SecretStr] = None
158+
159+
_from_index_add_arches_check = model_validator(mode='after')(from_index_add_arches)
160+
161+
@model_validator(mode='after')
162+
def verify_overwrite_from_index_token(self) -> 'RmPydanticModel':
163+
validate_overwrite_params(self.overwrite_from_index, self.overwrite_from_index_token)
164+
return self
165+
166+
def get_json_for_request(self):
167+
"""Return json with the parameters we store in the db."""
168+
return self.model_dump(
169+
exclude=["add_arches", "build_tags", "overwrite_from_index", "overwrite_from_index_token"],
170+
exclude_defaults=True,
171+
)
172+
173+
def _get_all_keys_to_check_in_db(self):
174+
return ["binary_image", "from_index", "operators"]
175+
176+
177+
class AddRmBatchPydanticModel(BaseModel):
178+
annotations: Dict[str, Any]
179+
build_requests: List[Union[AddPydanticModel, RmPydanticModel]]
180+
181+
182+
class RegistryAuth(BaseModel):
183+
auth: SecretStr
184+
185+
186+
class RegistryAuths(BaseModel): # is {"auths":{}} allowed?
187+
auths: Annotated[Dict[SecretStr, RegistryAuth], AfterValidator(length_validator)]
188+
189+
190+
class RegenerateBundlePydanticModel(PydanticModel):
191+
"""Datastructure of the request to /builds/regenerate-bundle API point."""
192+
193+
# BUNDLE_IMAGE, from_bundle_image_resolved, build_tags?
194+
bundle_replacements: Optional[Dict[str, str]] = {}
195+
from_bundle_image: Annotated[str, AfterValidator(image_format_check)]
196+
organization: Optional[str] = None
197+
registry_auths: Optional[RegistryAuths] = None # not in db
198+
199+
def get_json_for_request(self):
200+
"""Return json with the parameters we store in the db."""
201+
return self.model_dump(
202+
exclude=["registry_auths"],
203+
exclude_defaults=True,
204+
)
205+
206+
def _get_all_keys_to_check_in_db(self):
207+
return ["from_bundle_image"]
208+
209+
210+
class RegenerateBundleBatchPydanticModel(BaseModel):
211+
build_requests: List[RegenerateBundlePydanticModel]
212+
annotations: Dict[str, Any]
213+
214+
215+
class MergeIndexImagePydanticModel(PydanticModel):
216+
"""Datastructure of the request to /builds/regenerate-bundle API point."""
217+
218+
binary_image: Annotated[
219+
Optional[str],
220+
AfterValidator(image_format_check),
221+
AfterValidator(binary_image_check),
222+
] = None
223+
build_tags: Optional[List[str]] = []
224+
deprecation_list: Annotated[
225+
Optional[List[str]],
226+
AfterValidator(get_unique_deprecation_list_items),
227+
AfterValidator(images_format_check),
228+
] = []
229+
distribution_scope: Annotated[
230+
Optional[DISTRIBUTION_SCOPE_LITERAL], BeforeValidator(distribution_scope_lower),
231+
] = None
232+
graph_update_mode: Optional[GRAPH_MODE_LITERAL] = None
233+
overwrite_target_index: Optional[bool] = False # Why do we need this bool? Isn't the token enough?
234+
overwrite_target_index_token: Optional[SecretStr] = None
235+
source_from_index: Annotated[str, AfterValidator(image_format_check)]
236+
target_index: Annotated[Optional[str], AfterValidator(image_format_check)] = None
237+
batch: Optional[str] = None # TODO Not sure with presence
238+
user: Optional[str] = None # TODO Not sure with presence
239+
240+
@model_validator(mode='after')
241+
def verify_graph_update_mode_with_target_index(self) -> 'MergeIndexImagePydanticModel':
242+
validate_graph_mode_index_image(self.graph_update_mode, self.target_index)
243+
return self
244+
245+
@model_validator(mode='after')
246+
def verify_overwrite_from_index_token(self) -> 'MergeIndexImagePydanticModel':
247+
validate_overwrite_params(
248+
self.overwrite_target_index,
249+
self.overwrite_target_index_token,
250+
disable_auth_check=True,
251+
)
252+
return self
253+
254+
def get_json_for_request(self):
255+
"""Return json with the parameters we store in the db."""
256+
return self.model_dump(
257+
exclude=["build_tags", "overwrite_target_index", "overwrite_target_index_token"],
258+
exclude_defaults=True,
259+
)
260+
261+
def _get_all_keys_to_check_in_db(self):
262+
return ["binary_image", "deprecation_list", "source_from_index", "target_index", "target_index"]
263+
264+
265+
class CreateEmptyIndexPydanticModel(PydanticModel):
266+
"""Datastructure of the request to /builds/regenerate-bundle API point."""
267+
268+
binary_image: Annotated[
269+
Optional[str],
270+
AfterValidator(image_format_check),
271+
AfterValidator(binary_image_check),
272+
] = None
273+
from_index: Annotated[
274+
str,
275+
AfterValidator(image_format_check),
276+
AfterValidator(length_validator),
277+
]
278+
labels: Optional[Dict[str, str]] = {}
279+
output_fbc: Optional[bool] = False
280+
281+
def get_json_for_request(self):
282+
"""Return json with the parameters we store in the db."""
283+
return self.model_dump(
284+
exclude_defaults=True,
285+
)
286+
287+
def _get_all_keys_to_check_in_db(self):
288+
return ["binary_image", "from_index"]
289+
290+
291+
class RecursiveRelatedBundlesPydanticModel(PydanticModel):
292+
organization: Optional[str] = None
293+
parent_bundle_image: Annotated[
294+
str,
295+
AfterValidator(image_format_check),
296+
AfterValidator(length_validator),
297+
]
298+
registry_auths: Optional[RegistryAuths] = None # not in db
299+
300+
def get_json_for_request(self):
301+
"""Return json with the parameters we store in the db."""
302+
return self.model_dump(
303+
exclude=["registry_auths"],
304+
exclude_defaults=True,
305+
)
306+
307+
308+
def _get_all_keys_to_check_in_db(self):
309+
return ["parent_bundle_image"]
310+
311+
312+
class FbcOperationsPydanticModel(PydanticModel):
313+
add_arches: Optional[List[str]] = []
314+
binary_image: Annotated[
315+
Optional[str],
316+
AfterValidator(image_format_check),
317+
AfterValidator(binary_image_check),
318+
] = None
319+
bundles: Annotated[
320+
Optional[List[str]],
321+
AfterValidator(length_validator),
322+
AfterValidator(get_unique_bundles),
323+
AfterValidator(images_format_check),
324+
] = []
325+
build_tags: Optional[List[str]] = []
326+
distribution_scope: Annotated[
327+
Optional[DISTRIBUTION_SCOPE_LITERAL], BeforeValidator(distribution_scope_lower),
328+
] = None
329+
fbc_fragment: Annotated[
330+
str,
331+
AfterValidator(image_format_check),
332+
AfterValidator(length_validator),
333+
]
334+
from_index: Annotated[
335+
str,
336+
AfterValidator(image_format_check),
337+
AfterValidator(length_validator),
338+
]
339+
organization: Optional[str] = None
340+
overwrite_from_index: Optional[bool] = False
341+
overwrite_from_index_token: Optional[SecretStr] = None
342+
343+
@model_validator(mode='after')
344+
def verify_overwrite_from_index_token(self) -> 'FbcOperationsPydanticModel':
345+
validate_overwrite_params(self.overwrite_from_index, self.overwrite_from_index_token)
346+
return self
347+
348+
def get_json_for_request(self):
349+
"""Return json with the parameters we store in the db."""
350+
return self.model_dump(
351+
exclude=["add_arches", "build_tags", "overwrite_from_index", "overwrite_from_index_token"],
352+
exclude_defaults=True,
353+
)
354+
355+
def _get_all_keys_to_check_in_db(self):
356+
return ["binary_image", "bundles", "fbc_fragment", "from_index"]

0 commit comments

Comments
 (0)