feat: update pydantic
This commit is contained in:
		| @@ -10,6 +10,8 @@ from aiohttp.web_exceptions import HTTPMethodNotAllowed | |||||||
| from aiohttp.web_response import StreamResponse | from aiohttp.web_response import StreamResponse | ||||||
| from pydantic import ValidationError | from pydantic import ValidationError | ||||||
|  |  | ||||||
|  | from pydantic_core import ErrorDetails | ||||||
|  |  | ||||||
| from .injectors import ( | from .injectors import ( | ||||||
|     AbstractInjector, |     AbstractInjector, | ||||||
|     BodyGetter, |     BodyGetter, | ||||||
| @@ -22,6 +24,10 @@ from .injectors import ( | |||||||
| ) | ) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | class PydanticValidationError(ErrorDetails): | ||||||
|  |     loc_in: CONTEXT | ||||||
|  |  | ||||||
|  |  | ||||||
| class PydanticView(AbstractView): | class PydanticView(AbstractView): | ||||||
|     """ |     """ | ||||||
|     An AIOHTTP View that validate request using function annotations. |     An AIOHTTP View that validate request using function annotations. | ||||||
| @@ -91,7 +97,7 @@ class PydanticView(AbstractView): | |||||||
|         return injectors |         return injectors | ||||||
|  |  | ||||||
|     async def on_validation_error( |     async def on_validation_error( | ||||||
|         self, exception: ValidationError, context: CONTEXT |             self, exception: ValidationError, context: CONTEXT | ||||||
|     ) -> StreamResponse: |     ) -> StreamResponse: | ||||||
|         """ |         """ | ||||||
|         This method is a hook to intercept ValidationError. |         This method is a hook to intercept ValidationError. | ||||||
| @@ -101,14 +107,13 @@ class PydanticView(AbstractView): | |||||||
|         "headers", "path" or "query string" |         "headers", "path" or "query string" | ||||||
|         """ |         """ | ||||||
|         errors = exception.errors() |         errors = exception.errors() | ||||||
|         for error in errors: |         own_errors = [PydanticValidationError(**x, loc_in=context) for x in errors] | ||||||
|             error["in"] = context |  | ||||||
|  |  | ||||||
|         return json_response(data=errors, status=400) |         return json_response(data=own_errors, status=400) | ||||||
|  |  | ||||||
|  |  | ||||||
| def inject_params( | def inject_params( | ||||||
|     handler, parse_func_signature: Callable[[Callable], Iterable[AbstractInjector]] |         handler, parse_func_signature: Callable[[Callable], Iterable[AbstractInjector]] | ||||||
| ): | ): | ||||||
|     """ |     """ | ||||||
|     Decorator to unpack the query string, route path, body and http header in |     Decorator to unpack the query string, route path, body and http header in | ||||||
| @@ -146,6 +151,7 @@ def is_pydantic_view(obj) -> bool: | |||||||
|  |  | ||||||
|  |  | ||||||
| __all__ = ( | __all__ = ( | ||||||
|  |     "PydanticValidationError", | ||||||
|     "AbstractInjector", |     "AbstractInjector", | ||||||
|     "BodyGetter", |     "BodyGetter", | ||||||
|     "HeadersGetter", |     "HeadersGetter", | ||||||
|   | |||||||
| @@ -1,28 +1,8 @@ | |||||||
| aiohttp==3.8.1 | aiohttp==3.8.4 | ||||||
| aiosignal==1.2.0 | pydantic==2.0.2 | ||||||
| async-timeout==4.0.2 | jinja2==3.1.2 | ||||||
| atomicwrites==1.4.1 | swagger-4-ui-bundle==0.0.4 | ||||||
| attrs==21.4.0 | pytest==7.4.0 | ||||||
| bleach==5.0.1 |  | ||||||
| charset-normalizer==2.1.0 |  | ||||||
| colorama==0.4.5 |  | ||||||
| coverage==6.4.2 |  | ||||||
| docutils==0.19 |  | ||||||
| frozenlist==1.3.0 |  | ||||||
| idna==3.3 |  | ||||||
| iniconfig==1.1.1 |  | ||||||
| multidict==6.0.2 |  | ||||||
| packaging==21.3 |  | ||||||
| pluggy==1.0.0 |  | ||||||
| py==1.11.0 |  | ||||||
| Pygments==2.12.0 |  | ||||||
| pyparsing==3.0.9 |  | ||||||
| pytest==7.1.2 |  | ||||||
| pytest-aiohttp==1.0.4 | pytest-aiohttp==1.0.4 | ||||||
| pytest-asyncio==0.19.0 | pytest-asyncio==0.21.1 | ||||||
| pytest-cov==3.0.0 | pytest-cov==4.1.0 | ||||||
| readme-renderer==35.0 |  | ||||||
| six==1.16.0 |  | ||||||
| tomli==2.0.1 |  | ||||||
| webencodings==0.5.1 |  | ||||||
| yarl==1.7.2 |  | ||||||
|   | |||||||
| @@ -18,8 +18,8 @@ classifiers = | |||||||
|     Programming Language :: Python |     Programming Language :: Python | ||||||
|     Programming Language :: Python :: 3 |     Programming Language :: Python :: 3 | ||||||
|     Programming Language :: Python :: 3 :: Only |     Programming Language :: Python :: 3 :: Only | ||||||
|     Programming Language :: Python :: 3.8 |     Programming Language :: Python :: 3.10 | ||||||
|     Programming Language :: Python :: 3.9 |     Programming Language :: Python :: 3.11 | ||||||
|     Topic :: Software Development :: Libraries :: Application Frameworks |     Topic :: Software Development :: Libraries :: Application Frameworks | ||||||
|     Framework :: aiohttp |     Framework :: aiohttp | ||||||
|     License :: OSI Approved :: MIT License |     License :: OSI Approved :: MIT License | ||||||
| @@ -28,10 +28,10 @@ classifiers = | |||||||
| zip_safe = False | zip_safe = False | ||||||
| include_package_data = True | include_package_data = True | ||||||
| packages = find: | packages = find: | ||||||
| python_requires = >=3.8 | python_requires = >=3.10 | ||||||
| install_requires = | install_requires = | ||||||
|     aiohttp |     aiohttp | ||||||
|     pydantic>=1.7 |     pydantic>=2.0.0 | ||||||
|     swagger-4-ui-bundle |     swagger-4-ui-bundle | ||||||
|  |  | ||||||
| [options.extras_require] | [options.extras_require] | ||||||
|   | |||||||
| @@ -4,9 +4,10 @@ from typing import Iterator, List, Optional | |||||||
|  |  | ||||||
| from aiohttp import web | from aiohttp import web | ||||||
| from aiohttp.web_response import json_response | from aiohttp.web_response import json_response | ||||||
| from pydantic import BaseModel | from pydantic import BaseModel, RootModel | ||||||
|  |  | ||||||
| from aiohttp_pydantic import PydanticView | from aiohttp_pydantic import PydanticView | ||||||
|  | from aiohttp_pydantic.view import PydanticValidationError | ||||||
|  |  | ||||||
|  |  | ||||||
| class ArticleModel(BaseModel): | class ArticleModel(BaseModel): | ||||||
| @@ -14,11 +15,11 @@ class ArticleModel(BaseModel): | |||||||
|     nb_page: Optional[int] |     nb_page: Optional[int] | ||||||
|  |  | ||||||
|  |  | ||||||
| class ArticleModels(BaseModel): | class ArticleModels(RootModel): | ||||||
|     __root__: List[ArticleModel] |     root: List[ArticleModel] | ||||||
|  |  | ||||||
|     def __iter__(self) -> Iterator[ArticleModel]: |     def __iter__(self) -> Iterator[ArticleModel]: | ||||||
|         return iter(self.__root__) |         return iter(self.root) | ||||||
|  |  | ||||||
|  |  | ||||||
| class ArticleView(PydanticView): | class ArticleView(PydanticView): | ||||||
| @@ -30,14 +31,12 @@ class ArticleView(PydanticView): | |||||||
|  |  | ||||||
|     async def on_validation_error(self, exception, context): |     async def on_validation_error(self, exception, context): | ||||||
|         errors = exception.errors() |         errors = exception.errors() | ||||||
|         for error in errors: |         own_errors = [PydanticValidationError(**x, loc_in=context) for x in errors] | ||||||
|             error["in"] = context |         return json_response(data=own_errors, status=400) | ||||||
|             error["custom"] = "custom" |  | ||||||
|         return json_response(data=errors, status=400) |  | ||||||
|  |  | ||||||
|  |  | ||||||
| async def test_post_an_article_with_wrong_type_field_should_return_an_error_message( | async def test_post_an_article_with_wrong_type_field_should_return_an_error_message( | ||||||
|     aiohttp_client, event_loop |         aiohttp_client, event_loop | ||||||
| ): | ): | ||||||
|     app = web.Application() |     app = web.Application() | ||||||
|     app.router.add_view("/article", ArticleView) |     app.router.add_view("/article", ArticleView) | ||||||
| @@ -47,12 +46,14 @@ async def test_post_an_article_with_wrong_type_field_should_return_an_error_mess | |||||||
|  |  | ||||||
|     assert resp.status == 400 |     assert resp.status == 400 | ||||||
|     assert resp.content_type == "application/json" |     assert resp.content_type == "application/json" | ||||||
|  |  | ||||||
|     assert await resp.json() == [ |     assert await resp.json() == [ | ||||||
|         { |         { | ||||||
|             "in": "body", |             'loc_in': 'body', | ||||||
|             "loc": ["nb_page"], |             'input': 'foo', | ||||||
|             "msg": "value is not a valid integer", |             'loc': ['nb_page'], | ||||||
|             "custom": "custom", |             'msg': 'Input should be a valid integer, unable to parse string as an integer', | ||||||
|             "type": "type_error.integer", |             'type': 'int_parsing', | ||||||
|  |             'url': 'https://errors.pydantic.dev/2.1.2/v/int_parsing' | ||||||
|         } |         } | ||||||
|     ] |     ] | ||||||
|   | |||||||
| @@ -6,7 +6,7 @@ from uuid import UUID | |||||||
|  |  | ||||||
| import pytest | import pytest | ||||||
| from aiohttp import web | from aiohttp import web | ||||||
| from pydantic import Field | from pydantic import Field, RootModel | ||||||
| from pydantic.main import BaseModel | from pydantic.main import BaseModel | ||||||
|  |  | ||||||
| from aiohttp_pydantic import PydanticView, oas | from aiohttp_pydantic import PydanticView, oas | ||||||
| @@ -52,8 +52,7 @@ class Dog(BaseModel): | |||||||
|     barks: float |     barks: float | ||||||
|  |  | ||||||
|  |  | ||||||
| class Animal(BaseModel): | Animal = RootModel[Annotated[Union[Cat, Dog], Field(discriminator='pet_type')]] | ||||||
|     __root__: Annotated[Union[Cat, Dog], Field(discriminator='pet_type')] |  | ||||||
|  |  | ||||||
|  |  | ||||||
| class PetCollectionView(PydanticView): | class PetCollectionView(PydanticView): | ||||||
|   | |||||||
| @@ -3,21 +3,21 @@ from __future__ import annotations | |||||||
| from typing import Iterator, List, Optional | from typing import Iterator, List, Optional | ||||||
|  |  | ||||||
| from aiohttp import web | from aiohttp import web | ||||||
| from pydantic import BaseModel | from pydantic import BaseModel, RootModel | ||||||
|  |  | ||||||
| from aiohttp_pydantic import PydanticView | from aiohttp_pydantic import PydanticView | ||||||
|  |  | ||||||
|  |  | ||||||
| class ArticleModel(BaseModel): | class ArticleModel(BaseModel): | ||||||
|     name: str |     name: str | ||||||
|     nb_page: Optional[int] |     nb_page: Optional[int] = None | ||||||
|  |  | ||||||
|  |  | ||||||
| class ArticleModels(BaseModel): | class ArticleModels(RootModel): | ||||||
|     __root__: List[ArticleModel] |     root: List[ArticleModel] | ||||||
|  |  | ||||||
|     def __iter__(self) -> Iterator[ArticleModel]: |     def __iter__(self) -> Iterator[ArticleModel]: | ||||||
|         return iter(self.__root__) |         return iter(self.root) | ||||||
|  |  | ||||||
|  |  | ||||||
| class ArticleView(PydanticView): | class ArticleView(PydanticView): | ||||||
| @@ -29,7 +29,7 @@ class ArticleView(PydanticView): | |||||||
|  |  | ||||||
|  |  | ||||||
| async def test_post_an_article_without_required_field_should_return_an_error_message( | async def test_post_an_article_without_required_field_should_return_an_error_message( | ||||||
|     aiohttp_client, event_loop |         aiohttp_client, event_loop | ||||||
| ): | ): | ||||||
|     app = web.Application() |     app = web.Application() | ||||||
|     app.router.add_view("/article", ArticleView) |     app.router.add_view("/article", ArticleView) | ||||||
| @@ -38,18 +38,21 @@ async def test_post_an_article_without_required_field_should_return_an_error_mes | |||||||
|     resp = await client.post("/article", json={}) |     resp = await client.post("/article", json={}) | ||||||
|     assert resp.status == 400 |     assert resp.status == 400 | ||||||
|     assert resp.content_type == "application/json" |     assert resp.content_type == "application/json" | ||||||
|  |  | ||||||
|     assert await resp.json() == [ |     assert await resp.json() == [ | ||||||
|         { |         { | ||||||
|             "in": "body", |             'input': {}, | ||||||
|             "loc": ["name"], |             'loc': ['name'], | ||||||
|             "msg": "field required", |             'loc_in': 'body', | ||||||
|             "type": "value_error.missing", |             'msg': 'Field required', | ||||||
|  |             'type': 'missing', | ||||||
|  |             'url': 'https://errors.pydantic.dev/2.1.2/v/missing' | ||||||
|         } |         } | ||||||
|     ] |     ] | ||||||
|  |  | ||||||
|  |  | ||||||
| async def test_post_an_article_with_wrong_type_field_should_return_an_error_message( | async def test_post_an_article_with_wrong_type_field_should_return_an_error_message( | ||||||
|     aiohttp_client, event_loop |         aiohttp_client, event_loop | ||||||
| ): | ): | ||||||
|     app = web.Application() |     app = web.Application() | ||||||
|     app.router.add_view("/article", ArticleView) |     app.router.add_view("/article", ArticleView) | ||||||
| @@ -60,10 +63,12 @@ async def test_post_an_article_with_wrong_type_field_should_return_an_error_mess | |||||||
|     assert resp.content_type == "application/json" |     assert resp.content_type == "application/json" | ||||||
|     assert await resp.json() == [ |     assert await resp.json() == [ | ||||||
|         { |         { | ||||||
|             "in": "body", |             'input': 'foo', | ||||||
|             "loc": ["nb_page"], |             'loc': ['nb_page'], | ||||||
|             "msg": "value is not a valid integer", |             'loc_in': 'body', | ||||||
|             "type": "type_error.integer", |             'msg': 'Input should be a valid integer, unable to parse string as an integer', | ||||||
|  |             'type': 'int_parsing', | ||||||
|  |             'url': 'https://errors.pydantic.dev/2.1.2/v/int_parsing' | ||||||
|         } |         } | ||||||
|     ] |     ] | ||||||
|  |  | ||||||
| @@ -81,7 +86,7 @@ async def test_post_an_array_json_is_supported(aiohttp_client, event_loop): | |||||||
|  |  | ||||||
|  |  | ||||||
| async def test_post_an_array_json_to_an_object_model_should_return_an_error( | async def test_post_an_array_json_to_an_object_model_should_return_an_error( | ||||||
|     aiohttp_client, event_loop |         aiohttp_client, event_loop | ||||||
| ): | ): | ||||||
|     app = web.Application() |     app = web.Application() | ||||||
|     app.router.add_view("/article", ArticleView) |     app.router.add_view("/article", ArticleView) | ||||||
| @@ -101,7 +106,7 @@ async def test_post_an_array_json_to_an_object_model_should_return_an_error( | |||||||
|  |  | ||||||
|  |  | ||||||
| async def test_post_an_object_json_to_a_list_model_should_return_an_error( | async def test_post_an_object_json_to_a_list_model_should_return_an_error( | ||||||
|     aiohttp_client, event_loop |         aiohttp_client, event_loop | ||||||
| ): | ): | ||||||
|     app = web.Application() |     app = web.Application() | ||||||
|     app.router.add_view("/article", ArticleView) |     app.router.add_view("/article", ArticleView) | ||||||
| @@ -110,12 +115,15 @@ async def test_post_an_object_json_to_a_list_model_should_return_an_error( | |||||||
|     resp = await client.put("/article", json={"name": "foo", "nb_page": 3}) |     resp = await client.put("/article", json={"name": "foo", "nb_page": 3}) | ||||||
|     assert resp.status == 400 |     assert resp.status == 400 | ||||||
|     assert resp.content_type == "application/json" |     assert resp.content_type == "application/json" | ||||||
|  |  | ||||||
|     assert await resp.json() == [ |     assert await resp.json() == [ | ||||||
|         { |         { | ||||||
|             "in": "body", |             'input': {'name': 'foo', 'nb_page': 3}, | ||||||
|             "loc": ["__root__"], |             'loc': [], | ||||||
|             "msg": "value is not a valid list", |             'loc_in': 'body', | ||||||
|             "type": "type_error.list", |             'msg': 'Input should be a valid list', | ||||||
|  |             'type': 'list_type', | ||||||
|  |             'url': 'https://errors.pydantic.dev/2.1.2/v/list_type' | ||||||
|         } |         } | ||||||
|     ] |     ] | ||||||
|  |  | ||||||
|   | |||||||
| @@ -50,9 +50,9 @@ class Signature(Group): | |||||||
|  |  | ||||||
| class ArticleViewWithSignatureGroup(PydanticView): | class ArticleViewWithSignatureGroup(PydanticView): | ||||||
|     async def get( |     async def get( | ||||||
|         self, |             self, | ||||||
|         *, |             *, | ||||||
|         signature: Signature, |             signature: Signature, | ||||||
|     ): |     ): | ||||||
|         return web.json_response( |         return web.json_response( | ||||||
|             {"expired": signature.expired, "scope": signature.scope}, |             {"expired": signature.expired, "scope": signature.scope}, | ||||||
| @@ -61,7 +61,7 @@ class ArticleViewWithSignatureGroup(PydanticView): | |||||||
|  |  | ||||||
|  |  | ||||||
| async def test_get_article_without_required_header_should_return_an_error_message( | async def test_get_article_without_required_header_should_return_an_error_message( | ||||||
|     aiohttp_client, event_loop |         aiohttp_client, event_loop | ||||||
| ): | ): | ||||||
|     app = web.Application() |     app = web.Application() | ||||||
|     app.router.add_view("/article", ArticleView) |     app.router.add_view("/article", ArticleView) | ||||||
| @@ -70,18 +70,24 @@ async def test_get_article_without_required_header_should_return_an_error_messag | |||||||
|     resp = await client.get("/article", headers={}) |     resp = await client.get("/article", headers={}) | ||||||
|     assert resp.status == 400 |     assert resp.status == 400 | ||||||
|     assert resp.content_type == "application/json" |     assert resp.content_type == "application/json" | ||||||
|     assert await resp.json() == [ |  | ||||||
|  |     result = await resp.json() | ||||||
|  |     assert len(result) == 1 | ||||||
|  |     result[0].pop('input') | ||||||
|  |  | ||||||
|  |     assert result == [ | ||||||
|         { |         { | ||||||
|             "in": "headers", |             'type': 'missing', | ||||||
|             "loc": ["signature_expired"], |             'loc': ['signature_expired'], | ||||||
|             "msg": "field required", |             'msg': 'Field required', | ||||||
|             "type": "value_error.missing", |             'url': 'https://errors.pydantic.dev/2.1.2/v/missing', | ||||||
|  |             'loc_in': 'headers' | ||||||
|         } |         } | ||||||
|     ] |     ] | ||||||
|  |  | ||||||
|  |  | ||||||
| async def test_get_article_with_wrong_header_type_should_return_an_error_message( | async def test_get_article_with_wrong_header_type_should_return_an_error_message( | ||||||
|     aiohttp_client, event_loop |         aiohttp_client, event_loop | ||||||
| ): | ): | ||||||
|     app = web.Application() |     app = web.Application() | ||||||
|     app.router.add_view("/article", ArticleView) |     app.router.add_view("/article", ArticleView) | ||||||
| @@ -90,18 +96,22 @@ async def test_get_article_with_wrong_header_type_should_return_an_error_message | |||||||
|     resp = await client.get("/article", headers={"signature_expired": "foo"}) |     resp = await client.get("/article", headers={"signature_expired": "foo"}) | ||||||
|     assert resp.status == 400 |     assert resp.status == 400 | ||||||
|     assert resp.content_type == "application/json" |     assert resp.content_type == "application/json" | ||||||
|  |  | ||||||
|     assert await resp.json() == [ |     assert await resp.json() == [ | ||||||
|         { |         { | ||||||
|             "in": "headers", |             'type': 'datetime_parsing', | ||||||
|             "loc": ["signature_expired"], |             'loc': ['signature_expired'], | ||||||
|             "msg": "invalid datetime format", |             'msg': 'Input should be a valid datetime, input is too short', | ||||||
|             "type": "value_error.datetime", |             'input': 'foo', | ||||||
|  |             'ctx': {'error': 'input is too short'}, | ||||||
|  |             'url': 'https://errors.pydantic.dev/2.1.2/v/datetime_parsing', | ||||||
|  |             'loc_in': 'headers' | ||||||
|         } |         } | ||||||
|     ] |     ] | ||||||
|  |  | ||||||
|  |  | ||||||
| async def test_get_article_with_valid_header_should_return_the_parsed_type( | async def test_get_article_with_valid_header_should_return_the_parsed_type( | ||||||
|     aiohttp_client, event_loop |         aiohttp_client, event_loop | ||||||
| ): | ): | ||||||
|     app = web.Application() |     app = web.Application() | ||||||
|     app.router.add_view("/article", ArticleView) |     app.router.add_view("/article", ArticleView) | ||||||
| @@ -116,7 +126,7 @@ async def test_get_article_with_valid_header_should_return_the_parsed_type( | |||||||
|  |  | ||||||
|  |  | ||||||
| async def test_get_article_with_valid_header_containing_hyphen_should_be_returned( | async def test_get_article_with_valid_header_containing_hyphen_should_be_returned( | ||||||
|     aiohttp_client, event_loop |         aiohttp_client, event_loop | ||||||
| ): | ): | ||||||
|     app = web.Application() |     app = web.Application() | ||||||
|     app.router.add_view("/article", ArticleView) |     app.router.add_view("/article", ArticleView) | ||||||
| @@ -136,18 +146,20 @@ async def test_wrong_value_to_header_defined_with_str_enum(aiohttp_client, event | |||||||
|  |  | ||||||
|     client = await aiohttp_client(app) |     client = await aiohttp_client(app) | ||||||
|     resp = await client.get("/coord", headers={"format": "WGS84"}) |     resp = await client.get("/coord", headers={"format": "WGS84"}) | ||||||
|  |  | ||||||
|     assert ( |     assert ( | ||||||
|         await resp.json() |             await resp.json() | ||||||
|         == [ |             == [ | ||||||
|             { |                 { | ||||||
|                 "ctx": {"enum_values": ["UMT", "MGRS"]}, |                     'ctx': {'expected': "'UMT' or 'MGRS'"}, | ||||||
|                 "in": "headers", |                     'input': 'WGS84', | ||||||
|                 "loc": ["format"], |                     'loc': ['format'], | ||||||
|                 "msg": "value is not a valid enumeration member; permitted: 'UMT', 'MGRS'", |                     'loc_in': 'headers', | ||||||
|                 "type": "type_error.enum", |                     'msg': "Input should be 'UMT' or 'MGRS'", | ||||||
|             } |                     'type': 'enum' | ||||||
|         ] |                 } | ||||||
|         != {"signature": "2020-10-04T18:01:00"} |             ] | ||||||
|  |             != {"signature": "2020-10-04T18:01:00"} | ||||||
|     ) |     ) | ||||||
|     assert resp.status == 400 |     assert resp.status == 400 | ||||||
|     assert resp.content_type == "application/json" |     assert resp.content_type == "application/json" | ||||||
|   | |||||||
| @@ -33,11 +33,14 @@ async def test_get_article_with_wrong_path_parameters_should_return_error( | |||||||
|     resp = await client.get("/article/1234/tag/music/before/now") |     resp = await client.get("/article/1234/tag/music/before/now") | ||||||
|     assert resp.status == 400 |     assert resp.status == 400 | ||||||
|     assert resp.content_type == "application/json" |     assert resp.content_type == "application/json" | ||||||
|  |  | ||||||
|     assert await resp.json() == [ |     assert await resp.json() == [ | ||||||
|         { |         { | ||||||
|             "in": "path", |             'input': 'now', | ||||||
|             "loc": ["date"], |             'loc': ['date'], | ||||||
|             "msg": "value is not a valid integer", |             'loc_in': 'path', | ||||||
|             "type": "type_error.integer", |             'msg': 'Input should be a valid integer, unable to parse string as an integer', | ||||||
|  |             'type': 'int_parsing', | ||||||
|  |             'url': 'https://errors.pydantic.dev/2.1.2/v/int_parsing' | ||||||
|         } |         } | ||||||
|     ] |     ] | ||||||
|   | |||||||
| @@ -11,11 +11,11 @@ from aiohttp_pydantic.injectors import Group | |||||||
|  |  | ||||||
| class ArticleView(PydanticView): | class ArticleView(PydanticView): | ||||||
|     async def get( |     async def get( | ||||||
|         self, |             self, | ||||||
|         with_comments: bool, |             with_comments: bool, | ||||||
|         age: Optional[int] = None, |             age: Optional[int] = None, | ||||||
|         nb_items: int = 7, |             nb_items: int = 7, | ||||||
|         tags: List[str] = Field(default_factory=list), |             tags: List[str] = Field(default_factory=list), | ||||||
|     ): |     ): | ||||||
|         return web.json_response( |         return web.json_response( | ||||||
|             { |             { | ||||||
| @@ -42,9 +42,9 @@ class Pagination(Group): | |||||||
|  |  | ||||||
| class ArticleViewWithPaginationGroup(PydanticView): | class ArticleViewWithPaginationGroup(PydanticView): | ||||||
|     async def get( |     async def get( | ||||||
|         self, |             self, | ||||||
|         with_comments: bool, |             with_comments: bool, | ||||||
|         page: Pagination, |             page: Pagination, | ||||||
|     ): |     ): | ||||||
|         return web.json_response( |         return web.json_response( | ||||||
|             { |             { | ||||||
| @@ -70,7 +70,7 @@ class ArticleViewWithEnumInQuery(PydanticView): | |||||||
|  |  | ||||||
|  |  | ||||||
| async def test_get_article_without_required_qs_should_return_an_error_message( | async def test_get_article_without_required_qs_should_return_an_error_message( | ||||||
|     aiohttp_client, event_loop |         aiohttp_client, event_loop | ||||||
| ): | ): | ||||||
|     app = web.Application() |     app = web.Application() | ||||||
|     app.router.add_view("/article", ArticleView) |     app.router.add_view("/article", ArticleView) | ||||||
| @@ -79,18 +79,21 @@ async def test_get_article_without_required_qs_should_return_an_error_message( | |||||||
|     resp = await client.get("/article") |     resp = await client.get("/article") | ||||||
|     assert resp.status == 400 |     assert resp.status == 400 | ||||||
|     assert resp.content_type == "application/json" |     assert resp.content_type == "application/json" | ||||||
|  |  | ||||||
|     assert await resp.json() == [ |     assert await resp.json() == [ | ||||||
|         { |         { | ||||||
|             "in": "query string", |             'input': {}, | ||||||
|             "loc": ["with_comments"], |             'loc': ['with_comments'], | ||||||
|             "msg": "field required", |             'loc_in': 'query string', | ||||||
|             "type": "value_error.missing", |             'msg': 'Field required', | ||||||
|  |             'type': 'missing', | ||||||
|  |             'url': 'https://errors.pydantic.dev/2.1.2/v/missing' | ||||||
|         } |         } | ||||||
|     ] |     ] | ||||||
|  |  | ||||||
|  |  | ||||||
| async def test_get_article_with_wrong_qs_type_should_return_an_error_message( | async def test_get_article_with_wrong_qs_type_should_return_an_error_message( | ||||||
|     aiohttp_client, event_loop |         aiohttp_client, event_loop | ||||||
| ): | ): | ||||||
|     app = web.Application() |     app = web.Application() | ||||||
|     app.router.add_view("/article", ArticleView) |     app.router.add_view("/article", ArticleView) | ||||||
| @@ -101,16 +104,18 @@ async def test_get_article_with_wrong_qs_type_should_return_an_error_message( | |||||||
|     assert resp.content_type == "application/json" |     assert resp.content_type == "application/json" | ||||||
|     assert await resp.json() == [ |     assert await resp.json() == [ | ||||||
|         { |         { | ||||||
|             "in": "query string", |             'input': 'foo', | ||||||
|             "loc": ["with_comments"], |             'loc': ['with_comments'], | ||||||
|             "msg": "value could not be parsed to a boolean", |             'loc_in': 'query string', | ||||||
|             "type": "type_error.bool", |             'msg': 'Input should be a valid boolean, unable to interpret input', | ||||||
|  |             'type': 'bool_parsing', | ||||||
|  |             'url': 'https://errors.pydantic.dev/2.1.2/v/bool_parsing' | ||||||
|         } |         } | ||||||
|     ] |     ] | ||||||
|  |  | ||||||
|  |  | ||||||
| async def test_get_article_with_valid_qs_should_return_the_parsed_type( | async def test_get_article_with_valid_qs_should_return_the_parsed_type( | ||||||
|     aiohttp_client, event_loop |         aiohttp_client, event_loop | ||||||
| ): | ): | ||||||
|     app = web.Application() |     app = web.Application() | ||||||
|     app.router.add_view("/article", ArticleView) |     app.router.add_view("/article", ArticleView) | ||||||
| @@ -129,7 +134,7 @@ async def test_get_article_with_valid_qs_should_return_the_parsed_type( | |||||||
|  |  | ||||||
|  |  | ||||||
| async def test_get_article_with_valid_qs_and_omitted_optional_should_return_default_value( | async def test_get_article_with_valid_qs_and_omitted_optional_should_return_default_value( | ||||||
|     aiohttp_client, event_loop |         aiohttp_client, event_loop | ||||||
| ): | ): | ||||||
|     app = web.Application() |     app = web.Application() | ||||||
|     app.router.add_view("/article", ArticleView) |     app.router.add_view("/article", ArticleView) | ||||||
| @@ -148,7 +153,7 @@ async def test_get_article_with_valid_qs_and_omitted_optional_should_return_defa | |||||||
|  |  | ||||||
|  |  | ||||||
| async def test_get_article_with_multiple_value_for_qs_age_must_failed( | async def test_get_article_with_multiple_value_for_qs_age_must_failed( | ||||||
|     aiohttp_client, event_loop |         aiohttp_client, event_loop | ||||||
| ): | ): | ||||||
|     app = web.Application() |     app = web.Application() | ||||||
|     app.router.add_view("/article", ArticleView) |     app.router.add_view("/article", ArticleView) | ||||||
| @@ -158,10 +163,12 @@ async def test_get_article_with_multiple_value_for_qs_age_must_failed( | |||||||
|     resp = await client.get("/article", params={"age": ["2", "3"], "with_comments": 1}) |     resp = await client.get("/article", params={"age": ["2", "3"], "with_comments": 1}) | ||||||
|     assert await resp.json() == [ |     assert await resp.json() == [ | ||||||
|         { |         { | ||||||
|             "in": "query string", |             'input': ['2', '3'], | ||||||
|             "loc": ["age"], |             'loc': ['age'], | ||||||
|             "msg": "value is not a valid integer", |             'loc_in': 'query string', | ||||||
|             "type": "type_error.integer", |             'msg': 'Input should be a valid integer', | ||||||
|  |             'type': 'int_type', | ||||||
|  |             'url': 'https://errors.pydantic.dev/2.1.2/v/int_type' | ||||||
|         } |         } | ||||||
|     ] |     ] | ||||||
|     assert resp.status == 400 |     assert resp.status == 400 | ||||||
| @@ -215,10 +222,12 @@ async def test_get_article_without_required_field_page(aiohttp_client, event_loo | |||||||
|     resp = await client.get("/article", params={"with_comments": 1}) |     resp = await client.get("/article", params={"with_comments": 1}) | ||||||
|     assert await resp.json() == [ |     assert await resp.json() == [ | ||||||
|         { |         { | ||||||
|             "in": "query string", |             'input': {'with_comments': '1'}, | ||||||
|             "loc": ["page_num"], |             'loc': ['page_num'], | ||||||
|             "msg": "field required", |             'loc_in': 'query string', | ||||||
|             "type": "value_error.missing", |             'msg': 'Field required', | ||||||
|  |             'type': 'missing', | ||||||
|  |             'url': 'https://errors.pydantic.dev/2.1.2/v/missing' | ||||||
|         } |         } | ||||||
|     ] |     ] | ||||||
|     assert resp.status == 400 |     assert resp.status == 400 | ||||||
| @@ -276,10 +285,13 @@ async def test_get_article_with_page_and_wrong_page_size(aiohttp_client, event_l | |||||||
|     ) |     ) | ||||||
|     assert await resp.json() == [ |     assert await resp.json() == [ | ||||||
|         { |         { | ||||||
|             "in": "query string", |             'input': 'large', | ||||||
|             "loc": ["page_size"], |             'loc': ['page_size'], | ||||||
|             "msg": "value is not a valid integer", |             'loc_in': 'query string', | ||||||
|             "type": "type_error.integer", |             'msg': 'Input should be a valid integer, unable to parse string as an ' | ||||||
|  |                    'integer', | ||||||
|  |             'type': 'int_parsing', | ||||||
|  |             'url': 'https://errors.pydantic.dev/2.1.2/v/int_parsing' | ||||||
|         } |         } | ||||||
|     ] |     ] | ||||||
|     assert resp.status == 400 |     assert resp.status == 400 | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user