strawberry-graphql / strawberry

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

NameError with lazy types #2833

Closed jacobjove closed 1 year ago

jacobjove commented 1 year ago

Describe the Bug

When I use lazy types as described in https://strawberry.rocks/docs/types/lazy I get a NameError like the following:

NameError: name 'Attribute' is not defined

Relevant code:

from typing import TYPE_CHECKING, Annotated
from strawberry_django_plus import gql
...

if TYPE_CHECKING:
    from api.attribute.types.attribute import Attribute

...
@gql.django.type(models.ProductType)
class ProductType(gql.relay.Node):
    @gql.relay.connection
    def attributes(self) -> list[Annotated["Attribute", gql.lazy("api.attribute.types.attribute")]]:
        pass

The error goes away if I use a regular forward reference without Annotation and lazy, like so:

    @gql.relay.connection
    def attributes(self) -> list["Attribute"]:
        pass

(Although I'm guessing this would prevent the schema from being generated correctly. By the way... Is it actually necessary to use Annotated and lazy? It feels weird to write out the import path twice... Why not treat all forward references as lazy types?)

System Information

Python 3.10.7

Upvote & Fund

Fund with Polar

jacobjove commented 1 year ago

https://github.com/blb-ventures/strawberry-django-plus/issues/234

jacobjove commented 1 year ago

I originally noticed this issue while working with the relay implementation in strawberry_django_plus. Recently, the relay implementation was contributed to the main strawberry library, so I guess this issue would still be relevant here.

I seem to run into trouble when a type inheriting from the Node interface has a field that is annotated with a lazy type.

Example error:

File "/Users/jacob/code/saleor/api/api/webhook/types.py", line 99, in <module>
    class Webhook(relay.Node):
  File "/Users/jacob/code/saleor/api/.venv/lib/python3.10/site-packages/strawberry/relay/types.py", line 364, in __init_subclass__
    annotations = get_type_hints(cls, include_extras=True)
  File "/Users/jacob/code/saleor/api/.venv/lib/python3.10/site-packages/typing_extensions.py", line 1183, in get_type_hints
    hint = typing.get_type_hints(
  File "/usr/local/Cellar/python@3.10/3.10.7/Frameworks/Python.framework/Versions/3.10/lib/python3.10/typing.py", line 1833, in get_type_hints
    value = _eval_type(value, base_globals, base_locals)
  File "/usr/local/Cellar/python@3.10/3.10.7/Frameworks/Python.framework/Versions/3.10/lib/python3.10/typing.py", line 329, in _eval_type
    ev_args = tuple(_eval_type(a, globalns, localns, recursive_guard) for a in t.__args__)
  File "/usr/local/Cellar/python@3.10/3.10.7/Frameworks/Python.framework/Versions/3.10/lib/python3.10/typing.py", line 329, in <genexpr>
    ev_args = tuple(_eval_type(a, globalns, localns, recursive_guard) for a in t.__args__)
  File "/usr/local/Cellar/python@3.10/3.10.7/Frameworks/Python.framework/Versions/3.10/lib/python3.10/typing.py", line 327, in _eval_type
    return t._evaluate(globalns, localns, recursive_guard)
  File "/usr/local/Cellar/python@3.10/3.10.7/Frameworks/Python.framework/Versions/3.10/lib/python3.10/typing.py", line 694, in _evaluate
    eval(self.__forward_code__, globalns, localns),
  File "<string>", line 1, in <module>
NameError: name 'App' is not defined

Code:

from typing import TYPE_CHECKING, Annotated

if TYPE_CHECKING:
    from api.app.types import App

...

class Webhook(relay.Node):
    id: relay.NodeID
    app: Annotated["App", gql.lazy("api.app.types")]