Skip to content

Inconsistent handling of Union of generics and regular generics in return types #4052

@sebastiandev

Description

@sebastiandev

In my queries/mutations I'm returning sqlalchemy objects. The GQL types might expose the entire set of fields or a subset. We usually have 1-1, but we might get a GQL type that resolves fields from multiple sqlalchemy models.

For example, when I build a Page object, I have

return Page[Product](items=[SQLProduct(), SQLProductt()])

If I define the output of an operation like

products: Page[Product] | Error = strawberry.field(resolver=get_products)

Then I get an error saying the root is not a type of the union possible types.

If I define the query without returning an Union it works fine

products: Page[Product] = strawberry.field(resolver=get_products)

My guess is that somewhere in the resolution it is not resolving the type properly, so in the end i get the error

"Page is not in the union types [Product, Error]"

Context

I have a base class for all my gql types, where I configure the mapping to our sql types
where I implement is_type_of. Here i check that the obj param matches with my mapping. This allows me to pass sqlobjects to resolvers that expect object types. That's something we have been doing with graphene for ages, but found abit more challenging with strawberry because of all the type checking, but I feel like its how gql was meant to be. Otherwise I would have to convert all my results to gql types before returning them, which kinda defeats the purpose of having the field resolvers.

It looks something like this

@strawberry.type
class BaseGQLType:
    """
    Base type for all GQL strawberry types.
    """

    __domain_type__: ClassVar[type | None] = None

    @classmethod
    def is_type_of(cls, obj: Any, _info: GraphQLResolveInfo) -> bool:
        if (cls == obj.__class__) or (cls.__domain_type__ and isinstance(obj, cls.__domain_type__)):
            return True
        return super().is_type_of(obj, _info) if hasattr(super(), "is_type_of") else False  # type: ignore[misc]

So my GQL Product type would look like

@strawberry.type
class Product(BaseGQLType):
    __domain_type__ : strawberry.Private[type| None] = SQLProduct

    name: str
    price: Decimal

And my query would look like

@strawberry.type
class Error(BaseGQLType):
    message: str


@strawberry.type
class Page(Generic[T], BaseGQLType):
    items: list[T]


def get_products() -> Page[Product] | Error:
    result = fetch_sql_products()
    return Page[Product](items=result)  # <== here's where we return a sql object instead of a gql object type


@strawberry.type
class Query:
    products: Page[Product] | Error = strawberry.field(resolver=get_products)

If I change to just return the generic Page (no union with error) it works

def get_products() -> Page[Product]:
    result = fetch_sql_products()
    return Page[Product](items=result)


@strawberry.type
class Query:
    products: Page[Product] = strawberry.field(resolver=get_products)

I have also noticed that if I pass items as [] then it works fine

# Any of this two work as well

def get_products() -> Page[Product] | Error:
    result = fetch_sql_products()
    return Page[Product](items=[])

So it looks like there's an issue with sending other than the actual gql type in the result when using unions of generics.

System Information

  • Operating system: ubuntu 24.04
  • Python version: 3.12
  • Strawberry version (if applicable): 0.283.3

Metadata

Metadata

Assignees

Labels

bugSomething isn't working

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions