failed WRONG_FIX UNSUBMITTED wrong_fix_unsubmitted(budget) · 41 tool calls · 304 s · fastapi/fastapi
🐛 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 | Arguments | Result |
|---|---|---|---|
| 1 | run_command | {
"command": "# 1: Search for where annotations are evaluated in the codebase.\n( grep -rn 'get_type_hints' --include=*.py . || echo '<IMPORTANT> NO MATCH FOUND. DO NOT TRY SAME COMMAND.' ) | head -40\n"
} | {"status": "ok", "stdout": "<IMPORTANT> NO MATCH FOUND. DO NOT TRY SAME COMMAND.\n", "stderr": "", "exit_code": 0}full{"status": "ok", "stdout": "<IMPORTANT> NO MATCH FOUND. DO NOT TRY SAME COMMAND.\n", "stderr": "", "exit_code": 0} |
| 2 | run_command | {
"command": "# 1.1: Search for where typing.get_type_hints might be imported or used under a different name.\n( grep -rn 'get_type_hints' --include=*.py . || echo '<IMPORTANT> NO MATCH FOUND. DO NOT TRY SAME COMMAND.' ) | head -40\n"
} | {"status": "ok", "stdout": "<IMPORTANT> NO MATCH FOUND. DO NOT TRY SAME COMMAND.\n", "stderr": "", "exit_code": 0}full{"status": "ok", "stdout": "<IMPORTANT> NO MATCH FOUND. DO NOT TRY SAME COMMAND.\n", "stderr": "", "exit_code": 0} |
| 3 | run_command | {
"command": "# 2: Search for 'ForwardRef' or 'evaluate' in the codebase to see how stringified annotations are handled.\n( grep -rn 'ForwardRef' --include=*.py . || echo '<IMPORTANT> NO MATCH FOUND. DO NOT TRY SAME COMMAND.' ) | head -40\n"
} | {"status": "ok", "stdout": "./tests/forward_reference_type.py:4:def forwardref_method(input: \"ForwardRefModel\") -> \"ForwardRefModel\":\n./tests/forward_referfull{"status": "ok", "stdout": "./tests/forward_reference_type.py:4:def forwardref_method(input: \"ForwardRefModel\") -> \"ForwardRefModel\":\n./tests/forward_reference_type.py:5: return ForwardRefModel(x=input.x + 1)\n./tests/forward_reference_type.py:8:class ForwardRefModel(BaseModel):\n./fastapi/dependencies/utils.py:11: ForwardRef,\n./fastapi/dependencies/utils.py:212: annotation = ForwardRef(annotation)\n", "stderr": "", "exit_code": 0} |
| 4 | run_command | {
"command": "# 3: Examine fastapi/dependencies/utils.py around line 212.\nsed -n '200,230p' fastapi/dependencies/utils.py\n"
} | {"status": "ok", "stdout": " kind=param.kind,\n default=param.default,\n annotation=get_typed_annotation(param.annotation, globalns),\n )\n for param in signatufull{"status": "ok", "stdout": " 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", "stderr": "", "exit_code": 0} |
| 5 | run_command | {
"command": "# 4: Find where evaluate_forwardref is defined.\n( grep -rn 'def evaluate_forwardref' --include=*.py . || echo '<IMPORTANT> NO MATCH FOUND. DO NOT TRY SAME COMMAND.' ) | head -40\n"
} | {"status": "ok", "stdout": "<IMPORTANT> NO MATCH FOUND. DO NOT TRY SAME COMMAND.\n", "stderr": "", "exit_code": 0}full{"status": "ok", "stdout": "<IMPORTANT> NO MATCH FOUND. DO NOT TRY SAME COMMAND.\n", "stderr": "", "exit_code": 0} |
| 6 | run_command | {
"command": "# 5: Check imports in fastapi/dependencies/utils.py.\nhead -n 50 fastapi/dependencies/utils.py\n"
} | {"status": "ok", "stdout": "import dataclasses\nimport inspect\nfrom contextlib import AsyncExitStack, contextmanager\nfrom copy import copy, deepcopy\nfrom datfull{"status": "ok", "stdout": "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)\n", "stderr": "", "exit_code": 0} |
| 7 | run_command | {
"command": "# 6: Look at fastapi/_compat.py to see how evaluate_forwardref is implemented.\n( grep -n 'def evaluate_forwardref' fastapi/_compat.py || echo '<IMPORTANT> NO MATCH FOUND. DO NOT TRY SAME COMMAND.' )\n"
} | {"status": "ok", "stdout": "<IMPORTANT> NO MATCH FOUND. DO NOT TRY SAME COMMAND.\n", "stderr": "grep: fastapi/_compat.py: No such file or directory\n", "exit_cofull{"status": "ok", "stdout": "<IMPORTANT> NO MATCH FOUND. DO NOT TRY SAME COMMAND.\n", "stderr": "grep: fastapi/_compat.py: No such file or directory\n", "exit_code": 0} |
| 8 | run_command | {
"command": "# 7: Let me find where evaluate_forwardref is defined in the whole repository.\n( grep -rn 'def evaluate_forwardref' . || echo '<IMPORTANT> NO MATCH FOUND. DO NOT TRY SAME COMMAND.' )\n"
} | {"status": "ok", "stdout": "<IMPORTANT> NO MATCH FOUND. DO NOT TRY SAME COMMAND.\n", "stderr": "", "exit_code": 0}full{"status": "ok", "stdout": "<IMPORTANT> NO MATCH FOUND. DO NOT TRY SAME COMMAND.\n", "stderr": "", "exit_code": 0} |
| 9 | run_command | {
"command": "# 8: Maybe it's an import from elsewhere? Let's look for where 'evaluate_forwardref' is used in fastapi/_compat.py (if it exists).\n# Wait, I saw it was imported from fastapi._compat in fastapi/dependencies/utils.py\n# But grep didn't find it in fastapi/_compat.py. Let's check if it's in another file.\nfind fastapi -name \"_compat.py\"\n"
} | {"status": "ok", "stdout": "", "stderr": "", "exit_code": 0}full{"status": "ok", "stdout": "", "stderr": "", "exit_code": 0} |
| 10 | run_command | {
"command": "# 9: That's strange. Let me check the file list again.\nls -R fastapi | grep _compat.py\n"
} | {"status": "error", "error_type": "CommandError", "error_message": "", "details": {"stdout": "", "stderr": "", "exit_code": 1}}full{"status": "error", "error_type": "CommandError", "error_message": "", "details": {"stdout": "", "stderr": "", "exit_code": 1}} |
| 11 | run_command | {
"command": "# 10: Let's look at the directory structure of fastapi.\nfind fastapi -maxdepth 2\n"
} | {"status": "ok", "stdout": "fastapi\nfastapi/middleware\nfastapi/middleware/gzip.py\nfastapi/middleware/cors.py\nfastapi/middleware/asyncexitstack.py\nfastapi/mfull{"status": "ok", "stdout": "fastapi\nfastapi/middleware\nfastapi/middleware/gzip.py\nfastapi/middleware/cors.py\nfastapi/middleware/asyncexitstack.py\nfastapi/middleware/__init__.py\nfastapi/middleware/httpsredirect.py\nfastapi/middleware/trustedhost.py\nfastapi/middleware/wsgi.py\nfastapi/params.py\nfastapi/responses.py\nfastapi/templating.py\nfastapi/security\nfastapi/security/open_id_connect_url.py\nfastapi/security/oauth2.py\nfastapi/security/__init__.py\nfastapi/security/api_key.py\nfastapi/security/utils.py\nfastapi/security/http.py\nfastapi/security/base.py\nfastapi/exception_handlers.py\nfastapi/websockets.py\nfastapi/applications.py\nfastapi/concurrency.py\nfastapi/background.py\nfastapi/dependencies\nfastapi/dependencies/models.py\nfastapi/dependencies/__init__.py\nfastapi/dependencies/utils.py\nfastapi/__init__.py\nfastapi/encoders.py\nfastapi/types.py\nfastapi/logger.py\nfastapi/openapi\nfastapi/openapi/models.py\nfastapi/openapi/constants.py\nfastapi/openapi/__init__.py\nfastapi/openapi/docs.py\nfastapi/openapi/utils.py\nfastapi/staticfiles.py\nfastapi/cli.py\nfastapi/utils.py\nfastapi/routing.py\nfastapi/testclient.py\nfastapi/temp_pydantic_v1_params.py\nfastapi/py.typed\nfastapi/exceptions.py\nfastapi/param_functions.py\nfastapi/_compat\nfastapi/_compat/v1.py\nfastapi/_compat/__init__.py\nfastapi/_compat/may_v1.py\nfastapi/_compat/shared.py\nfastapi/_compat/model_field.py\nfastapi/_compat/main.py\nfastapi/_compat/v2.py\nfastapi/requests.py\nfastapi/datastructures.py\nfastapi/__main__.py\n", "stderr": "", "exit_code": 0} |
| 12 | run_command | {
"command": "# 11: Ah, fastapi/_compat is a directory. Let's search in that directory.\n( grep -rn 'def evaluate_forwardref' fastapi/_compat | echo '<IMPORTANT> NO MATCH FOUND. DO NOT TRY SAME COMMAND.' )\n"
} | {"status": "ok", "stdout": "<IMPORTANT> NO MATCH FOUND. DO NOT TRY SAME COMMAND.\n", "stderr": "", "exit_code": 0}full{"status": "ok", "stdout": "<IMPORTANT> NO MATCH FOUND. DO NOT TRY SAME COMMAND.\n", "stderr": "", "exit_code": 0} |
| 13 | run_command | {
"command": "# 12: Maybe it's not defined with 'def evaluate_forwardref' but assigned?\n( grep -rn 'evaluate_forwardref =' fastapi/_compat | echo '<IMPORTANT> NO MATCH FOUND. DO NOT TRY SAME COMMAND.' )\n"
} | {"status": "ok", "stdout": "<IMPORTANT> NO MATCH FOUND. DO NOT TRY SAME COMMAND.\n", "stderr": "", "exit_code": 0}full{"status": "ok", "stdout": "<IMPORTANT> NO MATCH FOUND. DO NOT TRY SAME COMMAND.\n", "stderr": "", "exit_code": 0} |
| 14 | run_command | {
"command": "# 13: Let's search for 'evaluate_forwardref' in all files in fastapi/_compat.\ngrep -rn 'evaluate_forwardref' fastapi/_compat\n"
} | {"status": "ok", "stdout": "fastapi/_compat/v1.py:69: from pydantic.typing import evaluate_forwardref as evaluate_forwardref\nfastapi/_compat/v1.py:122: from pyfull{"status": "ok", "stdout": "fastapi/_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/__init__.py:17:from .main 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", "stderr": "", "exit_code": 0} |
| 15 | run_command | {
"command": "# 14: Let's check fastapi/_compat/v2.py and find eval_type_lenient.\ngrep -n 'def eval_type_lenient' fastapi/_compat/v2.py\n"
} | {"status": "error", "error_type": "CommandError", "error_message": "", "details": {"stdout": "", "stderr": "", "exit_code": 1}}full{"status": "error", "error_type": "CommandError", "error_message": "", "details": {"stdout": "", "stderr": "", "exit_code": 1}} |
| 16 | run_command | {
"command": "# 15: Let's search for 'eval_type_lenient' in fastapi/_compat/v2.py.\ngrep -rn 'eval_type_lenient' fastapi/_compat/v2.py\n"
} | {"status": "ok", "stdout": "fastapi/_compat/v2.py:28:from pydantic._internal._typing_extra import eval_type_lenient\nfastapi/_compat/v2.py:50:evaluate_forwardrefull{"status": "ok", "stdout": "fastapi/_compat/v2.py:28:from pydantic._internal._typing_extra import eval_type_lenient\nfastapi/_compat/v2.py:50:evaluate_forwardref = eval_type_lenient\n", "stderr": "", "exit_code": 0} |
| 17 | run_command | {
"command": "# 16: So evaluate_forwardref in v2 is actually pydantic._internal._typing_extra.eval_type_lenient.\n# Let's look at the problem statement again. \"Fix evaluating stringified annotations in Python 3.10\".\n# It mentions typer/pull/721. Let's see what was done there if possible (although I can't access the internet).\n# The issue is about stringified annotations in Python 3.10.\n# In Python 3.10, some types might be stringified or handled differently.\n# Let's look at how ForwardRef is evaluated.\n# In fastapi/dependencies/utils.py:\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# return annotation\n\n# If the annotation is a string, it's converted to a ForwardRef and then evaluated.\n# In Python 3.10, maybe evaluate_forwardref needs different namespaces or handling?\n# The typer pull request might have added something about using `typing.get_type_hints` or similar.\n\n# Let's see if there's any other place where ForwardRef is evaluated.\ngrep -rn 'ForwardRef' .\n"
} | {"status": "ok", "stdout": "./tests/forward_reference_type.py:4:def forwardref_method(input: \"ForwardRefModel\") -> \"ForwardRefModel\":\n./tests/forward_referfull{"status": "ok", "stdout": "./tests/forward_reference_type.py:4:def forwardref_method(input: \"ForwardRefModel\") -> \"ForwardRefModel\":\n./tests/forward_reference_type.py:5: return ForwardRefModel(x=input.x + 1)\n./tests/forward_reference_type.py:8:class ForwardRefModel(BaseModel):\n./fastapi/dependencies/utils.py:11: ForwardRef,\n./fastapi/dependencies/utils.py:212: annotation = ForwardRef(annotation)\n", "stderr": "", "exit_code": 0} |
| 18 | run_command | {
"command": "# 17: Let's check if there are any other places where evaluate_forwardref is used.\ngrep -rn 'evaluate_forwardref' .\n"
} | {"status": "ok", "stdout": "./fastapi/dependencies/utils.py:33: evaluate_forwardref,\n./fastapi/dependencies/utils.py:213: annotation = evaluate_forwardref(annofull{"status": "ok", "stdout": "./fastapi/dependencies/utils.py:33: evaluate_forwardref,\n./fastapi/dependencies/utils.py:213: annotation = evaluate_forwardref(annotation, globalns, globalns)\n./fastapi/_compat/v1.py:69: from pydantic.typing import evaluate_forwardref as evaluate_forwardref\n./fastapi/_compat/v1.py:122: from pydantic.v1.typing import evaluate_forwardref as evaluate_forwardref\n./fastapi/_compat/__init__.py:17:from .main import evaluate_forwardref as evaluate_forwardref\n./fastapi/_compat/main.py:29: from .v2 import evaluate_forwardref as evaluate_forwardref\n./fastapi/_compat/main.py:45: from .v1 import evaluate_forwardref as evaluate_forwardref\n./fastapi/_compat/v2.py:50:evaluate_forwardref = eval_type_lenient\n", "stderr": "", "exit_code": 0} |
| 19 | run_command | {
"command": "# 18: Let's try to reproduce the issue.\n# The issue is about stringified annotations in Python 3.10.\n# I'll create a small reproduction script.\ncat > reproduce_issue.py <<'PY'\nfrom typing import ForwardRef\nfrom fastapi.dependencies.utils import get_typed_annotation\n\nclass Model:\n pass\n\nglobalns = {\"Model\": Model}\nannotation = \"Model\"\nprint(f\"Evaluating '{annotation}' with globalns {globalns}\")\ntry:\n result = get_typed_annotation(annotation, globalns)\n print(f\"Result: {result}\")\nexcept Exception as e:\n print(f\"Error: {e}\")\n\n# Now try with a more complex case that might fail in 3.10\nfrom typing import List\nannotation_list = \"List[Model]\"\nprint(f\"Evaluating '{annotation_list}' with globalns {globalns}\")\ntry:\n result = get_typed_annotation(annotation_list, globalns)\n print(f\"Result: {result}\")\nexcept Exception as e:\n print(f\"Error: {e}\")\nPY\npython3 reproduce_issue.py\n"
} | {"status": "ok", "stdout": "Evaluating 'Model' with globalns {'Model': <class '__main__.Model'>}\nResult: <class '__main__.Model'>\nEvaluating 'List[Model]' witfull{"status": "ok", "stdout": "Evaluating 'Model' with globalns {'Model': <class '__main__.Model'>}\nResult: <class '__main__.Model'>\nEvaluating 'List[Model]' with globalns {'Model': <class '__main__.Model'>, '__builtins__': {'__name__': 'builtins', '__doc__': \"Built-in functions, types, exceptions, and other objects.\\n\\nThis module provides direct access to all 'built-in'\\nidentifiers of Python; for example, builtins.len is\\nthe full name for the built-in function len().\\n\\nThis module is not normally accessed explicitly by most\\napplications, but can be useful in modules that provide\\nobjects with the same name as a built-in value, but in\\nwhich the built-in of that name is also needed.\", '__package__': '', '__loader__': <class '_frozen_importlib.BuiltinImporter'>, '__spec__': ModuleSpec(name='builtins', loader=<class '_frozen_importlib.BuiltinImporter'>, origin='built-in'), '__build_class__': <built-in function __build_class__>, '__import__': <built-in function __import__>, 'abs': <built-in function abs>, 'all': <built-in function all>, 'any': <built-in function any>, 'ascii': <built-in function ascii>, 'bin': <built-in function bin>, 'breakpoint': <built-in function breakpoint>, 'callable': <built-in function callable>, 'chr': <built-in function chr>, 'compile': <built-in function compile>, 'delattr': <built-in function delattr>, 'dir': <built-in function dir>, 'divmod': <built-in function divmod>, 'eval': <built-in function eval>, 'exec': <built-in function exec>, 'format': <built-in function format>, 'getattr': <built-in function getattr>, 'globals': <built-in function globals>, 'hasattr': <built-in function hasattr>, 'hash': <built-in function hash>, 'hex': <built-in function hex>, 'id': <built-in function id>, 'input': <built-in function input>, 'isinstance': <built-in function isinstance>, 'issubclass': <built-in function issubclass>, 'iter': <built-in function iter>, 'aiter': <built-in function aiter>, 'len': <built-in function len>, 'locals': <bu |
| 20 | run_command | {
"command": "# 19: The reproduction didn't fail. Let's try to see if it's related to how ForwardRef is used in Python 3.10.\n# The issue report mentions typer/pull/721. In that PR, they changed:\n# from:\n# annotation = ForwardRef(annotation)\n# annotation = evaluate_forwardref(annotation, globalns, globalns)\n# to:\n# if isinstance(annotation, str):\n# try:\n# # This is the key change!\n# # In Python 3.10+, we can use get_type_hints to evaluate stringified annotations.\n# # But the PR actually does something different.\n# pass\n\n# Let's see if I can find the typer/pull/721 change.\n# Since I can't access the internet, I have to guess.\n# Common issue with ForwardRef in 3.10 is that it might not resolve correctly if the namespace is not perfectly set.\n# Or maybe the issue is that in 3.10, ForwardRef evaluation changed.\n\n# Let's try to use typing.get_type_hints as it's the standard way to resolve forward references.\n# But get_type_hints requires an object (like a class or function).\n\n# Let's look at fastapi/dependencies/utils.py again.\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# return annotation\n\n# If evaluate_forwardref is just calling pydantic's evaluate_forwardref (v1) or eval_type_lenient (v2).\n# Maybe the problem is that it doesn't handle some cases in 3.10.\n\n# Let's try to see if we can use typing.get_type_hints if we wrap the annotation in a dummy class.\ncat > test_get_type_hints.py <<'PY'\nfrom typing import get_type_hints, ForwardRef\nimport inspect\n\nclass Dummy:\n x: \"Model\"\n\nclass Model:\n pass\n\n# This should work\nprint(get_type_hints(Dummy, globalns={\"Model\": Model}))\nPY\npython3 test_get_type_hints.py\n"
} | {"status": "ok", "stdout": "{'x': <class '__main__.Model'>}\n", "stderr": "", "exit_code": 0}full{"status": "ok", "stdout": "{'x': <class '__main__.Model'>}\n", "stderr": "", "exit_code": 0} |
| 21 | run_command | {
"command": "# 20: Let's try to see what happen if we have a string that is not just a simple type name.\n# For example, \"List[Model]\"\ncat > test_get_type_hints_complex.py <<'PY'\nfrom typing import get_type_hints, List\nclass Model:\n pass\n\nclass Dummy:\n x: \"List[Model]\"\n\nprint(get_type_hints(Dummy, globalns={\"Model\": Model, \"List\": List}))\nPY\npython3 test_get_type_hints_complex.py\n"
} | {"status": "ok", "stdout": "{'x': typing.List[__main__.Model]}\n", "stderr": "", "exit_code": 0}full{"status": "ok", "stdout": "{'x': typing.List[__main__.Model]}\n", "stderr": "", "exit_code": 0} |
| 22 | run_command | {
"command": "# 21: If the problem is in Python 3.10 and related to stringified annotations.\n# Let's check if we are running on Python 3.10.\npython3 --version\n"
} | {"status": "ok", "stdout": "Python 3.13.15\n", "stderr": "", "exit_code": 0}full{"status": "ok", "stdout": "Python 3.13.15\n", "stderr": "", "exit_code": 0} |
| 23 | run_command | {
"command": "# 22: I am on Python 3.13. The issue is specifically for 3.10.\n# However, if it was a bug in 3.10, it might also be present in 3.13 or fixed.\n# But if it's a bug in how FastAPI handles it, it might be reproducible.\n# Let's look at the typer pull request 721 logic if I can find any clue in the codebase.\n# Wait, I can search for \"evaluate_forwardref\" in the whole project again, maybe I missed something.\ngrep -rn \"evaluate_forwardref\" .\n"
} | {"status": "ok", "stdout": "./fastapi/dependencies/utils.py:33: evaluate_forwardref,\n./fastapi/dependencies/utils.py:213: annotation = evaluate_forwardref(annofull{"status": "ok", "stdout": "./fastapi/dependencies/utils.py:33: evaluate_forwardref,\n./fastapi/dependencies/utils.py:213: annotation = evaluate_forwardref(annotation, globalns, globalns)\n./fastapi/_compat/v1.py:69: from pydantic.typing import evaluate_forwardref as evaluate_forwardref\n./fastapi/_compat/v1.py:122: from pydantic.v1.typing import evaluate_forwardref as evaluate_forwardref\n./fastapi/_compat/__init__.py:17:from .main import evaluate_forwardref as evaluate_forwardref\n./fastapi/_compat/main.py:29: from .v2 import evaluate_forwardref as evaluate_forwardref\n./fastapi/_compat/main.py:45: from .v1 import evaluate_forwardref as evaluate_forwardref\n./fastapi/_compat/v2.py:50:evaluate_forwardref = eval_type_lenient\n", "stderr": "", "exit_code": 0} |
| 24 | run_command | {
"command": "# 23: I suspect that the issue is that evaluate_forwardref might fail if globalns doesn't contain certain things.\n# In Python 3.10, maybe we need to be more careful.\n\n# Let's look at what typer did in PR 721. I can't see it, but usually it's about using `typing.get_type_hints`.\n# But since we only have the annotation string and the namespace, we can't easily use `get_type_hints`.\n\n# Wait, let's check if there's any other place where annotations are evaluated.\n# Maybe in fastapi/params.py?\ngrep -rn 'annotation' fastapi/params.py | head -20\n"
} | {"status": "ok", "stdout": "fastapi/params.py:34: annotation: Optional[Any] = None,\nfastapi/params.py:120: \"annotation\": annotation,\nfastapi/params.py:148: full{"status": "ok", "stdout": "fastapi/params.py:34: annotation: Optional[Any] = None,\nfastapi/params.py:120: \"annotation\": annotation,\nfastapi/params.py:148: annotation: Optional[Any] = None,\nfastapi/params.py:195: annotation=annotation,\nfastapi/params.py:234: annotation: Optional[Any] = None,\nfastapi/params.py:279: annotation=annotation,\nfastapi/params.py:318: annotation: Optional[Any] = None,\nfastapi/params.py:365: annotation=annotation,\nfastapi/params.py:404: annotation: Optional[Any] = None,\nfastapi/params.py:449: annotation=annotation,\nfastapi/params.py:486: annotation: Optional[Any] = None,\nfastapi/params.py:576: \"annotation\": annotation,\nfastapi/params.py:603: annotation: Optional[Any] = None,\nfastapi/params.py:649: annotation=annotation,\nfastapi/params.py:687: annotation: Optional[Any] = None,\nfastapi/params.py:733: annotation=annotation,\n", "stderr": "", "exit_code": 0} |
| 25 | run_command | {
"command": "# 24: Let's look at how FastAPI handles the function parameters in general.\n# It's likely in fastapi/routing.py or fastapi/applications.py.\ngrep -rn 'inspect.signature' fastapi/routing.py | head -20\n"
} | {"status": "ok", "stdout": "", "stderr": "", "exit_code": 0}full{"status": "ok", "stdout": "", "stderr": "", "exit_code": 0} |
| 26 | run_command | {
"command": "# 25: Let's try to see if there is anything about \"stringified annotations\" in the codebase.\ngrep -rn \"stringified\" .\n"
} | {"status": "ok", "stdout": "Binary file ./.git/index matches\n", "stderr": "", "exit_code": 0}full{"status": "ok", "stdout": "Binary file ./.git/index matches\n", "stderr": "", "exit_code": 0} |
| 27 | run_command | {
"command": "# 26: Let's search for \"ForwardRef\" again, maybe I missed a file.\ngrep -rn \"ForwardRef\" .\n"
} | {"status": "ok", "stdout": "./reproduce_issue.py:1:from typing import ForwardRef\n./tests/forward_reference_type.py:4:def forwardref_method(input: \"ForwardRefMfull{"status": "ok", "stdout": "./reproduce_issue.py:1:from typing import ForwardRef\n./tests/forward_reference_type.py:4:def forwardref_method(input: \"ForwardRefModel\") -> \"ForwardRefModel\":\n./tests/forward_reference_type.py:5: return ForwardRefModel(x=input.x + 1)\n./tests/forward_reference_type.py:8:class ForwardRefModel(BaseModel):\n./test_get_type_hints.py:1:from typing import get_type_hints, ForwardRef\n./fastapi/dependencies/utils.py:11: ForwardRef,\n./fastapi/dependencies/utils.py:212: annotation = ForwardRef(annotation)\n", "stderr": "", "exit_code": 0} |
| 28 | run_command | {
"command": "# 27: Let's look at fastapi/dependencies/utils.py again.\n# The function get_typed_annotation is used in get_typed_signature.\n# Let's see where get_typed_signature is used.\ngrep -rn 'get_typed_signature' .\n"
} | {"status": "ok", "stdout": "./fastapi/dependencies/utils.py:193:def get_typed_signature(call: Callable[..., Any]) -> inspect.Signature:\n./fastapi/dependencies/full{"status": "ok", "stdout": "./fastapi/dependencies/utils.py:193:def get_typed_signature(call: Callable[..., Any]) -> inspect.Signature:\n./fastapi/dependencies/utils.py:252: endpoint_signature = get_typed_signature(call)\n", "stderr": "", "exit_code": 0} |
| 29 | run_command | {
"command": "# 28: Let's look at fastapi/dependencies/utils.py around line 252.\nsed -n '240,270p' fastapi/dependencies/utils.py\n"
} | {"status": "ok", "stdout": ") -> Dependant:\n dependant = Dependant(\n call=call,\n name=name,\n path=path,\n use_cache=use_cache,\n scope=scope,\n own_oauth_scfull{"status": "ok", "stdout": ") -> Dependant:\n dependant = Dependant(\n call=call,\n name=name,\n path=path,\n use_cache=use_cache,\n scope=scope,\n own_oauth_scopes=own_oauth_scopes,\n parent_oauth_scopes=parent_oauth_scopes,\n )\n current_scopes = (parent_oauth_scopes or []) + (own_oauth_scopes or [])\n path_param_names = get_path_param_names(path)\n endpoint_signature = get_typed_signature(call)\n signature_params = endpoint_signature.parameters\n if isinstance(call, SecurityBase):\n security_requirement = SecurityRequirement(\n security_scheme=call, scopes=current_scopes\n )\n dependant.security_requirements.append(security_requirement)\n for param_name, param in signature_params.items():\n is_path_param = param_name in path_param_names\n param_details = analyze_param(\n param_name=param_name,\n annotation=param.annotation,\n value=param.default,\n is_path_param=is_path_param,\n )\n if param_details.depends is not None:\n assert param_details.depends.dependency\n if (\n (dependant.is_gen_callable or dependant.is_async_gen_callable)\n", "stderr": "", "exit_code": 0} |
| 30 | run_command | {
"command": "# 29: Let's search for 'analyze_param' in fastapi/dependencies/utils.py.\ngrep -n 'def analyze_param' fastapi/dependencies/utils.py\n"
} | {"status": "ok", "stdout": "344:def analyze_param(\n", "stderr": "", "exit_code": 0}full{"status": "ok", "stdout": "344:def analyze_param(\n", "stderr": "", "exit_code": 0} |
| 31 | run_command | {
"command": "# 30: Let's look at analyze_param in fastapi/dependencies/utils.py.\nsed -n '344,400p' fastapi/dependencies/utils.py\n"
} | {"status": "ok", "stdout": "def analyze_param(\n *,\n param_name: str,\n annotation: Any,\n value: Any,\n is_path_param: bool,\n) -> ParamDetails:\n field_info full{"status": "ok", "stdout": "def analyze_param(\n *,\n param_name: str,\n annotation: Any,\n value: Any,\n is_path_param: bool,\n) -> ParamDetails:\n field_info = None\n depends = None\n type_annotation: Any = Any\n use_annotation: Any = Any\n if annotation is not inspect.Signature.empty:\n use_annotation = annotation\n type_annotation = annotation\n # Extract Annotated info\n if get_origin(use_annotation) is Annotated:\n annotated_args = get_args(annotation)\n type_annotation = annotated_args[0]\n fastapi_annotations = [\n arg\n for arg in annotated_args[1:]\n if isinstance(arg, (FieldInfo, may_v1.FieldInfo, params.Depends))\n ]\n fastapi_specific_annotations = [\n arg\n for arg in fastapi_annotations\n if isinstance(\n arg,\n (\n params.Param,\n temp_pydantic_v1_params.Param,\n params.Body,\n temp_pydantic_v1_params.Body,\n params.Depends,\n ),\n )\n ]\n if fastapi_specific_annotations:\n fastapi_annotation: Union[\n FieldInfo, may_v1.FieldInfo, params.Depends, None\n ] = fastapi_specific_annotations[-1]\n else:\n fastapi_annotation = None\n # Set default for Annotated FieldInfo\n if isinstance(fastapi_annotation, (FieldInfo, may_v1.FieldInfo)):\n # Copy `field_info` because we mutate `field_info.default` below.\n field_info = copy_field_info(\n field_info=fastapi_annotation, annotation=use_annotation\n )\n assert field_info.default in {\n Undefined,\n may_v1.Undefined,\n } or field_info.default in {RequiredParam, may_v1.RequiredParam}, (\n f\"`{field_info.__class__.__name__ |
| 32 | run_command | {
"command": "# 31: The issue seems to be in get_typed_annotation.\n# Let's look at the typer PR 721 again in my mind.\n# In Python 3.10+, there's a known issue where ForwardRef evaluation might need the globals and locals.\n# In get_typed_annotation:\n# annotation = evaluate_forwardref(annotation, globalns, globalns)\n# It uses globalns for both globalns and localns.\n\n# Let's see if that's the problem.\n# If the annotation is in a function, it might be in the function's globals.\n# Let's try to reproduce the issue with a function.\ncat > reproduce_issue_v2.py <<'PY'\nfrom typing import ForwardRef, List\nfrom fastapi.dependencies.utils import get_typed_annotation\n\nclass Model:\n pass\n\ndef my_func(x: \"Model\"):\n pass\n\n# In Python 3.10, get_type_hints(my_func) would resolve \"Model\" using my_func.__globals__\n# FastAPI's get_typed_annotation is called with globalns.\n\n# Let's see if we can reproduce a failure by using a local class.\ndef test():\n class LocalModel:\n pass\n \n globalns = {} # No LocalModel here\n annotation = \"LocalModel\"\n # This should fail because LocalModel is not in globalns\n try:\n result = get_typed_annotation(annotation, globalns)\n print(f\"Result: {result}\")\n except Exception as e:\n print(f\"Error: {e}\")\n\ntest()\nPY\npython3 reproduce_issue_v2.py\n"
} | {"status": "ok", "stdout": "Result: ForwardRef('LocalModel')\n", "stderr": "", "exit_code": 0}full{"status": "ok", "stdout": "Result: ForwardRef('LocalModel')\n", "stderr": "", "exit_code": 0} |
| 33 | run_command | {
"command": "# 32: Wait, it didn't fail, it just returned ForwardRef('LocalModel').\n# Let's look at get_typed_annotation again.\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# return annotation\n\n# If evaluate_forwardref (eval_type_lenient) doesn't find the type, it probably just returns the ForwardRef.\n\n# Let's look at the typer PR again.\n# If I recall correctly, the issue in 3.10 was that `evaluate_forwardref` sometimes didn't resolve types that should have been resolved.\n# Or maybe it was about how it was called.\n\n# Actually, I found a clue. In some versions of Python, ForwardRef evaluation needs the globals.\n# Let's see if we can change it to use typing.get_type_hints if possible.\n# But we don't have the object.\n\n# Let's try to search for \"evaluate_forwardref\" in the typer repository if I could... but I can't.\n# However, I can search for \"typing.get_type_hints\" in the FastAPI codebase to see if it's used anywhere.\ngrep -rn 'get_type_hints' .\n"
} | {"status": "ok", "stdout": "./test_get_type_hints_complex.py:1:from typing import get_type_hints, List\n./test_get_type_hints_complex.py:8:print(get_type_hints(full{"status": "ok", "stdout": "./test_get_type_hints_complex.py:1:from typing import get_type_hints, List\n./test_get_type_hints_complex.py:8:print(get_type_hints(Dummy, globalns={\"Model\": Model, \"List\": List}))\n./reproduce_issue_v2.py:10:# In Python 3.10, get_type_hints(my_func) would resolve \"Model\" using my_func.__globals__\n./test_get_type_hints.py:1:from typing import get_type_hints, ForwardRef\n./test_get_type_hints.py:11:print(get_type_hints(Dummy, globalns={\"Model\": Model}))\n", "stderr": "", "exit_code": 0} |
| 34 | run_command | {
"command": "# 33: It's not used in the library code.\n# Let's look at fastapi/_compat/v2.py again.\nsed -n '1,100p' fastapi/_compat/v2.py\n"
} | {"status": "ok", "stdout": "import re\nimport warnings\nfrom copy import copy, deepcopy\nfrom dataclasses import dataclass\nfrom enum import Enum\nfrom typing ifull{"status": "ok", "stdout": "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\nclass ErrorWrapper(Exception):\n pass |
| 35 | run_command | {
"command": "# 34: If I can't reproduce it on 3.13, maybe it's really a 3.10 specific issue.\n# But the task says \"Fix evaluating stringified annotations in Python 3.10\".\n# Let's think about what might be different in 3.10.\n# In 3.10, ForwardRef.get_type_hints (not a thing) or typing.get_type_hints might behave differently.\n# Actually, there was a change in how ForwardRef is handled in 3.10.\n\n# Let's try to see if I can find any other clues in the codebase about \"evaluate_forwardref\".\n# I already did that.\n\n# Let's look at the Typer PR 721 again. I'll search for similar issues in FastAPI.\n# Actually, I can't.\n\n# But wait! I see that `evaluate_forwardref` in `fastapi/_compat/v2.py` is just `eval_type_lenient` from `pydantic._internal._typing_extra`.\n# Maybe the issue is that `eval_type_lenient` doesn't handle some cases in 3.10?\n\n# Let's try to change `get_typed_annotation` to use something more robust.\n# One common way to resolve forward references in 3.10+ is to use `typing.get_type_hints`.\n# But since we don't have the object, we can use a dummy object.\n\n# Let's try this:\n# def get_typed_annotation(annotation: Any, globalns: Dict[str, Any]) -> Any:\n# if isinstance(annotation, str):\n# class Dummy:\n# x: Any\n# Dummy.__annotations__ = {\"x\": annotation}\n# try:\n# return get_type_hints(Dummy, globalns=globalns)[\"x\"]\n# except Exception:\n# # fallback\n# annotation = ForwardRef(annotation)\n# return evaluate_forwardref(annotation, globalns, globalns)\n# return annotation\n\n# Let's see if this is what Typer did.\n# Actually, if I look at how `evaluate_forwardref` is called:\n# annotation = evaluate_forwardref(annotation, globalns, globalns)\n# It passes `globalns` as both the global and local namespaces.\n\n# Let's try to see if there's a way to make it more compatible with 3.10.\n# In 3.10, `typing._eval_type` was changed.\n\n# Let's try to see if we can use `typing.get_type_hints` on a dummy class.\n# Let's test this approach.\ncat > test_dummy_get_type_hints.py <<'PY'\nfrom typing import get_type_hints, ForwardRef, List\nfrom fastapi.dependencies.utils import get_typed_annotation\n\nclass Model:\n pass\n\nglobalns = {\"Model\": Model}\nannotation = \"List[Model]\"\n\ndef get_typed_annotation_dummy(annotation, globalns):\n if isinstance(annotation, str):\n class Dummy:\n x: Any\n Dummy.__annotations__ = {\"x\": annotation}\n try:\n return get_type_hints(Dummy, globalns=globalns)[\"x\"]\n except Exception as e:\n print(f\"Dummy failed: {e}\")\n return None\n return annotation\n\nfrom typing import Any\nprint(f\"Original: {get_typed_annotation(annotation, globalns)}\")\nprint(f\"Dummy: {get_typed_annotation_dummy(annotation, globalns)}\")\nPY\npython3 test_dummy_get_type_hints.py\n"
} | {"status": "ok", "stdout": "Original: ForwardRef('List[Model]')\nDummy failed: name 'List' is not defined\nDummy: None\n", "stderr": "", "exit_code": 0}full{"status": "ok", "stdout": "Original: ForwardRef('List[Model]')\nDummy failed: name 'List' is not defined\nDummy: None\n", "stderr": "", "exit_code": 0} |
| 36 | run_command | {
"command": "# 35: Ah, 'List' was not in globalns.\n# Let's add it.\ncat > test_dummy_get_type_hints.py <<'PY'\nfrom typing import get_type_hints, ForwardRef, List, Any\nfrom fastapi.dependencies.utils import get_typed_annotation\n\nclass Model:\n pass\n\nglobalns = {\"Model\": Model, \"List\": List}\nannotation = \"List[Model]\"\n\ndef get_typed_annotation_dummy(annotation, globalns):\n if isinstance(annotation, str):\n class Dummy:\n x: Any\n Dummy.__annotations__ = {\"x\": annotation}\n try:\n return get_type_hints(Dummy, globalns=globalns)[\"x\"]\n except Exception as e:\n print(f\"Dummy failed: {e}\")\n return None\n return annotation\n\nprint(f\"Original: {get_typed_annotation(annotation, globalns)}\")\nprint(f\"Dummy: {get_typed_annotation_dummy(annotation, globalns)}\")\nPY\npython3 test_dummy_get_type_hints.py\n"
} | {"status": "ok", "stdout": "Original: typing.List[__main__.Model]\nDummy: typing.List[__main__.Model]\n", "stderr": "", "exit_code": 0}full{"status": "ok", "stdout": "Original: typing.List[__main__.Model]\nDummy: typing.List[__main__.Model]\n", "stderr": "", "exit_code": 0} |
| 37 | run_command | {
"command": "# 36: Both worked. But why did the original one return ForwardRef('List[Model]') in the previous run?\n# Wait, in my previous run (reproduce_issue.py), it worked!\n# In the previous run:\n# Evaluating 'List[Model]' with globalns {'Model': <class '__main__.Model'>, '__builtins__': ...}\n# Result: <class '__main__.Model'> <-- WAIT! This is WRONG!\n# Let me re-read the output of reproduce_issue.py.\n\n# reproduce_issue.py output:\n# Evaluating 'Model' with globalns {'Model': <class '__main__.Model'>}\n# Result: <class '__main__.Model'>\n# Evaluating 'List[Model]' with globalns {'Model': <class '__main__.Model'>, '__builtins__': ...}\n# Result: <class '__main__.Model'> <-- THIS IS THE BUG!\n# If the annotation is \"List[Model]\", the result should be typing.List[Model], not just Model.\n# Let me check reproduce_issue.py again.\n"
} | {"status": "ok", "stdout": "", "stderr": "", "exit_code": 0}full{"status": "ok", "stdout": "", "stderr": "", "exit_code": 0} |
| 38 | run_command | {
"command": "# 37: Let's re-run reproduce_issue.py and look closely.\npython3 reproduce_issue.py\n"
} | {"status": "ok", "stdout": "Evaluating 'Model' with globalns {'Model': <class '__main__.Model'>}\nResult: <class '__main__.Model'>\nEvaluating 'List[Model]' witfull{"status": "ok", "stdout": "Evaluating 'Model' with globalns {'Model': <class '__main__.Model'>}\nResult: <class '__main__.Model'>\nEvaluating 'List[Model]' with globalns {'Model': <class '__main__.Model'>, '__builtins__': {'__name__': 'builtins', '__doc__': \"Built-in functions, types, exceptions, and other objects.\\n\\nThis module provides direct access to all 'built-in'\\nidentifiers of Python; for example, builtins.len is\\nthe full name for the built-in function len().\\n\\nThis module is not normally accessed explicitly by most\\napplications, but can be useful in modules that provide\\nobjects with the same name as a built-in value, but in\\nwhich the built-in of that name is also needed.\", '__package__': '', '__loader__': <class '_frozen_importlib.BuiltinImporter'>, '__spec__': ModuleSpec(name='builtins', loader=<class '_frozen_importlib.BuiltinImporter'>, origin='built-in'), '__build_class__': <built-in function __build_class__>, '__import__': <built-in function __import__>, 'abs': <built-in function abs>, 'all': <built-in function all>, 'any': <built-in function any>, 'ascii': <built-in function ascii>, 'bin': <built-in function bin>, 'breakpoint': <built-in function breakpoint>, 'callable': <built-in function callable>, 'chr': <built-in function chr>, 'compile': <built-in function compile>, 'delattr': <built-in function delattr>, 'dir': <built-in function dir>, 'divmod': <built-in function divmod>, 'eval': <built-in function eval>, 'exec': <built-in function exec>, 'format': <built-in function format>, 'getattr': <built-in function getattr>, 'globals': <built-in function globals>, 'hasattr': <built-in function hasattr>, 'hash': <built-in function hash>, 'hex': <built-in function hex>, 'id': <built-in function id>, 'input': <built-in function input>, 'isinstance': <built-in function isinstance>, 'issubclass': <built-in function issubclass>, 'iter': <built-in function iter>, 'aiter': <built-in function aiter>, 'len': <built-in function len>, 'locals': <bu |
| 39 | run_command | {
"command": "# 38: I suspect the output was truncated.\n# Let's run it again and pipe it to a file.\npython3 reproduce_issue.py > output.txt\ncat output.txt\n"
} | {"status": "ok", "stdout": "Evaluating 'Model' with globalns {'Model': <class '__main__.Model'>}\nResult: <class '__main__.Model'>\nEvaluating 'List[Model]' witfull{"status": "ok", "stdout": "Evaluating 'Model' with globalns {'Model': <class '__main__.Model'>}\nResult: <class '__main__.Model'>\nEvaluating 'List[Model]' with globalns {'Model': <class '__main__.Model'>, '__builtins__': {'__name__': 'builtins', '__doc__': \"Built-in functions, types, exceptions, and other objects.\\n\\nThis module provides direct access to all 'built-in'\\nidentifiers of Python; for example, builtins.len is\\nthe full name for the built-in function len().\\n\\nThis module is not normally accessed explicitly by most\\napplications, but can be useful in modules that provide\\nobjects with the same name as a built-in value, but in\\nwhich the built-in of that name is also needed.\", '__package__': '', '__loader__': <class '_frozen_importlib.BuiltinImporter'>, '__spec__': ModuleSpec(name='builtins', loader=<class '_frozen_importlib.BuiltinImporter'>, origin='built-in'), '__build_class__': <built-in function __build_class__>, '__import__': <built-in function __import__>, 'abs': <built-in function abs>, 'all': <built-in function all>, 'any': <built-in function any>, 'ascii': <built-in function ascii>, 'bin': <built-in function bin>, 'breakpoint': <built-in function breakpoint>, 'callable': <built-in function callable>, 'chr': <built-in function chr>, 'compile': <built-in function compile>, 'delattr': <built-in function delattr>, 'dir': <built-in function dir>, 'divmod': <built-in function divmod>, 'eval': <built-in function eval>, 'exec': <built-in function exec>, 'format': <built-in function format>, 'getattr': <built-in function getattr>, 'globals': <built-in function globals>, 'hasattr': <built-in function hasattr>, 'hash': <built-in function hash>, 'hex': <built-in function hex>, 'id': <built-in function id>, 'input': <built-in function input>, 'isinstance': <built-in function isinstance>, 'issubclass': <built-in function issubclass>, 'iter': <built-in function iter>, 'aiter': <built-in function aiter>, 'len': <built-in function len>, 'locals': <bu |
| 40 | run_command | {
"command": "# 39: The output is still truncated by the shell/environment.\n# Let's use python to print the output.\npython3 -c \"print(open('output.txt').read())\"\n"
} | {"status": "ok", "stdout": "Evaluating 'Model' with globalns {'Model': <class '__main__.Model'>}\nResult: <class '__main__.Model'>\nEvaluating 'List[Model]' witfull{"status": "ok", "stdout": "Evaluating 'Model' with globalns {'Model': <class '__main__.Model'>}\nResult: <class '__main__.Model'>\nEvaluating 'List[Model]' with globalns {'Model': <class '__main__.Model'>, '__builtins__': {'__name__': 'builtins', '__doc__': \"Built-in functions, types, exceptions, and other objects.\\n\\nThis module provides direct access to all 'built-in'\\nidentifiers of Python; for example, builtins.len is\\nthe full name for the built-in function len().\\n\\nThis module is not normally accessed explicitly by most\\napplications, but can be useful in modules that provide\\nobjects with the same name as a built-in value, but in\\nwhich the built-in of that name is also needed.\", '__package__': '', '__loader__': <class '_frozen_importlib.BuiltinImporter'>, '__spec__': ModuleSpec(name='builtins', loader=<class '_frozen_importlib.BuiltinImporter'>, origin='built-in'), '__build_class__': <built-in function __build_class__>, '__import__': <built-in function __import__>, 'abs': <built-in function abs>, 'all': <built-in function all>, 'any': <built-in function any>, 'ascii': <built-in function ascii>, 'bin': <built-in function bin>, 'breakpoint': <built-in function breakpoint>, 'callable': <built-in function callable>, 'chr': <built-in function chr>, 'compile': <built-in function compile>, 'delattr': <built-in function delattr>, 'dir': <built-in function dir>, 'divmod': <built-in function divmod>, 'eval': <built-in function eval>, 'exec': <built-in function exec>, 'format': <built-in function format>, 'getattr': <built-in function getattr>, 'globals': <built-in function globals>, 'hasattr': <built-in function hasattr>, 'hash': <built-in function hash>, 'hex': <built-in function hex>, 'id': <built-in function id>, 'input': <built-in function input>, 'isinstance': <built-in function isinstance>, 'issubclass': <built-in function issubclass>, 'iter': <built-in function iter>, 'aiter': <built-in function aiter>, 'len': <built-in function len>, 'locals': <bu |
| 41 | run_command | {
"command": "# 40: The output is STILL truncated! This is because the `__builtins__` dict is huge.\n# Let's modify reproduce_issue.py to not print the globalns.\nsed -i \"s/print(f\\\"Evaluating '{annotation_list}' with globalns {globalns}\\\")/print(f\\\"Evaluating '{annotation_list}'\\\")/\" reproduce_issue.py\npython3 reproduce_issue.py\n"
} | {"status": "ok", "stdout": "Evaluating 'Model' with globalns {'Model': <class '__main__.Model'>}\nResult: <class '__main__.Model'>\nEvaluating 'List[Model]' witfull{"status": "ok", "stdout": "Evaluating 'Model' with globalns {'Model': <class '__main__.Model'>}\nResult: <class '__main__.Model'>\nEvaluating 'List[Model]' with globalns {'Model': <class '__main__.Model'>, '__builtins__': {'__name__': 'builtins', '__doc__': \"Built-in functions, types, exceptions, and other objects.\\n\\nThis module provides direct access to all 'built-in'\\nidentifiers of Python; for example, builtins.len is\\nthe full name for the built-in function len().\\n\\nThis module is not normally accessed explicitly by most\\napplications, but can be useful in modules that provide\\nobjects with the same name as a built-in value, but in\\nwhich the built-in of that name is also needed.\", '__package__': '', '__loader__': <class '_frozen_importlib.BuiltinImporter'>, '__spec__': ModuleSpec(name='builtins', loader=<class '_frozen_importlib.BuiltinImporter'>, origin='built-in'), '__build_class__': <built-in function __build_class__>, '__import__': <built-in function __import__>, 'abs': <built-in function abs>, 'all': <built-in function all>, 'any': <built-in function any>, 'ascii': <built-in function ascii>, 'bin': <built-in function bin>, 'breakpoint': <built-in function breakpoint>, 'callable': <built-in function callable>, 'chr': <built-in function chr>, 'compile': <built-in function compile>, 'delattr': <built-in function delattr>, 'dir': <built-in function dir>, 'divmod': <built-in function divmod>, 'eval': <built-in function eval>, 'exec': <built-in function exec>, 'format': <built-in function format>, 'getattr': <built-in function getattr>, 'globals': <built-in function globals>, 'hasattr': <built-in function hasattr>, 'hash': <built-in function hash>, 'hex': <built-in function hex>, 'id': <built-in function id>, 'input': <built-in function input>, 'isinstance': <built-in function isinstance>, 'issubclass': <built-in function issubclass>, 'iter': <built-in function iter>, 'aiter': <built-in function aiter>, 'len': <built-in function len>, 'locals': <bu |
diff --git a/output.txt b/output.txt
new file mode 100644
index 00000000..7d2ee6df
--- /dev/null
+++ b/output.txt
@@ -0,0 +1,16 @@
+Evaluating 'Model' with globalns {'Model': <class '__main__.Model'>}
+Result: <class '__main__.Model'>
+Evaluating 'List[Model]' with globalns {'Model': <class '__main__.Model'>, '__builtins__': {'__name__': 'builtins', '__doc__': "Built-in functions, types, exceptions, and other objects.\n\nThis module provides direct access to all 'built-in'\nidentifiers of Python; for example, builtins.len is\nthe full name for the built-in function len().\n\nThis module is not normally accessed explicitly by most\napplications, but can be useful in modules that provide\nobjects with the same name as a built-in value, but in\nwhich the built-in of that name is also needed.", '__package__': '', '__loader__': <class '_frozen_importlib.BuiltinImporter'>, '__spec__': ModuleSpec(name='builtins', loader=<class '_frozen_importlib.BuiltinImporter'>, origin='built-in'), '__build_class__': <built-in function __build_class__>, '__import__': <built-in function __import__>, 'abs': <built-in function abs>, 'all': <built-in function all>, 'any': <built-in function any>, 'ascii': <built-in function ascii>, 'bin': <built-in function bin>, 'breakpoint': <built-in function breakpoint>, 'callable': <built-in function callable>, 'chr': <built-in function chr>, 'compile': <built-in function compile>, 'delattr': <built-in function delattr>, 'dir': <built-in function dir>, 'divmod': <built-in function divmod>, 'eval': <built-in function eval>, 'exec': <built-in function exec>, 'format': <built-in function format>, 'getattr': <built-in function getattr>, 'globals': <built-in function globals>, 'hasattr': <built-in function hasattr>, 'hash': <built-in function hash>, 'hex': <built-in function hex>, 'id': <built-in function id>, 'input': <built-in function input>, 'isinstance': <built-in function isinstance>, 'issubclass': <built-in function issubclass>, 'iter': <built-in function iter>, 'aiter': <built-in function aiter>, 'len': <built-in function len>, 'locals': <built-in function locals>, 'max': <built-in function max>, 'min': <built-in function min>, 'next': <built-in function next>, 'anext': <built-in function anext>, 'oct': <built-in function oct>, 'ord': <built-in function ord>, 'pow': <built-in function pow>, 'print': <built-in function print>, 'repr': <built-in function repr>, 'round': <built-in function round>, 'setattr': <built-in function setattr>, 'sorted': <built-in function sorted>, 'sum': <built-in function sum>, 'vars': <built-in function vars>, 'None': None, 'Ellipsis': Ellipsis, 'NotImplemented': NotImplemented, 'False': False, 'True': True, 'bool': <class 'bool'>, 'memoryview': <class 'memoryview'>, 'bytearray': <class 'bytearray'>, 'bytes': <class 'bytes'>, 'classmethod': <class 'classmethod'>, 'complex': <class 'complex'>, 'dict': <class 'dict'>, 'enumerate': <class 'enumerate'>, 'filter': <class 'filter'>, 'float': <class 'float'>, 'frozenset': <class 'frozenset'>, 'property': <class 'property'>, 'int': <class 'int'>, 'list': <class 'list'>, 'map': <class 'map'>, 'object': <class 'object'>, 'range': <class 'range'>, 'reversed': <class 'reversed'>, 'set': <class 'set'>, 'slice': <class 'slice'>, 'staticmethod': <class 'staticmethod'>, 'str': <class 'str'>, 'super': <class 'super'>, 'tuple': <class 'tuple'>, 'type': <class 'type'>, 'zip': <class 'zip'>, '__debug__': True, 'BaseException': <class 'BaseException'>, 'BaseExceptionGroup': <class 'BaseExceptionGroup'>, 'Exception': <class 'Exception'>, 'GeneratorExit': <class 'GeneratorExit'>, 'KeyboardInterrupt': <class 'KeyboardInterrupt'>, 'SystemExit': <class 'SystemExit'>, 'ArithmeticError': <class 'ArithmeticError'>, 'AssertionError': <class 'AssertionError'>, 'AttributeError': <class 'AttributeError'>, 'BufferError': <class 'BufferError'>, 'EOFError': <class 'EOFError'>, 'ImportError': <class 'ImportError'>, 'LookupError': <class 'LookupError'>, 'MemoryError': <class 'MemoryError'>, 'NameError': <class 'NameError'>, 'OSError': <class 'OSError'>, 'ReferenceError': <class 'ReferenceError'>, 'RuntimeError': <class 'RuntimeError'>, 'StopAsyncIteration': <class 'StopAsyncIteration'>, 'StopIteration': <class 'StopIteration'>, 'SyntaxError': <class 'SyntaxError'>, 'SystemError': <class 'SystemError'>, 'TypeError': <class 'TypeError'>, 'ValueError': <class 'ValueError'>, 'Warning': <class 'Warning'>, 'FloatingPointError': <class 'FloatingPointError'>, 'OverflowError': <class 'OverflowError'>, 'ZeroDivisionError': <class 'ZeroDivisionError'>, 'BytesWarning': <class 'BytesWarning'>, 'DeprecationWarning': <class 'DeprecationWarning'>, 'EncodingWarning': <class 'EncodingWarning'>, 'FutureWarning': <class 'FutureWarning'>, 'ImportWarning': <class 'ImportWarning'>, 'PendingDeprecationWarning': <class 'PendingDeprecationWarning'>, 'ResourceWarning': <class 'ResourceWarning'>, 'RuntimeWarning': <class 'RuntimeWarning'>, 'SyntaxWarning': <class 'SyntaxWarning'>, 'UnicodeWarning': <class 'UnicodeWarning'>, 'UserWarning': <class 'UserWarning'>, 'BlockingIOError': <class 'BlockingIOError'>, 'ChildProcessError': <class 'ChildProcessError'>, 'ConnectionError': <class 'ConnectionError'>, 'FileExistsError': <class 'FileExistsError'>, 'FileNotFoundError': <class 'FileNotFoundError'>, 'InterruptedError': <class 'InterruptedError'>, 'IsADirectoryError': <class 'IsADirectoryError'>, 'NotADirectoryError': <class 'NotADirectoryError'>, 'PermissionError': <class 'PermissionError'>, 'ProcessLookupError': <class 'ProcessLookupError'>, 'TimeoutError': <class 'TimeoutError'>, 'IndentationError': <class 'IndentationError'>, '_IncompleteInputError': <class '_IncompleteInputError'>, 'IndexError': <class 'IndexError'>, 'KeyError': <class 'KeyError'>, 'ModuleNotFoundError': <class 'ModuleNotFoundError'>, 'NotImplementedError': <class 'NotImplementedError'>, 'PythonFinalizationError': <class 'PythonFinalizationError'>, 'RecursionError': <class 'RecursionError'>, 'UnboundLocalError': <class 'UnboundLocalError'>, 'UnicodeError': <class 'UnicodeError'>, 'BrokenPipeError': <class 'BrokenPipeError'>, 'ConnectionAbortedError': <class 'ConnectionAbortedError'>, 'ConnectionRefusedError': <class 'ConnectionRefusedError'>, 'ConnectionResetError': <class 'ConnectionResetError'>, 'TabError': <class 'TabError'>, 'UnicodeDecodeError': <class 'UnicodeDecodeError'>, 'UnicodeEncodeError': <class 'UnicodeEncodeError'>, 'UnicodeTranslateError': <class 'UnicodeTranslateError'>, 'ExceptionGroup': <class 'ExceptionGroup'>, 'EnvironmentError': <class 'OSError'>, 'IOError': <class 'OSError'>, 'open': <built-in function open>, 'quit': Use quit() or Ctrl-D (i.e. EOF) to exit, 'exit': Use exit() or Ctrl-D (i.e. EOF) to exit, 'copyright': Copyright (c) 2001-2024 Python Software Foundation.
+All Rights Reserved.
+
+Copyright (c) 2000 BeOpen.com.
+All Rights Reserved.
+
+Copyright (c) 1995-2001 Corporation for National Research Initiatives.
+All Rights Reserved.
+
+Copyright (c) 1991-1995 Stichting Mathematisch Centrum, Amsterdam.
+All Rights Reserved., 'credits': Thanks to CWI, CNRI, BeOpen, Zope Corporation, the Python Software
+Foundation, and a cast of thousands for supporting Python
+development. See www.python.org for more information., 'license': Type license() to see the full license text, 'help': Type help() for interactive help, or help(object) for help about object.}}
+Result: ForwardRef('List[Model]')
diff --git a/reproduce_issue.py b/reproduce_issue.py
new file mode 100644
index 00000000..c72bed51
--- /dev/null
+++ b/reproduce_issue.py
@@ -0,0 +1,24 @@
+from typing import ForwardRef
+from fastapi.dependencies.utils import get_typed_annotation
+
+class Model:
+ pass
+
+globalns = {"Model": Model}
+annotation = "Model"
+print(f"Evaluating '{annotation}' with globalns {globalns}")
+try:
+ result = get_typed_annotation(annotation, globalns)
+ print(f"Result: {result}")
+except Exception as e:
+ print(f"Error: {e}")
+
+# Now try with a more complex case that might fail in 3.10
+from typing import List
+annotation_list = "List[Model]"
+print(f"Evaluating '{annotation_list}' with globalns {globalns}")
+try:
+ result = get_typed_annotation(annotation_list, globalns)
+ print(f"Result: {result}")
+except Exception as e:
+ print(f"Error: {e}")
diff --git a/reproduce_issue_v2.py b/reproduce_issue_v2.py
new file mode 100644
index 00000000..8a023990
--- /dev/null
+++ b/reproduce_issue_v2.py
@@ -0,0 +1,27 @@
+from typing import ForwardRef, List
+from fastapi.dependencies.utils import get_typed_annotation
+
+class Model:
+ pass
+
+def my_func(x: "Model"):
+ pass
+
+# In Python 3.10, get_type_hints(my_func) would resolve "Model" using my_func.__globals__
+# FastAPI's get_typed_annotation is called with globalns.
+
+# Let's see if we can reproduce a failure by using a local class.
+def test():
+ class LocalModel:
+ pass
+
+ globalns = {} # No LocalModel here
+ annotation = "LocalModel"
+ # This should fail because LocalModel is not in globalns
+ try:
+ result = get_typed_annotation(annotation, globalns)
+ print(f"Result: {result}")
+ except Exception as e:
+ print(f"Error: {e}")
+
+test()
diff --git a/test_dummy_get_type_hints.py b/test_dummy_get_type_hints.py
new file mode 100644
index 00000000..7a8be4e8
--- /dev/null
+++ b/test_dummy_get_type_hints.py
@@ -0,0 +1,23 @@
+from typing import get_type_hints, ForwardRef, List, Any
+from fastapi.dependencies.utils import get_typed_annotation
+
+class Model:
+ pass
+
+globalns = {"Model": Model, "List": List}
+annotation = "List[Model]"
+
+def get_typed_annotation_dummy(annotation, globalns):
+ if isinstance(annotation, str):
+ class Dummy:
+ x: Any
+ Dummy.__annotations__ = {"x": annotation}
+ try:
+ return get_type_hints(Dummy, globalns=globalns)["x"]
+ except Exception as e:
+ print(f"Dummy failed: {e}")
+ return None
+ return annotation
+
+print(f"Original: {get_typed_annotation(annotation, globalns)}")
+print(f"Dummy: {get_typed_annotation_dummy(annotation, globalns)}")
diff --git a/test_get_type_hints.py b/test_get_type_hints.py
new file mode 100644
index 00000000..b081a004
--- /dev/null
+++ b/test_get_type_hints.py
@@ -0,0 +1,11 @@
+from typing import get_type_hints, ForwardRef
+import inspect
+
+class Dummy:
+ x: "Model"
+
+class Model:
+ pass
+
+# This should work
+print(get_type_hints(Dummy, globalns={"Model": Model}))
diff --git a/test_get_type_hints_complex.py b/test_get_type_hints_complex.py
new file mode 100644
index 00000000..76b26bf5
--- /dev/null
+++ b/test_get_type_hints_complex.py
@@ -0,0 +1,8 @@
+from typing import get_type_hints, List
+class Model:
+ pass
+
+class Dummy:
+ x: "List[Model]"
+
+print(get_type_hints(Dummy, globalns={"Model": Model, "List": List}))
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 ===============================
../../../../../../../Users/jp/repos/kaggle-gemini-coding-agent-post-training/.envs/overlays/starlette-0.50.0-py3-none-any/starlette/testclient.py:45
/Users/jp/repos/kaggle-gemini-coding-agent-post-training/.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.55s