Skip to content

Commit 64092c4

Browse files
authored
Merge pull request #9 from taskiq-python/feature/pydanticv2
2 parents 0b8c6d6 + 11ab87c commit 64092c4

File tree

8 files changed

+388
-261
lines changed

8 files changed

+388
-261
lines changed

README.md

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -154,7 +154,7 @@ class UserInfo(BaseModel):
154154

155155
@router.post("/users")
156156
async def new_data(user: UserInfo = Depends(Json())):
157-
return web.json_response({"user": user.dict()})
157+
return web.json_response({"user": user.model_dump()})
158158

159159
```
160160

@@ -168,7 +168,7 @@ If you want to make this data optional, just mark it as optional.
168168
async def new_data(user: Optional[UserInfo] = Depends(Json())):
169169
if user is None:
170170
return web.json_response({"user": None})
171-
return web.json_response({"user": user.dict()})
171+
return web.json_response({"user": user.model_dump()})
172172

173173
```
174174

@@ -275,19 +275,18 @@ To make the magic happen, please add `arbitrary_types_allowed` to the config of
275275

276276

277277
```python
278-
from pydantic import BaseModel
278+
import pydantic
279279
from aiohttp_deps import Router, Depends, Form
280280
from aiohttp import web
281281

282282
router = Router()
283283

284284

285-
class MyForm(BaseModel):
285+
class MyForm(pydantic.BaseModel):
286286
id: int
287287
file: web.FileField
288288

289-
class Config:
290-
arbitrary_types_allowed = True
289+
model_config = pydantic.ConfigDict(arbitrary_types_allowed=True)
291290

292291

293292
@router.post("/")

aiohttp_deps/swagger.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,8 @@
33
from logging import getLogger
44
from typing import Any, Awaitable, Callable, Dict, Optional, Union
55

6+
import pydantic
67
from aiohttp import web
7-
from pydantic import schema_of
88
from pydantic.utils import deep_update
99
from taskiq_dependencies import DependencyGraph
1010

@@ -103,8 +103,9 @@ def _add_route_def( # noqa: C901
103103
dependency.signature
104104
and dependency.signature.annotation != inspect.Parameter.empty
105105
):
106-
input_schema = schema_of(
106+
input_schema = pydantic.TypeAdapter(
107107
dependency.signature.annotation,
108+
).json_schema(
108109
ref_template=REF_TEMPLATE,
109110
)
110111
openapi_schema["components"]["schemas"].update(

aiohttp_deps/utils.py

Lines changed: 91 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import inspect
22
import json
3-
from typing import Any, Optional
3+
from typing import Any, Optional, Union
44

55
import pydantic
66
from aiohttp import web
@@ -32,8 +32,10 @@ def __init__(
3232
self.alias = alias
3333
self.multiple = multiple
3434
self.description = description
35+
self.type_initialized = False
36+
self.type_cache: "Union[pydantic.TypeAdapter[Any], None]" = None
3537

36-
def __call__( # noqa: C901, WPS210
38+
def __call__( # noqa: C901
3739
self,
3840
param_info: ParamInfo = Depends(),
3941
request: web.Request = Depends(),
@@ -52,30 +54,34 @@ def __call__( # noqa: C901, WPS210
5254
if self.default is not ...:
5355
default_value = self.default
5456

57+
if not self.type_initialized:
58+
if (
59+
param_info.definition
60+
and param_info.definition.annotation != inspect.Parameter.empty
61+
):
62+
self.type_cache = pydantic.TypeAdapter(param_info.definition.annotation)
63+
else:
64+
self.type_cache = None
65+
self.type_initialized = True
66+
5567
if self.multiple:
5668
value = request.headers.getall(header_name, default_value)
5769
else:
5870
value = request.headers.getone(header_name, default_value)
5971

60-
definition = None
61-
if (
62-
param_info.definition
63-
and param_info.definition.annotation != inspect.Parameter.empty
64-
):
65-
definition = param_info.definition.annotation
66-
67-
if definition is None:
72+
if self.type_cache is None:
6873
return value
6974

7075
try:
71-
return pydantic.parse_obj_as(definition, value)
76+
return self.type_cache.validate_python(value)
7277
except pydantic.ValidationError as err:
73-
errors = err.errors()
78+
errors = err.errors(include_url=False)
7479
for error in errors:
7580
error["loc"] = (
7681
"header",
7782
header_name,
7883
) + error["loc"]
84+
error.pop("input", None) # type: ignore
7985
raise web.HTTPBadRequest(
8086
headers={"Content-Type": "application/json"},
8187
text=json.dumps(errors),
@@ -90,6 +96,10 @@ class Json:
9096
and then converts it to type from your typehints.
9197
"""
9298

99+
def __init__(self) -> None:
100+
self.type_initialized = False
101+
self.type_cache: "Union[pydantic.TypeAdapter[Any], None]" = None
102+
93103
async def __call__( # noqa: C901
94104
self,
95105
param_info: ParamInfo = Depends(),
@@ -109,22 +119,26 @@ async def __call__( # noqa: C901
109119
except ValueError:
110120
body = None
111121

112-
definition = None
113-
if (
114-
param_info.definition
115-
and param_info.definition.annotation != inspect.Parameter.empty
116-
):
117-
definition = param_info.definition.annotation
118-
119-
if definition is None:
122+
if not self.type_initialized:
123+
if (
124+
param_info.definition
125+
and param_info.definition.annotation != inspect.Parameter.empty
126+
):
127+
self.type_cache = pydantic.TypeAdapter(param_info.definition.annotation)
128+
else:
129+
self.type_cache = None
130+
self.type_initialized = True
131+
132+
if self.type_cache is None:
120133
return body
121134

122135
try:
123-
return pydantic.parse_obj_as(definition, body)
136+
return self.type_cache.validate_python(body)
124137
except pydantic.ValidationError as err:
125-
errors = err.errors()
138+
errors = err.errors(include_url=False)
126139
for error in errors:
127140
error["loc"] = ("body",) + error["loc"]
141+
error.pop("input", None) # type: ignore
128142
raise web.HTTPBadRequest(
129143
headers={"Content-Type": "application/json"},
130144
text=json.dumps(errors),
@@ -156,8 +170,10 @@ def __init__(
156170
self.alias = alias
157171
self.multiple = multiple
158172
self.description = description
173+
self.type_initialized = False
174+
self.type_cache: "Union[pydantic.TypeAdapter[Any], None]" = None
159175

160-
def __call__( # noqa: C901, WPS210
176+
def __call__( # noqa: C901
161177
self,
162178
param_info: ParamInfo = Depends(),
163179
request: web.Request = Depends(),
@@ -176,30 +192,34 @@ def __call__( # noqa: C901, WPS210
176192
if self.default is not ...:
177193
default_value = self.default
178194

195+
if not self.type_initialized:
196+
if (
197+
param_info.definition
198+
and param_info.definition.annotation != inspect.Parameter.empty
199+
):
200+
self.type_cache = pydantic.TypeAdapter(param_info.definition.annotation)
201+
else:
202+
self.type_cache = None
203+
self.type_initialized = True
204+
179205
if self.multiple:
180206
value = request.query.getall(param_name, default_value)
181207
else:
182208
value = request.query.getone(param_name, default_value)
183209

184-
definition = None
185-
if (
186-
param_info.definition
187-
and param_info.definition.annotation != inspect.Parameter.empty
188-
):
189-
definition = param_info.definition.annotation
190-
191-
if definition is None:
210+
if self.type_cache is None:
192211
return value
193212

194213
try:
195-
return pydantic.parse_obj_as(definition, value)
214+
return self.type_cache.validate_python(value)
196215
except pydantic.ValidationError as err:
197-
errors = err.errors()
216+
errors = err.errors(include_url=False)
198217
for error in errors:
199218
error["loc"] = (
200219
"query",
201220
param_name,
202221
) + error["loc"]
222+
error.pop("input", None) # type: ignore
203223
raise web.HTTPBadRequest(
204224
headers={"Content-Type": "application/json"},
205225
text=json.dumps(errors),
@@ -216,7 +236,11 @@ class Form:
216236
You should provide schema with typehints.
217237
"""
218238

219-
async def __call__(
239+
def __init__(self) -> None:
240+
self.type_initialized = False
241+
self.type_cache: "Union[pydantic.TypeAdapter[Any], None]" = None
242+
243+
async def __call__( # noqa: C901
220244
self,
221245
param_info: ParamInfo = Depends(),
222246
request: web.Request = Depends(),
@@ -231,21 +255,26 @@ async def __call__(
231255
:return: parsed data.
232256
"""
233257
form_data = await request.post()
234-
definition = None
235-
if (
236-
param_info.definition
237-
and param_info.definition.annotation != inspect.Parameter.empty
238-
):
239-
definition = param_info.definition.annotation
240-
241-
if definition is None:
258+
259+
if not self.type_initialized:
260+
if (
261+
param_info.definition
262+
and param_info.definition.annotation != inspect.Parameter.empty
263+
):
264+
self.type_cache = pydantic.TypeAdapter(param_info.definition.annotation)
265+
else:
266+
self.type_cache = None
267+
self.type_initialized = True
268+
269+
if self.type_cache is None:
242270
return form_data
243271

244272
try:
245-
return pydantic.parse_obj_as(definition, form_data)
273+
return self.type_cache.validate_python(form_data)
246274
except pydantic.ValidationError as err:
247-
errors = err.errors()
275+
errors = err.errors(include_url=False)
248276
for error in errors:
277+
error.pop("input", None) # type: ignore
249278
error["loc"] = ("form",) + error["loc"]
250279
raise web.HTTPBadRequest(
251280
headers={"Content-Type": "application/json"},
@@ -272,8 +301,10 @@ def __init__(
272301
self.default = default
273302
self.alias = alias
274303
self.description = description
304+
self.type_initialized = False
305+
self.type_cache: "Union[pydantic.TypeAdapter[Any], None]" = None
275306

276-
def __call__(
307+
def __call__( # noqa: C901
277308
self,
278309
param_info: ParamInfo = Depends(),
279310
request: web.Request = Depends(),
@@ -288,21 +319,26 @@ def __call__(
288319
:return: parsed data.
289320
"""
290321
matched_data = request.match_info.get(self.alias or param_info.name)
291-
definition = None
292-
if (
293-
param_info.definition
294-
and param_info.definition.annotation != inspect.Parameter.empty
295-
):
296-
definition = param_info.definition.annotation
297-
298-
if definition is None:
322+
323+
if not self.type_initialized:
324+
if (
325+
param_info.definition
326+
and param_info.definition.annotation != inspect.Parameter.empty
327+
):
328+
self.type_cache = pydantic.TypeAdapter(param_info.definition.annotation)
329+
else:
330+
self.type_cache = None
331+
self.type_initialized = True
332+
333+
if self.type_cache is None:
299334
return matched_data
300335

301336
try:
302-
return pydantic.parse_obj_as(definition, matched_data)
337+
return self.type_cache.validate_python(matched_data)
303338
except pydantic.ValidationError as err:
304-
errors = err.errors()
339+
errors = err.errors(include_url=False)
305340
for error in errors:
341+
error.pop("input", None) # type: ignore
306342
error["loc"] = ("path",) + error["loc"]
307343
raise web.HTTPBadRequest(
308344
headers={"Content-Type": "application/json"},

0 commit comments

Comments
 (0)