Repository: litestar-org/litestar

Issue:
Bug: Openapi schema generation using stringized return annotation
### Description

If a handler has a string return annotation, either through ` -> "Model":` or `from __future__ import annotations` the responses generated for openapi do not make an attempt to de-stringize them leading to a no-content response being generated.

![image](https://user-images.githubusercontent.com/20659309/228678971-2f61688c-2191-48eb-ae7f-f5721fae9fd1.png)


### URL to code causing the issue

_No response_

### MCVE

```python
"""Minimal Starlite application."""
from __future__ import annotations

from starlite import Starlite, get


@get("/")
def hello_world() -> dict[str, str]:
    """Route Handler that outputs hello world."""
    return {"hello": "world"}


app = Starlite(route_handlers=[hello_world], debug=True)
```


### Steps to reproduce

```bash
1. Go to '...'
2. Click on '....'
3. Scroll down to '....'
4. See error
```


### Screenshots

```bash
"![SCREENSHOT_DESCRIPTION](SCREENSHOT_LINK.png)"
```


### Logs

_No response_

### Starlite Version

main

### Platform

- [ ] Linux
- [ ] Mac
- [ ] Windows
- [ ] Other (Please specify in the description above)


Discussion:

Resolving patch:
diff --git a/starlite/_openapi/responses.py b/starlite/_openapi/responses.py
--- a/starlite/_openapi/responses.py
+++ b/starlite/_openapi/responses.py
@@ -4,6 +4,7 @@
 from copy import copy
 from dataclasses import asdict
 from http import HTTPStatus
+from inspect import Signature
 from operator import attrgetter
 from typing import TYPE_CHECKING, Any, Iterator
 
@@ -11,6 +12,7 @@
 
 from starlite._openapi.schema_generation import create_schema
 from starlite._signature.models import SignatureField
+from starlite._signature.utils import get_signature_model
 from starlite.enums import MediaType
 from starlite.exceptions import HTTPException, ValidationException
 from starlite.openapi.spec import OpenAPIResponse
@@ -20,6 +22,7 @@
 from starlite.openapi.spec.schema import Schema
 from starlite.response import Response as StarliteResponse
 from starlite.response_containers import File, Redirect, Stream, Template
+from starlite.types.builtin_types import NoneType
 from starlite.utils import get_enum_string_value, get_name, is_class_and_subclass
 
 if TYPE_CHECKING:
@@ -67,7 +70,7 @@ def create_success_response(
     schemas: dict[str, "Schema"],
 ) -> OpenAPIResponse:
     """Create the schema for a success response."""
-    signature = route_handler.signature
+    return_annotation = get_signature_model(route_handler).return_annotation
     default_descriptions: dict[Any, str] = {
         Stream: "Stream Response",
         Redirect: "Redirect Response",
@@ -75,17 +78,16 @@ def create_success_response(
     }
     description = (
         route_handler.response_description
-        or default_descriptions.get(signature.return_annotation)
+        or default_descriptions.get(return_annotation)
         or HTTPStatus(route_handler.status_code).description
     )
 
-    if signature.return_annotation not in {signature.empty, None, Redirect, File, Stream}:
-        return_annotation = signature.return_annotation
-        if signature.return_annotation is Template:
+    if return_annotation not in {Signature.empty, None, NoneType, Redirect, File, Stream}:
+        if return_annotation is Template:
             return_annotation = str
             route_handler.media_type = get_enum_string_value(MediaType.HTML)
-        elif is_class_and_subclass(get_origin(signature.return_annotation), StarliteResponse):
-            return_annotation = get_args(signature.return_annotation)[0] or Any
+        elif is_class_and_subclass(get_origin(return_annotation), StarliteResponse):
+            return_annotation = get_args(return_annotation)[0] or Any
 
         result = create_schema(
             field=SignatureField.create(field_type=return_annotation),
@@ -108,7 +110,7 @@ def create_success_response(
             description=description,
         )
 
-    elif signature.return_annotation is Redirect:
+    elif return_annotation is Redirect:
         response = OpenAPIResponse(
             content=None,
             description=description,
@@ -119,7 +121,7 @@ def create_success_response(
             },
         )
 
-    elif signature.return_annotation in (File, Stream):
+    elif return_annotation in (File, Stream):
         response = OpenAPIResponse(
             content={
                 route_handler.media_type: OpenAPIMediaType(
diff --git a/starlite/_signature/parsing.py b/starlite/_signature/parsing.py
--- a/starlite/_signature/parsing.py
+++ b/starlite/_signature/parsing.py
@@ -183,7 +183,7 @@ def parse_fn_signature(
 
         parsed_params.append(parameter)
 
-    return parsed_params, signature.return_annotation, field_plugin_mappings, dependency_names
+    return parsed_params, fn_type_hints.get("return", Signature.empty), field_plugin_mappings, dependency_names
 
 
 def create_signature_model(


Test patch:
diff --git a/tests/openapi/test_responses.py b/tests/openapi/test_responses.py
--- a/tests/openapi/test_responses.py
+++ b/tests/openapi/test_responses.py
@@ -1,13 +1,14 @@
 from dataclasses import dataclass
 from http import HTTPStatus
 from pathlib import Path
-from typing import Dict
+from types import ModuleType
+from typing import Callable, Dict
 
 import pytest
 from pydantic import BaseModel
 from typing_extensions import TypedDict
 
-from starlite import MediaType, Response, Starlite, get
+from starlite import Controller, MediaType, Response, Starlite, get
 from starlite._openapi.responses import (
     create_additional_responses,
     create_error_responses,
@@ -20,6 +21,7 @@
     PermissionDeniedException,
     ValidationException,
 )
+from starlite.handlers import HTTPRouteHandler
 from starlite.openapi.datastructures import ResponseSpec
 from starlite.openapi.spec import OpenAPIHeader, OpenAPIMediaType, Reference, Schema
 from starlite.openapi.spec.enums import OpenAPIType
@@ -36,6 +38,11 @@
 from tests.openapi.utils import PersonController, PetController, PetException
 
 
+def get_registered_route_handler(handler: "HTTPRouteHandler | type[Controller]", name: str) -> HTTPRouteHandler:
+    app = Starlite(route_handlers=[handler])
+    return app.asgi_router.route_handler_index[name]  # type: ignore[return-value]
+
+
 def test_create_responses() -> None:
     for route in Starlite(route_handlers=[PersonController]).routes:
         assert isinstance(route, HTTPRoute)
@@ -52,8 +59,9 @@ def test_create_responses() -> None:
                 assert str(route_handler.status_code) in responses
                 assert str(HTTP_400_BAD_REQUEST) in responses
 
+    handler = get_registered_route_handler(PetController, "tests.openapi.utils.PetController.get_pets_or_owners")
     responses = create_responses(
-        route_handler=PetController.get_pets_or_owners,
+        route_handler=handler,
         raises_validation_error=False,
         generate_examples=True,
         plugins=[],
@@ -137,10 +145,12 @@ def test_create_success_response_with_headers() -> None:
         response_description="test",
         content_encoding="base64",
         content_media_type="image/png",
+        name="test",
     )
     def handler() -> list:
         return []
 
+    handler = get_registered_route_handler(handler, "test")
     response = create_success_response(handler, True, plugins=[], schemas={})
     assert response.description == "test"
 
@@ -166,10 +176,12 @@ def test_create_success_response_with_cookies() -> None:
             Cookie(key="first-cookie", httponly=True, samesite="strict", description="the first cookie", secure=True),
             Cookie(key="second-cookie", max_age=500, description="the second cookie"),
         ],
+        name="test",
     )
     def handler() -> list:
         return []
 
+    handler = get_registered_route_handler(handler, "test")
     response = create_success_response(handler, True, plugins=[], schemas={})
 
     assert isinstance(response.headers, dict)
@@ -191,10 +203,11 @@ def handler() -> list:
 
 
 def test_create_success_response_with_response_class() -> None:
-    @get(path="/test")
+    @get(path="/test", name="test")
     def handler() -> Response[Person]:
         return Response(content=PersonFactory.build())
 
+    handler = get_registered_route_handler(handler, "test")
     schemas: Dict[str, Schema] = {}
     response = create_success_response(handler, True, plugins=[], schemas=schemas)
 
@@ -208,20 +221,23 @@ def handler() -> Response[Person]:
 
 
 def test_create_success_response_with_stream() -> None:
-    @get(path="/test")
+    @get(path="/test", name="test")
     def handler() -> Stream:
         return Stream(iterator=iter([]))
 
+    handler = get_registered_route_handler(handler, "test")
     response = create_success_response(handler, True, plugins=[], schemas={})
     assert response.description == "Stream Response"
 
 
 def test_create_success_response_redirect() -> None:
-    @get(path="/test", status_code=HTTP_307_TEMPORARY_REDIRECT)
+    @get(path="/test", status_code=HTTP_307_TEMPORARY_REDIRECT, name="test")
     def redirect_handler() -> Redirect:
         return Redirect(path="/target")
 
-    response = create_success_response(redirect_handler, True, plugins=[], schemas={})
+    handler = get_registered_route_handler(redirect_handler, "test")
+
+    response = create_success_response(handler, True, plugins=[], schemas={})
     assert response.description == "Redirect Response"
     assert response.headers
     location = response.headers["location"]
@@ -232,11 +248,13 @@ def redirect_handler() -> Redirect:
 
 
 def test_create_success_response_file_data() -> None:
-    @get(path="/test")
+    @get(path="/test", name="test")
     def file_handler() -> File:
         return File(path=Path("test_responses.py"))
 
-    response = create_success_response(file_handler, True, plugins=[], schemas={})
+    handler = get_registered_route_handler(file_handler, "test")
+
+    response = create_success_response(handler, True, plugins=[], schemas={})
     assert response.description == "File Download"
     assert response.headers
 
@@ -257,11 +275,13 @@ def file_handler() -> File:
 
 
 def test_create_success_response_template() -> None:
-    @get(path="/template")
+    @get(path="/template", name="test")
     def template_handler() -> Template:
         return Template(name="none")
 
-    response = create_success_response(template_handler, True, plugins=[], schemas={})
+    handler = get_registered_route_handler(template_handler, "test")
+
+    response = create_success_response(handler, True, plugins=[], schemas={})
     assert response.description == "Request fulfilled, document follows"
     assert response.content
     assert response.content[MediaType.HTML.value]
@@ -329,10 +349,11 @@ def test_additional_responses_overlap_with_other_responses() -> None:
     class OkResponse(BaseModel):
         message: str
 
-    @get(responses={200: ResponseSpec(data_container=OkResponse, description="Overwritten response")})
+    @get(responses={200: ResponseSpec(data_container=OkResponse, description="Overwritten response")}, name="test")
     def handler() -> Person:
         return PersonFactory.build()
 
+    handler = get_registered_route_handler(handler, "test")
     responses = create_responses(handler, raises_validation_error=True, generate_examples=False, plugins=[], schemas={})
 
     assert responses is not None
@@ -347,10 +368,13 @@ class ErrorResponse(BaseModel):
     @get(
         raises=[ValidationException],
         responses={400: ResponseSpec(data_container=ErrorResponse, description="Overwritten response")},
+        name="test",
     )
     def handler() -> Person:
         raise ValidationException()
 
+    handler = get_registered_route_handler(handler, "test")
+
     responses = create_responses(handler, raises_validation_error=True, generate_examples=False, plugins=[], schemas={})
 
     assert responses is not None
@@ -362,10 +386,12 @@ def test_create_response_for_response_subclass() -> None:
     class CustomResponse(Response[T]):
         pass
 
-    @get(path="/test")
+    @get(path="/test", name="test")
     def handler() -> CustomResponse[Person]:
         return CustomResponse(content=PersonFactory.build())
 
+    handler = get_registered_route_handler(handler, "test")
+
     schemas: Dict[str, Schema] = {}
     response = create_success_response(handler, True, plugins=[], schemas=schemas)
     assert response.content
@@ -374,3 +400,19 @@ def handler() -> CustomResponse[Person]:
     assert isinstance(reference, Reference)
     schema = schemas[reference.value]
     assert schema.title == "Person"
+
+
+def test_success_response_with_future_annotations(create_module: Callable[[str], ModuleType]) -> None:
+    module = create_module(
+        """
+from __future__ import annotations
+from starlite import get
+
+@get(path="/test", name="test")
+def handler() -> int:
+    ...
+"""
+    )
+    handler = get_registered_route_handler(module.handler, "test")
+    response = create_success_response(handler, True, plugins=[], schemas={})
+    assert next(iter(response.content.values())).schema.type == OpenAPIType.INTEGER  # type: ignore[union-attr]