|
1 | 1 | """Pydantic request/response models for API v1 endpoints.""" |
2 | 2 |
|
3 | 3 | from datetime import datetime |
4 | | -from enum import StrEnum |
5 | 4 | from uuid import UUID |
6 | 5 |
|
7 | | -from pydantic import BaseModel, field_validator |
| 6 | +from pydantic import BaseModel |
8 | 7 |
|
9 | 8 | from testgen.common.enums import JobSource, JobStatus, PublicJobKey |
| 9 | +from testgen.common.test_definition_export_import_service import ImportConfig, ImportPayload, ImportResponse |
10 | 10 |
|
11 | 11 | # --- Jobs --- |
12 | 12 |
|
@@ -127,183 +127,14 @@ class ErrorResponse(BaseModel): |
127 | 127 | errors: list[ErrorDetail] |
128 | 128 |
|
129 | 129 |
|
130 | | -# --- Test Definition Export/Import --- |
131 | | - |
132 | | - |
133 | | -class Origin(StrEnum): |
134 | | - manual = "manual" |
135 | | - auto = "auto" |
136 | | - both = "both" |
137 | | - |
138 | | - |
139 | | -class ImportMode(StrEnum): |
140 | | - preview = "preview" |
141 | | - apply = "apply" |
142 | | - apply_strict = "apply_strict" |
143 | | - |
144 | | - |
145 | | -class OnMatch(StrEnum): |
146 | | - overwrite_all = "overwrite_all" |
147 | | - overwrite_unlocked = "overwrite_unlocked" |
148 | | - skip = "skip" |
149 | | - |
150 | | - |
151 | | -class OnNew(StrEnum): |
152 | | - skip = "skip" |
153 | | - create = "create" |
154 | | - create_and_lock = "create_and_lock" |
155 | | - |
156 | | - |
157 | | -class OnAbsence(StrEnum): |
158 | | - do_nothing = "do_nothing" |
159 | | - delete_all = "delete_all" |
160 | | - delete_unlocked = "delete_unlocked" |
161 | | - |
162 | | - |
163 | | -class ImportAction(StrEnum): |
164 | | - create = "create" |
165 | | - update = "update" |
166 | | - skip = "skip" |
167 | | - delete = "delete" |
168 | | - |
169 | | - |
170 | | -class ImportReason(StrEnum): |
171 | | - matched = "matched" |
172 | | - no_match = "no_match" |
173 | | - policy = "policy" |
174 | | - locked = "locked" |
175 | | - invalid_test_type = "invalid_test_type" |
176 | | - invalid_table = "invalid_table" |
177 | | - missing_external_id = "missing_external_id" |
178 | | - absent = "absent" |
179 | | - |
180 | | - |
181 | | -# Non-None defaults must match the ORM column defaults in TestDefinition: |
182 | | -# test_active=True (YNString default="Y"), lock_refresh=False (YNString default="N"), |
183 | | -# skip_errors=0 (ZeroIfEmptyInteger), window_days=0 (ZeroIfEmptyInteger), |
184 | | -# history_lookback=0 (Column default=0). |
185 | | -# On export, the model_serializer omits fields matching these defaults to keep the file compact. |
186 | | -# On import, model_fields_set distinguishes explicit from defaulted. |
187 | | -class TestDefinitionExport(BaseModel): |
188 | | - """Test definition fields included in the export/import file.""" |
189 | | - |
190 | | - model_config = {"from_attributes": True} |
191 | | - |
192 | | - # Matching / identity |
193 | | - test_type: str |
194 | | - external_id: UUID | None = None |
195 | | - last_auto_gen_date: datetime | None = None |
196 | | - |
197 | | - # Definition fields |
198 | | - table_name: str | None = None |
199 | | - column_name: str | None = None |
200 | | - test_description: str | None = None |
201 | | - test_active: bool = True |
202 | | - severity: str | None = None |
203 | | - lock_refresh: bool = False |
204 | | - export_to_observability: bool | None = None |
205 | | - skip_errors: int = 0 |
206 | | - |
207 | | - # Calibration fields |
208 | | - baseline_ct: str | None = None |
209 | | - baseline_unique_ct: str | None = None |
210 | | - baseline_value: str | None = None |
211 | | - baseline_value_ct: str | None = None |
212 | | - threshold_value: str | None = None |
213 | | - baseline_sum: str | None = None |
214 | | - baseline_avg: str | None = None |
215 | | - baseline_sd: str | None = None |
216 | | - lower_tolerance: str | None = None |
217 | | - upper_tolerance: str | None = None |
218 | | - |
219 | | - # Subset / grouping |
220 | | - subset_condition: str | None = None |
221 | | - groupby_names: str | None = None |
222 | | - having_condition: str | None = None |
223 | | - window_date_column: str | None = None |
224 | | - window_days: int = 0 |
225 | | - |
226 | | - # Referential |
227 | | - match_schema_name: str | None = None |
228 | | - match_table_name: str | None = None |
229 | | - match_column_names: str | None = None |
230 | | - match_subset_condition: str | None = None |
231 | | - match_groupby_names: str | None = None |
232 | | - match_having_condition: str | None = None |
233 | | - |
234 | | - # Query / history |
235 | | - custom_query: str | None = None |
236 | | - history_calculation: str | None = None |
237 | | - history_calculation_upper: str | None = None |
238 | | - history_lookback: int = 0 |
239 | | - |
240 | | - @field_validator("skip_errors", "window_days", "history_lookback", mode="before") |
241 | | - @classmethod |
242 | | - def _coerce_none_to_zero(cls, v: int | None) -> int: |
243 | | - return v if v is not None else 0 |
244 | | - |
245 | | - |
246 | | -class ExportSource(BaseModel): |
247 | | - project_code: str |
248 | | - test_suite: str |
249 | | - table_group: str |
250 | | - table_group_schema: str |
251 | | - exported_at: datetime |
252 | | - testgen_version: str | None = None |
253 | | - |
254 | | - |
255 | | -class ExportDocument(BaseModel): |
256 | | - version: int = 1 |
257 | | - source: ExportSource |
258 | | - definitions: list[TestDefinitionExport] |
259 | | - |
260 | | - |
261 | | -# --- Import --- |
262 | | - |
263 | | - |
264 | | -class ImportConfig(BaseModel): |
265 | | - mode: ImportMode |
266 | | - on_match: OnMatch |
267 | | - on_new: OnNew |
268 | | - on_absence: OnAbsence |
269 | | - |
270 | | - |
271 | | -class ImportPayload(BaseModel): |
272 | | - """Import payload — same structure as an export document, but definitions are typed.""" |
273 | | - |
274 | | - version: int = 1 |
275 | | - source: ExportSource | None = None |
276 | | - definitions: list[TestDefinitionExport] |
| 130 | +# --- Test Definition Export/Import (wire types) --- |
277 | 131 |
|
278 | 132 |
|
279 | 133 | class ImportRequest(BaseModel): |
280 | 134 | config: ImportConfig |
281 | 135 | payload: ImportPayload |
282 | 136 |
|
283 | 137 |
|
284 | | -class ImportItemTD(BaseModel): |
285 | | - idx: int | None = None |
286 | | - target_id: UUID | None = None |
287 | | - |
288 | | - |
289 | | -class ImportItem(BaseModel): |
290 | | - action: ImportAction |
291 | | - reason: ImportReason |
292 | | - tds: list[ImportItemTD] |
293 | | - |
294 | | - |
295 | | -class ImportSummary(BaseModel): |
296 | | - created: int = 0 |
297 | | - updated: int = 0 |
298 | | - skipped: int = 0 |
299 | | - deleted: int = 0 |
300 | | - |
301 | | - |
302 | | -class ImportResponse(BaseModel): |
303 | | - summary: ImportSummary |
304 | | - items: list[ImportItem] |
305 | | - |
306 | | - |
307 | 138 | class ImportStrictError(ErrorResponse): |
308 | 139 | """400 response for apply_strict when entries would be skipped.""" |
309 | 140 |
|
|
0 commit comments