Skip to content

Commit 8c1af9e

Browse files
authored
pydantic>2 (#83)
1 parent cbcbe85 commit 8c1af9e

File tree

8 files changed

+191
-212
lines changed

8 files changed

+191
-212
lines changed

.pre-commit-config.yaml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
# See https://pre-commit.com for more information
22
# See https://pre-commit.com/hooks.html for more hooks
3+
default_language_version:
4+
python: python3
35
repos:
46
- repo: https://github.com/asottile/pyupgrade
57
rev: v3.10.1

openeo_pg_parser_networkx/graph.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
11
from __future__ import annotations
22

3+
import sys
4+
5+
sys.setrecursionlimit(16385) # Necessary when parsing really big graphs
36
import functools
47
import json
58
import logging
@@ -110,7 +113,7 @@ def _parse_datamodel(nested_graph: dict) -> ProcessGraph:
110113
Parses a nested process graph into the Pydantic datamodel for ProcessGraph.
111114
"""
112115

113-
return ProcessGraph.parse_obj(nested_graph)
116+
return ProcessGraph.model_validate(nested_graph)
114117

115118
def _parse_process_graph(self, process_graph: ProcessGraph, arg_name: str = None):
116119
"""

openeo_pg_parser_networkx/pg_schema.py

Lines changed: 87 additions & 75 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
import logging
66
from enum import Enum
77
from re import match
8-
from typing import Any, Optional, Union
8+
from typing import Annotated, Any, List, Optional, Union
99
from uuid import UUID, uuid4
1010

1111
import numpy as np
@@ -22,9 +22,13 @@
2222
BaseModel,
2323
Extra,
2424
Field,
25+
RootModel,
26+
StringConstraints,
2527
ValidationError,
2628
conlist,
2729
constr,
30+
field_validator,
31+
model_validator,
2832
validator,
2933
)
3034
from shapely.geometry import Polygon
@@ -65,13 +69,14 @@ class ParameterReference(BaseModel, extra=Extra.forbid):
6569

6670

6771
class ProcessNode(BaseModel, arbitrary_types_allowed=True):
68-
process_id: constr(regex=r'^\w+$')
72+
process_id: Annotated[str, StringConstraints(pattern=r'^\w+$')]
73+
6974
namespace: Optional[Optional[str]] = None
7075
result: Optional[bool] = False
7176
description: Optional[Optional[str]] = None
7277
arguments: dict[
7378
str,
74-
Optional[
79+
Annotated[
7580
Union[
7681
ResultReference,
7782
ParameterReference,
@@ -87,11 +92,12 @@ class ProcessNode(BaseModel, arbitrary_types_allowed=True):
8792
# GeoJson, disable while https://github.com/developmentseed/geojson-pydantic/issues/92 is open
8893
Time,
8994
float,
90-
str,
9195
bool,
9296
list,
9397
dict,
94-
]
98+
str,
99+
],
100+
Field(union_mode='left_to_right'),
95101
],
96102
]
97103

@@ -133,9 +139,9 @@ class BoundingBox(BaseModel, arbitrary_types_allowed=True):
133139
east: float
134140
north: float
135141
south: float
136-
base: Optional[float]
137-
height: Optional[float]
138-
crs: Optional[Union[str, int]]
142+
base: Optional[float] = None
143+
height: Optional[float] = None
144+
crs: Optional[Union[str, int]] = None
139145

140146
# validators
141147
_parse_crs: classmethod = crs_validator('crs')
@@ -153,48 +159,54 @@ def polygon(self) -> Polygon:
153159
)
154160

155161

156-
class Date(BaseModel):
157-
__root__: datetime.datetime
162+
class Date(RootModel):
163+
root: datetime.datetime
158164

159-
@validator("__root__", pre=True)
165+
@field_validator("root", mode="before")
160166
def validate_time(cls, value: Any) -> Any:
161167
if (
162168
isinstance(value, str)
163169
and len(value) <= 11
164170
and match(r"[0-9]{4}[-/][0-9]{2}[-/][0-9]{2}T?", value)
165171
):
166172
return pendulum.parse(value)
167-
raise ValidationError("Could not parse `Date` from input.")
173+
raise ValueError("Could not parse `Date` from input.")
168174

169175
def to_numpy(self):
170-
return np.datetime64(self.__root__)
176+
return np.datetime64(self.root)
171177

172178
def __repr__(self):
173-
return self.__root__.__repr__()
179+
return self.root.__repr__()
180+
181+
def __gt__(self, date1):
182+
return self.root > date1.root
174183

175184

176-
class DateTime(BaseModel):
177-
__root__: datetime.datetime
185+
class DateTime(RootModel):
186+
root: datetime.datetime
178187

