Hello! First of all, thank you for this library!
I have a bug probably related to #3466 - consider creating a boolean algebra of AND
, OR
and NOT
and trying to encode it within GraphQL:
import dataclasses
import typing as t
import strawberry
_T = t.TypeVar("_T")
@strawberry.input(one_of=True)
class BoolOp(t.Generic[_T]):
and_: t.Optional[t.List["BoolOp[_T]"]] = strawberry.UNSET
or_: t.Optional[t.List["BoolOp[_T]"]] = strawberry.UNSET
not_: t.Optional["BoolOp[_T]"] = strawberry.UNSET
val: t.Optional[_T] = strawberry.UNSET
@strawberry.type
class Obj:
a: t.Optional[int] = strawberry.UNSET
b: t.Optional[str] = strawberry.UNSET
@strawberry.type
class Query:
@strawberry.field
def objs(self, where: BoolOp[Obj]) -> list:
return []
schema = strawberry.Schema(query=Query)
I would like to create a query akin to:
query Q {
objs(where: {or_: [{val: {a: 1, b: "abc"}, {and_: [{val: {a: 2, b: ""}}]}] {
...
}
}
however, RecursionError
is raised - here is a snippet of the traceback:
$ ipython dev/strawberry_nested_generics.py
---------------------------------------------------------------------------
RecursionError Traceback (most recent call last)
File ~/Desktop/alembic/root/projects/aerosani/dev/strawberry_nested_generics.py:24
19 a: t.Optional[int] = strawberry.UNSET
20 b: t.Optional[str] = strawberry.UNSET
23 @strawberry.type
---> 24 class Query:
25 @strawberry.field
26 def objs(self, where: BoolOp[Obj]) -> list:
27 return []
File ~/Desktop/alembic/root/projects/aerosani/dev/strawberry_nested_generics.py:26, in Query()
23 @strawberry.type
24 class Query:
25 @strawberry.field
---> 26 def objs(self, where: BoolOp[Obj]) -> list:
27 return []
File ~/.virtualenvs/alembic/lib/python3.10/site-packages/strawberry/types/field.py:596, in field(resolver, name, is_subscription, description, permission_classes, deprecation_reason, default, default_factory, metadata, directives, extensions, graphql_type, init)
594 if resolver:
595 assert init is not True, "Can't set init as True when passing a resolver."
--> 596 return field_(resolver)
597 return field_
File ~/.virtualenvs/alembic/lib/python3.10/site-packages/strawberry/types/field.py:199, in StrawberryField.__call__(self, resolver)
197 if isinstance(argument.type_annotation.annotation, str):
198 continue
--> 199 elif isinstance(argument.type, StrawberryUnion):
200 raise InvalidArgumentTypeError(
201 resolver,
202 argument,
203 )
204 elif has_object_definition(argument.type):
File ~/.virtualenvs/alembic/lib/python3.10/site-packages/strawberry/types/arguments.py:131, in StrawberryArgument.type(self)
129 @property
130 def type(self) -> Union[StrawberryType, type]:
--> 131 return self.type_annotation.resolve()
File ~/.virtualenvs/alembic/lib/python3.10/site-packages/strawberry/annotation.py:133, in StrawberryAnnotation.resolve(self)
131 """Return resolved (transformed) annotation."""
132 if self.__resolve_cache__ is None:
--> 133 self.__resolve_cache__ = self._resolve()
135 return self.__resolve_cache__
File ~/.virtualenvs/alembic/lib/python3.10/site-packages/strawberry/annotation.py:152, in StrawberryAnnotation._resolve(self)
149 if self._is_list(evaled_type):
150 return self.create_list(evaled_type)
--> 152 if self._is_graphql_generic(evaled_type):
153 if any(is_type_var(type_) for type_ in get_args(evaled_type)):
154 return evaled_type
File ~/.virtualenvs/alembic/lib/python3.10/site-packages/strawberry/annotation.py:281, in StrawberryAnnotation._is_graphql_generic(cls, annotation)
279 if hasattr(annotation, "__origin__"):
280 if definition := get_object_definition(annotation.__origin__):
--> 281 return definition.is_graphql_generic
283 return is_generic(annotation.__origin__)
285 return False
File ~/.virtualenvs/alembic/lib/python3.10/site-packages/strawberry/types/base.py:347, in StrawberryObjectDefinition.is_graphql_generic(self)
342 return False
344 # here we are checking if any exposed field is generic
345 # a Strawberry class can be "generic", but not expose any
346 # generic field to GraphQL
--> 347 return any(field.is_graphql_generic for field in self.fields)
File ~/.virtualenvs/alembic/lib/python3.10/site-packages/strawberry/types/base.py:347, in <genexpr>(.0)
342 return False
344 # here we are checking if any exposed field is generic
345 # a Strawberry class can be "generic", but not expose any
346 # generic field to GraphQL
--> 347 return any(field.is_graphql_generic for field in self.fields)
File ~/.virtualenvs/alembic/lib/python3.10/site-packages/strawberry/types/field.py:256, in StrawberryField.is_graphql_generic(self)
251 @property
252 def is_graphql_generic(self) -> bool:
253 return (
254 self.base_resolver.is_graphql_generic
255 if self.base_resolver
--> 256 else _is_generic(self.type)
257 )
File ~/.virtualenvs/alembic/lib/python3.10/site-packages/strawberry/types/field.py:305, in StrawberryField.type(self)
297 @property # type: ignore
298 def type(
299 self,
(...)
303 Literal[UNRESOLVED],
304 ]:
--> 305 return self.resolve_type()
File ~/.virtualenvs/alembic/lib/python3.10/site-packages/strawberry/types/field.py:352, in StrawberryField.resolve_type(self, type_definition)
349 with contextlib.suppress(NameError):
350 # Prioritise the field type over the resolver return type
351 if self.type_annotation is not None:
--> 352 resolved = self.type_annotation.resolve()
353 elif self.base_resolver is not None and self.base_resolver.type is not None:
354 # Handle unannotated functions (such as lambdas)
355 # Generics will raise MissingTypesForGenericError later
356 # on if we let it be returned. So use `type_annotation` instead
357 # which is the same behaviour as having no type information.
358 resolved = self.base_resolver.type
File ~/.virtualenvs/alembic/lib/python3.10/site-packages/strawberry/annotation.py:133, in StrawberryAnnotation.resolve(self)
131 """Return resolved (transformed) annotation."""
132 if self.__resolve_cache__ is None:
--> 133 self.__resolve_cache__ = self._resolve()
135 return self.__resolve_cache__
File ~/.virtualenvs/alembic/lib/python3.10/site-packages/strawberry/annotation.py:162, in StrawberryAnnotation._resolve(self)
160 return self.create_enum(evaled_type)
161 elif self._is_optional(evaled_type, args):
--> 162 return self.create_optional(evaled_type)
163 elif self._is_union(evaled_type, args):
164 return self.create_union(evaled_type, args)
File ~/.virtualenvs/alembic/lib/python3.10/site-packages/strawberry/annotation.py:220, in StrawberryAnnotation.create_optional(self, evaled_type)
210 # Note that passing a single type to `Union` is equivalent to not using `Union`
211 # at all. This allows us to not di any checks for how many types have been
212 # passed as we can safely use `Union` for both optional types
213 # (e.g. `Optional[str]`) and optional unions (e.g.
214 # `Optional[Union[TypeA, TypeB]]`)
215 child_type = Union[non_optional_types] # type: ignore
217 of_type = StrawberryAnnotation(
218 annotation=child_type,
219 namespace=self.namespace,
--> 220 ).resolve()
222 return StrawberryOptional(of_type)
[... skipping similar frames: StrawberryAnnotation.resolve at line 133 (1 times)]
File ~/.virtualenvs/alembic/lib/python3.10/site-packages/strawberry/annotation.py:150, in StrawberryAnnotation._resolve(self)
148 return evaled_type
149 if self._is_list(evaled_type):
--> 150 return self.create_list(evaled_type)
152 if self._is_graphql_generic(evaled_type):
153 if any(is_type_var(type_) for type_ in get_args(evaled_type)):
File ~/.virtualenvs/alembic/lib/python3.10/site-packages/strawberry/annotation.py:197, in StrawberryAnnotation.create_list(self, evaled_type)
192 def create_list(self, evaled_type: Any) -> StrawberryList:
193 item_type, *_ = get_args(evaled_type)
194 of_type = StrawberryAnnotation(
195 annotation=item_type,
196 namespace=self.namespace,
--> 197 ).resolve()
199 return StrawberryList(of_type)
[... skipping similar frames: StrawberryAnnotation.resolve at line 133 (1 times)]
File ~/.virtualenvs/alembic/lib/python3.10/site-packages/strawberry/annotation.py:152, in StrawberryAnnotation._resolve(self)
149 if self._is_list(evaled_type):
150 return self.create_list(evaled_type)
--> 152 if self._is_graphql_generic(evaled_type):
153 if any(is_type_var(type_) for type_ in get_args(evaled_type)):
154 return evaled_type
File ~/.virtualenvs/alembic/lib/python3.10/site-packages/strawberry/annotation.py:281, in StrawberryAnnotation._is_graphql_generic(cls, annotation)
279 if hasattr(annotation, "__origin__"):
280 if definition := get_object_definition(annotation.__origin__):
--> 281 return definition.is_graphql_generic
283 return is_generic(annotation.__origin__)
285 return False
File ~/.virtualenvs/alembic/lib/python3.10/site-packages/strawberry/types/base.py:347, in StrawberryObjectDefinition.is_graphql_generic(self)
342 return False
344 # here we are checking if any exposed field is generic
345 # a Strawberry class can be "generic", but not expose any
346 # generic field to GraphQL
--> 347 return any(field.is_graphql_generic for field in self.fields)
File ~/.virtualenvs/alembic/lib/python3.10/site-packages/strawberry/types/base.py:347, in <genexpr>(.0)
342 return False
344 # here we are checking if any exposed field is generic
345 # a Strawberry class can be "generic", but not expose any
346 # generic field to GraphQL
--> 347 return any(field.is_graphql_generic for field in self.fields)
File ~/.virtualenvs/alembic/lib/python3.10/site-packages/strawberry/types/field.py:256, in StrawberryField.is_graphql_generic(self)
251 @property
252 def is_graphql_generic(self) -> bool:
253 return (
254 self.base_resolver.is_graphql_generic
255 if self.base_resolver
--> 256 else _is_generic(self.type)
257 )
File ~/.virtualenvs/alembic/lib/python3.10/site-packages/strawberry/types/field.py:305, in StrawberryField.type(self)
297 @property # type: ignore
298 def type(
299 self,
(...)
303 Literal[UNRESOLVED],
304 ]:
--> 305 return self.resolve_type()
File ~/.virtualenvs/alembic/lib/python3.10/site-packages/strawberry/types/field.py:352, in StrawberryField.resolve_type(self, type_definition)
349 with contextlib.suppress(NameError):
350 # Prioritise the field type over the resolver return type
351 if self.type_annotation is not None:
--> 352 resolved = self.type_annotation.resolve()
353 elif self.base_resolver is not None and self.base_resolver.type is not None:
354 # Handle unannotated functions (such as lambdas)
355 # Generics will raise MissingTypesForGenericError later
356 # on if we let it be returned. So use `type_annotation` instead
357 # which is the same behaviour as having no type information.
358 resolved = self.base_resolver.type
[... skipping similar frames: StrawberryAnnotation.resolve at line 133 (1 times)]
File ~/.virtualenvs/alembic/lib/python3.10/site-packages/strawberry/annotation.py:162, in StrawberryAnnotation._resolve(self)
160 return self.create_enum(evaled_type)
161 elif self._is_optional(evaled_type, args):
--> 162 return self.create_optional(evaled_type)
163 elif self._is_union(evaled_type, args):
164 return self.create_union(evaled_type, args)
File ~/.virtualenvs/alembic/lib/python3.10/site-packages/strawberry/annotation.py:220, in StrawberryAnnotation.create_optional(self, evaled_type)
210 # Note that passing a single type to `Union` is equivalent to not using `Union`
211 # at all. This allows us to not di any checks for how many types have been
212 # passed as we can safely use `Union` for both optional types
213 # (e.g. `Optional[str]`) and optional unions (e.g.
214 # `Optional[Union[TypeA, TypeB]]`)
215 child_type = Union[non_optional_types] # type: ignore
217 of_type = StrawberryAnnotation(
218 annotation=child_type,
219 namespace=self.namespace,
--> 220 ).resolve()
222 return StrawberryOptional(of_type)
[... skipping similar frames: StrawberryAnnotation.resolve at line 133 (1 times)]
File ~/.virtualenvs/alembic/lib/python3.10/site-packages/strawberry/annotation.py:150, in StrawberryAnnotation._resolve(self)
148 return evaled_type
149 if self._is_list(evaled_type):
--> 150 return self.create_list(evaled_type)
152 if self._is_graphql_generic(evaled_type):
153 if any(is_type_var(type_) for type_ in get_args(evaled_type)):
File ~/.virtualenvs/alembic/lib/python3.10/site-packages/strawberry/annotation.py:197, in StrawberryAnnotation.create_list(self, evaled_type)
192 def create_list(self, evaled_type: Any) -> StrawberryList:
193 item_type, *_ = get_args(evaled_type)
194 of_type = StrawberryAnnotation(
195 annotation=item_type,
196 namespace=self.namespace,
--> 197 ).resolve()
199 return StrawberryList(of_type)
[... skipping similar frames: StrawberryAnnotation.resolve at line 133 (584 times), <genexpr> at line 347 (195 times), StrawberryAnnotation._is_graphql_generic at line 281 (195 times), StrawberryAnnotation._resolve at line 152 (195 times), StrawberryObjectDefinition.is_graphql_generic at line 347 (195 times), StrawberryField.is_graphql_generic at line 256 (195 times), StrawberryField.resolve_type at line 352 (195 times), StrawberryField.type at line 305 (195 times), StrawberryAnnotation._resolve at line 162 (194 times), StrawberryAnnotation._resolve at line 150 (194 times), StrawberryAnnotation.create_list at line 197 (194 times), StrawberryAnnotation.create_optional at line 220 (194 times)]
... SNIPPED ...
If I wanted to contribute this fix (seems like a major feature though π ), where should I start?
Thank you for your time!
Libor
Pay now to fund the work behind this issue.
Get updates on progress being made.
Maintainer is rewarded once the issue is completed.
You're funding impactful open source efforts
You want to contribute to this effort
You want to get funding like this too