strawberry icon indicating copy to clipboard operation
strawberry copied to clipboard

intermittent AssertionError from applying NodeExtension to the same field twice

Open instil-chloe opened this issue 5 months ago • 3 comments

Describe the Bug

sometimes, for reasons known only to the old gods, Strawberry attempts to apply field extensions twice—once during initial schema collection and once during the transformation into a GraphQLSchema object (specifically during collect_referenced_types, and even more specifically when accessing the fields cached property on the Query type). i can't figure out the specific reason why this happens, but it's deterministic; if the error occurs when starting the server, it will keep happening until you change the schema definition, at which point it might go away.

i don't know whether this would be an issue with other extensions, but applying NodeExtension twice to the same field raises an AssertionError (fields.py#58), so if you use the provided relay.node() field, the server fails to start when this behavior is triggered. this is obviously problematic.

i'm happy to fix the bug myself but i don't know how lmao. i've tried tracing Strawberry and cannot figure out the direct cause of this behavior (probably because i don't know anything about how the project is structured).

three ideas:

  1. is it necessary to raise AssertionError in this case, or could we simply skip applying the extension (and maybe log a warning) if base_resolver is already defined?
  2. it appears to be more common when working on circular dependencies with relay.Node types, so i wonder if the bug is actually somewhere in the lazy module resolution logic?
  3. is something blowing out the cache for the fields property on the Query object?

System Information

  • Operating system: macOS Ventura 13.5
  • Strawberry version (if applicable): 0.219.0

Additional Context

stack trace:

