-
-
Notifications
You must be signed in to change notification settings - Fork 603
Add support for lazy unions #4017
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add support for lazy unions #4017
Conversation
Reviewer's GuideUnwraps Annotated types in GraphQLCoreConverter to support lazy union definitions, accompanied by new tests and a release note update. File-Level Changes
Possibly linked issues
Tips and commandsInteracting with Sourcery
Customizing Your ExperienceAccess your dashboard to:
Getting Help
|
for more information, see https://pre-commit.ci
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hey there - I've reviewed your changes and they look great!
Prompt for AI Agents
Please address the comments from this code review:
## Individual Comments
### Comment 1
<location> `tests/schema/test_lazy_types/test_lazy_unions.py:26-52` </location>
<code_context>
+], strawberry.union("LazyABUnion", types=[TypeALazy, TypeBLazy])]
+
+
+def test_lazy_union_with_non_lazy_members():
+ @strawberry.type
+ class Query:
+ ab: Annotated["ABUnion", strawberry.lazy("tests.schema.test_lazy_types.test_lazy_unions")]
+
+ expected = """
+ union ABUnion = TypeA | TypeB
+
+ type Query {
+ ab: ABUnion!
+ }
+
+ type TypeA {
+ a: Int!
+ }
+
+ type TypeB {
+ b: Int!
+ }
+ """
+
+ schema = strawberry.Schema(query=Query)
+ assert print_schema(schema) == textwrap.dedent(expected).strip()
+
+
</code_context>
<issue_to_address>
**suggestion (testing):** Missing tests for error conditions and invalid lazy union definitions.
Add tests for cases like referencing non-existent types, invalid module paths, and empty type lists to verify proper error handling in lazy unions.
```suggestion
+
+
+import pytest
+
+def test_lazy_union_with_non_lazy_members():
+ @strawberry.type
+ class Query:
+ ab: Annotated["ABUnion", strawberry.lazy("tests.schema.test_lazy_types.test_lazy_unions")]
+
+ expected = """
+ union ABUnion = TypeA | TypeB
+
+ type Query {
+ ab: ABUnion!
+ }
+
+ type TypeA {
+ a: Int!
+ }
+
+ type TypeB {
+ b: Int!
+ }
+ """
+
+ schema = strawberry.Schema(query=Query)
+ assert print_schema(schema) == textwrap.dedent(expected).strip()
+
+
+# Error condition: referencing non-existent type in lazy union
+def test_lazy_union_with_nonexistent_type():
+ NonExistentTypeLazy = Annotated["NonExistentType", strawberry.lazy("tests.schema.test_lazy_types.test_lazy_unions")]
+ with pytest.raises(ModuleNotFoundError):
+ strawberry.union("BadUnion", types=[NonExistentTypeLazy])
+
+
+# Error condition: invalid module path in lazy annotation
+def test_lazy_union_with_invalid_module_path():
+ InvalidTypeLazy = Annotated["TypeA", strawberry.lazy("invalid.module.path")]
+ with pytest.raises(ModuleNotFoundError):
+ strawberry.union("InvalidUnion", types=[InvalidTypeLazy])
+
+
+# Error condition: empty type list in lazy union
+def test_lazy_union_with_empty_type_list():
+ with pytest.raises(ValueError):
+ strawberry.union("EmptyUnion", types=[])
+
```
</issue_to_address>
### Comment 2
<location> `tests/schema/test_lazy_types/test_lazy_unions.py:53-50` </location>
<code_context>
+ assert print_schema(schema) == textwrap.dedent(expected).strip()
+
+
+def test_lazy_union_with_lazy_members():
+ @strawberry.type
+ class Query:
+ ab: Annotated["LazyABUnion", strawberry.lazy("tests.schema.test_lazy_types.test_lazy_unions")]
+
+ expected = """
+ union LazyABUnion = TypeA | TypeB
+
+ type Query {
+ ab: LazyABUnion!
+ }
+
+ type TypeA {
+ a: Int!
+ }
+
+ type TypeB {
+ b: Int!
+ }
+ """
+
+ schema = strawberry.Schema(query=Query)
+ assert print_schema(schema) == textwrap.dedent(expected).strip()
</code_context>
<issue_to_address>
**suggestion (testing):** Tests only verify schema printing, not query execution or runtime type resolution.
Consider adding tests that execute queries to ensure lazy unions resolve correctly at runtime, verifying expected results for each member type.
Suggested implementation:
```python
def test_lazy_union_with_lazy_members():
import strawberry
from typing import Annotated, Union
import textwrap
@strawberry.type
class TypeA:
a: int
@strawberry.type
class TypeB:
b: int
LazyABUnion = strawberry.union(
"LazyABUnion",
types=(TypeA, TypeB),
)
@strawberry.type
class Query:
ab: Annotated[LazyABUnion, strawberry.lazy("tests.schema.test_lazy_types.test_lazy_unions")]
@strawberry.field
def ab_type_a(self) -> LazyABUnion:
return TypeA(a=123)
@strawberry.field
def ab_type_b(self) -> LazyABUnion:
return TypeB(b=456)
expected = """
union LazyABUnion = TypeA | TypeB
```
```python
schema = strawberry.Schema(query=Query)
assert print_schema(schema) == textwrap.dedent(expected).strip()
# Runtime query execution tests
from strawberry.test import GraphQLTestClient
client = GraphQLTestClient(schema)
# Test TypeA resolution
response_a = client.query(
"""
{
abTypeA {
... on TypeA {
a
}
... on TypeB {
b
}
}
}
"""
)
assert response_a.data == {"abTypeA": {"a": 123, "b": None}}
# Test TypeB resolution
response_b = client.query(
"""
{
abTypeB {
... on TypeA {
a
}
... on TypeB {
b
}
}
}
"""
)
assert response_b.data == {"abTypeB": {"a": None, "b": 456}}
```
</issue_to_address>Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Greptile Overview
Summary
This PR adds support for lazy unions in Strawberry GraphQL by modifying the `GraphQLCoreConverter.from_type` method to handle `Annotated` types containing union metadata. The change addresses issue #3381 where users encountered TypeErrors when attempting to use lazy types with unions.The core implementation involves a simple but effective check that detects Annotated types with exactly 2 arguments and extracts the union definition from the second argument. This allows the existing union handling logic to process lazy unions of the form Annotated[Union[...], strawberry.union(...)] without requiring extensive changes to the union resolution system.
The fix integrates seamlessly with Strawberry's existing lazy type infrastructure, which already uses Annotated types to wrap lazy references with strawberry.lazy() metadata. By extending this pattern to unions, developers can now define lazy references to union types just like they can with regular types, maintaining consistency across the type system.
Comprehensive test coverage has been added that validates both lazy unions with non-lazy members and lazy unions with lazy members, ensuring the implementation works correctly across different lazy loading scenarios.
Important Files Changed
Changed Files
| Filename | Score | Overview |
|---|---|---|
| strawberry/schema/schema_converter.py | 4/5 | Added 3-line check to extract union definitions from Annotated types, enabling lazy union support |
| tests/schema/test_lazy_types/test_lazy_unions.py | 5/5 | Added comprehensive test coverage for lazy unions with both lazy and non-lazy members |
| RELEASE.md | 5/5 | Added release notes indicating patch-level support for lazy unions |
Confidence score: 4/5
- This PR is safe to merge with minimal risk as it addresses a specific bug with a focused solution
- Score reflects the targeted nature of the fix and comprehensive test coverage, though the implementation could benefit from additional edge case handling
- Pay close attention to strawberry/schema/schema_converter.py to ensure the type extraction logic handles all
Annotatedunion patterns correctly
Sequence Diagram
sequenceDiagram
participant User
participant Schema as "strawberry.Schema"
participant Converter as "GraphQLCoreConverter"
participant LazyType as "LazyType"
participant Union as "StrawberryUnion"
User->>Schema: "Create schema with lazy union field"
Schema->>Converter: "from_type(lazy_union_field)"
Note over Converter: Check if type is Annotated with 2 args
Converter->>Converter: "typing.get_origin(type_) is Annotated?"
Converter->>Converter: "len(typing.get_args(type_)) == 2?"
Converter->>Converter: "Extract actual type from args[1]"
alt Type is StrawberryUnion
Converter->>Union: "from_union(union_type)"
Union->>Union: "Validate union member types"
loop For each union member type
Union->>Converter: "from_type(member_type)"
alt Member is LazyType
Converter->>LazyType: "from_type(lazy_type)"
LazyType->>LazyType: "resolve_type()"
LazyType-->>Converter: "resolved_type"
Converter->>Converter: "from_type(resolved_type)"
else Member is regular type
Converter->>Converter: "Process regular type"
end
Converter-->>Union: "GraphQL type"
end
Union->>Union: "Create GraphQLUnionType"
Union-->>Converter: "GraphQLUnionType"
end
Converter-->>Schema: "GraphQL type"
Schema-->>User: "Compiled schema"
3 files reviewed, 2 comments
| # to handle lazy unions | ||
| if typing.get_origin(type_) is Annotated and len(typing.get_args(type_)) == 2: | ||
| type_ = typing.get_args(type_)[1] | ||
|
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It isn't the best place for this code. It would be better if resolving a lazy type would return a StrawberryUnion object in this case, but I couldn't make it work 🤔
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
could be worth trying in another PR!
Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com>
This reverts commit f38f73f.
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## main #4017 +/- ##
==========================================
- Coverage 94.75% 94.29% -0.47%
==========================================
Files 520 533 +13
Lines 33902 34893 +991
Branches 1754 1846 +92
==========================================
+ Hits 32123 32901 +778
- Misses 1497 1687 +190
- Partials 282 305 +23 🚀 New features to boost your workflow:
|
CodSpeed Performance ReportMerging #4017 will not alter performanceComparing Summary
|
| TypeALazy, | ||
| TypeBLazy, | ||
| ], | ||
| strawberry.union("LazyABUnion", types=[TypeALazy, TypeBLazy]), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If a union does not define types, an error will be raised that union has to have at least one member
patrick91
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thank you so much!
| # to handle lazy unions | ||
| if typing.get_origin(type_) is Annotated and len(typing.get_args(type_)) == 2: | ||
| type_ = typing.get_args(type_)[1] | ||
|
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
could be worth trying in another PR!

Description
Adds support for lazy unions by adjusting
GraphQLCoreConverter.from_typeTypes of Changes
Issues Fixed or Closed by This PR
Checklist
Summary by Sourcery
Add support for lazy unions by adjusting type conversion logic to unwrap Annotated types, include tests for both eager and lazy union members, and update release notes
Bug Fixes:
Enhancements:
Documentation:
Tests: