Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions graphene_pydantic/converters.py
Original file line number Diff line number Diff line change
Expand Up @@ -209,6 +209,13 @@ def find_graphene_type(
if isinstance(type_, UnionType):
type_ = T.Union[type_.__args__]

# Unwrap Annotated[T, ...] -> T. Pydantic v2 strips top-level Annotated
# from FieldInfo.annotation, but does NOT strip inside Union arms or
# generic containers, so e.g. Optional[PositiveFloat] reaches us with
# the inner arm still wrapped as Annotated[float, Gt(gt=0)].
if T.get_origin(type_) is T.Annotated:
type_ = T.get_args(type_)[0]

if type_ == uuid.UUID:
return UUID
elif type_ in (str, bytes):
Expand Down
32 changes: 30 additions & 2 deletions tests/test_converters.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,15 @@
import graphene
import graphene.types
import pytest
from pydantic import BaseModel
from pydantic import BaseModel, PositiveFloat, PositiveInt
from pydantic import create_model

import graphene_pydantic.converters as converters
from graphene_pydantic.converters import ConversionError, convert_pydantic_field
from graphene_pydantic.converters import (
ConversionError,
convert_pydantic_field,
find_graphene_type,
)
from graphene_pydantic.objecttype import PydanticObjectType
from graphene_pydantic.registry import Placeholder, get_global_registry

Expand Down Expand Up @@ -212,6 +216,30 @@ def test_unresolved_placeholders():
)


def test_annotated_unwrapped_at_top_level():
# Pydantic strips top-level Annotated from FieldInfo.annotation, but
# find_graphene_type may still receive a raw Annotated[T, ...] from
# callers; verify it resolves to the underlying type.
field = _get_field_from_spec("attr", (PositiveFloat, 1.0))
assert find_graphene_type(PositiveFloat, field, None) is graphene.Float


def test_annotated_inside_optional():
# Regression: Optional[PositiveFloat] reaches find_graphene_type as
# Union[Annotated[float, Gt(gt=0)], None]; the inner arm is still
# Annotated and previously raised ConversionError.
field = _get_field_from_spec("attr", (T.Optional[PositiveFloat], 1.0))
# Optional[T] decomposes to T in graphene_pydantic's union handling.
assert find_graphene_type(field.annotation, field, None) is graphene.Float


def test_annotated_inside_list():
field = _get_field_from_spec("attr", (T.List[PositiveInt], [1]))
result = find_graphene_type(field.annotation, field, None)
assert isinstance(result, graphene.List)
assert result.of_type is graphene.Int


def test_self_referencing():
class NodeModel(BaseModel):
id: int
Expand Down