179-
@validator("__root__", pre=True)
188+
@field_validator("root", mode="before")
180189
def validate_time(cls, value: Any) -> Any:
181190
if isinstance(value, str) and match(
182191
r"[0-9]{4}-[0-9]{2}-[0-9]{2}T?[0-9]{2}:[0-9]{2}:?([0-9]{2})?Z?", value
183192
):
184193
return pendulum.parse(value)
185-
raise ValidationError("Could not parse `DateTime` from input.")
194+
raise ValueError("Could not parse `DateTime` from input.")
186195

187196
def to_numpy(self):
188-
return np.datetime64(self.__root__)
197+
return np.datetime64(self.root)
189198

190199
def __repr__(self):
191-
return self.__root__.__repr__()
200+
return self.root.__repr__()
201+
202+
def __gt__(self, date1):
203+
return self.root > date1.root
192204

193205

194-
class Time(BaseModel):
195-
__root__: pendulum.Time
206+
class Time(RootModel):
207+
root: datetime.time
196208

197-
@validator("__root__", pre=True)
209+
@field_validator("root", mode="before")
198210
def validate_time(cls, value: Any) -> Any:
199211
if (
200212
isinstance(value, str)
@@ -203,145 +215,145 @@ def validate_time(cls, value: Any) -> Any:
203215
and match(r"[0-9]{2}:[0-9]{2}:?([0-9]{2})?Z?", value)
204216
):
205217
return pendulum.parse(value).time()
206-
raise ValidationError("Could not parse `Time` from input.")
218+
raise ValueError("Could not parse `Time` from input.")
207219

208220
def to_numpy(self):
209221
raise NotImplementedError
210222

211223
def __repr__(self):
212-
return self.__root__.__repr__()
224+
return self.time.__repr__()
213225

214226

215-
class Year(BaseModel):
216-
__root__: datetime.datetime
227+
class Year(RootModel):
228+
root: datetime.datetime
217229

218-
@validator("__root__", pre=True)
230+
@field_validator("root", mode="before")
219231
def validate_time(cls, value: Any) -> Any:
220232
if isinstance(value, str) and len(value) <= 4 and match(r"^\d{4}$", value):
221233
return pendulum.parse(value)
222-
raise ValidationError("Could not parse `Year` from input.")
234+
raise ValueError("Could not parse `Year` from input.")
223235

224236
def to_numpy(self):
225-
return np.datetime64(self.__root__)
237+
return np.datetime64(self.root)
226238

227239
def __repr__(self):
228-
return self.__root__.__repr__()
240+
return self.root.__repr__()
229241

230242

231-
class Duration(BaseModel):
232-
__root__: datetime.timedelta
243+
class Duration(RootModel):
244+
root: datetime.timedelta
233245

234-
@validator("__root__", pre=True)
246+
@field_validator("root", mode="before")
235247
def validate_time(cls, value: Any) -> Any:
236248
if isinstance(value, str) and match(
237249
r"P[0-9]*Y?[0-9]*M?[0-9]*D?T?[0-9]*H?[0-9]*M?[0-9]*S?", value
238250
):
239251
return pendulum.parse(value).as_timedelta()
240-
raise ValidationError("Could not parse `Duration` from input.")
252+
raise ValueError("Could not parse `Duration` from input.")
241253

242254
def to_numpy(self):
243-
return np.timedelta64(self.__root__)
255+
return np.timedelta64(self.root)
244256

245257
def __repr__(self):
246-
return self.__root__.__repr__()
258+
return self.root.__repr__()
247259

248260

249-
class TemporalInterval(BaseModel):
250-
__root__: conlist(Union[Year, Date, DateTime, Time, None], min_items=2, max_items=2)
261+
class TemporalInterval(RootModel):
262+
root: conlist(Union[Year, Date, DateTime, Time, None], min_length=2, max_length=2)
251263

252-
@validator("__root__")
264+
@field_validator("root")
253265
def validate_temporal_interval(cls, value: Any) -> Any:
254266
start = value[0]
255267
end = value[1]
256268

257269
if start is None and end is None:
258-
raise ValidationError("Could not parse `TemporalInterval` from input.")
270+
raise ValueError("Could not parse `TemporalInterval` from input.")
259271

