-
Notifications
You must be signed in to change notification settings - Fork 35
/
base.py
73 lines (55 loc) · 2.71 KB
/
base.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
"""pydantic BaseModel for GeoJSON objects."""
from __future__ import annotations
from typing import Any, Dict, List, Optional, Set
from pydantic import BaseModel, SerializationInfo, field_validator, model_serializer
from geojson_pydantic.types import BBox
class _GeoJsonBase(BaseModel):
bbox: Optional[BBox] = None
# These fields will not be included when serializing in json mode
# `.model_dump_json()` or `.model_dump(mode="json")`
__geojson_exclude_if_none__: Set[str] = {"bbox"}
@property
def __geo_interface__(self) -> Dict[str, Any]:
"""GeoJSON-like protocol for geo-spatial (GIS) vector data.
ref: https://gist.github.com/sgillies/2217756#__geo_interface
"""
return self.model_dump(mode="json")
@field_validator("bbox")
def validate_bbox(cls, bbox: Optional[BBox]) -> Optional[BBox]:
"""Validate BBox values are ordered correctly."""
# If bbox is None, there is nothing to validate.
if bbox is None:
return None
# A list to store any errors found so we can raise them all at once.
errors: List[str] = []
# Determine where the second position starts. 2 for 2D, 3 for 3D.
offset = len(bbox) // 2
# Check X
if bbox[0] > bbox[offset]:
errors.append(f"Min X ({bbox[0]}) must be <= Max X ({bbox[offset]}).")
# Check Y
if bbox[1] > bbox[1 + offset]:
errors.append(f"Min Y ({bbox[1]}) must be <= Max Y ({bbox[1 + offset]}).")
# If 3D, check Z values.
if offset > 2 and bbox[2] > bbox[2 + offset]:
errors.append(f"Min Z ({bbox[2]}) must be <= Max Z ({bbox[2 + offset]}).")
# Raise any errors found.
if errors:
raise ValueError("Invalid BBox. Error(s): " + " ".join(errors))
return bbox
@model_serializer(when_used="always", mode="wrap")
def clean_model(self, serializer: Any, info: SerializationInfo) -> Dict[str, Any]:
"""Custom Model serializer to match the GeoJSON specification.
Used to remove fields which are optional but cannot be null values.
"""
# This seems like the best way to have the least amount of unexpected consequences.
# We want to avoid forcing values in `exclude_none` or `exclude_unset` which could
# cause issues or unexpected behavior for downstream users.
# ref: https://github.com/pydantic/pydantic/issues/6575
data: Dict[str, Any] = serializer(self)
# Only remove fields when in JSON mode.
if info.mode_is_json():
for field in self.__geojson_exclude_if_none__:
if field in data and data[field] is None:
del data[field]
return data