Skip to content

Commit 3a380e0

Browse files
committed
Create pydantic models
1 parent 9dd6918 commit 3a380e0

File tree

4 files changed

+639
-10
lines changed

4 files changed

+639
-10
lines changed

iib/common/pydantic_models.py

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

0 commit comments

Comments
 (0)