strawberry-graphql / strawberry-django

Strawberry GraphQL Django extension
MIT License
391 stars 115 forks source link

Returning `bool` from mutation causes exception. #507

Open baseplate-admin opened 3 months ago

baseplate-admin commented 3 months ago

Describe the Bug

Hi i have a mutation like this:

@strawberry.type
class UserMutation:
    @strawberry_django.mutation()
    def login(self, info: Info, data: UserLoginInput) -> TokenType | None:
        request: HttpRequest = cast(HttpRequest, info.context["request"])
        user = django_authenticate(request, username=data.username, password=data.password)
        if user is not None:
            django_login(request, user)
            token, _ = Token.objects.get_or_create(user=user.pk)
            return token

    @strawberry_django.mutation()
    def logout(self, info: Info, data: TokenType) -> bool:
        pass

and it raises an error like this:

C:\Programming\CoreProject\backend\django_core\apps\gql\mutations\user.py changed, reloading.
Watching for file changes with StatReloader
Performing system checks...

Traceback (most recent call last):
  File "C:\Users\baseplate-admin\AppData\Local\pypoetry\Cache\virtualenvs\coreproject-CGb-jJ-B-py3.12\Lib\site-packages\graphql\type\definition.py", line 808, in fields
    fields = resolve_thunk(self._fields)
             ^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "C:\Users\baseplate-admin\AppData\Local\pypoetry\Cache\virtualenvs\coreproject-CGb-jJ-B-py3.12\Lib\site-packages\graphql\type\definition.py", line 300, in resolve_thunk
    return thunk() if callable(thunk) else thunk
           ^^^^^^^
  File "C:\Users\baseplate-admin\AppData\Local\pypoetry\Cache\virtualenvs\coreproject-CGb-jJ-B-py3.12\Lib\site-packages\strawberry\schema\schema_converter.py", line 521, in <lambda>
    fields=lambda: self.get_graphql_fields(object_type),
                   ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "C:\Users\baseplate-admin\AppData\Local\pypoetry\Cache\virtualenvs\coreproject-CGb-jJ-B-py3.12\Lib\site-packages\strawberry\schema\schema_converter.py", line 378, in get_graphql_fields
    return _get_thunk_mapping(
           ^^^^^^^^^^^^^^^^^^^
  File "C:\Users\baseplate-admin\AppData\Local\pypoetry\Cache\virtualenvs\coreproject-CGb-jJ-B-py3.12\Lib\site-packages\strawberry\schema\schema_converter.py", line 136, in _get_thunk_mapping
    thunk_mapping[name_converter(field)] = field_converter(
                                           ^^^^^^^^^^^^^^^^
  File "C:\Users\baseplate-admin\AppData\Local\pypoetry\Cache\virtualenvs\coreproject-CGb-jJ-B-py3.12\Lib\site-packages\strawberry\schema\schema_converter.py", line 332, in from_field
    graphql_arguments[argument_name] = self.from_argument(argument)
                                       ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "C:\Users\baseplate-admin\AppData\Local\pypoetry\Cache\virtualenvs\coreproject-CGb-jJ-B-py3.12\Lib\site-packages\strawberry\schema\schema_converter.py", line 197, in from_argument
    return GraphQLArgument(
           ^^^^^^^^^^^^^^^^
  File "C:\Users\baseplate-admin\AppData\Local\pypoetry\Cache\virtualenvs\coreproject-CGb-jJ-B-py3.12\Lib\site-packages\graphql\type\definition.py", line 662, in __init__
    raise TypeError("Argument type must be a GraphQL input type.")
TypeError: Argument type must be a GraphQL input type.

The above exception was the direct cause of the following exception:

Traceback (most recent call last):
  File "C:\Users\baseplate-admin\AppData\Local\Programs\Python\Python312\Lib\threading.py", line 1073, in _bootstrap_inner
    self.run()
  File "C:\Users\baseplate-admin\AppData\Local\Programs\Python\Python312\Lib\threading.py", line 1010, in run
    self._target(*self._args, **self._kwargs)
  File "C:\Users\baseplate-admin\AppData\Local\pypoetry\Cache\virtualenvs\coreproject-CGb-jJ-B-py3.12\Lib\site-packages\django\utils\autoreload.py", line 64, in wrapper
    fn(*args, **kwargs)
  File "C:\Users\baseplate-admin\AppData\Local\pypoetry\Cache\virtualenvs\coreproject-CGb-jJ-B-py3.12\Lib\site-packages\django\core\management\commands\runserver.py", line 133, in inner_run
    self.check(display_num_errors=True)
  File "C:\Users\baseplate-admin\AppData\Local\pypoetry\Cache\virtualenvs\coreproject-CGb-jJ-B-py3.12\Lib\site-packages\django\core\management\base.py", line 485, in check
    all_issues = checks.run_checks(
                 ^^^^^^^^^^^^^^^^^^
  File "C:\Users\baseplate-admin\AppData\Local\pypoetry\Cache\virtualenvs\coreproject-CGb-jJ-B-py3.12\Lib\site-packages\django\core\checks\registry.py", line 88, in run_checks
    new_errors = check(app_configs=app_configs, databases=databases)
                 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "C:\Users\baseplate-admin\AppData\Local\pypoetry\Cache\virtualenvs\coreproject-CGb-jJ-B-py3.12\Lib\site-packages\django\core\checks\urls.py", line 14, in check_url_config
    return check_resolver(resolver)
           ^^^^^^^^^^^^^^^^^^^^^^^^
  File "C:\Users\baseplate-admin\AppData\Local\pypoetry\Cache\virtualenvs\coreproject-CGb-jJ-B-py3.12\Lib\site-packages\django\core\checks\urls.py", line 24, in check_resolver
    return check_method()
           ^^^^^^^^^^^^^^
  File "C:\Users\baseplate-admin\AppData\Local\pypoetry\Cache\virtualenvs\coreproject-CGb-jJ-B-py3.12\Lib\site-packages\django\urls\resolvers.py", line 516, in check
    for pattern in self.url_patterns:
                   ^^^^^^^^^^^^^^^^^
  File "C:\Users\baseplate-admin\AppData\Local\pypoetry\Cache\virtualenvs\coreproject-CGb-jJ-B-py3.12\Lib\site-packages\django\utils\functional.py", line 47, in __get__
    res = instance.__dict__[self.name] = self.func(instance)
                                         ^^^^^^^^^^^^^^^^^^^
  File "C:\Users\baseplate-admin\AppData\Local\pypoetry\Cache\virtualenvs\coreproject-CGb-jJ-B-py3.12\Lib\site-packages\django\urls\resolvers.py", line 735, in url_patterns
    patterns = getattr(self.urlconf_module, "urlpatterns", self.urlconf_module)
                       ^^^^^^^^^^^^^^^^^^^
  File "C:\Users\baseplate-admin\AppData\Local\pypoetry\Cache\virtualenvs\coreproject-CGb-jJ-B-py3.12\Lib\site-packages\django\utils\functional.py", line 47, in __get__
    res = instance.__dict__[self.name] = self.func(instance)
                                         ^^^^^^^^^^^^^^^^^^^
  File "C:\Users\baseplate-admin\AppData\Local\pypoetry\Cache\virtualenvs\coreproject-CGb-jJ-B-py3.12\Lib\site-packages\django\urls\resolvers.py", line 728, in urlconf_module
    return import_module(self.urlconf_name)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "C:\Users\baseplate-admin\AppData\Local\Programs\Python\Python312\Lib\importlib\__init__.py", line 90, in import_module
    return _bootstrap._gcd_import(name[level:], package, level)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "<frozen importlib._bootstrap>", line 1387, in _gcd_import
  File "<frozen importlib._bootstrap>", line 1360, in _find_and_load
  File "<frozen importlib._bootstrap>", line 1331, in _find_and_load_unlocked
  File "<frozen importlib._bootstrap>", line 935, in _load_unlocked
  File "<frozen importlib._bootstrap_external>", line 995, in exec_module
  File "<frozen importlib._bootstrap>", line 488, in _call_with_frames_removed
  File "C:\Programming\CoreProject\backend\django_core\core\urls.py", line 44, in <module>
    path("graphql/", include("apps.gql.urls")),
                     ^^^^^^^^^^^^^^^^^^^^^^^^
  File "C:\Users\baseplate-admin\AppData\Local\pypoetry\Cache\virtualenvs\coreproject-CGb-jJ-B-py3.12\Lib\site-packages\django\urls\conf.py", line 38, in include
    urlconf_module = import_module(urlconf_module)
                     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "C:\Users\baseplate-admin\AppData\Local\Programs\Python\Python312\Lib\importlib\__init__.py", line 90, in import_module
    return _bootstrap._gcd_import(name[level:], package, level)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "<frozen importlib._bootstrap>", line 1387, in _gcd_import
  File "<frozen importlib._bootstrap>", line 1360, in _find_and_load
  File "<frozen importlib._bootstrap>", line 1331, in _find_and_load_unlocked
  File "<frozen importlib._bootstrap>", line 935, in _load_unlocked
  File "<frozen importlib._bootstrap_external>", line 995, in exec_module
  File "<frozen importlib._bootstrap>", line 488, in _call_with_frames_removed
  File "C:\Programming\CoreProject\backend\django_core\apps\gql\urls.py", line 3, in <module>
    from .schema import schema as schema
  File "C:\Programming\CoreProject\backend\django_core\apps\gql\schema.py", line 27, in <module>
    schema = strawberry.Schema(
             ^^^^^^^^^^^^^^^^^^
  File "C:\Users\baseplate-admin\AppData\Local\pypoetry\Cache\virtualenvs\coreproject-CGb-jJ-B-py3.12\Lib\site-packages\strawberry\schema\schema.py", line 143, in __init__
    self._schema = GraphQLSchema(
                   ^^^^^^^^^^^^^^
  File "C:\Users\baseplate-admin\AppData\Local\pypoetry\Cache\virtualenvs\coreproject-CGb-jJ-B-py3.12\Lib\site-packages\graphql\type\schema.py", line 226, in __init__
    collect_referenced_types(mutation)
  File "C:\Users\baseplate-admin\AppData\Local\pypoetry\Cache\virtualenvs\coreproject-CGb-jJ-B-py3.12\Lib\site-packages\graphql\type\schema.py", line 432, in collect_referenced_types
    for field in named_type.fields.values():
                 ^^^^^^^^^^^^^^^^^
  File "C:\Users\baseplate-admin\AppData\Local\Programs\Python\Python312\Lib\functools.py", line 995, in __get__
    val = self.func(instance)
          ^^^^^^^^^^^^^^^^^^^
  File "C:\Users\baseplate-admin\AppData\Local\pypoetry\Cache\virtualenvs\coreproject-CGb-jJ-B-py3.12\Lib\site-packages\graphql\type\definition.py", line 811, in fields
    raise cls(f"{self.name} fields cannot be resolved. {error}") from error
TypeError: Mutation fields cannot be resolved. Argument type must be a GraphQL input type.

System Information

Upvote & Fund

Fund with Polar

bellini666 commented 3 months ago

Hi @baseplate-admin ,

It is probably not the bool that is causing the issue, instead it is the data: TokenType you are passing to logout

Any argument for any resolver needs to be an input type. Considering that you are returning TokenType | None in the login mutation, I'm assuming it is a type and not an input type.

baseplate-admin commented 3 months ago

Oh makes sense. Thank you , sorry for noise

baseplate-admin commented 3 months ago

I got this new exception if i return bool:

C:\Programming\CoreProject\backend\django_core\apps\gql\mutations\user.py changed, reloading.
Watching for file changes with StatReloader
Performing system checks...

Traceback (most recent call last):
  File "C:\Users\baseplate-admin\AppData\Local\Programs\Python\Python312\Lib\threading.py", line 1073, in _bootstrap_inner
    self.run()
  File "C:\Users\baseplate-admin\AppData\Local\Programs\Python\Python312\Lib\threading.py", line 1010, in run
    self._target(*self._args, **self._kwargs)
  File "C:\Users\baseplate-admin\AppData\Local\pypoetry\Cache\virtualenvs\coreproject-CGb-jJ-B-py3.12\Lib\site-packages\django\utils\autoreload.py", line 64, in wrapper
    fn(*args, **kwargs)
  File "C:\Users\baseplate-admin\AppData\Local\pypoetry\Cache\virtualenvs\coreproject-CGb-jJ-B-py3.12\Lib\site-packages\django\core\management\commands\runserver.py", line 133, in inner_run       
    self.check(display_num_errors=True)
  File "C:\Users\baseplate-admin\AppData\Local\pypoetry\Cache\virtualenvs\coreproject-CGb-jJ-B-py3.12\Lib\site-packages\django\core\management\base.py", line 485, in check
    all_issues = checks.run_checks(
                 ^^^^^^^^^^^^^^^^^^
  File "C:\Users\baseplate-admin\AppData\Local\pypoetry\Cache\virtualenvs\coreproject-CGb-jJ-B-py3.12\Lib\site-packages\django\core\checks\registry.py", line 88, in run_checks
    new_errors = check(app_configs=app_configs, databases=databases)
                 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "C:\Users\baseplate-admin\AppData\Local\pypoetry\Cache\virtualenvs\coreproject-CGb-jJ-B-py3.12\Lib\site-packages\django\core\checks\urls.py", line 14, in check_url_config
    return check_resolver(resolver)
           ^^^^^^^^^^^^^^^^^^^^^^^^
  File "C:\Users\baseplate-admin\AppData\Local\pypoetry\Cache\virtualenvs\coreproject-CGb-jJ-B-py3.12\Lib\site-packages\django\core\checks\urls.py", line 24, in check_resolver
    return check_method()
           ^^^^^^^^^^^^^^
  File "C:\Users\baseplate-admin\AppData\Local\pypoetry\Cache\virtualenvs\coreproject-CGb-jJ-B-py3.12\Lib\site-packages\django\urls\resolvers.py", line 516, in check
    for pattern in self.url_patterns:
                   ^^^^^^^^^^^^^^^^^
  File "C:\Users\baseplate-admin\AppData\Local\pypoetry\Cache\virtualenvs\coreproject-CGb-jJ-B-py3.12\Lib\site-packages\django\utils\functional.py", line 47, in __get__
    res = instance.__dict__[self.name] = self.func(instance)
                                         ^^^^^^^^^^^^^^^^^^^
  File "C:\Users\baseplate-admin\AppData\Local\pypoetry\Cache\virtualenvs\coreproject-CGb-jJ-B-py3.12\Lib\site-packages\django\urls\resolvers.py", line 735, in url_patterns
    patterns = getattr(self.urlconf_module, "urlpatterns", self.urlconf_module)
                       ^^^^^^^^^^^^^^^^^^^
  File "C:\Users\baseplate-admin\AppData\Local\pypoetry\Cache\virtualenvs\coreproject-CGb-jJ-B-py3.12\Lib\site-packages\django\utils\functional.py", line 47, in __get__
    res = instance.__dict__[self.name] = self.func(instance)
                                         ^^^^^^^^^^^^^^^^^^^
  File "C:\Users\baseplate-admin\AppData\Local\pypoetry\Cache\virtualenvs\coreproject-CGb-jJ-B-py3.12\Lib\site-packages\django\urls\resolvers.py", line 728, in urlconf_module
    return import_module(self.urlconf_name)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "C:\Users\baseplate-admin\AppData\Local\Programs\Python\Python312\Lib\importlib\__init__.py", line 90, in import_module
    return _bootstrap._gcd_import(name[level:], package, level)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "<frozen importlib._bootstrap>", line 1387, in _gcd_import
  File "<frozen importlib._bootstrap>", line 1360, in _find_and_load
  File "<frozen importlib._bootstrap>", line 1331, in _find_and_load_unlocked
  File "<frozen importlib._bootstrap>", line 935, in _load_unlocked
  File "<frozen importlib._bootstrap_external>", line 995, in exec_module
  File "<frozen importlib._bootstrap>", line 488, in _call_with_frames_removed
  File "C:\Programming\CoreProject\backend\django_core\core\urls.py", line 44, in <module>
    path("graphql/", include("apps.gql.urls")),
                     ^^^^^^^^^^^^^^^^^^^^^^^^
  File "C:\Users\baseplate-admin\AppData\Local\pypoetry\Cache\virtualenvs\coreproject-CGb-jJ-B-py3.12\Lib\site-packages\django\urls\conf.py", line 38, in include
    urlconf_module = import_module(urlconf_module)
                     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "C:\Users\baseplate-admin\AppData\Local\Programs\Python\Python312\Lib\importlib\__init__.py", line 90, in import_module
    return _bootstrap._gcd_import(name[level:], package, level)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "<frozen importlib._bootstrap>", line 1387, in _gcd_import
  File "<frozen importlib._bootstrap>", line 1360, in _find_and_load
  File "<frozen importlib._bootstrap>", line 1331, in _find_and_load_unlocked
  File "<frozen importlib._bootstrap>", line 935, in _load_unlocked
  File "<frozen importlib._bootstrap_external>", line 995, in exec_module
  File "<frozen importlib._bootstrap>", line 488, in _call_with_frames_removed
  File "C:\Programming\CoreProject\backend\django_core\apps\gql\urls.py", line 3, in <module>
    from .schema import schema as schema
  File "C:\Programming\CoreProject\backend\django_core\apps\gql\schema.py", line 27, in <module>
    schema = strawberry.Schema(
             ^^^^^^^^^^^^^^^^^^
  File "C:\Users\baseplate-admin\AppData\Local\pypoetry\Cache\virtualenvs\coreproject-CGb-jJ-B-py3.12\Lib\site-packages\strawberry\schema\schema.py", line 162, in __init__
    raise error.__cause__ from None
  File "C:\Users\baseplate-admin\AppData\Local\pypoetry\Cache\virtualenvs\coreproject-CGb-jJ-B-py3.12\Lib\site-packages\graphql\type\definition.py", line 808, in fields
    fields = resolve_thunk(self._fields)
             ^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "C:\Users\baseplate-admin\AppData\Local\pypoetry\Cache\virtualenvs\coreproject-CGb-jJ-B-py3.12\Lib\site-packages\graphql\type\definition.py", line 300, in resolve_thunk
    return thunk() if callable(thunk) else thunk
           ^^^^^^^
  File "C:\Users\baseplate-admin\AppData\Local\pypoetry\Cache\virtualenvs\coreproject-CGb-jJ-B-py3.12\Lib\site-packages\strawberry\schema\schema_converter.py", line 521, in <lambda>
    fields=lambda: self.get_graphql_fields(object_type),
                   ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "C:\Users\baseplate-admin\AppData\Local\pypoetry\Cache\virtualenvs\coreproject-CGb-jJ-B-py3.12\Lib\site-packages\strawberry\schema\schema_converter.py", line 378, in get_graphql_fields     
    return _get_thunk_mapping(
           ^^^^^^^^^^^^^^^^^^^
  File "C:\Users\baseplate-admin\AppData\Local\pypoetry\Cache\virtualenvs\coreproject-CGb-jJ-B-py3.12\Lib\site-packages\strawberry\schema\schema_converter.py", line 136, in _get_thunk_mapping     
    thunk_mapping[name_converter(field)] = field_converter(
                                           ^^^^^^^^^^^^^^^^
  File "C:\Users\baseplate-admin\AppData\Local\pypoetry\Cache\virtualenvs\coreproject-CGb-jJ-B-py3.12\Lib\site-packages\strawberry\schema\schema_converter.py", line 319, in from_field
    self.from_maybe_optional(
  File "C:\Users\baseplate-admin\AppData\Local\pypoetry\Cache\virtualenvs\coreproject-CGb-jJ-B-py3.12\Lib\site-packages\strawberry\schema\schema_converter.py", line 748, in from_maybe_optional    
    return GraphQLNonNull(self.from_type(type_))
                          ^^^^^^^^^^^^^^^^^^^^^
  File "C:\Users\baseplate-admin\AppData\Local\pypoetry\Cache\virtualenvs\coreproject-CGb-jJ-B-py3.12\Lib\site-packages\strawberry\schema\schema_converter.py", line 773, in from_type
    return self.from_union(type_)
           ^^^^^^^^^^^^^^^^^^^^^^
  File "C:\Users\baseplate-admin\AppData\Local\pypoetry\Cache\virtualenvs\coreproject-CGb-jJ-B-py3.12\Lib\site-packages\strawberry\schema\schema_converter.py", line 791, in from_union
    raise InvalidUnionTypeError(union_name, type_, union_definition=union)
strawberry.exceptions.invalid_union_type.InvalidUnionTypeError: Type `bool` cannot be used in a GraphQL Union
bellini666 commented 3 months ago

That's indeed related to bool.

Are you passing handle_django_errors=True to the mutation (or maybe you have MUTATIONS_DEFAULT_HANDLE_ERRORS=True in your settings)?

If so, as you can see in the docs, the mutation will automatically create a union of your return type and OperationInfo. The issue with that is that graphql doesn't allow unions of types with scalars, meaning you can't return a bool/int/str/etc in there

baseplate-admin commented 3 months ago

Yep, good catch.

The issue was with handle_django_errors=True.


but should this be an issue?

bellini666 commented 3 months ago

but should this be an issue?

Probably the error can be better, and the documentation can also be improved 😅