Skip to content

Commit 00b5574

Browse files
Merge pull request #61 from ecmwf-projects/COPDS-1175-pydantic-v2
[WIP] migrate to pydantic v2
2 parents dfd3b67 + 0afe69d commit 00b5574

File tree

7 files changed

+134
-100
lines changed

7 files changed

+134
-100
lines changed

ci/environment-ci.yml

+1
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ channels:
33
- conda-forge
44
- nodefaults
55
dependencies:
6+
- httpx
67
- make
78
- mypy
89
- myst-parser

environment.yml

+3-1
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@ channels:
88
# - package2
99
# DO NOT EDIT ABOVE THIS LINE, ADD DEPENDENCIES BELOW AS SHOWN IN THE EXAMPLE
1010
dependencies:
11-
- httpx
11+
- attrs
1212
- fastapi
13+
- pydantic>2
14+
- pydantic-settings>2
1315
- requests

ogc_api_processes_fastapi/main.py

+1-3
Original file line numberDiff line numberDiff line change
@@ -24,8 +24,6 @@ def set_response_model(
2424
route_name,
2525
__base__=base_model,
2626
)
27-
if route_name in ("GetProcesses", "GetJobs"):
28-
response_model.__fields__["links"].required = True
2927

3028
return response_model # type: ignore
3129

@@ -41,7 +39,7 @@ def register_route(
4139
response_model_exclude_unset=True,
4240
response_model_exclude_none=True,
4341
endpoint=route_endpoint,
44-
**config.ROUTES[route_name].dict(exclude={"client_method"})
42+
**config.ROUTES[route_name].model_dump(exclude={"client_method"})
4543
)
4644

4745

ogc_api_processes_fastapi/models.py

+44-47
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
from typing import Any, Dict, ForwardRef, List, Optional, Union, cast
2020

2121
import pydantic
22+
import typing_extensions
2223

2324

2425
class Metadata(pydantic.BaseModel):
@@ -39,7 +40,7 @@ class JobControlOptions(enum.Enum):
3940

4041

4142
class TransmissionMode(enum.Enum):
42-
value: str = "value" # type:ignore
43+
value: str = "value"
4344
reference: str = "reference"
4445

4546

@@ -50,26 +51,36 @@ class PaginationQueryParameters(pydantic.BaseModel):
5051

5152
class Link(pydantic.BaseModel):
5253
href: str
53-
rel: Optional[str] = pydantic.Field(None, example="service")
54-
type: Optional[str] = pydantic.Field(None, example="application/json")
55-
hreflang: Optional[str] = pydantic.Field(None, example="en")
54+
rel: Optional[str] = pydantic.Field(
55+
default=None, json_schema_extra={"example": "service"}
56+
)
57+
type: Optional[str] = pydantic.Field(
58+
default=None, json_schema_extra={"example": "application/json"}
59+
)
60+
hreflang: Optional[str] = pydantic.Field(
61+
default=None, json_schema_extra={"example": "en"}
62+
)
5663
title: Optional[str] = None
5764

5865

5966
class LandingPage(pydantic.BaseModel):
6067
title: Optional[str] = pydantic.Field(
61-
default=None, example="Example processing server"
68+
default=None, json_schema_extra={"example": "Example processing server"}
6269
)
6370
description: Optional[str] = pydantic.Field(
6471
default=None,
65-
example="Example server implementing the OGC API - Processes 1.0 Standard",
72+
json_schema_extra={
73+
"example": "Example server implementing the OGC API - Processes 1.0 Standard"
74+
},
6675
)
6776
links: List[Link]
6877

6978

7079
class ConfClass(pydantic.BaseModel):
7180
conformsTo: List[str] = pydantic.Field(
72-
example="http://www.opengis.net/spec/ogcapi-processes-1/1.0/conf/core"
81+
json_schema_extra={
82+
"example": "http://www.opengis.net/spec/ogcapi-processes-1/1.0/conf/core"
83+
}
7384
)
7485

7586

@@ -94,11 +105,8 @@ class ProcessSummary(DescriptionType):
94105

95106

96107
class ProcessList(pydantic.BaseModel):
97-
class Config:
98-
underscore_attrs_are_private = True
99-
100108
processes: List[ProcessSummary]
101-
links: Optional[List[Link]] = None
109+
links: List[Link]
102110
_pagination_query_params: Optional[PaginationQueryParameters] = None
103111

104112

@@ -116,22 +124,18 @@ class ObjectType(enum.Enum):
116124

117125

118126
class Reference(pydantic.BaseModel):
119-
class Config:
120-
extra = pydantic.Extra.forbid
121-
122-
_ref: str = pydantic.Field(..., alias="$ref")
127+
model_config = pydantic.ConfigDict(extra="forbid")
128+
ref: str
123129

124130

125-
class PositiveInt(pydantic.ConstrainedInt):
126-
ge = 0
131+
PositiveInt = typing_extensions.Annotated[int, pydantic.Field(ge=0)]
127132

128133

129134
SchemaItem = ForwardRef("SchemaItem")
130135

131136

132137
class SchemaItem(pydantic.BaseModel): # type: ignore
133-
class Config:
134-
extra = pydantic.Extra.forbid
138+
model_config = pydantic.ConfigDict(extra="forbid")
135139

