strawberry
strawberry copied to clipboard
intermittent AssertionError from applying NodeExtension to the same field twice
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:
- is it necessary to raise
AssertionError
in this case, or could we simply skip applying the extension (and maybe log a warning) ifbase_resolver
is already defined? - 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? - is something blowing out the cache for the
fields
property on theQuery
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.
@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
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
thanks folks. i'll see if i can reproduce it in a fresh project on monday!
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!