← eval_submission

fastapi_11355

failed WRONG_FIX wrong_fix · 27 tool calls · 252 s · fastapi/fastapi

Task input

🐛 Fix evaluating stringified annotations in Python 3.10

I've bumped into the exact same issue as https://github.com/tiangolo/typer/discussions/598 so I solved it like https://github.com/tiangolo/typer/pull/721  :smile: 

I'll try to add tests soon but if you can do it faster, please feel free !!

Tool calls (28)

#ToolArgumentsResult
1run_command
{
 "command": "grep -rn \"get_type_hints\" fastapi | head -20"
}
{"status": "ok", "stdout": "", "exit_code": 0}
full
{"status": "ok", "stdout": "", "exit_code": 0}
2run_command
{
 "command": "grep -rn \"get_type_hints\" . | head -20"
}
{"status": "ok", "stdout": "", "exit_code": 0}
full
{"status": "ok", "stdout": "", "exit_code": 0}
3run_command
{
 "command": "grep -rn \"eval\" fastapi | head -20"
}
{"status": "ok", "stdout": "fastapi/dependencies/utils.py:33: evaluate_forwardref,\nfastapi/dependencies/utils.py:213: annotation = evaluate_forwardref(annotati
full
{"status": "ok", "stdout": "fastapi/dependencies/utils.py:33:    evaluate_forwardref,\nfastapi/dependencies/utils.py:213:        annotation = evaluate_forwardref(annotation, globalns, globalns)\nfastapi/_compat/__init__.py:17:from .main import evaluate_forwardref as evaluate_forwardref\nfastapi/_compat/v1.py:69:    from pydantic.typing import evaluate_forwardref as evaluate_forwardref\nfastapi/_compat/v1.py:122:    from pydantic.v1.typing import evaluate_forwardref as evaluate_forwardref\nfastapi/_compat/main.py:29:    from .v2 import evaluate_forwardref as evaluate_forwardref\nfastapi/_compat/main.py:45:    from .v1 import evaluate_forwardref as evaluate_forwardref\nfastapi/_compat/v2.py:28:from pydantic._internal._typing_extra import eval_type_lenient\nfastapi/_compat/v2.py:50:evaluate_forwardref = eval_type_lenient\nfastapi/openapi/models.py:162:    unevaluatedItems: Optional[\"SchemaOrBool\"] = None\nfastapi/openapi/models.py:163:    unevaluatedProperties: Optional[\"SchemaOrBool\"] = None\n", "exit_code": 0}
4read_file
{
 "filepath": "fastapi/_compat/v2.py"
}
{"status": "ok", "filepath": "fastapi/_compat/v2.py", "content": "import re\nimport warnings\nfrom copy import copy, deepcopy\nfrom dataclasses import dataclass
full
{"status": "ok", "filepath": "fastapi/_compat/v2.py", "content": "import re\nimport warnings\nfrom copy import copy, deepcopy\nfrom dataclasses import dataclass\nfrom enum import Enum\nfrom typing import (\n    Any,\n    Dict,\n    List,\n    Sequence,\n    Set,\n    Tuple,\n    Type,\n    Union,\n    cast,\n)\n\nfrom fastapi._compat import may_v1, shared\nfrom fastapi.openapi.constants import REF_TEMPLATE\nfrom fastapi.types import IncEx, ModelNameMap, UnionType\nfrom pydantic import BaseModel, TypeAdapter, create_model\nfrom pydantic import PydanticSchemaGenerationError as PydanticSchemaGenerationError\nfrom pydantic import PydanticUndefinedAnnotation as PydanticUndefinedAnnotation\nfrom pydantic import ValidationError as ValidationError\nfrom pydantic._internal._schema_generation_shared import (  # type: ignore[attr-defined]\n    GetJsonSchemaHandler as GetJsonSchemaHandler,\n)\nfrom pydantic._internal._typing_extra import eval_type_lenient\nfrom pydantic._internal._utils import lenient_issubclass as lenient_issubclass\nfrom pydantic.fields import FieldInfo as FieldInfo\nfrom pydantic.json_schema import GenerateJsonSchema as GenerateJsonSchema\nfrom pydantic.json_schema import JsonSchemaValue as JsonSchemaValue\nfrom pydantic_core import CoreSchema as CoreSchema\nfrom pydantic_core import PydanticUndefined, PydanticUndefinedType\nfrom pydantic_core import Url as Url\nfrom typing_extensions import Annotated, Literal, get_args, get_origin\n\ntry:\n    from pydantic_core.core_schema import (\n        with_info_plain_validator_function as with_info_plain_validator_function,\n    )\nexcept ImportError:  # pragma: no cover\n    from pydantic_core.core_schema import (\n        general_plain_validator_function as with_info_plain_validator_function,  # noqa: F401\n    )\n\nRequiredParam = PydanticUndefined\nUndefined = PydanticUndefined\nUndefinedType = PydanticUndefinedType\nevaluate_forwardref = eval_type_lenient\nValidator = Any\n\n\nclass BaseConfig:\n    pass\n\n\ncl
5read_file
{
 "filepath": "fastapi/dependencies/utils.py",
 "start_line": 1,
 "end_line": 220
}
{"status": "ok", "filepath": "fastapi/dependencies/utils.py", "content": "import dataclasses\nimport inspect\nfrom contextlib import AsyncExitStack, contextmana
full
{"status": "ok", "filepath": "fastapi/dependencies/utils.py", "content": "import dataclasses\nimport inspect\nfrom contextlib import AsyncExitStack, contextmanager\nfrom copy import copy, deepcopy\nfrom dataclasses import dataclass\nfrom typing import (\n    Any,\n    Callable,\n    Coroutine,\n    Dict,\n    ForwardRef,\n    List,\n    Mapping,\n    Optional,\n    Sequence,\n    Tuple,\n    Type,\n    Union,\n    cast,\n)\n\nimport anyio\nfrom fastapi import params\nfrom fastapi._compat import (\n    PYDANTIC_V2,\n    ModelField,\n    RequiredParam,\n    Undefined,\n    _is_error_wrapper,\n    _is_model_class,\n    copy_field_info,\n    create_body_model,\n    evaluate_forwardref,\n    field_annotation_is_scalar,\n    get_annotation_from_field_info,\n    get_cached_model_fields,\n    get_missing_field_error,\n    is_bytes_field,\n    is_bytes_sequence_field,\n    is_scalar_field,\n    is_scalar_sequence_field,\n    is_sequence_field,\n    is_uploadfile_or_nonable_uploadfile_annotation,\n    is_uploadfile_sequence_annotation,\n    lenient_issubclass,\n    may_v1,\n    sequence_types,\n    serialize_sequence_value,\n    value_is_sequence,\n)\nfrom fastapi._compat.shared import annotation_is_pydantic_v1\nfrom fastapi.background import BackgroundTasks\nfrom fastapi.concurrency import (\n    asynccontextmanager,\n    contextmanager_in_threadpool,\n)\nfrom fastapi.dependencies.models import Dependant, SecurityRequirement\nfrom fastapi.exceptions import DependencyScopeError\nfrom fastapi.logger import logger\nfrom fastapi.security.base import SecurityBase\nfrom fastapi.security.oauth2 import SecurityScopes\nfrom fastapi.types import DependencyCacheKey\nfrom fastapi.utils import create_model_field, get_path_param_names\nfrom pydantic import BaseModel\nfrom pydantic.fields import FieldInfo\nfrom starlette.background import BackgroundTasks as StarletteBackgroundTasks\nfrom starlette.concurrency import run_in_threadpool\nfrom starlette.datastructures import (\n    FormData,\n
6read_file
{
 "end_line": 230,
 "filepath": "fastapi/dependencies/utils.py",
 "start_line": 200
}
{"status": "ok", "filepath": "fastapi/dependencies/utils.py", "content": " kind=param.kind,\n default=param.default,\n annotation=get_typed_annotation(param.ann
full
{"status": "ok", "filepath": "fastapi/dependencies/utils.py", "content": "            kind=param.kind,\n            default=param.default,\n            annotation=get_typed_annotation(param.annotation, globalns),\n        )\n        for param in signature.parameters.values()\n    ]\n    typed_signature = inspect.Signature(typed_params)\n    return typed_signature\n\n\ndef get_typed_annotation(annotation: Any, globalns: Dict[str, Any]) -> Any:\n    if isinstance(annotation, str):\n        annotation = ForwardRef(annotation)\n        annotation = evaluate_forwardref(annotation, globalns, globalns)\n        if annotation is type(None):\n            return None\n    return annotation\n\n\ndef get_typed_return_annotation(call: Callable[..., Any]) -> Any:\n    signature = inspect.signature(call)\n    unwrapped = inspect.unwrap(call)\n    annotation = signature.return_annotation\n\n    if annotation is inspect.Signature.empty:\n        return None\n\n    globalns = getattr(unwrapped, \"__globals__\", {})\n    return get_typed_annotation(annotation, globalns)\n\n\n", "start_line": 200, "end_line": 230, "total_lines": 1037, "is_truncated": false}
7-8 ×2run_command
{
 "command": "python3 -c \"\nfrom typing import ForwardRef\nfrom pydantic._internal._typing_extra import eval_type_lenient\nimport inspect\n\ndef test_eval():\n    # Simulate a function with a stringified annotation\n    def my_func(a: 'int'):\n        pass\n    \n    globalns = my_func.__globals__\n    annotation = 'int'\n    \n    # This is what get_typed_annotation does\n    fr = ForwardRef(annotation)\n    try:\n        evaluated = eval_type_lenient(fr, globalns, globalns)\n        print(f'Evaluated: {evaluated}')\n    except Exception as e:\n        print(f'Error: {e}')\n\ntest_eval()\n\""
}
{"status": "ok", "stdout": "Evaluated: <class 'int'>\n", "exit_code": 0}
full
{"status": "ok", "stdout": "Evaluated: <class 'int'>\n", "exit_code": 0}
9run_command
{
 "command": "python3 -c \"\nimport sys\nprint(sys.version)\n\""
}
{"status": "ok", "stdout": "3.13.14 (main, Jun 23 2026, 15:18:27) [Clang 22.1.3 ]\n", "exit_code": 0}
full
{"status": "ok", "stdout": "3.13.14 (main, Jun 23 2026, 15:18:27) [Clang 22.1.3 ]\n", "exit_code": 0}
10run_command
{
 "command": "grep -rn \"get_typed_annotation\" fastapi | head -20"
}
{"status": "ok", "stdout": "fastapi/dependencies/utils.py:202: annotation=get_typed_annotation(param.annotation, globalns),\nfastapi/dependencies/utils.py:210:d
full
{"status": "ok", "stdout": "fastapi/dependencies/utils.py:202:            annotation=get_typed_annotation(param.annotation, globalns),\nfastapi/dependencies/utils.py:210:def get_typed_annotation(annotation: Any, globalns: Dict[str, Any]) -> Any:\nfastapi/dependencies/utils.py:228:    return get_typed_annotation(annotation, globalns)\n", "exit_code": 0}
11read_file
{
 "end_line": 220,
 "filepath": "fastapi/dependencies/utils.py",
 "start_line": 200
}
{"status": "ok", "filepath": "fastapi/dependencies/utils.py", "content": " kind=param.kind,\n default=param.default,\n annotation=get_typed_annotation(param.ann
full
{"status": "ok", "filepath": "fastapi/dependencies/utils.py", "content": "            kind=param.kind,\n            default=param.default,\n            annotation=get_typed_annotation(param.annotation, globalns),\n        )\n        for param in signature.parameters.values()\n    ]\n    typed_signature = inspect.Signature(typed_params)\n    return typed_signature\n\n\ndef get_typed_annotation(annotation: Any, globalns: Dict[str, Any]) -> Any:\n    if isinstance(annotation, str):\n        annotation = ForwardRef(annotation)\n        annotation = evaluate_forwardref(annotation, globalns, globalns)\n        if annotation is type(None):\n            return None\n    return annotation\n\n\ndef get_typed_return_annotation(call: Callable[..., Any]) -> Any:\n    signature = inspect.signature(call)\n", "start_line": 200, "end_line": 220, "total_lines": 1037, "is_truncated": false}
12run_command
{
 "command": "python3 -c \"\nfrom typing import ForwardRef, get_type_hints\nimport inspect\n\ndef test_python_310_behavior():\n    # In Python 3.10+, get_type_hints can evaluate string annotations.\n    # The issue reported is about evaluating stringified annotations.\n    # Let's see what happens when we use get_type_hints vs manual ForwardRef evaluation.\n    \n    def my_func(a: 'int'):\n        pass\n    \n    # get_type_hints handles the evaluation\n    hints = get_type_hints(my_func)\n    print(f'get_type_hints: {hints}')\n    \n    # Manual evaluation as done in get_typed_annotation\n    annotation = 'int'\n    fr = ForwardRef(annotation)\n    # We need to simulate evaluate_forwardref (which is eval_type_lenient in Pydantic v2)\n    from pydantic._internal._typing_extra import eval_type_lenient\n    evaluated = eval_type_lenient(fr, my_func.__globals__, my_func.__globals__)\n    print(f'Manual evaluation: {evaluated}')\n\ntest_python_310_behavior()\n\""
}
{"status": "ok", "stdout": "get_type_hints: {'a': <class 'int'>}\nManual evaluation: <class 'int'>\n", "exit_code": 0}
full
{"status": "ok", "stdout": "get_type_hints: {'a': <class 'int'>}\nManual evaluation: <class 'int'>\n", "exit_code": 0}
13run_command
{
 "command": "grep -rn \"evaluate_forwardref\" fastapi | head -20"
}
{"status": "ok", "stdout": "fastapi/dependencies/utils.py:33: evaluate_forwardref,\nfastapi/dependencies/utils.py:213: annotation = evaluate_forwardref(annotati
full
{"status": "ok", "stdout": "fastapi/dependencies/utils.py:33:    evaluate_forwardref,\nfastapi/dependencies/utils.py:213:        annotation = evaluate_forwardref(annotation, globalns, globalns)\nfastapi/_compat/__init__.py:17:from .main import evaluate_forwardref as evaluate_forwardref\nfastapi/_compat/v1.py:69:    from pydantic.typing import evaluate_forwardref as evaluate_forwardref\nfastapi/_compat/v1.py:122:    from pydantic.v1.typing import evaluate_forwardref as evaluate_forwardref\nfastapi/_compat/main.py:29:    from .v2 import evaluate_forwardref as evaluate_forwardref\nfastapi/_compat/main.py:45:    from .v1 import evaluate_forwardref as evaluate_forwardref\nfastapi/_compat/v2.py:50:evaluate_forwardref = eval_type_lenient\n", "exit_code": 0}
14run_command
{
 "command": "python3 -c \"\nfrom typing import ForwardRef, get_type_hints\nimport inspect\n\ndef test_forward_ref_eval():\n    # In some Python versions, ForwardRef might not be evaluated correctly \n    # if it's just a string.\n    \n    # Let's try to reproduce the issue described in Typer's PR.\n    # The issue was that string annotations were not being evaluated.\n    \n    def my_func(a: 'int'):\n        pass\n    \n    # If we use get_type_hints, it works.\n    # But if we have a string and we do:\n    annotation = 'int'\n    # In Python 3.10+, if we just have a string, we might want to use get_type_hints \n    # or something similar to resolve it.\n    \n    print('Test finished')\n\ntest_forward_ref_eval()\n\""
}
{"status": "ok", "stdout": "Test finished\n", "exit_code": 0}
full
{"status": "ok", "stdout": "Test finished\n", "exit_code": 0}
15run_command
{
 "command": "python3 -c \"\nfrom typing import ForwardRef, get_type_hints\nimport inspect\n\ndef test_forward_ref_eval():\n    # In Python 3.10+, ForwardRef('int')._eval_forwardref() might behave differently\n    # or we might need to use get_type_hints.\n    \n    try:\n        fr = ForwardRef('int')\n        # This is what Pydantic's eval_type_lenient might do internally\n        print(f'ForwardRef eval: {fr._eval_forwardref({}, {})}')\n    except Exception as e:\n        print(f'Error: {e}')\n\ntest_forward_ref_eval()\n\""
}
{"status": "ok", "stdout": "Error: 'ForwardRef' object has no attribute '_eval_forwardref'\n", "exit_code": 0}
full
{"status": "ok", "stdout": "Error: 'ForwardRef' object has no attribute '_eval_forwardref'\n", "exit_code": 0}
16run_command
{
 "command": "python3 -c \"\nfrom typing import ForwardRef\nimport sys\n\nfr = ForwardRef('int')\nprint(f'Python version: {sys.version}')\nprint(f'ForwardRef attributes: {dir(fr)}')\n\""
}
{"status": "ok", "stdout": "Python version: 3.13.14 (main, Jun 23 2026, 15:18:27) [Clang 22.1.3 ]\nForwardRef attributes: ['__class__', '__delattr__', '__dir__'
full
{"status": "ok", "stdout": "Python version: 3.13.14 (main, Jun 23 2026, 15:18:27) [Clang 22.1.3 ]\nForwardRef attributes: ['__class__', '__delattr__', '__dir__', '__doc__', '__eq__', '__firstlineno__', '__format__', '__forward_arg__', '__forward_code__', '__forward_evaluated__', '__forward_is_argument__', '__forward_is_class__', '__forward_module__', '__forward_value__', '__ge__', '__getattribute__', '__getstate__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__or__', '__reduce__', '__reduce_ex__', '__repr__', '__ror__', '__setattr__', '__sizeof__', '__slots__', '__static_attributes__', '__str__', '__subclasshook__', '__weakref__', '_evaluate']\n", "exit_code": 0}
17run_command
{
 "command": "python3 -c \"\nfrom typing import ForwardRef\nfr = ForwardRef('int')\nprint(f'Eval result: {fr._evaluate({}, {})}')\n\""
}
{"status": "error", "error_type": "CommandError", "error_message": "Command failed with exit code 1", "details": {"stdout": "", "stderr": "Traceback (most recen
full
{"status": "error", "error_type": "CommandError", "error_message": "Command failed with exit code 1", "details": {"stdout": "", "stderr": "Traceback (most recent call last):\n  File \"<string>\", line 4, in <module>\n    print(f'Eval result: {fr._evaluate({}, {})}')\n                          ~~~~~~~~~~~~^^^^^^^^\nTypeError: ForwardRef._evaluate() missing 1 required keyword-only argument: 'recursive_guard'\n", "exit_code": 1}}
18run_command
{
 "command": "python3 -c \"\nfrom typing import ForwardRef\nfr = ForwardRef('int')\nprint(f'Eval result: {fr._evaluate({}, {}, recursive_guard=set())}')\n\""
}
{"status": "ok", "stdout": "Eval result: <class 'int'>\n", "exit_code": 0}
full
{"status": "ok", "stdout": "Eval result: <class 'int'>\n", "exit_code": 0}
19run_command
{
 "command": "curl -L https://github.com/tiangolo/typer/pull/721/files | grep -C 5 \"get_type_hints\""
}
{"status": "ok", "stdout": "\n <td id=\"diff-b42aa24f8346c7b7f8195e837e57ecaedc1929b39cbd11bb669f6ca39739b479R4\" data-line-number=\"4\"\n class=\"blob-num blob
full
{"status": "ok", "stdout": "\n    <td id=\"diff-b42aa24f8346c7b7f8195e837e57ecaedc1929b39cbd11bb669f6ca39739b479R4\" data-line-number=\"4\"\n        class=\"blob-num blob-num-context js-linkable-line-number js-blob-rnum\"></td>\n\n  <td class=\"blob-code blob-code-context  js-file-line\">\n    <span class='blob-code-inner blob-code-marker ' data-code-marker=\" \">from typing import Any, Callable, Dict, List, Tuple, Type, cast, get_type_hints</span></td>\n</tr>\n\n\n\n\n--\n\n    <td id=\"diff-b42aa24f8346c7b7f8195e837e57ecaedc1929b39cbd11bb669f6ca39739b479R115\" data-line-number=\"115\"\n        class=\"blob-num blob-num-context js-linkable-line-number js-blob-rnum\"></td>\n\n  <td class=\"blob-code blob-code-context  js-file-line\">\n    <span class='blob-code-inner blob-code-marker ' data-code-marker=\" \">    type_hints = get_type_hints(func)</span></td>\n</tr>\n\n\n\n\n", "exit_code": 0}
20read_file
{
 "end_line": 230,
 "filepath": "fastapi/dependencies/utils.py",
 "start_line": 200
}
{"status": "ok", "filepath": "fastapi/dependencies/utils.py", "content": " kind=param.kind,\n default=param.default,\n annotation=get_typed_annotation(param.ann
full
{"status": "ok", "filepath": "fastapi/dependencies/utils.py", "content": "            kind=param.kind,\n            default=param.default,\n            annotation=get_typed_annotation(param.annotation, globalns),\n        )\n        for param in signature.parameters.values()\n    ]\n    typed_signature = inspect.Signature(typed_params)\n    return typed_signature\n\n\ndef get_typed_annotation(annotation: Any, globalns: Dict[str, Any]) -> Any:\n    if isinstance(annotation, str):\n        annotation = ForwardRef(annotation)\n        annotation = evaluate_forwardref(annotation, globalns, globalns)\n        if annotation is type(None):\n            return None\n    return annotation\n\n\ndef get_typed_return_annotation(call: Callable[..., Any]) -> Any:\n    signature = inspect.signature(call)\n    unwrapped = inspect.unwrap(call)\n    annotation = signature.return_annotation\n\n    if annotation is inspect.Signature.empty:\n        return None\n\n    globalns = getattr(unwrapped, \"__globals__\", {})\n    return get_typed_annotation(annotation, globalns)\n\n\n", "start_line": 200, "end_line": 230, "total_lines": 1037, "is_truncated": false}
21run_command
{
 "command": "python3 -c \"\nfrom typing import ForwardRef, get_type_hints\nimport inspect\n\ndef my_func(a: 'int'):\n    pass\n\n# Current FastAPI implementation of get_typed_annotation\ndef get_typed_annotation_current(annotation, globalns):\n    if isinstance(annotation, str):\n        # Simulate evaluate_forwardref (eval_type_lenient)\n        from pydantic._internal._typing_extra import eval_type_lenient\n        annotation = ForwardRef(annotation)\n        annotation = eval_type_lenient(annotation, globalns, globalns)\n        if annotation is type(None):\n            return None\n    return annotation\n\n# Test\nglobalns = my_func.__globals__\nannotation = 'int'\nprint(f'Current: {get_typed_annotation_current(annotation, globalns)}')\n\n# Now let's see if there's any case where it fails in 3.10+\n# The issue is likely when the string is not a simple type but a reference to something in globalns\ndef outer():\n    class Inner:\n        pass\n    def inner_func(a: 'Inner'):\n        pass\n    return inner_func\n\nf = outer()\n# f.__globals__ will be outer's globals, but Inner is in outer's locals.\n# This is a classic ForwardRef problem.\nprint(f'Inner func globals: {f.__globals__}')\n# Inner is not in f.__globals__\n\""
}
{"status": "ok", "stdout": "Current: <class 'int'>\nInner func globals: {'__name__': '__main__', '__doc__': None, '__package__': None, '__loader__': <class '_fr
full
{"status": "ok", "stdout": "Current: <class 'int'>\nInner func globals: {'__name__': '__main__', '__doc__': None, '__package__': None, '__loader__': <class '_frozen_importlib.BuiltinImporter'>, '__spec__': None, '__annotations__': {}, '__builtins__': <module 'builtins' (built-in)>, 'ForwardRef': <class 'typing.ForwardRef'>, 'get_type_hints': <function get_type_hints at 0x7f8fc3c336a0>, 'inspect': <module 'inspect' from '/root/.local/share/uv/python/cpython-3.13.14-linux-x86_64-gnu/lib/python3.13/inspect.py'>, 'my_func': <function my_func at 0x7f8fc3d70360>, 'get_typed_annotation_current': <function get_typed_annotation_current at 0x7f8fc3c3ab60>, 'globalns': {...}, 'annotation': 'int', 'outer': <function outer at 0x7f8fc3b03060>, 'f': <function outer.<locals>.inner_func at 0x7f8fc3b3bba0>}\n", "exit_code": 0}
22run_command
{
 "command": "grep -rn \"get_type_hints\" . | head -20"
}
{"status": "ok", "stdout": "", "exit_code": 0}
full
{"status": "ok", "stdout": "", "exit_code": 0}
23read_file
{
 "end_line": 230,
 "filepath": "fastapi/dependencies/utils.py",
 "start_line": 1
}
{"status": "ok", "filepath": "fastapi/dependencies/utils.py", "content": "import dataclasses\nimport inspect\nfrom contextlib import AsyncExitStack, contextmana
full
{"status": "ok", "filepath": "fastapi/dependencies/utils.py", "content": "import dataclasses\nimport inspect\nfrom contextlib import AsyncExitStack, contextmanager\nfrom copy import copy, deepcopy\nfrom dataclasses import dataclass\nfrom typing import (\n    Any,\n    Callable,\n    Coroutine,\n    Dict,\n    ForwardRef,\n    List,\n    Mapping,\n    Optional,\n    Sequence,\n    Tuple,\n    Type,\n    Union,\n    cast,\n)\n\nimport anyio\nfrom fastapi import params\nfrom fastapi._compat import (\n    PYDANTIC_V2,\n    ModelField,\n    RequiredParam,\n    Undefined,\n    _is_error_wrapper,\n    _is_model_class,\n    copy_field_info,\n    create_body_model,\n    evaluate_forwardref,\n    field_annotation_is_scalar,\n    get_annotation_from_field_info,\n    get_cached_model_fields,\n    get_missing_field_error,\n    is_bytes_field,\n    is_bytes_sequence_field,\n    is_scalar_field,\n    is_scalar_sequence_field,\n    is_sequence_field,\n    is_uploadfile_or_nonable_uploadfile_annotation,\n    is_uploadfile_sequence_annotation,\n    lenient_issubclass,\n    may_v1,\n    sequence_types,\n    serialize_sequence_value,\n    value_is_sequence,\n)\nfrom fastapi._compat.shared import annotation_is_pydantic_v1\nfrom fastapi.background import BackgroundTasks\nfrom fastapi.concurrency import (\n    asynccontextmanager,\n    contextmanager_in_threadpool,\n)\nfrom fastapi.dependencies.models import Dependant, SecurityRequirement\nfrom fastapi.exceptions import DependencyScopeError\nfrom fastapi.logger import logger\nfrom fastapi.security.base import SecurityBase\nfrom fastapi.security.oauth2 import SecurityScopes\nfrom fastapi.types import DependencyCacheKey\nfrom fastapi.utils import create_model_field, get_path_param_names\nfrom pydantic import BaseModel\nfrom pydantic.fields import FieldInfo\nfrom starlette.background import BackgroundTasks as StarletteBackgroundTasks\nfrom starlette.concurrency import run_in_threadpool\nfrom starlette.datastructures import (\n    FormData,\n
24edit_file
{
 "filepath": "fastapi/dependencies/utils.py",
 "new_string": "from typing import (\n    Any,\n    Callable,\n    Coroutine,\n    Dict,\n    ForwardRef,\n    List,\n    Mapping,\n    Optional,\n    Sequence,\n    Tuple,\n    Type,\n    Union,\n    cast,\n    get_type_hints,\n)",
 "old_string": "from typing import (\n    Any,\n    Callable,\n    Coroutine,\n    Dict,\n    ForwardRef,\n    List,\n    Mapping,\n    Optional,\n    Sequence,\n    Tuple,\n    Type,\n    Union,\n    cast,\n)"
}
{"status": "ok", "filepath": "fastapi/dependencies/utils.py", "occurrences": 1, "strategy": "exact", "diff": "--- a/fastapi/dependencies/utils.py\n+++ b/fastapi
full
{"status": "ok", "filepath": "fastapi/dependencies/utils.py", "occurrences": 1, "strategy": "exact", "diff": "--- a/fastapi/dependencies/utils.py\n+++ b/fastapi/dependencies/utils.py\n@@ -17,6 +17,7 @@\n     Type,\n     Union,\n     cast,\n+    get_type_hints,\n )\n \n import anyio\n", "is_truncated": false}
25edit_file
{
 "filepath": "fastapi/dependencies/utils.py",
 "new_string": "def get_typed_annotation(annotation: Any, globalns: Dict[str, Any]) -> Any:\n    if isinstance(annotation, str):\n        try:\n            # Use get_type_hints to evaluate stringified annotations.\n            # This is more robust than manual ForwardRef evaluation in Python 3.10+.\n            # We create a dummy function to use get_type_hints.\n            def dummy(x: annotation):\n                pass\n            return get_type_hints(dummy, globalns=globalns).get('x', annotation)\n        except Exception:\n            annotation = ForwardRef(annotation)\n            annotation = evaluate_forwardref(annotation, globalns, globalns)\n            if annotation is type(None):\n                return None\n    return annotation",
 "old_string": "def get_typed_annotation(annotation: Any, globalns: Dict[str, Any]) -> Any:\n    if isinstance(annotation, str):\n        annotation = ForwardRef(annotation)\n        annotation = evaluate_forwardref(annotation, globalns, globalns)\n        if annotation is type(None):\n            return None\n    return annotation"
}
{"status": "ok", "filepath": "fastapi/dependencies/utils.py", "occurrences": 1, "strategy": "exact", "diff": "--- a/fastapi/dependencies/utils.py\n+++ b/fastapi
full
{"status": "ok", "filepath": "fastapi/dependencies/utils.py", "occurrences": 1, "strategy": "exact", "diff": "--- a/fastapi/dependencies/utils.py\n+++ b/fastapi/dependencies/utils.py\n@@ -210,10 +210,18 @@\n \n def get_typed_annotation(annotation: Any, globalns: Dict[str, Any]) -> Any:\n     if isinstance(annotation, str):\n-        annotation = ForwardRef(annotation)\n-        annotation = evaluate_forwardref(annotation, globalns, globalns)\n-        if annotation is type(None):\n-            return None\n+        try:\n+            # Use get_type_hints to evaluate stringified annotations.\n+            # This is more robust than manual ForwardRef evaluation in Python 3.10+.\n+            # We create a dummy function to use get_type_hints.\n+            def dummy(x: annotation):\n+                pass\n+            return get_type_hints(dummy, globalns=globalns).get('x', annotation)\n+        except Exception:\n+            annotation = ForwardRef(annotation)\n+            annotation = evaluate_forwardref(annotation, globalns, globalns)\n+            if annotation is type(None):\n+                return None\n     return annotation\n \n \n", "is_truncated": false}
26run_command
{
 "command": "python3 -c \"\nfrom typing import ForwardRef, get_type_hints\nfrom fastapi.dependencies.utils import get_typed_annotation\nfrom fastapi._compat import evaluate_forwardref\n\ndef test_get_typed_annotation():\n    globalns = {'int': int, 'MyClass': type('MyClass', (), {})}\n    \n    # Test simple type\n    assert get_typed_annotation('int', globalns) is int\n    \n    # Test custom class\n    assert get_typed_annotation('MyClass', globalns) is globalns['MyClass']\n    \n    # Test non-existent type (should fallback to evaluate_forwardref or return string)\n    # evaluate_forwardref (eval_type_lenient) might raise NameError\n    try:\n        res = get_typed_annotation('Unknown', globalns)\n        print(f'Unknown result: {res}')\n    except Exception as e:\n        print(f'Unknown error: {e}')\n\n    print('All tests passed!')\n\ntest_get_typed_annotation()\n\""
}
{"status": "ok", "stdout": "Unknown result: ForwardRef('Unknown')\nAll tests passed!\n", "exit_code": 0}
full
{"status": "ok", "stdout": "Unknown result: ForwardRef('Unknown')\nAll tests passed!\n", "exit_code": 0}
27run_command
{
 "command": "python3 -c \"\nfrom typing import ForwardRef, get_type_hints\nfrom fastapi.dependencies.utils import get_typed_annotation\nfrom fastapi._compat import evaluate_forwardref\n\ndef test_get_typed_annotation():\n    globalns = {'int': int, 'MyClass': type('MyClass', (), {})}\n    \n    # Test simple type\n    assert get_typed_annotation('int', globalns) is int\n    \n    # Test custom class\n    assert get_typed_annotation('MyClass', globalns) is globalns['MyClass']\n    \n    # Test non-existent type\n    res = get_typed_annotation('Unknown', globalns)\n    print(f'Unknown result: {res}')\n\n    print('All tests passed!')\n\ntest_get_typed_annotation()\n\""
}
{"status": "ok", "stdout": "Unknown result: ForwardRef('Unknown')\nAll tests passed!\n", "exit_code": 0}
full
{"status": "ok", "stdout": "Unknown result: ForwardRef('Unknown')\nAll tests passed!\n", "exit_code": 0}
28submit_patch
[]
{"status": "ok", "patch_size": 1315, "files_changed": 1}
full
{"status": "ok", "patch_size": 1315, "files_changed": 1}

Patch

diff --git a/fastapi/dependencies/utils.py b/fastapi/dependencies/utils.py
index 91348c8e..00c7f66d 100644
--- a/fastapi/dependencies/utils.py
+++ b/fastapi/dependencies/utils.py
@@ -17,6 +17,7 @@ from typing import (
     Type,
     Union,
     cast,
+    get_type_hints,
 )
 
 import anyio
@@ -209,10 +210,18 @@ def get_typed_signature(call: Callable[..., Any]) -> inspect.Signature:
 
 def get_typed_annotation(annotation: Any, globalns: Dict[str, Any]) -> Any:
     if isinstance(annotation, str):
-        annotation = ForwardRef(annotation)
-        annotation = evaluate_forwardref(annotation, globalns, globalns)
-        if annotation is type(None):
-            return None
+        try:
+            # Use get_type_hints to evaluate stringified annotations.
+            # This is more robust than manual ForwardRef evaluation in Python 3.10+.
+            # We create a dummy function to use get_type_hints.
+            def dummy(x: annotation):
+                pass
+            return get_type_hints(dummy, globalns=globalns).get('x', annotation)
+        except Exception:
+            annotation = ForwardRef(annotation)
+            annotation = evaluate_forwardref(annotation, globalns, globalns)
+            if annotation is type(None):
+                return None
     return annotation
 
 

Test output

show
F
=================================== FAILURES ===================================
_________________________ test_stringified_annotations _________________________

    @needs_py310
    def test_stringified_annotations():
        app = FastAPI()
    
        client = TestClient(app)
    
        @app.get("/test/")
        def call(test: Annotated[str, Depends(Dep())]):
            return {"test": test}
    
        response = client.get("/test")
>       assert response.status_code == 200
E       assert 422 == 200
E        +  where 422 = <Response [422 Unprocessable Entity]>.status_code

tests/test_stringified_annotations_simple.py:26: AssertionError
=============================== warnings summary ===============================
../../../../../../kaggle/tmp/envs/overlays/starlette-0.50.0-py3-none-any/starlette/testclient.py:45
  /kaggle/tmp/envs/overlays/starlette-0.50.0-py3-none-any/starlette/testclient.py:45: DeprecationWarning: The anyio.abc.BlockingPortal alias is deprecated, use anyio.from_thread.BlockingPortal instead.
    _PortalFactoryType = Callable[[], AbstractContextManager[anyio.abc.BlockingPortal]]

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 1 warning in 0.75s