Traceback (most recent call last):
  File "/Users/rose/Projects/instil/platform-service-main/venv/lib/python3.12/site-packages/graphql/type/definition.py", line 808, in fields
    fields = resolve_thunk(self._fields)
             ^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/rose/Projects/instil/platform-service-main/venv/lib/python3.12/site-packages/graphql/type/definition.py", line 300, in resolve_thunk
    return thunk() if callable(thunk) else thunk
           ^^^^^^^
  File "/Users/rose/Projects/instil/platform-service-main/venv/lib/python3.12/site-packages/strawberry/schema/schema_converter.py", line 521, in <lambda>
    fields=lambda: self.get_graphql_fields(object_type),
                   ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/rose/Projects/instil/platform-service-main/venv/lib/python3.12/site-packages/strawberry/schema/schema_converter.py", line 378, in get_graphql_fields
    return _get_thunk_mapping(
           ^^^^^^^^^^^^^^^^^^^
  File "/Users/rose/Projects/instil/platform-service-main/venv/lib/python3.12/site-packages/strawberry/schema/schema_converter.py", line 136, in _get_thunk_mapping
    thunk_mapping[name_converter(field)] = field_converter(
                                           ^^^^^^^^^^^^^^^^
  File "/Users/rose/Projects/instil/platform-service-main/venv/lib/python3.12/site-packages/strawberry/schema/schema_converter.py", line 316, in from_field
    resolver = self.from_resolver(field)
               ^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/rose/Projects/instil/platform-service-main/venv/lib/python3.12/site-packages/strawberry/schema/schema_converter.py", line 666, in from_resolver
    _get_result_with_extensions = wrap_field_extensions()
                                  ^^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/rose/Projects/instil/platform-service-main/venv/lib/python3.12/site-packages/strawberry/schema/schema_converter.py", line 622, in wrap_field_extensions
    extension.apply(field)
  File "/Users/rose/Projects/instil/platform-service-main/venv/lib/python3.12/site-packages/strawberry/relay/fields.py", line 58, in apply
    assert field.base_resolver is None
AssertionError

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

Traceback (most recent call last):
  File "/Library/Frameworks/Python.framework/Versions/3.12/lib/python3.12/threading.py", line 1052, in _bootstrap_inner
    self.run()
  File "/Library/Frameworks/Python.framework/Versions/3.12/lib/python3.12/threading.py", line 989, in run
    self._target(*self._args, **self._kwargs)
  File "/Users/rose/Projects/instil/platform-service-main/venv/lib/python3.12/site-packages/django/utils/autoreload.py", line 64, in wrapper
    fn(*args, **kwargs)
  File "/Users/rose/Projects/instil/platform-service-main/venv/lib/python3.12/site-packages/django/core/management/commands/runserver.py", line 133, in inner_run
    self.check(display_num_errors=True)
  File "/Users/rose/Projects/instil/platform-service-main/venv/lib/python3.12/site-packages/django/core/management/base.py", line 485, in check
    all_issues = checks.run_checks(
                 ^^^^^^^^^^^^^^^^^^
  File "/Users/rose/Projects/instil/platform-service-main/venv/lib/python3.12/site-packages/django/core/checks/registry.py", line 88, in run_checks
    new_errors = check(app_configs=app_configs, databases=databases)
                 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/rose/Projects/instil/platform-service-main/venv/lib/python3.12/site-packages/django/core/checks/urls.py", line 42, in check_url_namespaces_unique
    all_namespaces = _load_all_namespaces(resolver)
                     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/rose/Projects/instil/platform-service-main/venv/lib/python3.12/site-packages/django/core/checks/urls.py", line 61, in _load_all_namespaces
    url_patterns = getattr(resolver, "url_patterns", [])
                   ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/rose/Projects/instil/platform-service-main/venv/lib/python3.12/site-packages/django/utils/functional.py", line 57, in __get__
    res = instance.__dict__[self.name] = self.func(instance)
                                         ^^^^^^^^^^^^^^^^^^^
  File "/Users/rose/Projects/instil/platform-service-main/venv/lib/python3.12/site-packages/django/urls/resolvers.py", line 715, in url_patterns
    patterns = getattr(self.urlconf_module, "urlpatterns", self.urlconf_module)
                       ^^^^^^^^^^^^^^^^^^^
  File "/Users/rose/Projects/instil/platform-service-main/venv/lib/python3.12/site-packages/django/utils/functional.py", line 57, in __get__
    res = instance.__dict__[self.name] = self.func(instance)
                                         ^^^^^^^^^^^^^^^^^^^
  File "/Users/rose/Projects/instil/platform-service-main/venv/lib/python3.12/site-packages/django/urls/resolvers.py", line 708, in urlconf_module
    return import_module(self.urlconf_name)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Library/Frameworks/Python.framework/Versions/3.12/lib/python3.12/importlib/__init__.py", line 90, in import_module
    return _bootstrap._gcd_import(name[level:], package, level)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "<frozen importlib._bootstrap>", line 1381, in _gcd_import
  File "<frozen importlib._bootstrap>", line 1354, in _find_and_load
  File "<frozen importlib._bootstrap>", line 1325, in _find_and_load_unlocked
  File "<frozen importlib._bootstrap>", line 929, in _load_unlocked
  File "<frozen importlib._bootstrap_external>", line 994, in exec_module
  File "<frozen importlib._bootstrap>", line 488, in _call_with_frames_removed
  File "/Users/rose/Projects/instil/platform-service-main/instil/site/urls.py", line 21, in <module>
    path("", include("instil.app.urls")),
             ^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/rose/Projects/instil/platform-service-main/venv/lib/python3.12/site-packages/django/urls/conf.py", line 38, in include
    urlconf_module = import_module(urlconf_module)
                     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Library/Frameworks/Python.framework/Versions/3.12/lib/python3.12/importlib/__init__.py", line 90, in import_module
    return _bootstrap._gcd_import(name[level:], package, level)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "<frozen importlib._bootstrap>", line 1381, in _gcd_import
  File "<frozen importlib._bootstrap>", line 1354, in _find_and_load
  File "<frozen importlib._bootstrap>", line 1325, in _find_and_load_unlocked
  File "<frozen importlib._bootstrap>", line 929, in _load_unlocked
  File "<frozen importlib._bootstrap_external>", line 994, in exec_module
  File "<frozen importlib._bootstrap>", line 488, in _call_with_frames_removed
  File "/Users/rose/Projects/instil/platform-service-main/instil/app/urls.py", line 4, in <module>
    from instil.graphql import schema
  File "/Users/rose/Projects/instil/platform-service-main/instil/graphql/__init__.py", line 1, in <module>
    from .schema import schema
  File "/Users/rose/Projects/instil/platform-service-main/instil/graphql/schema.py", line 6, in <module>
    schema = strawberry.Schema(query=Query, mutation=Mutation)
             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/rose/Projects/instil/platform-service-main/venv/lib/python3.12/site-packages/strawberry/schema/schema.py", line 143, in __init__
    self._schema = GraphQLSchema(
                   ^^^^^^^^^^^^^^
  File "/Users/rose/Projects/instil/platform-service-main/venv/lib/python3.12/site-packages/graphql/type/schema.py", line 224, in __init__
    collect_referenced_types(query)
  File "/Users/rose/Projects/instil/platform-service-main/venv/lib/python3.12/site-packages/graphql/type/schema.py", line 436, in collect_referenced_types
    for field in named_type.fields.values():
                 ^^^^^^^^^^^^^^^^^
  File "/Library/Frameworks/Python.framework/Versions/3.12/lib/python3.12/functools.py", line 995, in __get__
    val = self.func(instance)
          ^^^^^^^^^^^^^^^^^^^
  File "/Users/rose/Projects/instil/platform-service-main/venv/lib/python3.12/site-packages/graphql/type/definition.py", line 811, in fields
    raise cls(f"{self.name} fields cannot be resolved. {error}") from error
TypeError: Query fields cannot be resolved. 

Upvote & Fund

  • We're using Polar.sh so you can upvote and help fund this issue.
  • We receive the funding once the issue is completed & confirmed by you.
  • Thank you in advance for helping prioritize & fund our backlog.
Fund with Polar

instil-chloe avatar Jan 25 '24 16:01 instil-chloe

@instil-chloe I'm assuming you don't have a good reproduction for this?

I wonder if @erikwrede or @bellini666 might have some ideas 😊

I think we could remove (or work around) the assert, but I think it's best to find the underlying cause for this

patrick91 avatar Jan 26 '24 19:01 patrick91

OMG those "it happens sometimes" are the worst to debug T_T

The only idea I have here is: in case @instil-chloe is using strawberry-django (based on the traceback, I'm assuming he is), we do copy fields when processing types, and we might be hitting an inheritance corner case here.

A good reproduction would be nice to have to really dig into this

bellini666 avatar Jan 27 '24 14:01 bellini666

thanks folks. i'll see if i can reproduce it in a fresh project on monday!

instil-chloe avatar Jan 27 '24 14:01 instil-chloe

update on this: i was never able to reproduce in a clean project—my interim solution was to fork strawberry and remove the assertion. yesterday i updated strawberry to the latest published version, and the AssertionError no longer triggers. thanks folks!

instil-chloe avatar Apr 24 '24 17:04 instil-chloe