While it's a bit niche, we have a use case where we want to return Query
from an object field (specifically this is in a mutation result to allow callers to request arbitrary parts of the schema after a mutation).
This works fine on a standard strawberry.Schema
, but breaks down when using strawberry.federation.Schema
.
This works fine without federation, for example this minimal test case:
from __future__ import annotations
import strawberry
@strawberry.type
class Query:
@strawberry.field
def foo(self) -> Foo:
return Foo()
@strawberry.type
class Foo:
bar: int = 42
@strawberry.field
def query(self) -> Query:
return Query()
schema = strawberry.Schema(query=Query)
result = schema.execute_sync("query { foo { query { foo { bar } } } }")
assert result.data == {'foo': {'query': {'foo': {'bar': 42}}}}
Works fine (either run python <file>
or strawberry export-schema <module>:schema
).
However if you use the federation integration:
diff --git a/test/schema.py b/test/schema_federated.py
index f3b1a0d485eb..1bb2c1f5d101 100644
--- a/test/schema.py
+++ b/test/schema_federated.py
@@ -19,7 +19,7 @@ class Foo:
return Query()
-schema = strawberry.Schema(query=Query)
+strawberry.federation.Schema(query=Query)
result = schema.execute_sync("query { foo { query { foo { bar } } } }")
from __future__ import annotations
import strawberry
@strawberry.type
class Query:
@strawberry.field
def foo(self) -> Foo:
return Foo()
@strawberry.type
class Foo:
bar: int = 42
@strawberry.field
def query(self) -> Query:
return Query()
schema = strawberry.federation.Schema(query=Query)
result = schema.execute_sync("query { foo { query { foo { bar } } } }")
assert result.data == {'foo': {'query': {'foo': {'bar': 42}}}}
You get:
error: Type `Query` is defined multiple times in the schema
@ test/schema_federated.py:7
6 | @strawberry.type
β± 7 | class Query:
^^^^^ first class defined here
8 | @strawberry.field
9 | def foo(self) -> Foo:
10 | return Foo()
To fix this error you should either rename the type or remove the duplicated definition.
Read more about this error on https://errors.strawberry.rocks/duplicated-type-name
(the link isn't useful here as the issue isn't non unique names)
I've tracked it down to how the federation schema works, specifically _get_federation_query_type
(1) which creates a new type that's purely internal and when the converter tries to match them up in validate_same_type_definition
(2) the types aren't the same anymore, one is the type as defined in the consumer python module and the other is the internal type to which the federation fields have been added. When we reach the raise statement (3), the values extracted from the rich
formatted exception are:
β β first_type_definition = StrawberryObjectDefinition( β β
β β β name='Query', β β
β β β is_input=False, β β
β β β is_interface=False, β β
β β β origin=<class 'strawberry.tools.merge_types.Query'>, β β
β β β description=None, β β
β β β interfaces=[], β β
β β β extend=False, β β
β β β directives=(), β β
β β β is_type_of=None, β β
β β β resolve_type=None, β β
β β β fields=[ β β
β β β β Field(name='service',type=<class β β
β β 'strawberry.federation.schema.Schema._get_federation_query_type.<locals>.Service'>,default=<dataclasses._MISSING_TYPE object at β β
β β 0x10487f2c0>,default_factory=<dataclasses._MISSING_TYPE object at β β
β β 0x10487f2c0>,init=False,repr=False,hash=None,compare=False,metadata=mappingproxy({}),kw_only=True,_field_type=_FIELD), β β
β β β β Field(name='foo',type=<class 'test.schema_federated.Foo'>,default=<dataclasses._MISSING_TYPE object at β β
β β 0x10487f2c0>,default_factory=<dataclasses._MISSING_TYPE object at β β
β β 0x10487f2c0>,init=False,repr=False,hash=None,compare=False,metadata=mappingproxy({}),kw_only=True,_field_type=_FIELD) β β
β β β ], β β
β β β concrete_of=None, β β
β β β type_var_map={} β β
β β )
and:
β β second_type_definition = StrawberryObjectDefinition( β β
β β β name='Query', β β
β β β is_input=False, β β
β β β is_interface=False, β β
β β β origin=<class 'test.schema_federated.Query'>, β β
β β β description=None, β β
β β β interfaces=[], β β
β β β extend=False, β β
β β β directives=(), β β
β β β is_type_of=None, β β
β β β resolve_type=None, β β
β β β fields=[ β β
β β β β Field(name='foo',type=<class 'test.schema_federated.Foo'>,default=<dataclasses._MISSING_TYPE object at β β
β β 0x10487f2c0>,default_factory=<dataclasses._MISSING_TYPE object at β β
β β 0x10487f2c0>,init=False,repr=False,hash=None,compare=False,metadata=mappingproxy({}),kw_only=True,_field_type=_FIELD) β β
β β β ], β β
β β β concrete_of=None, β β
β β β type_var_map={} β β
β β ) β β
This feels like it should be supported with or without federation but as far as I could find, there's no currently supported way to have a field type be a forward reference to "the final Query
type".
We haven't found a solution we're happy with yet, but it looks like either extracting the bits creating the new type so we can get a hold of a reference to the type or possibly a custom SchemaConverter
could work as a workaround (will update when we have something working, hoping there's something that can be turned into a PR but reporting for now for tracking).
Also to note using lazy
here doesn't help as it requires an importable type, but a variation/expansion on the lazy
concept could work to express "reference to the final schema type", although probably can't be made type safe.
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