strawberry-graphql / strawberry

A GraphQL library for Python that leverages type annotations 🍓
https://strawberry.rocks
MIT License
4.01k stars 533 forks source link

Generics broken when using multiple TypeVars #905

Open patrick91 opened 3 years ago

patrick91 commented 3 years ago

While chatting with @BryceBeagle we found a bug with Generics, the following code should work:

import strawberry

from typing import Generic, TypeVar, List

T = TypeVar("T")
S = TypeVar("S")

@strawberry.type
class MyType(Generic[T]):
    my_field: T

@strawberry.type
class OtherType(Generic[S]):
    some_field: List[MyType[S]]

@strawberry.type
class Query:
    a: OtherType[int]

schema = strawberry.Schema(Query)

but instead it crashes with the following error:

Traceback (most recent call last):
  File "/Users/patrick/Documents/github/strawberry-graphql/strawberry/.venv/lib/python3.7/site-packages/graphql/type/definition.py", line 758, in fields
    fields = resolve_thunk(self._fields)
  File "/Users/patrick/Documents/github/strawberry-graphql/strawberry/.venv/lib/python3.7/site-packages/graphql/type/definition.py", line 296, in resolve_thunk
    return thunk() if callable(thunk) else thunk
  File "./strawberry/schema/schema_converter.py", line 281, in get_graphql_fields
    for field in type_definition.fields:
  File "./strawberry/types/types.py", line 40, in fields
    return _resolve_types(self._fields)
  File "./strawberry/types/type_resolver.py", line 319, in _resolve_types
    resolve_type_field(field)
  File "./strawberry/types/type_resolver.py", line 170, in resolve_type_field
    field.type = copy_type_with(field.type, *args)
  File "./strawberry/types/generics.py", line 102, in copy_type_with
    field.child.type, params_to_type=params_to_type
  File "./strawberry/types/generics.py", line 115, in copy_type_with
    field.type, params_to_type=params_to_type
  File "./strawberry/types/generics.py", line 150, in copy_type_with
    return params_to_type[base]  # type: ignore
KeyError: ~T

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "<string>", line 1, in <module>
  File "/Users/patrick/Documents/github/strawberry-graphql/strawberry/.venv/lib/python3.7/site-packages/hupper/ipc.py", line 319, in spawn_main
    func(**kwargs)
  File "/Users/patrick/Documents/github/strawberry-graphql/strawberry/.venv/lib/python3.7/site-packages/hupper/worker.py", line 252, in worker_main
    func(*spec_args, **spec_kwargs)
  File "/Users/patrick/Documents/github/strawberry-graphql/strawberry/.venv/lib/python3.7/site-packages/click/core.py", line 829, in __call__
    return self.main(*args, **kwargs)
  File "/Users/patrick/Documents/github/strawberry-graphql/strawberry/.venv/lib/python3.7/site-packages/click/core.py", line 782, in main
    rv = self.invoke(ctx)
  File "/Users/patrick/Documents/github/strawberry-graphql/strawberry/.venv/lib/python3.7/site-packages/click/core.py", line 1259, in invoke
    return _process_result(sub_ctx.command.invoke(sub_ctx))
  File "/Users/patrick/Documents/github/strawberry-graphql/strawberry/.venv/lib/python3.7/site-packages/click/core.py", line 1066, in invoke
    return ctx.invoke(self.callback, **ctx.params)
  File "/Users/patrick/Documents/github/strawberry-graphql/strawberry/.venv/lib/python3.7/site-packages/click/core.py", line 610, in invoke
    return callback(*args, **kwargs)
  File "./strawberry/cli/commands/server.py", line 33, in server
    schema_module = importlib.import_module(module)
  File "/Users/patrick/.asdf/installs/python/3.7.10/lib/python3.7/importlib/__init__.py", line 127, in import_module
    return _bootstrap._gcd_import(name[level:], package, level)
  File "<frozen importlib._bootstrap>", line 1006, in _gcd_import
  File "<frozen importlib._bootstrap>", line 983, in _find_and_load
  File "<frozen importlib._bootstrap>", line 967, in _find_and_load_unlocked
  File "<frozen importlib._bootstrap>", line 677, in _load_unlocked
  File "<frozen importlib._bootstrap_external>", line 728, in exec_module
  File "<frozen importlib._bootstrap>", line 219, in _call_with_frames_removed
  File "./demo.py", line 24, in <module>
    schema = strawberry.Schema(Query)
  File "./strawberry/schema/schema.py", line 69, in __init__
    types=list(map(self.schema_converter.from_object_type, types)),
  File "/Users/patrick/Documents/github/strawberry-graphql/strawberry/.venv/lib/python3.7/site-packages/graphql/type/schema.py", line 208, in __init__
    collect_referenced_types(query)
  File "/Users/patrick/Documents/github/strawberry-graphql/strawberry/.venv/lib/python3.7/site-packages/graphql/type/schema.py", line 422, in collect_referenced_types
    for field in named_type.fields.values():
  File "/Users/patrick/Documents/github/strawberry-graphql/strawberry/.venv/lib/python3.7/site-packages/graphql/pyutils/cached_property.py", line 30, in __get__
    value = obj.__dict__[self.func.__name__] = self.func(obj)
  File "/Users/patrick/Documents/github/strawberry-graphql/strawberry/.venv/lib/python3.7/site-packages/graphql/type/definition.py", line 760, in fields
    raise TypeError(f"{self.name} fields cannot be resolved. {error}")
TypeError: Query fields cannot be resolved. ~T

Upvote & Fund

Fund with Polar

huonw commented 1 month ago

(A light bit of triage since I encountered this in our codebase just now:)

Seemingly related issues:

In any case, this seems to still reproduce, although with slightly different symptoms:

import strawberry
from typing import Generic, TypeVar

import strawberry

T = TypeVar("T")
U = TypeVar("U")

@strawberry.input
class InnerGeneric(Generic[T]):
    field: T

@strawberry.input
class OuterGeneric(Generic[U]):
    outer: InnerGeneric[U]

# @strawberry.input
# class Subclass(OuterGeneric[int]): pass

@strawberry.type
class Query:
    @strawberry.field
    def direct(self, input: OuterGeneric[int]) -> int:
        return 0

    # @strawberry.field
    # def via_subclass(self, input: Subclass) -> int:
    #     return 0

schema = strawberry.Schema(query=Query)
patrick91 commented 1 month ago

@huonw thanks for the reproduction, I'll try to check it on the weekend 😊