fix(core): use get_type_hints for Python 3.14 TypedDict compatibility (#34390)

Replace direct `__annotations__` access with `get_type_hints()` in
`_convert_any_typed_dicts_to_pydantic` to handle [PEP
649](https://peps.python.org/pep-0649/) deferred annotations in Python
3.14:

> [`Changed in version 3.14: Annotations are now lazily evaluated by
default`](https://docs.python.org/3/reference/compound_stmts.html#annotations)

Before:

```python
class MyTool(TypedDict):
    name: str

MyTool.__annotations__  # {'name': 'str'} - string, not type
issubclass('str', ...)  # TypeError: arg 1 must be a class
```

After:

```python
get_type_hints(MyTool)  # {'name': <class 'str'>} - actual type
```

Fixes #34291
This commit is contained in:
Mason Daugherty
2025-12-16 14:08:01 -05:00
committed by GitHub
parent c85f7b6061
commit 516d74b6df
2 changed files with 13 additions and 3 deletions

View File

@@ -22,6 +22,7 @@ from typing import (
get_type_hints,
)
import typing_extensions
from pydantic import (
BaseModel,
ConfigDict,
@@ -94,7 +95,7 @@ def _is_annotated_type(typ: type[Any]) -> bool:
Returns:
`True` if the type is an Annotated type, `False` otherwise.
"""
return get_origin(typ) is typing.Annotated
return get_origin(typ) in (typing.Annotated, typing_extensions.Annotated)
def _get_annotation_description(arg_type: type) -> str | None:

View File

@@ -18,8 +18,10 @@ from typing import (
cast,
get_args,
get_origin,
get_type_hints,
)
import typing_extensions
from pydantic import BaseModel
from pydantic.v1 import BaseModel as BaseModelV1
from pydantic.v1 import Field as Field_v1
@@ -232,13 +234,20 @@ def _convert_any_typed_dicts_to_pydantic(
if is_typeddict(type_):
typed_dict = type_
docstring = inspect.getdoc(typed_dict)
annotations_ = typed_dict.__annotations__
# Use get_type_hints to properly resolve forward references and
# string annotations in Python 3.14+ (PEP 649 deferred annotations).
# include_extras=True preserves Annotated metadata.
try:
annotations_ = get_type_hints(typed_dict, include_extras=True)
except Exception:
# Fallback for edge cases where get_type_hints might fail
annotations_ = typed_dict.__annotations__
description, arg_descriptions = _parse_google_docstring(
docstring, list(annotations_)
)
fields: dict = {}
for arg, arg_type in annotations_.items():
if get_origin(arg_type) is Annotated: # type: ignore[comparison-overlap]
if get_origin(arg_type) in (Annotated, typing_extensions.Annotated):
annotated_args = get_args(arg_type)
new_arg_type = _convert_any_typed_dicts_to_pydantic(
annotated_args[0], depth=depth + 1, visited=visited