|
31 | 31 | # |
32 | 32 | # ================================================================= |
33 | 33 |
|
34 | | -from dataclasses import dataclass, field, fields as dc_fields |
| 34 | +from dataclasses import dataclass, field |
35 | 35 | from datetime import datetime |
36 | 36 | from enum import Enum |
37 | 37 | import json |
38 | 38 | from pathlib import Path |
39 | | -from typing import Any, Dict, List, Optional, get_type_hints |
| 39 | +from typing import Any, Dict, List, Optional |
40 | 40 |
|
| 41 | +from pygeoapi.models.validation import validate_type |
41 | 42 | from pygeoapi.util import DEFINITIONSDIR |
42 | 43 |
|
43 | 44 | TMS_DIR = DEFINITIONSDIR / 'tiles' |
44 | 45 |
|
45 | 46 |
|
46 | | -def _validate_type(dc_instance: Any) -> None: |
47 | | - """ |
48 | | - Validate field types on a dataclass instance. |
49 | | -
|
50 | | - Checks each field value against its declared type, |
51 | | - matching dataclass runtime type. |
52 | | - Supports Optional[T], List[T], and plain types. |
53 | | -
|
54 | | - :param dc_instance: dataclass instance to validate |
55 | | -
|
56 | | - :raises ValueError: if a field value has the wrong type |
57 | | - """ |
58 | | - hints = get_type_hints(dc_instance.__class__) |
59 | | - for f in dc_fields(dc_instance): |
60 | | - value = getattr(dc_instance, f.name) |
61 | | - expected = hints[f.name] |
62 | | - |
63 | | - # Extract inner type from Optional[T] |
64 | | - origin = getattr(expected, '__origin__', None) |
65 | | - args = getattr(expected, '__args__', ()) |
66 | | - |
67 | | - is_optional = ( |
68 | | - origin is type(None) # noqa: E721 |
69 | | - or (origin is not None |
70 | | - and type(None) in args) |
71 | | - ) |
72 | | - |
73 | | - if is_optional and value is None: |
74 | | - continue |
75 | | - |
76 | | - # Unwrap Optional to get the inner type |
77 | | - if is_optional and args: |
78 | | - inner_types = [ |
79 | | - a for a in args if a is not type(None) |
80 | | - ] |
81 | | - if len(inner_types) == 1: |
82 | | - expected = inner_types[0] |
83 | | - origin = getattr(expected, '__origin__', None) |
84 | | - args = getattr(expected, '__args__', ()) |
85 | | - |
86 | | - # Check List[T] |
87 | | - if origin is list: |
88 | | - if not isinstance(value, list): |
89 | | - raise ValueError( |
90 | | - f"{f.name} must be a list, " |
91 | | - f"got {type(value).__name__}" |
92 | | - ) |
93 | | - # Check plain types (str, int, float, bool, Enum) |
94 | | - elif origin is None: |
95 | | - if isinstance(expected, type): |
96 | | - # bool is subclass of int, check bool first |
97 | | - if expected is bool: |
98 | | - if not isinstance(value, bool): |
99 | | - raise ValueError( |
100 | | - f"{f.name} must be a bool, " |
101 | | - f"got {type(value).__name__}" |
102 | | - ) |
103 | | - elif expected is int: |
104 | | - if isinstance(value, bool) \ |
105 | | - or not isinstance(value, int): |
106 | | - raise ValueError( |
107 | | - f"{f.name} must be an int, " |
108 | | - f"got {type(value).__name__}" |
109 | | - ) |
110 | | - elif not isinstance(value, expected): |
111 | | - raise ValueError( |
112 | | - f"{f.name} must be a " |
113 | | - f"{expected.__name__}, " |
114 | | - f"got {type(value).__name__}" |
115 | | - ) |
116 | | - |
117 | | - |
118 | 47 | class TilesMetadataFormat(str, Enum): |
119 | 48 | # Tile Set Metadata |
120 | 49 | JSON = "JSON" |
@@ -160,7 +89,7 @@ class TileMatrixSetEnumType: |
160 | 89 | tileMatrices: List[dict] = field(default_factory=list) |
161 | 90 |
|
162 | 91 | def __post_init__(self): |
163 | | - _validate_type(self) |
| 92 | + validate_type(self) |
164 | 93 |
|
165 | 94 | def model_dump( |
166 | 95 | self, exclude_none: bool = False |
@@ -244,7 +173,7 @@ class TileMatrixLimitsType: |
244 | 173 | maxTileCol: int = 0 |
245 | 174 |
|
246 | 175 | def __post_init__(self): |
247 | | - _validate_type(self) |
| 176 | + validate_type(self) |
248 | 177 |
|
249 | 178 | def model_dump( |
250 | 179 | self, exclude_none: bool = False |
@@ -274,7 +203,7 @@ class TwoDBoundingBoxType: |
274 | 203 | crs: Optional[str] = None |
275 | 204 |
|
276 | 205 | def __post_init__(self): |
277 | | - _validate_type(self) |
| 206 | + validate_type(self) |
278 | 207 |
|
279 | 208 | def model_dump( |
280 | 209 | self, exclude_none: bool = False |
@@ -305,7 +234,7 @@ class LinkType: |
305 | 234 | length: Optional[int] = None |
306 | 235 |
|
307 | 236 | def __post_init__(self): |
308 | | - _validate_type(self) |
| 237 | + validate_type(self) |
309 | 238 |
|
310 | 239 | def model_dump( |
311 | 240 | self, exclude_none: bool = False |
@@ -351,7 +280,7 @@ class GeospatialDataType: |
351 | 280 | propertiesSchema: Optional[dict] = None |
352 | 281 |
|
353 | 282 | def __post_init__(self): |
354 | | - _validate_type(self) |
| 283 | + validate_type(self) |
355 | 284 |
|
356 | 285 | def model_dump( |
357 | 286 | self, exclude_none: bool = False |
@@ -383,7 +312,7 @@ class StyleType: |
383 | 312 | links: Optional[LinkType] = None |
384 | 313 |
|
385 | 314 | def __post_init__(self): |
386 | | - _validate_type(self) |
| 315 | + validate_type(self) |
387 | 316 |
|
388 | 317 | def model_dump( |
389 | 318 | self, exclude_none: bool = False |
@@ -413,7 +342,7 @@ class TilePointType: |
413 | 342 | tileMatrix: str = '' |
414 | 343 |
|
415 | 344 | def __post_init__(self): |
416 | | - _validate_type(self) |
| 345 | + validate_type(self) |
417 | 346 |
|
418 | 347 | def model_dump( |
419 | 348 | self, exclude_none: bool = False |
@@ -463,7 +392,7 @@ class TileSetMetadata: |
463 | 392 | links: Optional[List[LinkType]] = None |
464 | 393 |
|
465 | 394 | def __post_init__(self): |
466 | | - _validate_type(self) |
| 395 | + validate_type(self) |
467 | 396 |
|
468 | 397 | def model_dump( |
469 | 398 | self, exclude_none: bool = False |
|
0 commit comments