Skip to content

Commit 47d4d92

Browse files
committed
update datetime serialization to be compatible with pydantic v2
1 parent 10204b0 commit 47d4d92

File tree

5 files changed

+505
-59
lines changed

5 files changed

+505
-59
lines changed

pyproject.toml

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -151,10 +151,7 @@ addopts = """
151151
--tb=native -vv --doctest-modules --doctest-glob="*.rst"
152152
"""
153153
# https://iscinumpy.gitlab.io/post/bound-version-constraints/#watch-for-warnings
154-
filterwarnings = [
155-
"error",
156-
"ignore::pydantic.warnings.PydanticDeprecatedSince20" # temporarily supress Pydantic deprecation warnings
157-
]
154+
filterwarnings = ["error"]
158155
# Doctest python code in docs, python code in src docstrings, test functions in tests
159156
testpaths = "docs src tests"
160157
# Exclude Alembic migration files from pytest collection (they're not meant to be imported directly)

src/smartem_backend/model/http_request.py

Lines changed: 34 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
from datetime import datetime
22
from typing import Literal
33

4-
from pydantic import BaseModel, ConfigDict
4+
from pydantic import BaseModel, ConfigDict, field_serializer
55

66
from smartem_common.entity_status import (
77
AcquisitionStatus,
@@ -28,12 +28,13 @@ class AcquisitionBaseFields(BaseModel):
2828
instrument_id: str | None = None
2929
computer_name: str | None = None
3030

31-
model_config = {
32-
"use_enum_values": True,
33-
"json_encoders": {
34-
datetime: lambda v: v.isoformat(),
35-
},
36-
}
31+
model_config = ConfigDict(use_enum_values=True)
32+
33+
@field_serializer("*", when_used="json")
34+
def serialize_datetime_fields(self, v, _info):
35+
if isinstance(v, datetime):
36+
return v.isoformat()
37+
return v
3738

3839

3940
class AcquisitionBaseRequest(AcquisitionBaseFields):
@@ -58,11 +59,11 @@ class AtlasBaseFields(BaseModel):
5859
description: str | None = None
5960
name: str | None = None
6061

61-
model_config = {
62-
"json_encoders": {
63-
datetime: lambda v: v.isoformat(),
64-
}
65-
}
62+
@field_serializer("*", when_used="json")
63+
def serialize_datetime_fields(self, v, _info):
64+
if isinstance(v, datetime):
65+
return v.isoformat()
66+
return v
6667

6768

6869
class AtlasTileBaseFields(BaseModel):
@@ -114,12 +115,13 @@ class GridBaseFields(BaseModel):
114115
scan_start_time: datetime | None = None
115116
scan_end_time: datetime | None = None
116117

117-
model_config = {
118-
"use_enum_values": True,
119-
"json_encoders": {
120-
datetime: lambda v: v.isoformat(),
121-
},
122-
}
118+
model_config = ConfigDict(use_enum_values=True)
119+
120+
@field_serializer("*", when_used="json")
121+
def serialize_datetime_fields(self, v, _info):
122+
if isinstance(v, datetime):
123+
return v.isoformat()
124+
return v
123125

124126

125127
class GridBaseRequest(GridBaseFields):
@@ -173,9 +175,13 @@ class GridSquareBaseFields(BaseModel):
173175
applied_defocus: float | None = None
174176
status: GridSquareStatus | None = None
175177

176-
model_config = ConfigDict(
177-
use_enum_values=True, json_encoders={datetime: lambda v: v.isoformat() if v else None}, from_attributes=True
178-
)
178+
model_config = ConfigDict(use_enum_values=True, from_attributes=True)
179+
180+
@field_serializer("*", when_used="json")
181+
def serialize_datetime_fields(self, v, _info):
182+
if isinstance(v, datetime):
183+
return v.isoformat()
184+
return v
179185

180186

181187
class GridSquareBaseRequest(GridSquareBaseFields):
@@ -261,12 +267,13 @@ class MicrographBaseFields(BaseModel):
261267
pick_distribution: str | None = None
262268
status: MicrographStatus = MicrographStatus.NONE # Default to NONE
263269

264-
model_config = {
265-
"use_enum_values": True,
266-
"json_encoders": {
267-
datetime: lambda v: v.isoformat() if v else None,
268-
},
269-
}
270+
model_config = ConfigDict(use_enum_values=True)
271+
272+
@field_serializer("*", when_used="json")
273+
def serialize_datetime_fields(self, v, _info):
274+
if isinstance(v, datetime):
275+
return v.isoformat()
276+
return v
270277

271278

272279
class MicrographBaseRequest(MicrographBaseFields):

src/smartem_backend/model/http_response.py

Lines changed: 57 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
from datetime import datetime
22

3-
from pydantic import BaseModel, ConfigDict
3+
from pydantic import BaseModel, ConfigDict, field_serializer
44

55
from smartem_common.entity_status import (
66
AcquisitionStatus,
@@ -44,7 +44,13 @@ class AtlasResponse(BaseModel):
4444
name: str
4545
tiles: list[AtlasTileResponse] | None = []
4646

47-
model_config = ConfigDict(from_attributes=True, json_encoders={datetime: lambda v: v.isoformat() if v else None})
47+
model_config = ConfigDict(from_attributes=True)
48+
49+
@field_serializer("*", when_used="json")
50+
def serialize_datetime_fields(self, v, _info):
51+
if isinstance(v, datetime):
52+
return v.isoformat()
53+
return v
4854

4955

5056
class AcquisitionResponse(BaseModel):
@@ -62,9 +68,13 @@ class AcquisitionResponse(BaseModel):
6268
instrument_id: str | None
6369
computer_name: str | None
6470

65-
model_config = ConfigDict(
66-
from_attributes=True, use_enum_values=True, json_encoders={datetime: lambda v: v.isoformat() if v else None}
67-
)
71+
model_config = ConfigDict(from_attributes=True, use_enum_values=True)
72+
73+
@field_serializer("*", when_used="json")
74+
def serialize_datetime_fields(self, v, _info):
75+
if isinstance(v, datetime):
76+
return v.isoformat()
77+
return v
6878

6979

7080
class GridResponse(BaseModel):
@@ -77,9 +87,13 @@ class GridResponse(BaseModel):
7787
scan_start_time: datetime | None
7888
scan_end_time: datetime | None
7989

80-
model_config = ConfigDict(
81-
from_attributes=True, use_enum_values=True, json_encoders={datetime: lambda v: v.isoformat() if v else None}
82-
)
90+
model_config = ConfigDict(from_attributes=True, use_enum_values=True)
91+
92+
@field_serializer("*", when_used="json")
93+
def serialize_datetime_fields(self, v, _info):
94+
if isinstance(v, datetime):
95+
return v.isoformat()
96+
return v
8397

8498

8599
class GridSquareResponse(BaseModel):
@@ -110,9 +124,13 @@ class GridSquareResponse(BaseModel):
110124
detector_name: str | None
111125
applied_defocus: float | None
112126

113-
model_config = ConfigDict(
114-
from_attributes=True, use_enum_values=True, json_encoders={datetime: lambda v: v.isoformat() if v else None}
115-
)
127+
model_config = ConfigDict(from_attributes=True, use_enum_values=True)
128+
129+
@field_serializer("*", when_used="json")
130+
def serialize_datetime_fields(self, v, _info):
131+
if isinstance(v, datetime):
132+
return v.isoformat()
133+
return v
116134

117135

118136
class FoilHoleResponse(BaseModel):
@@ -163,9 +181,13 @@ class MicrographResponse(BaseModel):
163181
number_of_particles_picked: int | None = None
164182
pick_distribution: str | None = None
165183

166-
model_config = ConfigDict(
167-
from_attributes=True, use_enum_values=True, json_encoders={datetime: lambda v: v.isoformat() if v else None}
168-
)
184+
model_config = ConfigDict(from_attributes=True, use_enum_values=True)
185+
186+
@field_serializer("*", when_used="json")
187+
def serialize_datetime_fields(self, v, _info):
188+
if isinstance(v, datetime):
189+
return v.isoformat()
190+
return v
169191

170192

171193
# ============ Quality Prediction Response Models ============
@@ -186,7 +208,13 @@ class QualityPredictionResponse(BaseModel):
186208
gridsquare_uuid: str | None = None
187209
foilhole_uuid: str | None = None
188210

189-
model_config = ConfigDict(from_attributes=True, json_encoders={datetime: lambda v: v.isoformat() if v else None})
211+
model_config = ConfigDict(from_attributes=True)
212+
213+
@field_serializer("*", when_used="json")
214+
def serialize_datetime_fields(self, v, _info):
215+
if isinstance(v, datetime):
216+
return v.isoformat()
217+
return v
190218

191219

192220
class QualityPredictionModelParameterResponse(BaseModel):
@@ -198,7 +226,13 @@ class QualityPredictionModelParameterResponse(BaseModel):
198226
value: float
199227
group: str
200228

201-
model_config = ConfigDict(from_attributes=True, json_encoders={datetime: lambda v: v.isoformat() if v else None})
229+
model_config = ConfigDict(from_attributes=True)
230+
231+
@field_serializer("*", when_used="json")
232+
def serialize_datetime_fields(self, v, _info):
233+
if isinstance(v, datetime):
234+
return v.isoformat()
235+
return v
202236

203237

204238
class QualityPredictionModelWeightResponse(BaseModel):
@@ -211,7 +245,13 @@ class QualityPredictionModelWeightResponse(BaseModel):
211245
prediction_model_name: str
212246
weight: float
213247

214-
model_config = ConfigDict(from_attributes=True, json_encoders={datetime: lambda v: v.isoformat() if v else None})
248+
model_config = ConfigDict(from_attributes=True)
249+
250+
@field_serializer("*", when_used="json")
251+
def serialize_datetime_fields(self, v, _info):
252+
if isinstance(v, datetime):
253+
return v.isoformat()
254+
return v
215255

216256

217257
class QualityMetricsResponse(BaseModel):

src/smartem_common/schemas.py

Lines changed: 52 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
from datetime import datetime
22
from pathlib import Path
33

4-
from pydantic import BaseModel, ConfigDict, Field
4+
from pydantic import BaseModel, ConfigDict, Field, field_serializer
55

66
from smartem_common.utils import generate_uuid
77

@@ -18,7 +18,13 @@ class MicrographManifest(BaseModel):
1818
binning_x: int
1919
binning_y: int
2020

21-
model_config = ConfigDict(json_encoders={datetime: lambda v: v.isoformat() if v else None}, from_attributes=True)
21+
model_config = ConfigDict(from_attributes=True)
22+
23+
@field_serializer("*", when_used="json")
24+
def serialize_datetime_fields(self, v, _info):
25+
if isinstance(v, datetime):
26+
return v.isoformat()
27+
return v
2228

2329
def validate_natural_numbers(self):
2430
for natural_num in ["image_size_x", "image_size_y", "binning_x", "binning_y"]:
@@ -71,10 +77,15 @@ class GridSquareManifest(BaseModel):
7177
applied_defocus: float | None
7278
data_dir: Path | None = None
7379

74-
model_config = ConfigDict(
75-
json_encoders={datetime: lambda v: v.isoformat() if v else None, Path: lambda v: str(v) if v else None},
76-
from_attributes=True,
77-
)
80+
model_config = ConfigDict(from_attributes=True)
81+
82+
@field_serializer("*", when_used="json")
83+
def serialize_special_fields(self, v, _info):
84+
if isinstance(v, datetime):
85+
return v.isoformat()
86+
if isinstance(v, Path):
87+
return str(v)
88+
return v
7889

7990

8091
class GridSquareStagePosition(BaseModel):
@@ -115,7 +126,13 @@ class GridSquareMetadata(BaseModel):
115126
unusable: bool
116127
foilhole_positions: dict[int, FoilHolePosition] | None = {}
117128

118-
model_config = ConfigDict(json_encoders={Path: lambda v: str(v) if v else None}, from_attributes=True)
129+
model_config = ConfigDict(from_attributes=True)
130+
131+
@field_serializer("*", when_used="json")
132+
def serialize_path_fields(self, v, _info):
133+
if isinstance(v, Path):
134+
return str(v)
135+
return v
119136

120137

121138
class GridSquareData(BaseModel):
@@ -131,7 +148,13 @@ class GridSquareData(BaseModel):
131148
registered: bool = False
132149
uuid: str = Field(default_factory=generate_uuid)
133150

134-
model_config = ConfigDict(json_encoders={Path: lambda v: str(v) if v else None}, from_attributes=True)
151+
model_config = ConfigDict(from_attributes=True)
152+
153+
@field_serializer("*", when_used="json")
154+
def serialize_path_fields(self, v, _info):
155+
if isinstance(v, Path):
156+
return str(v)
157+
return v
135158

136159

137160
class AtlasTilePosition(BaseModel):
@@ -180,7 +203,13 @@ class AtlasData(BaseModel):
180203
grid_uuid: str
181204
uuid: str = Field(default_factory=generate_uuid)
182205

183-
model_config = ConfigDict(json_encoders={datetime: lambda v: v.isoformat() if v else None}, from_attributes=True)
206+
model_config = ConfigDict(from_attributes=True)
207+
208+
@field_serializer("*", when_used="json")
209+
def serialize_datetime_fields(self, v, _info):
210+
if isinstance(v, datetime):
211+
return v.isoformat()
212+
return v
184213

185214

186215
class MicroscopeData(BaseModel):
@@ -202,7 +231,13 @@ class AcquisitionData(BaseModel):
202231
instrument: MicroscopeData | None = None
203232
uuid: str = Field(default_factory=generate_uuid)
204233

205-
model_config = ConfigDict(json_encoders={datetime: lambda v: v.isoformat() if v else None}, from_attributes=True)
234+
model_config = ConfigDict(from_attributes=True)
235+
236+
@field_serializer("*", when_used="json")
237+
def serialize_datetime_fields(self, v, _info):
238+
if isinstance(v, datetime):
239+
return v.isoformat()
240+
return v
206241

207242

208243
class GridData(BaseModel):
@@ -212,7 +247,13 @@ class GridData(BaseModel):
212247
atlas_data: AtlasData | None = None
213248
uuid: str = Field(default_factory=generate_uuid)
214249

215-
model_config = ConfigDict(json_encoders={Path: lambda v: str(v) if v else None}, from_attributes=True)
250+
model_config = ConfigDict(from_attributes=True)
251+
252+
@field_serializer("*", when_used="json")
253+
def serialize_path_fields(self, v, _info):
254+
if isinstance(v, Path):
255+
return str(v)
256+
return v
216257

217258
def model_post_init(self, __context__):
218259
if isinstance(self.data_dir, str):

0 commit comments

Comments
 (0)