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 pydantic import ValidationError | ||||
|  | ||||
| from pydantic_core import ErrorDetails | ||||
|  | ||||
| from .injectors import ( | ||||
|     AbstractInjector, | ||||
|     BodyGetter, | ||||
| @@ -22,6 +24,10 @@ from .injectors import ( | ||||
| ) | ||||
|  | ||||
|  | ||||
| class PydanticValidationError(ErrorDetails): | ||||
|     loc_in: CONTEXT | ||||
|  | ||||
|  | ||||
| class PydanticView(AbstractView): | ||||
|     """ | ||||
|     An AIOHTTP View that validate request using function annotations. | ||||
| @@ -91,7 +97,7 @@ class PydanticView(AbstractView): | ||||
|         return injectors | ||||
|  | ||||
|     async def on_validation_error( | ||||
|         self, exception: ValidationError, context: CONTEXT | ||||
|             self, exception: ValidationError, context: CONTEXT | ||||
|     ) -> StreamResponse: | ||||
|         """ | ||||
|         This method is a hook to intercept ValidationError. | ||||
| @@ -101,14 +107,13 @@ class PydanticView(AbstractView): | ||||
|         "headers", "path" or "query string" | ||||
|         """ | ||||
|         errors = exception.errors() | ||||
|         for error in errors: | ||||
|             error["in"] = context | ||||
|         own_errors = [PydanticValidationError(**x, loc_in=context) for x in errors] | ||||
|  | ||||
|         return json_response(data=errors, status=400) | ||||
|         return json_response(data=own_errors, status=400) | ||||
|  | ||||
|  | ||||
| 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 | ||||
| @@ -146,6 +151,7 @@ def is_pydantic_view(obj) -> bool: | ||||
|  | ||||
|  | ||||
| __all__ = ( | ||||
|     "PydanticValidationError", | ||||
|     "AbstractInjector", | ||||
|     "BodyGetter", | ||||
|     "HeadersGetter", | ||||
|   | ||||
| @@ -1,28 +1,8 @@ | ||||
| aiohttp==3.8.1 | ||||
| aiosignal==1.2.0 | ||||
| async-timeout==4.0.2 | ||||
| atomicwrites==1.4.1 | ||||
| attrs==21.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 | ||||
| aiohttp==3.8.4 | ||||
| pydantic==2.0.2 | ||||
| jinja2==3.1.2 | ||||
| swagger-4-ui-bundle==0.0.4 | ||||
| pytest==7.4.0 | ||||
| pytest-aiohttp==1.0.4 | ||||
| pytest-asyncio==0.19.0 | ||||
| pytest-cov==3.0.0 | ||||
| readme-renderer==35.0 | ||||
| six==1.16.0 | ||||
| tomli==2.0.1 | ||||
| webencodings==0.5.1 | ||||
| yarl==1.7.2 | ||||
| pytest-asyncio==0.21.1 | ||||
| pytest-cov==4.1.0 | ||||
|   | ||||
| @@ -18,8 +18,8 @@ classifiers = | ||||
|     Programming Language :: Python | ||||
|     Programming Language :: Python :: 3 | ||||
|     Programming Language :: Python :: 3 :: Only | ||||
|     Programming Language :: Python :: 3.8 | ||||
|     Programming Language :: Python :: 3.9 | ||||
|     Programming Language :: Python :: 3.10 | ||||
|     Programming Language :: Python :: 3.11 | ||||
|     Topic :: Software Development :: Libraries :: Application Frameworks | ||||
|     Framework :: aiohttp | ||||
|     License :: OSI Approved :: MIT License | ||||
| @@ -28,10 +28,10 @@ classifiers = | ||||
| zip_safe = False | ||||
| include_package_data = True | ||||
| packages = find: | ||||
| python_requires = >=3.8 | ||||
| python_requires = >=3.10 | ||||
| install_requires = | ||||
|     aiohttp | ||||
|     pydantic>=1.7 | ||||
|     pydantic>=2.0.0 | ||||
|     swagger-4-ui-bundle | ||||
|  | ||||
| [options.extras_require] | ||||
|   | ||||
| @@ -4,9 +4,10 @@ from typing import Iterator, List, Optional | ||||
|  | ||||
| from aiohttp import web | ||||
| from aiohttp.web_response import json_response | ||||
| from pydantic import BaseModel | ||||
| from pydantic import BaseModel, RootModel | ||||
|  | ||||
| from aiohttp_pydantic import PydanticView | ||||
| from aiohttp_pydantic.view import PydanticValidationError | ||||
|  | ||||
|  | ||||
| class ArticleModel(BaseModel): | ||||
| @@ -14,11 +15,11 @@ class ArticleModel(BaseModel): | ||||
|     nb_page: Optional[int] | ||||
|  | ||||
|  | ||||
| class ArticleModels(BaseModel): | ||||
|     __root__: List[ArticleModel] | ||||
| class ArticleModels(RootModel): | ||||
|     root: List[ArticleModel] | ||||
|  | ||||
|     def __iter__(self) -> Iterator[ArticleModel]: | ||||
|         return iter(self.__root__) | ||||
|         return iter(self.root) | ||||
|  | ||||
|  | ||||
| class ArticleView(PydanticView): | ||||
| @@ -30,14 +31,12 @@ class ArticleView(PydanticView): | ||||
|  | ||||
|     async def on_validation_error(self, exception, context): | ||||
|         errors = exception.errors() | ||||
|         for error in errors: | ||||
|             error["in"] = context | ||||
|             error["custom"] = "custom" | ||||
|         return json_response(data=errors, status=400) | ||||
|         own_errors = [PydanticValidationError(**x, loc_in=context) for x in errors] | ||||
|         return json_response(data=own_errors, status=400) | ||||
|  | ||||
|  | ||||
| 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.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.content_type == "application/json" | ||||
|  | ||||
|     assert await resp.json() == [ | ||||
|         { | ||||
|             "in": "body", | ||||
|             "loc": ["nb_page"], | ||||
|             "msg": "value is not a valid integer", | ||||
|             "custom": "custom", | ||||
|             "type": "type_error.integer", | ||||
|             'loc_in': 'body', | ||||
|             'input': 'foo', | ||||
|             'loc': ['nb_page'], | ||||
|             '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' | ||||
|         } | ||||
|     ] | ||||
|   | ||||
| @@ -6,7 +6,7 @@ from uuid import UUID | ||||
|  | ||||
| import pytest | ||||
| from aiohttp import web | ||||
| from pydantic import Field | ||||
| from pydantic import Field, RootModel | ||||
| from pydantic.main import BaseModel | ||||
|  | ||||
| from aiohttp_pydantic import PydanticView, oas | ||||
| @@ -52,8 +52,7 @@ class Dog(BaseModel): | ||||
|     barks: float | ||||
|  | ||||
|  | ||||
| class Animal(BaseModel): | ||||
|     __root__: Annotated[Union[Cat, Dog], Field(discriminator='pet_type')] | ||||
| Animal = RootModel[Annotated[Union[Cat, Dog], Field(discriminator='pet_type')]] | ||||
|  | ||||
|  | ||||
| class PetCollectionView(PydanticView): | ||||
|   | ||||
| @@ -3,21 +3,21 @@ from __future__ import annotations | ||||
| from typing import Iterator, List, Optional | ||||
|  | ||||
| from aiohttp import web | ||||
| from pydantic import BaseModel | ||||
| from pydantic import BaseModel, RootModel | ||||
|  | ||||
| from aiohttp_pydantic import PydanticView | ||||
|  | ||||
|  | ||||
| class ArticleModel(BaseModel): | ||||
|     name: str | ||||
|     nb_page: Optional[int] | ||||
|     nb_page: Optional[int] = None | ||||
|  | ||||
|  | ||||
| class ArticleModels(BaseModel): | ||||
|     __root__: List[ArticleModel] | ||||
| class ArticleModels(RootModel): | ||||
|     root: List[ArticleModel] | ||||
|  | ||||
|     def __iter__(self) -> Iterator[ArticleModel]: | ||||
|         return iter(self.__root__) | ||||
|         return iter(self.root) | ||||
|  | ||||
|  | ||||
| class ArticleView(PydanticView): | ||||
| @@ -29,7 +29,7 @@ class ArticleView(PydanticView): | ||||
|  | ||||
|  | ||||
| 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.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={}) | ||||
|     assert resp.status == 400 | ||||
|     assert resp.content_type == "application/json" | ||||
|  | ||||
|     assert await resp.json() == [ | ||||
|         { | ||||
|             "in": "body", | ||||
|             "loc": ["name"], | ||||
|             "msg": "field required", | ||||
|             "type": "value_error.missing", | ||||
|             'input': {}, | ||||
|             'loc': ['name'], | ||||
|             'loc_in': 'body', | ||||
|             '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( | ||||
|     aiohttp_client, event_loop | ||||
|         aiohttp_client, event_loop | ||||
| ): | ||||
|     app = web.Application() | ||||
|     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 await resp.json() == [ | ||||
|         { | ||||
|             "in": "body", | ||||
|             "loc": ["nb_page"], | ||||
|             "msg": "value is not a valid integer", | ||||
|             "type": "type_error.integer", | ||||
|             'input': 'foo', | ||||
|             'loc': ['nb_page'], | ||||
|             'loc_in': 'body', | ||||
|             '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( | ||||
|     aiohttp_client, event_loop | ||||
|         aiohttp_client, event_loop | ||||
| ): | ||||
|     app = web.Application() | ||||
|     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( | ||||
|     aiohttp_client, event_loop | ||||
|         aiohttp_client, event_loop | ||||
| ): | ||||
|     app = web.Application() | ||||
|     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}) | ||||
|     assert resp.status == 400 | ||||
|     assert resp.content_type == "application/json" | ||||
|  | ||||
|     assert await resp.json() == [ | ||||
|         { | ||||
|             "in": "body", | ||||
|             "loc": ["__root__"], | ||||
|             "msg": "value is not a valid list", | ||||
|             "type": "type_error.list", | ||||
|             'input': {'name': 'foo', 'nb_page': 3}, | ||||
|             'loc': [], | ||||
|             'loc_in': 'body', | ||||
|             '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): | ||||
|     async def get( | ||||
|         self, | ||||
|         *, | ||||
|         signature: Signature, | ||||
|             self, | ||||
|             *, | ||||
|             signature: Signature, | ||||
|     ): | ||||
|         return web.json_response( | ||||
|             {"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( | ||||
|     aiohttp_client, event_loop | ||||
|         aiohttp_client, event_loop | ||||
| ): | ||||
|     app = web.Application() | ||||
|     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={}) | ||||
|     assert resp.status == 400 | ||||
|     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", | ||||
|             "loc": ["signature_expired"], | ||||
|             "msg": "field required", | ||||
|             "type": "value_error.missing", | ||||
|             'type': 'missing', | ||||
|             'loc': ['signature_expired'], | ||||
|             'msg': 'Field required', | ||||
|             '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( | ||||
|     aiohttp_client, event_loop | ||||
|         aiohttp_client, event_loop | ||||
| ): | ||||
|     app = web.Application() | ||||
|     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"}) | ||||
|     assert resp.status == 400 | ||||
|     assert resp.content_type == "application/json" | ||||
|  | ||||
|     assert await resp.json() == [ | ||||
|         { | ||||
|             "in": "headers", | ||||
|             "loc": ["signature_expired"], | ||||
|             "msg": "invalid datetime format", | ||||
|             "type": "value_error.datetime", | ||||
|             'type': 'datetime_parsing', | ||||
|             'loc': ['signature_expired'], | ||||
|             'msg': 'Input should be a valid datetime, input is too short', | ||||
|             '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( | ||||
|     aiohttp_client, event_loop | ||||
|         aiohttp_client, event_loop | ||||
| ): | ||||
|     app = web.Application() | ||||
|     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( | ||||
|     aiohttp_client, event_loop | ||||
|         aiohttp_client, event_loop | ||||
| ): | ||||
|     app = web.Application() | ||||
|     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) | ||||
|     resp = await client.get("/coord", headers={"format": "WGS84"}) | ||||
|  | ||||
|     assert ( | ||||
|         await resp.json() | ||||
|         == [ | ||||
|             { | ||||
|                 "ctx": {"enum_values": ["UMT", "MGRS"]}, | ||||
|                 "in": "headers", | ||||
|                 "loc": ["format"], | ||||
|                 "msg": "value is not a valid enumeration member; permitted: 'UMT', 'MGRS'", | ||||
|                 "type": "type_error.enum", | ||||
|             } | ||||
|         ] | ||||
|         != {"signature": "2020-10-04T18:01:00"} | ||||
|             await resp.json() | ||||
|             == [ | ||||
|                 { | ||||
|                     'ctx': {'expected': "'UMT' or 'MGRS'"}, | ||||
|                     'input': 'WGS84', | ||||
|                     'loc': ['format'], | ||||
|                     'loc_in': 'headers', | ||||
|                     'msg': "Input should be 'UMT' or 'MGRS'", | ||||
|                     'type': 'enum' | ||||
|                 } | ||||
|             ] | ||||
|             != {"signature": "2020-10-04T18:01:00"} | ||||
|     ) | ||||
|     assert resp.status == 400 | ||||
|     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") | ||||
|     assert resp.status == 400 | ||||
|     assert resp.content_type == "application/json" | ||||
|  | ||||
|     assert await resp.json() == [ | ||||
|         { | ||||
|             "in": "path", | ||||
|             "loc": ["date"], | ||||
|             "msg": "value is not a valid integer", | ||||
|             "type": "type_error.integer", | ||||
|             'input': 'now', | ||||
|             'loc': ['date'], | ||||
|             'loc_in': 'path', | ||||
|             '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): | ||||
|     async def get( | ||||
|         self, | ||||
|         with_comments: bool, | ||||
|         age: Optional[int] = None, | ||||
|         nb_items: int = 7, | ||||
|         tags: List[str] = Field(default_factory=list), | ||||
|             self, | ||||
|             with_comments: bool, | ||||
|             age: Optional[int] = None, | ||||
|             nb_items: int = 7, | ||||
|             tags: List[str] = Field(default_factory=list), | ||||
|     ): | ||||
|         return web.json_response( | ||||
|             { | ||||
| @@ -42,9 +42,9 @@ class Pagination(Group): | ||||
|  | ||||
| class ArticleViewWithPaginationGroup(PydanticView): | ||||
|     async def get( | ||||
|         self, | ||||
|         with_comments: bool, | ||||
|         page: Pagination, | ||||
|             self, | ||||
|             with_comments: bool, | ||||
|             page: Pagination, | ||||
|     ): | ||||
|         return web.json_response( | ||||
|             { | ||||
| @@ -70,7 +70,7 @@ class ArticleViewWithEnumInQuery(PydanticView): | ||||
|  | ||||
|  | ||||
| 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.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") | ||||
|     assert resp.status == 400 | ||||
|     assert resp.content_type == "application/json" | ||||
|  | ||||
|     assert await resp.json() == [ | ||||
|         { | ||||
|             "in": "query string", | ||||
|             "loc": ["with_comments"], | ||||
|             "msg": "field required", | ||||
|             "type": "value_error.missing", | ||||
|             'input': {}, | ||||
|             'loc': ['with_comments'], | ||||
|             'loc_in': 'query string', | ||||
|             '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( | ||||
|     aiohttp_client, event_loop | ||||
|         aiohttp_client, event_loop | ||||
| ): | ||||
|     app = web.Application() | ||||
|     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 await resp.json() == [ | ||||
|         { | ||||
|             "in": "query string", | ||||
|             "loc": ["with_comments"], | ||||
|             "msg": "value could not be parsed to a boolean", | ||||
|             "type": "type_error.bool", | ||||
|             'input': 'foo', | ||||
|             'loc': ['with_comments'], | ||||
|             'loc_in': 'query string', | ||||
|             '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( | ||||
|     aiohttp_client, event_loop | ||||
|         aiohttp_client, event_loop | ||||
| ): | ||||
|     app = web.Application() | ||||
|     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( | ||||
|     aiohttp_client, event_loop | ||||
|         aiohttp_client, event_loop | ||||
| ): | ||||
|     app = web.Application() | ||||
|     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( | ||||
|     aiohttp_client, event_loop | ||||
|         aiohttp_client, event_loop | ||||
| ): | ||||
|     app = web.Application() | ||||
|     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}) | ||||
|     assert await resp.json() == [ | ||||
|         { | ||||
|             "in": "query string", | ||||
|             "loc": ["age"], | ||||
|             "msg": "value is not a valid integer", | ||||
|             "type": "type_error.integer", | ||||
|             'input': ['2', '3'], | ||||
|             'loc': ['age'], | ||||
|             'loc_in': 'query string', | ||||
|             '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 | ||||
| @@ -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}) | ||||
|     assert await resp.json() == [ | ||||
|         { | ||||
|             "in": "query string", | ||||
|             "loc": ["page_num"], | ||||
|             "msg": "field required", | ||||
|             "type": "value_error.missing", | ||||
|             'input': {'with_comments': '1'}, | ||||
|             'loc': ['page_num'], | ||||
|             'loc_in': 'query string', | ||||
|             'msg': 'Field required', | ||||
|             'type': 'missing', | ||||
|             'url': 'https://errors.pydantic.dev/2.1.2/v/missing' | ||||
|         } | ||||
|     ] | ||||
|     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() == [ | ||||
|         { | ||||
|             "in": "query string", | ||||
|             "loc": ["page_size"], | ||||
|             "msg": "value is not a valid integer", | ||||
|             "type": "type_error.integer", | ||||
|             'input': 'large', | ||||
|             'loc': ['page_size'], | ||||
|             'loc_in': 'query string', | ||||
|             '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 | ||||
|   | ||||
		Reference in New Issue
	
	Block a user