feat: update pydantic

This commit is contained in:
Georg K 2023-07-14 04:40:57 +03:00
parent a94c9d4863
commit 93e391b7b2
7 changed files with 72 additions and 74 deletions

View File

@ -85,7 +85,7 @@ class BodyGetter(AbstractInjector):
# to a dict. Prevent this by requiring the body to be a dict for object models. # to a dict. Prevent this by requiring the body to be a dict for object models.
if self._expect_object and not isinstance(body, dict): if self._expect_object and not isinstance(body, dict):
raise HTTPBadRequest( raise HTTPBadRequest(
text='[{"in": "body", "loc": ["__root__"], "msg": "value is not a ' text='[{"loc_in": "body", "loc": ["root"], "msg": "value is not a '
'valid dict", "type": "type_error.dict"}]', 'valid dict", "type": "type_error.dict"}]',
content_type="application/json", content_type="application/json",
) from None ) from None

View File

@ -5,7 +5,7 @@ from typing import List, Type, Optional, get_type_hints
from aiohttp.web import Response, json_response from aiohttp.web import Response, json_response
from aiohttp.web_app import Application from aiohttp.web_app import Application
from pydantic import BaseModel from pydantic import BaseModel, RootModel
from aiohttp_pydantic.oas.struct import OpenApiSpec3, OperationObject, PathItem from aiohttp_pydantic.oas.struct import OpenApiSpec3, OperationObject, PathItem
from . import docstring_parser from . import docstring_parser
@ -32,7 +32,7 @@ class _OASResponseBuilder:
response_schema = obj.schema( response_schema = obj.schema(
ref_template="#/components/schemas/{model}" ref_template="#/components/schemas/{model}"
).copy() ).copy()
if def_sub_schemas := response_schema.pop("definitions", None): if def_sub_schemas := response_schema.pop("$defs", None):
self._oas.components.schemas.update(def_sub_schemas) self._oas.components.schemas.update(def_sub_schemas)
self._oas.components.schemas.update({response_schema['title']: response_schema}) self._oas.components.schemas.update({response_schema['title']: response_schema})
return {'$ref': f'#/components/schemas/{response_schema["title"]}'} return {'$ref': f'#/components/schemas/{response_schema["title"]}'}
@ -99,7 +99,7 @@ def _add_http_method_to_oas(
.schema(ref_template="#/components/schemas/{model}") .schema(ref_template="#/components/schemas/{model}")
.copy() .copy()
) )
if def_sub_schemas := body_schema.pop("definitions", None): if def_sub_schemas := body_schema.pop("$defs", None):
oas.components.schemas.update(def_sub_schemas) oas.components.schemas.update(def_sub_schemas)
oas_operation.request_body.content = { oas_operation.request_body.content = {
@ -117,19 +117,19 @@ def _add_http_method_to_oas(
oas_operation.parameters[i].in_ = args_location oas_operation.parameters[i].in_ = args_location
oas_operation.parameters[i].name = name oas_operation.parameters[i].name = name
attrs = {"__annotations__": {"__root__": type_}} attrs = {"__annotations__": {"root": type_}}
if name in defaults: if name in defaults:
attrs["__root__"] = defaults[name] attrs["root"] = defaults[name]
oas_operation.parameters[i].required = False oas_operation.parameters[i].required = False
else: else:
oas_operation.parameters[i].required = True oas_operation.parameters[i].required = True
oas_operation.parameters[i].schema = type(name, (BaseModel,), attrs).schema( oas_operation.parameters[i].schema = type(name, (RootModel,), attrs).schema(
ref_template="#/components/schemas/{model}" ref_template="#/components/schemas/{model}"
) )
# move definitions # move definitions
if def_sub_schemas := oas_operation.parameters[i].schema.pop("definitions", None): if def_sub_schemas := oas_operation.parameters[i].schema.pop("$defs", None):
oas.components.schemas.update(def_sub_schemas) oas.components.schemas.update(def_sub_schemas)
return_type = get_type_hints(handler).get("return") return_type = get_type_hints(handler).get("return")

View File

@ -1,42 +1,11 @@
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
certifi==2022.6.15
charset-normalizer==2.1.0
codecov==2.1.12
colorama==0.4.5
commonmark==0.9.1
coverage==6.4.2
docutils==0.19
frozenlist==1.3.0
idna==3.3
importlib-metadata==4.12.0
iniconfig==1.1.1
keyring==23.7.0
multidict==6.0.2
packaging==21.3
pkginfo==1.8.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
pywin32-ctypes==0.2.0 readme-renderer==40.0
readme-renderer==35.0 codecov==2.1.13
requests==2.28.1 twine==4.0.2
requests-toolbelt==0.9.1
rfc3986==2.0.0
rich==12.5.1
six==1.16.0
tomli==2.0.1
twine==4.0.1
urllib3==1.26.11
webencodings==0.5.1
yarl==1.7.2
zipp==3.8.1

View File

@ -6,3 +6,4 @@ pytest==7.4.0
pytest-aiohttp==1.0.4 pytest-aiohttp==1.0.4
pytest-asyncio==0.21.1 pytest-asyncio==0.21.1
pytest-cov==4.1.0 pytest-cov==4.1.0
readme-renderer==40.0

View File

@ -36,14 +36,14 @@ install_requires =
[options.extras_require] [options.extras_require]
test = test =
pytest==7.1.2 pytest==7.4.0
pytest-aiohttp==1.0.4 pytest-aiohttp==1.0.4
pytest-cov==3.0.0 pytest-cov==4.1.0
readme-renderer==35.0 readme-renderer==40.0
ci = ci =
%(test)s %(test)s
codecov==2.1.12 codecov==2.1.13
twine==4.0.1 twine==4.0.2
[options.packages.find] [options.packages.find]
exclude = exclude =

View File

@ -16,6 +16,9 @@ from aiohttp_pydantic.oas.view import generate_oas
class Color(str, Enum): class Color(str, Enum):
"""
Pet color
"""
RED = "red" RED = "red"
GREEN = "green" GREEN = "green"
PINK = "pink" PINK = "pink"
@ -142,22 +145,18 @@ async def generated_oas(aiohttp_client, event_loop) -> web.Application:
async def test_generated_oas_should_have_components_schemas(generated_oas): async def test_generated_oas_should_have_components_schemas(generated_oas):
assert generated_oas["components"]["schemas"] == { assert generated_oas["components"]["schemas"] == {
'Cat': {'properties': {'meows': {'title': 'Meows', 'type': 'integer'}, 'Cat': {'properties': {'meows': {'title': 'Meows', 'type': 'integer'},
'pet_type': {'enum': ['cat'], 'pet_type': {'const': 'cat', 'title': 'Pet Type'}},
'title': 'Pet Type',
'type': 'string'}},
'required': ['pet_type', 'meows'], 'required': ['pet_type', 'meows'],
'title': 'Cat', 'title': 'Cat',
'type': 'object'}, 'type': 'object'},
"Color": { "Color": {
"description": "An enumeration.", "description": "Pet color",
"enum": ["red", "green", "pink"], "enum": ["red", "green", "pink"],
"title": "Color", "title": "Color",
"type": "string", "type": "string",
}, },
'Dog': {'properties': {'barks': {'title': 'Barks', 'type': 'number'}, 'Dog': {'properties': {'barks': {'title': 'Barks', 'type': 'number'},
'pet_type': {'enum': ['dog'], 'pet_type': {'const': 'dog', 'title': 'Pet Type'}},
'title': 'Pet Type',
'type': 'string'}},
'required': ['pet_type', 'barks'], 'required': ['pet_type', 'barks'],
'title': 'Dog', 'title': 'Dog',
'type': 'object'}, 'type': 'object'},
@ -170,7 +169,6 @@ async def test_generated_oas_should_have_components_schemas(generated_oas):
'type': 'object' 'type': 'object'
}, },
'Lang': { 'Lang': {
'description': 'An enumeration.',
'enum': ['en', 'fr'], 'enum': ['en', 'fr'],
'title': 'Lang', 'title': 'Lang',
'type': 'string' 'type': 'string'
@ -187,7 +185,13 @@ async def test_generated_oas_should_have_components_schemas(generated_oas):
'Pet': { 'Pet': {
'properties': { 'properties': {
'id': {'title': 'Id', 'type': 'integer'}, 'id': {'title': 'Id', 'type': 'integer'},
'name': {'title': 'Name', 'type': 'string'}, 'name': {
'anyOf': [
{'type': 'string'},
{'type': 'null'}
],
'default': None,
'title': 'Name'},
'toys': { 'toys': {
'items': {'$ref': '#/components/schemas/Toy'}, 'items': {'$ref': '#/components/schemas/Toy'},
'title': 'Toys', 'title': 'Toys',
@ -231,13 +235,24 @@ async def test_pets_route_should_have_get_method(generated_oas):
"in": "query", "in": "query",
"name": "name", "name": "name",
"required": False, "required": False,
"schema": {"title": "name", "type": "string"}, "schema": {
'anyOf': [{'type': 'string'}, {'type': 'null'}],
'default': None,
'title': 'name'
},
}, },
{ {
"in": "header", "in": "header",
"name": "promo", "name": "promo",
"required": False, "required": False,
"schema": {"format": "uuid", "title": "promo", "type": "string"}, "schema": {
'anyOf': [
{'format': 'uuid', 'type': 'string'},
{'type': 'null'}
],
'default': None,
'title': 'promo'
},
}, },
], ],
"responses": { "responses": {
@ -265,7 +280,14 @@ async def test_pets_route_should_have_post_method(generated_oas):
"schema": { "schema": {
"properties": { "properties": {
"id": {"title": "Id", "type": "integer"}, "id": {"title": "Id", "type": "integer"},
"name": {"title": "Name", "type": "string"}, "name": {
'anyOf': [
{'type': 'string'},
{'type': 'null'}
],
'default': None,
'title': 'Name'
},
"toys": { "toys": {
"items": {"$ref": "#/components/schemas/Toy"}, "items": {"$ref": "#/components/schemas/Toy"},
"title": "Toys", "title": "Toys",
@ -337,9 +359,9 @@ async def test_pets_id_route_should_have_get_method(generated_oas):
"name": "day", "name": "day",
"required": False, "required": False,
"schema": { "schema": {
"anyOf": [{"type": "integer"}, {"enum": ["now"], "type": "string"}], 'anyOf': [{'type': 'integer'}, {'const': 'now'}],
"default": "now", 'default': 'now',
"title": "day", 'title': 'day'
}, },
}, },
], ],
@ -370,7 +392,13 @@ async def test_pets_id_route_should_have_put_method(generated_oas):
"schema": { "schema": {
"properties": { "properties": {
"id": {"title": "Id", "type": "integer"}, "id": {"title": "Id", "type": "integer"},
"name": {"title": "Name", "type": "string"}, "name": {
'anyOf': [
{'type': 'string'},
{'type': 'null'}
],
'default': None,
'title': 'Name'},
"toys": { "toys": {
"items": {"$ref": "#/components/schemas/Toy"}, "items": {"$ref": "#/components/schemas/Toy"},
"title": "Toys", "title": "Toys",

View File

@ -97,10 +97,10 @@ async def test_post_an_array_json_to_an_object_model_should_return_an_error(
assert resp.content_type == "application/json" assert resp.content_type == "application/json"
assert await resp.json() == [ assert await resp.json() == [
{ {
"in": "body", 'loc': ['root'],
"loc": ["__root__"], 'loc_in': 'body',
"msg": "value is not a valid dict", 'msg': 'value is not a valid dict',
"type": "type_error.dict", 'type': 'type_error.dict'
} }
] ]