strawberry-graphql / strawberry-django

Strawberry GraphQL Django extension
https://strawberry.rocks/docs/django
MIT License
409 stars 118 forks source link

Order class inheritance is crashing schema converter #352

Open Mapiarz opened 1 year ago

Mapiarz commented 1 year ago

Describe the Bug

Consider code below

@dataclasses.dataclass
class FilterBase:
    name: strawberry.auto

@strawberry_django.filter(SomeModel, lookups=True)
class Filter(FilterBase):
    pass

@dataclasses.dataclass
class OrderBase:
    name: strawberry.auto

@strawberry_django.order(SomeModel)
class Order(OrderBase):
    pass

@strawberry_django.type(SomeModel, order=Order, filters=Filter)
class SomeModel(strawberry.relay.Node):
    name: strawberry.auto

The filter will work just fine but the order will crash the schema converter. If I remove OrderBase and inline the name annotation in Order then everything works as inteded.

Stacktrace:

Traceback (most recent call last):
  File "/workspace/backend/.venv/lib/python3.11/site-packages/graphql/type/definition.py", line 1456, in fields
    fields = resolve_thunk(self._fields)
             ^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/workspace/backend/.venv/lib/python3.11/site-packages/graphql/type/definition.py", line 300, in resolve_thunk
    return thunk() if callable(thunk) else thunk
           ^^^^^^^
  File "/workspace/backend/.venv/lib/python3.11/site-packages/strawberry/schema/schema_converter.py", line 403, in <lambda>
    fields=lambda: self.get_graphql_input_fields(type_definition),
                   ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/workspace/backend/.venv/lib/python3.11/site-packages/strawberry/schema/schema_converter.py", line 382, in get_graphql_input_fields
    return _get_thunk_mapping(
           ^^^^^^^^^^^^^^^^^^^
  File "/workspace/backend/.venv/lib/python3.11/site-packages/strawberry/schema/schema_converter.py", line 133, in _get_thunk_mapping
    thunk_mapping[name_converter(field)] = field_converter(
                                           ^^^^^^^^^^^^^^^^
  File "/workspace/backend/.venv/lib/python3.11/site-packages/strawberry/schema/schema_converter.py", line 349, in from_input_field
    self.from_maybe_optional(
  File "/workspace/backend/.venv/lib/python3.11/site-packages/strawberry/schema/schema_converter.py", line 769, in from_maybe_optional
    return GraphQLNonNull(self.from_type(type_))
                          ^^^^^^^^^^^^^^^^^^^^^
  File "/workspace/backend/.venv/lib/python3.11/site-packages/strawberry/schema/schema_converter.py", line 802, in from_type
    raise TypeError(f"Unexpected type '{type_}'")
TypeError: Unexpected type 'typing.Any'

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

Traceback (most recent call last):
  File "/usr/local/lib/python3.11/threading.py", line 1038, in _bootstrap_inner
    self.run()
  File "/usr/local/lib/python3.11/threading.py", line 975, in run
    self._target(*self._args, **self._kwargs)
  File "/workspace/backend/.venv/lib/python3.11/site-packages/django/utils/autoreload.py", line 64, in wrapper
    fn(*args, **kwargs)
  File "/workspace/backend/.venv/lib/python3.11/site-packages/django/core/management/commands/runserver.py", line 118, in inner_run
    self.check(display_num_errors=True)
  File "/workspace/backend/.venv/lib/python3.11/site-packages/django/core/management/base.py", line 419, in check
    all_issues = checks.run_checks(
                 ^^^^^^^^^^^^^^^^^^
  File "/workspace/backend/.venv/lib/python3.11/site-packages/django/core/checks/registry.py", line 76, in run_checks
    new_errors = check(app_configs=app_configs, databases=databases)
                 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/workspace/backend/.venv/lib/python3.11/site-packages/django/core/checks/urls.py", line 40, in check_url_namespaces_unique
    all_namespaces = _load_all_namespaces(resolver)
                     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/workspace/backend/.venv/lib/python3.11/site-packages/django/core/checks/urls.py", line 57, in _load_all_namespaces
    url_patterns = getattr(resolver, 'url_patterns', [])
                   ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/workspace/backend/.venv/lib/python3.11/site-packages/django/utils/functional.py", line 48, in __get__
    res = instance.__dict__[self.name] = self.func(instance)
                                         ^^^^^^^^^^^^^^^^^^^
  File "/workspace/backend/.venv/lib/python3.11/site-packages/django/urls/resolvers.py", line 602, in url_patterns
    patterns = getattr(self.urlconf_module, "urlpatterns", self.urlconf_module)
                       ^^^^^^^^^^^^^^^^^^^
  File "/workspace/backend/.venv/lib/python3.11/site-packages/django/utils/functional.py", line 48, in __get__
    res = instance.__dict__[self.name] = self.func(instance)
                                         ^^^^^^^^^^^^^^^^^^^
  File "/workspace/backend/.venv/lib/python3.11/site-packages/django/urls/resolvers.py", line 595, in urlconf_module
    return import_module(self.urlconf_name)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/local/lib/python3.11/importlib/__init__.py", line 126, in import_module
    return _bootstrap._gcd_import(name[level:], package, level)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "<frozen importlib._bootstrap>", line 1204, in _gcd_import
  File "<frozen importlib._bootstrap>", line 1176, in _find_and_load
  File "<frozen importlib._bootstrap>", line 1147, in _find_and_load_unlocked
  File "<frozen importlib._bootstrap>", line 690, in _load_unlocked
  File "<frozen importlib._bootstrap_external>", line 940, in exec_module
  File "<frozen importlib._bootstrap>", line 241, in _call_with_frames_removed
  File "/workspace/backend/foo/urls.py", line 13, in <module>
    from .app.graphql.schema import schema
  File "/workspace/backend/foo/app/graphql/schema.py", line 60, in <module>
    schema = Schema(
             ^^^^^^^
  File "/workspace/backend/.venv/lib/python3.11/site-packages/strawberry/schema/schema.py", line 141, in __init__
    self._schema = GraphQLSchema(
                   ^^^^^^^^^^^^^^
  File "/workspace/backend/.venv/lib/python3.11/site-packages/graphql/type/schema.py", line 224, in __init__
    collect_referenced_types(query)
  File "/workspace/backend/.venv/lib/python3.11/site-packages/graphql/type/schema.py", line 435, in collect_referenced_types
    collect_referenced_types(arg.type)
  File "/workspace/backend/.venv/lib/python3.11/site-packages/graphql/type/schema.py", line 438, in collect_referenced_types
    for field in named_type.fields.values():
                 ^^^^^^^^^^^^^^^^^
  File "/usr/local/lib/python3.11/functools.py", line 1001, in __get__
    val = self.func(instance)
          ^^^^^^^^^^^^^^^^^^^
  File "/workspace/backend/.venv/lib/python3.11/site-packages/graphql/type/definition.py", line 1459, in fields
    raise cls(f"{self.name} fields cannot be resolved. {error}") from error
TypeError: Order fields cannot be resolved. Unexpected type 'typing.Any'

System Information

Upvote & Fund

Fund with Polar

bellini666 commented 1 year ago

Hi @Mapiarz ,

The issue here is that you are using auto inside a dataclass. It would also have the same issue when using @strawberry.type

When using auto, you want to do that inside a @strawberry_django.type class, just like I mentioned in this comment

Mapiarz commented 1 year ago

@bellini666 Hey! Fair point about the dataclass. Nonetheless:

  1. It works with dataclass for the filter
  2. A strawberry_django.type has to be related to a specific django model. What if I want to reuse the base strawberry_django.type for multiple types associated with different django models? (this is not shown in my initial example above but that's my real world use case). Can I associate a strawberry_django.type with an abstract base django model?
  3. Even if I try to use strawberry_django.type I still get the same issue. Could it have something to do with inheritance? In my example, SomeModel get's the name field from a base abstract model.
bellini666 commented 1 year ago

It works with dataclass for the filter

Hrm, wondering if it has anything to do with the fact that inputs are handled differently.

Maybe there's something we can do to improve that for types as well?

A strawberry_django.type has to be related to a specific django model. What if I want to reuse the base strawberry_django.type for multiple types associated with different django models? (this is not shown in my initial example above but that's my real world use case). Can I associate a strawberry_django.type with an abstract base django model?

There are some possibilities here:

1) When using a dataclass or a strawberry.type, you can use the type annotation directly instead of auto. The main issue here is using auto in a non strawberry_django.type class.

2) I have some cases like this. Like, I have a base model or a mixin which I use on other models, and I want to reuse a type for that base model/mixin as well. What I usually do is to create an @strawberry_django.interface(BaseModelOrMixin) and inherit my types from it. Not only this solves the auto issue, but also expose some useful interfaces in the schema.

obs. I still think that we can find a way to solve this issue. Maybe not for @dataclasses.dataclass, but at least for @strawberry.type. But in the meantime you can use one of those 2 workarounds

Even if I try to use strawberry_django.type I still get the same issue. Could it have something to do with inheritance? In my example, SomeModel get's the name field from a base abstract model.

Can you give me an example of this?