Fix - Does not work with from __future__ import annotations

This commit is contained in:
Vincent Maillol 2022-02-05 10:28:05 +01:00
parent 3648dde1ea
commit 69fb553635
18 changed files with 54 additions and 14 deletions

View File

@ -1,5 +1,5 @@
from .view import PydanticView from .view import PydanticView
__version__ = "1.12.0" __version__ = "1.12.1"
__all__ = ("PydanticView", "__version__") __all__ = ("PydanticView", "__version__")

View File

@ -3,7 +3,7 @@ import typing
from inspect import signature, getmro from inspect import signature, getmro
from json.decoder import JSONDecodeError from json.decoder import JSONDecodeError
from types import SimpleNamespace from types import SimpleNamespace
from typing import Callable, Tuple, Literal, Type from typing import Callable, Tuple, Literal, Type, get_type_hints
from aiohttp.web_exceptions import HTTPBadRequest from aiohttp.web_exceptions import HTTPBadRequest
from aiohttp.web_request import BaseRequest from aiohttp.web_request import BaseRequest
@ -201,13 +201,19 @@ def _get_group_signature(cls) -> Tuple[dict, dict]:
mro = getmro(cls) mro = getmro(cls)
for base in reversed(mro[: mro.index(Group)]): for base in reversed(mro[: mro.index(Group)]):
attrs = vars(base) attrs = vars(base)
# Use __annotations__ to know if an attribute is
# overwrite to remove the default value.
for attr_name, type_ in base.__annotations__.items(): for attr_name, type_ in base.__annotations__.items():
sig[attr_name] = type_
if (default := attrs.get(attr_name)) is None: if (default := attrs.get(attr_name)) is None:
defaults.pop(attr_name, None) defaults.pop(attr_name, None)
else: else:
defaults[attr_name] = default defaults[attr_name] = default
# Use get_type_hints to have postponed annotations.
for attr_name, type_ in get_type_hints(base).items():
sig[attr_name] = type_
return sig, defaults return sig, defaults
@ -229,26 +235,29 @@ def _parse_func_signature(
header_args = {} header_args = {}
defaults = {} defaults = {}
annotations = get_type_hints(func)
for param_name, param_spec in signature(func).parameters.items(): for param_name, param_spec in signature(func).parameters.items():
if param_name == "self": if param_name == "self":
continue continue
if param_spec.annotation == param_spec.empty: if param_spec.annotation == param_spec.empty:
raise RuntimeError(f"The parameter {param_name} must have an annotation") raise RuntimeError(f"The parameter {param_name} must have an annotation")
annotation = annotations[param_name]
if param_spec.default is not param_spec.empty: if param_spec.default is not param_spec.empty:
defaults[param_name] = param_spec.default defaults[param_name] = param_spec.default
if param_spec.kind is param_spec.POSITIONAL_ONLY: if param_spec.kind is param_spec.POSITIONAL_ONLY:
path_args[param_name] = param_spec.annotation path_args[param_name] = annotation
elif param_spec.kind is param_spec.POSITIONAL_OR_KEYWORD: elif param_spec.kind is param_spec.POSITIONAL_OR_KEYWORD:
if is_pydantic_base_model(param_spec.annotation): if is_pydantic_base_model(annotation):
body_args[param_name] = param_spec.annotation body_args[param_name] = annotation
else: else:
qs_args[param_name] = param_spec.annotation qs_args[param_name] = annotation
elif param_spec.kind is param_spec.KEYWORD_ONLY: elif param_spec.kind is param_spec.KEYWORD_ONLY:
header_args[param_name] = param_spec.annotation header_args[param_name] = annotation
else: else:
raise RuntimeError(f"You cannot use {param_spec.VAR_POSITIONAL} parameters") raise RuntimeError(f"You cannot use {param_spec.VAR_POSITIONAL} parameters")

View File

@ -1,7 +1,7 @@
import typing import typing
from inspect import getdoc from inspect import getdoc
from itertools import count from itertools import count
from typing import List, Type, Optional 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
@ -126,7 +126,7 @@ def _add_http_method_to_oas(
ref_template="#/components/schemas/{model}" ref_template="#/components/schemas/{model}"
) )
return_type = handler.__annotations__.get("return") return_type = get_type_hints(handler).get("return")
if return_type is not None: if return_type is not None:
_OASResponseBuilder(oas, oas_operation, status_code_descriptions).build( _OASResponseBuilder(oas, oas_operation, status_code_descriptions).build(
return_type return_type

View File

@ -1,3 +1,5 @@
from __future__ import annotations
import pytest import pytest
from aiohttp_pydantic.injectors import ( from aiohttp_pydantic.injectors import (

View File

@ -1,3 +1,5 @@
from __future__ import annotations
from typing import Iterator, List, Optional from typing import Iterator, List, Optional
from aiohttp import web from aiohttp import web

View File

@ -1,3 +1,5 @@
from __future__ import annotations
from typing import Any from typing import Any
from aiohttp_pydantic import PydanticView from aiohttp_pydantic import PydanticView

View File

@ -1,3 +1,5 @@
from __future__ import annotations
from aiohttp import web from aiohttp import web
from aiohttp_pydantic import PydanticView from aiohttp_pydantic import PydanticView

View File

@ -1,3 +1,5 @@
from __future__ import annotations
import argparse import argparse
from textwrap import dedent from textwrap import dedent
from io import StringIO from io import StringIO

View File

@ -1,3 +1,5 @@
from __future__ import annotations
from textwrap import dedent from textwrap import dedent
from aiohttp_pydantic.oas.docstring_parser import ( from aiohttp_pydantic.oas.docstring_parser import (

View File

@ -1,3 +1,5 @@
from __future__ import annotations
import pytest import pytest
from aiohttp_pydantic.oas.struct import OpenApiSpec3 from aiohttp_pydantic.oas.struct import OpenApiSpec3

View File

@ -1,3 +1,5 @@
from __future__ import annotations
from aiohttp_pydantic.oas.struct import OpenApiSpec3 from aiohttp_pydantic.oas.struct import OpenApiSpec3

View File

@ -1,3 +1,5 @@
from __future__ import annotations
import pytest import pytest
from aiohttp_pydantic.oas.struct import OpenApiSpec3 from aiohttp_pydantic.oas.struct import OpenApiSpec3

View File

@ -1,3 +1,5 @@
from __future__ import annotations
from enum import Enum from enum import Enum
from typing import List, Optional, Union, Literal from typing import List, Optional, Union, Literal
from uuid import UUID from uuid import UUID
@ -385,15 +387,16 @@ async def test_generated_view_info_as_title():
} }
class Pagination(Group):
page: int = 1
page_size: int = 20
async def test_use_parameters_group_should_not_impact_the_oas(aiohttp_client): async def test_use_parameters_group_should_not_impact_the_oas(aiohttp_client):
class PetCollectionView1(PydanticView): class PetCollectionView1(PydanticView):
async def get(self, page: int = 1, page_size: int = 20) -> r200[List[Pet]]: async def get(self, page: int = 1, page_size: int = 20) -> r200[List[Pet]]:
return web.json_response() return web.json_response()
class Pagination(Group):
page: int = 1
page_size: int = 20
class PetCollectionView2(PydanticView): class PetCollectionView2(PydanticView):
async def get(self, pagination: Pagination) -> r200[List[Pet]]: async def get(self, pagination: Pagination) -> r200[List[Pet]]:
return web.json_response() return web.json_response()

View File

@ -1,3 +1,5 @@
from __future__ import annotations
from uuid import UUID from uuid import UUID
from pydantic import BaseModel from pydantic import BaseModel

View File

@ -1,3 +1,5 @@
from __future__ import annotations
from typing import Iterator, List, Optional from typing import Iterator, List, Optional
from aiohttp import web from aiohttp import web

View File

@ -1,3 +1,5 @@
from __future__ import annotations
import json import json
from datetime import datetime from datetime import datetime
from enum import Enum from enum import Enum

View File

@ -1,3 +1,5 @@
from __future__ import annotations
from aiohttp import web from aiohttp import web
from aiohttp_pydantic import PydanticView from aiohttp_pydantic import PydanticView

View File

@ -1,3 +1,5 @@
from __future__ import annotations
from typing import Optional, List from typing import Optional, List
from pydantic import Field from pydantic import Field
from aiohttp import web from aiohttp import web