260272
# Disambiguate the Time subtype
261273
if isinstance(start, Time) or isinstance(end, Time):
262274
if isinstance(start, Time) and isinstance(end, Time):
263-
raise ValidationError(
275+
raise ValueError(
264276
"Ambiguous TemporalInterval, both start and end are of type `Time`"
265277
)
266278
if isinstance(start, Time):
267279
if end is None:
268-
raise ValidationError(
280+
raise ValueError(
269281
"Cannot disambiguate TemporalInterval, start is `Time` and end is `None`"
270282
)
271283
logger.warning(
272284
"Start time of temporal interval is of type `time`. Assuming same date as the end time."
273285
)
274286
start = DateTime(
275-
__root__=pendulum.datetime(
276-
end.__root__.year,
277-
end.__root__.month,
278-
end.__root__.day,
279-
start.__root__.hour,
280-
start.__root__.minute,
281-
start.__root__.second,
282-
start.__root__.microsecond,
287+
root=pendulum.datetime(
288+
end.root.year,
289+
end.root.month,
290+
end.root.day,
291+
start.root.hour,
292+
start.root.minute,
293+
start.root.second,
294+
start.root.microsecond,
283295
).to_rfc3339_string()
284296
)
285297
elif isinstance(end, Time):
286298
if start is None:
287-
raise ValidationError(
299+
raise ValueError(
288300
"Cannot disambiguate TemporalInterval, start is `None` and end is `Time`"
289301
)
290302
logger.warning(
291303
"End time of temporal interval is of type `time`. Assuming same date as the start time."
292304
)
293305
end = DateTime(
294-
__root__=pendulum.datetime(
295-
start.__root__.year,
296-
start.__root__.month,
297-
start.__root__.day,
298-
end.__root__.hour,
299-
end.__root__.minute,
300-
end.__root__.second,
301-
end.__root__.microsecond,
306+
root=pendulum.datetime(
307+
start.root.year,
308+
start.root.month,
309+
start.root.day,
310+
end.root.hour,
311+
end.root.minute,
312+
end.root.second,
313+
end.root.microsecond,
302314
).to_rfc3339_string()
303315
)
304316

305-
if not (start is None or end is None) and start.__root__ > end.__root__:
306-
raise ValidationError("Start time > end time")
317+
if not (start is None or end is None) and start > end:
318+
raise ValueError("Start time > end time")
307319

308320
return [start, end]
309321

310322
@property
311323
def start(self):
312-
return self.__root__[0]
324+
return self.root[0]
313325

314326
@property
315327
def end(self):
316-
return self.__root__[1]
328+
return self.root[1]
317329

318330
def __iter__(self):
319-
return iter(self.__root__)
331+
return iter(self.root)
320332

321333
def __getitem__(self, item):
322-
return self.__root__[item]
334+
return self.root[item]
323335

324336

325-
class TemporalIntervals(BaseModel):
326-
__root__: list[TemporalInterval]
337+
class TemporalIntervals(RootModel):
338+
root: list[TemporalInterval]
327339

328340
def __iter__(self):
329-
return iter(self.__root__)
341+
return iter(self.root)
330342

331343
def __getitem__(self, item) -> TemporalInterval:
332-
return self.__root__[item]
344+
return self.root[item]
333345

334346

335347
GeoJson = Union[FeatureCollection, Feature, GeometryCollection, MultiPolygon, Polygon]
336348
# The GeoJson spec (https://www.rfc-editor.org/rfc/rfc7946.html#ref-GJ2008) doesn't
337349
# have a crs field anymore and recommends assuming it to be EPSG:4326, so we do the same.
338350

339351

340-
class JobId(BaseModel):
341-
__root__: str = Field(
342-
regex=r"(eodc-jb-|jb-)[a-z0-9]{8}-[a-z0-9]{4}-[a-z0-9]{4}-[a-z0-9]{4}-[a-z0-9]{12}"
352+
class JobId(RootModel):
353+
root: str = Field(
354+
pattern=r"(eodc-jb-|jb-)[a-z0-9]{8}-[a-z0-9]{4}-[a-z0-9]{4}-[a-z0-9]{4}-[a-z0-9]{12}"
343355
)
344356

345357

346-
ResultReference.update_forward_refs()
347-
ProcessNode.update_forward_refs()
358+
ResultReference.model_rebuild()
359+
ProcessNode.model_rebuild()

openeo_pg_parser_networkx/utils.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,14 +10,14 @@
1010
def parse_nested_parameter(parameter: Any):
1111
try:
1212
return ResultReference.parse_obj(parameter)
13-
except pydantic.error_wrappers.ValidationError:
13+
except pydantic.ValidationError:
1414
pass
1515
except TypeError:
1616
pass
1717

1818
try:
1919
return ParameterReference.parse_obj(parameter)
20-
except pydantic.error_wrappers.ValidationError:
20+
except pydantic.ValidationError:
2121
pass
2222
except TypeError:
2323
pass

pyproject.toml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,11 +24,11 @@ packages = [
2424

2525
[tool.poetry.dependencies]
2626
python = ">=3.9,<3.12"
27-
pydantic = "^1.9.1"
27+
pydantic = "^2.4.0"
2828
pyproj = "^3.4.0"
2929
networkx = "^2.8.6"
3030
shapely = ">=1.8"
31-
geojson-pydantic = "^0.5.0"
31+
geojson-pydantic = "^1.0.0"
3232
numpy = "^1.20.3"
3333
pendulum = "^2.1.2"
3434
matplotlib = { version = "^3.7.1", optional = true }

0 commit comments

Comments
 (0)