136140
title: Optional[str] = None
137141
multipleOf: Optional[pydantic.PositiveFloat] = None
@@ -147,8 +151,8 @@ class Config:
147151
uniqueItems: Optional[bool] = False
148152
maxProperties: Optional[PositiveInt] = None
149153
minProperties: Optional[PositiveInt] = cast(PositiveInt, 0)
150-
required: Optional[List[str]] = pydantic.Field(None, min_items=1)
151-
enum: Optional[List[Any]] = pydantic.Field(None, min_items=1)
154+
required: Optional[List[str]] = pydantic.Field(default=None, min_length=1)
155+
enum: Optional[List[Any]] = pydantic.Field(default=None, min_length=1)
152156
type: Optional[ObjectType] = None
153157
description: Optional[str] = None
154158
format: Optional[str] = None
@@ -165,27 +169,24 @@ class Config:
165169
properties: Optional[Dict[str, Union[Reference, SchemaItem]]] = None # type: ignore
166170

167171

168-
SchemaItem.update_forward_refs() # type: ignore
172+
SchemaItem.model_rebuild() # type: ignore
169173

170174

171175
class InputDescription(DescriptionType):
172-
class Config:
173-
allow_population_by_field_name = True
176+
model_config = pydantic.ConfigDict(populate_by_name=True)
174177

175178
minOccurs: Optional[int] = 1
176179
maxOccurs: Optional[Union[int, MaxOccur]] = None
177180
schema_: Union[Reference, SchemaItem] = pydantic.Field(..., alias="schema") # type: ignore
178181

179182

180183
class OutputDescription(DescriptionType):
181-
class Config:
182-
allow_population_by_field_name = True
184+
model_config = pydantic.ConfigDict(populate_by_name=True)
183185

184186
schema_: Union[Reference, SchemaItem] = pydantic.Field(..., alias="schema") # type: ignore
185187

186188

187-
class BinaryInputValue(pydantic.BaseModel):
188-
__root__: str
189+
BinaryInputValue = pydantic.RootModel[str]
189190

190191

191192
class Crs(enum.Enum):
@@ -202,26 +203,29 @@ class Bbox(pydantic.BaseModel):
202203
crs: Optional[Crs] = Crs.http___www_opengis_net_def_crs_OGC_1_3_CRS84
203204

204205

205-
class InputValueNoObject(pydantic.BaseModel):
206-
__root__: Union[str, float, int, bool, List[Any], BinaryInputValue, Bbox]
206+
InputValueNoObject = pydantic.RootModel[
207+
Union[str, float, int, bool, List[Any], BinaryInputValue, Bbox]
208+
]
207209

208210

209211
class Format(pydantic.BaseModel):
210212
mediaType: Optional[str] = None
211213
encoding: Optional[str] = None
212-
schema_: Optional[Union[str, Dict[str, Any]]] = pydantic.Field(None, alias="schema")
214+
schema_: Optional[Union[str, Dict[str, Any]]] = pydantic.Field(
215+
default=None, alias="schema"
216+
)
213217

214218

215-
class InputValue(pydantic.BaseModel):
216-
__root__: Union[Dict[str, Any], InputValueNoObject]
219+
InputValue = pydantic.RootModel[Union[Dict[str, Any], InputValueNoObject]]
217220

218221

219222
class QualifiedInputValue(Format):
220223
value: InputValue
221224

222225

223-
class InlineOrRefData(pydantic.BaseModel):
224-
__root__: Union[InputValueNoObject, QualifiedInputValue, Link]
226+
InlineOrRefData = pydantic.RootModel[
227+
Union[InputValueNoObject, QualifiedInputValue, Link]
228+
]
225229

226230

227231
class Output(pydantic.BaseModel):
@@ -252,9 +256,7 @@ class ProcessDescription(ProcessSummary):
252256
outputs: Optional[Dict[str, OutputDescription]] = None
253257

254258

255-
class ConInt(pydantic.ConstrainedInt):
256-
ge = 0
257-
le = 100
259+
ConInt = typing_extensions.Annotated[int, pydantic.Field(ge=0, le=100)]
258260

259261

260262
class StatusCode(str, enum.Enum):
@@ -270,8 +272,7 @@ class JobType(enum.Enum):
270272

271273

272274
class StatusInfo(pydantic.BaseModel):
273-
class Config:
274-
extra = pydantic.Extra.allow
275+
model_config = pydantic.ConfigDict(extra="allow")
275276

276277
processID: Optional[str] = None
277278
type: JobType
@@ -287,21 +288,17 @@ class Config:
287288

288289

289290
class JobList(pydantic.BaseModel):
290-
class Config:
291-
underscore_attrs_are_private = True
292-
293291
jobs: List[StatusInfo]
294292
links: Optional[List[Link]] = None
295293
_pagination_query_params: Optional[PaginationQueryParameters] = None
296294

297295

298-
class Results(pydantic.BaseModel):
299-
__root__: Optional[Dict[str, InlineOrRefData]] = None
296+
class Results(pydantic.RootModel[Optional[Dict[str, InlineOrRefData]]]):
297+
root: Optional[Dict[str, InlineOrRefData]] = None
300298

301299

302300
class Exception(pydantic.BaseModel):
303-
class Config:
304-
extra = pydantic.Extra.allow
301+
model_config = pydantic.ConfigDict(extra="allow")
305302

306303
type: str
307304
title: Optional[str] = None

pyproject.toml

+3-1
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,9 @@ classifiers = [
1515
]
1616
dependencies = [
1717
"attrs",
18-
"fastapi"
18+
"fastapi",
19+
"pydantic>2",
20+
"pydantic-settings>2"
1921
]
2022
description = "OGC API Processes service based on FastAPI"
2123
dynamic = ["version"]

0 commit comments

Comments
 (0)