From 69fb5536358bbc4ce08bdf6655de6daa502b78b9 Mon Sep 17 00:00:00 2001 From: Vincent Maillol Date: Sat, 5 Feb 2022 10:28:05 +0100 Subject: [PATCH] Fix - Does not work with `from __future__ import annotations` --- aiohttp_pydantic/__init__.py | 2 +- aiohttp_pydantic/injectors.py | 23 +++++++++++++++------- aiohttp_pydantic/oas/view.py | 4 ++-- tests/test_group.py | 2 ++ tests/test_hook_to_custom_response.py | 2 ++ tests/test_inheritance.py | 2 ++ tests/test_oas/test_cmd/sample.py | 2 ++ tests/test_oas/test_cmd/test_cmd.py | 2 ++ tests/test_oas/test_docstring_parser.py | 2 ++ tests/test_oas/test_struct/test_info.py | 2 ++ tests/test_oas/test_struct/test_paths.py | 2 ++ tests/test_oas/test_struct/test_servers.py | 2 ++ tests/test_oas/test_view.py | 11 +++++++---- tests/test_parse_func_signature.py | 2 ++ tests/test_validation_body.py | 2 ++ tests/test_validation_header.py | 2 ++ tests/test_validation_path.py | 2 ++ tests/test_validation_query_string.py | 2 ++ 18 files changed, 54 insertions(+), 14 deletions(-) diff --git a/aiohttp_pydantic/__init__.py b/aiohttp_pydantic/__init__.py index 81d1052..da5ba94 100644 --- a/aiohttp_pydantic/__init__.py +++ b/aiohttp_pydantic/__init__.py @@ -1,5 +1,5 @@ from .view import PydanticView -__version__ = "1.12.0" +__version__ = "1.12.1" __all__ = ("PydanticView", "__version__") diff --git a/aiohttp_pydantic/injectors.py b/aiohttp_pydantic/injectors.py index 848474c..335adae 100644 --- a/aiohttp_pydantic/injectors.py +++ b/aiohttp_pydantic/injectors.py @@ -3,7 +3,7 @@ import typing from inspect import signature, getmro from json.decoder import JSONDecodeError 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_request import BaseRequest @@ -201,13 +201,19 @@ def _get_group_signature(cls) -> Tuple[dict, dict]: mro = getmro(cls) for base in reversed(mro[: mro.index(Group)]): 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(): - sig[attr_name] = type_ if (default := attrs.get(attr_name)) is None: defaults.pop(attr_name, None) else: 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 @@ -229,26 +235,29 @@ def _parse_func_signature( header_args = {} defaults = {} + annotations = get_type_hints(func) for param_name, param_spec in signature(func).parameters.items(): + if param_name == "self": continue if param_spec.annotation == param_spec.empty: raise RuntimeError(f"The parameter {param_name} must have an annotation") + annotation = annotations[param_name] if param_spec.default is not param_spec.empty: defaults[param_name] = param_spec.default 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: - if is_pydantic_base_model(param_spec.annotation): - body_args[param_name] = param_spec.annotation + if is_pydantic_base_model(annotation): + body_args[param_name] = annotation else: - qs_args[param_name] = param_spec.annotation + qs_args[param_name] = annotation elif param_spec.kind is param_spec.KEYWORD_ONLY: - header_args[param_name] = param_spec.annotation + header_args[param_name] = annotation else: raise RuntimeError(f"You cannot use {param_spec.VAR_POSITIONAL} parameters") diff --git a/aiohttp_pydantic/oas/view.py b/aiohttp_pydantic/oas/view.py index 2d10609..832c5db 100644 --- a/aiohttp_pydantic/oas/view.py +++ b/aiohttp_pydantic/oas/view.py @@ -1,7 +1,7 @@ import typing from inspect import getdoc 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_app import Application @@ -126,7 +126,7 @@ def _add_http_method_to_oas( 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: _OASResponseBuilder(oas, oas_operation, status_code_descriptions).build( return_type diff --git a/tests/test_group.py b/tests/test_group.py index 24a903f..c734109 100644 --- a/tests/test_group.py +++ b/tests/test_group.py @@ -1,3 +1,5 @@ +from __future__ import annotations + import pytest from aiohttp_pydantic.injectors import ( diff --git a/tests/test_hook_to_custom_response.py b/tests/test_hook_to_custom_response.py index c9aa6b5..dd3b5b5 100644 --- a/tests/test_hook_to_custom_response.py +++ b/tests/test_hook_to_custom_response.py @@ -1,3 +1,5 @@ +from __future__ import annotations + from typing import Iterator, List, Optional from aiohttp import web diff --git a/tests/test_inheritance.py b/tests/test_inheritance.py index d759c0d..baa69ac 100644 --- a/tests/test_inheritance.py +++ b/tests/test_inheritance.py @@ -1,3 +1,5 @@ +from __future__ import annotations + from typing import Any from aiohttp_pydantic import PydanticView diff --git a/tests/test_oas/test_cmd/sample.py b/tests/test_oas/test_cmd/sample.py index 2c24c7e..7662041 100644 --- a/tests/test_oas/test_cmd/sample.py +++ b/tests/test_oas/test_cmd/sample.py @@ -1,3 +1,5 @@ +from __future__ import annotations + from aiohttp import web from aiohttp_pydantic import PydanticView diff --git a/tests/test_oas/test_cmd/test_cmd.py b/tests/test_oas/test_cmd/test_cmd.py index f9d78ca..1dd43e8 100644 --- a/tests/test_oas/test_cmd/test_cmd.py +++ b/tests/test_oas/test_cmd/test_cmd.py @@ -1,3 +1,5 @@ +from __future__ import annotations + import argparse from textwrap import dedent from io import StringIO diff --git a/tests/test_oas/test_docstring_parser.py b/tests/test_oas/test_docstring_parser.py index aaf964f..77c69c7 100644 --- a/tests/test_oas/test_docstring_parser.py +++ b/tests/test_oas/test_docstring_parser.py @@ -1,3 +1,5 @@ +from __future__ import annotations + from textwrap import dedent from aiohttp_pydantic.oas.docstring_parser import ( diff --git a/tests/test_oas/test_struct/test_info.py b/tests/test_oas/test_struct/test_info.py index 642072b..ce3a3c5 100644 --- a/tests/test_oas/test_struct/test_info.py +++ b/tests/test_oas/test_struct/test_info.py @@ -1,3 +1,5 @@ +from __future__ import annotations + import pytest from aiohttp_pydantic.oas.struct import OpenApiSpec3 diff --git a/tests/test_oas/test_struct/test_paths.py b/tests/test_oas/test_struct/test_paths.py index 78453cd..ece2a70 100644 --- a/tests/test_oas/test_struct/test_paths.py +++ b/tests/test_oas/test_struct/test_paths.py @@ -1,3 +1,5 @@ +from __future__ import annotations + from aiohttp_pydantic.oas.struct import OpenApiSpec3 diff --git a/tests/test_oas/test_struct/test_servers.py b/tests/test_oas/test_struct/test_servers.py index d2bb1a4..cccb9cb 100644 --- a/tests/test_oas/test_struct/test_servers.py +++ b/tests/test_oas/test_struct/test_servers.py @@ -1,3 +1,5 @@ +from __future__ import annotations + import pytest from aiohttp_pydantic.oas.struct import OpenApiSpec3 diff --git a/tests/test_oas/test_view.py b/tests/test_oas/test_view.py index af9c338..83363ce 100644 --- a/tests/test_oas/test_view.py +++ b/tests/test_oas/test_view.py @@ -1,3 +1,5 @@ +from __future__ import annotations + from enum import Enum from typing import List, Optional, Union, Literal 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): class PetCollectionView1(PydanticView): async def get(self, page: int = 1, page_size: int = 20) -> r200[List[Pet]]: return web.json_response() - class Pagination(Group): - page: int = 1 - page_size: int = 20 - class PetCollectionView2(PydanticView): async def get(self, pagination: Pagination) -> r200[List[Pet]]: return web.json_response() diff --git a/tests/test_parse_func_signature.py b/tests/test_parse_func_signature.py index 6e588e7..256e128 100644 --- a/tests/test_parse_func_signature.py +++ b/tests/test_parse_func_signature.py @@ -1,3 +1,5 @@ +from __future__ import annotations + from uuid import UUID from pydantic import BaseModel diff --git a/tests/test_validation_body.py b/tests/test_validation_body.py index 6a7fdd7..6a03316 100644 --- a/tests/test_validation_body.py +++ b/tests/test_validation_body.py @@ -1,3 +1,5 @@ +from __future__ import annotations + from typing import Iterator, List, Optional from aiohttp import web diff --git a/tests/test_validation_header.py b/tests/test_validation_header.py index 134c6ca..95d317a 100644 --- a/tests/test_validation_header.py +++ b/tests/test_validation_header.py @@ -1,3 +1,5 @@ +from __future__ import annotations + import json from datetime import datetime from enum import Enum diff --git a/tests/test_validation_path.py b/tests/test_validation_path.py index bfa2016..ae8a453 100644 --- a/tests/test_validation_path.py +++ b/tests/test_validation_path.py @@ -1,3 +1,5 @@ +from __future__ import annotations + from aiohttp import web from aiohttp_pydantic import PydanticView diff --git a/tests/test_validation_query_string.py b/tests/test_validation_query_string.py index 344cea0..d6041f1 100644 --- a/tests/test_validation_query_string.py +++ b/tests/test_validation_query_string.py @@ -1,3 +1,5 @@ +from __future__ import annotations + from typing import Optional, List from pydantic import Field from aiohttp import web