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