strawberry
strawberry copied to clipboard
Add support for generic unions
Description
This PR merges union types in strawberry.schema.schema_converter.GraphQLCoreCoverter:from_union. This allows creating a more stable interface, as a union can be created between a TypeVar and an annotated union. In the examples in #3393, this changes SomeTypeByIdNotFoundError => SomeTypeByIdResult, the latter of which won't change as more error cases are added.
Types of Changes
- [ ] Core
- [x] Bugfix
- [ ] New feature
- [ ] Enhancement/optimization
- [ ] Documentation
Issues Fixed or Closed by This PR
- #3393 - The related issues have been checked as well, but this doesn't seem to fix them; except for #2959 which seems to have already been fixed as far as I can tell.
Checklist
- [x] My code follows the code style of this project.
- [x] My change requires a change to the documentation.
- [x] I have updated the documentation accordingly.
- [x] I have read the CONTRIBUTING document.
- [x] I have added tests to cover my changes.
- [x] I have tested the changes and verified that they work and don't break anything (as well as I can manage).
Thanks for adding the RELEASE.md file!

Here's a preview of the changelog:
This release adds support for using strawberry.union with generics, like in this example:
@strawberry.type
class ObjectQueries[T]:
@strawberry.field
def by_id(
self, id: strawberry.ID
) -> Union[T, Annotated[NotFoundError, strawberry.union("ByIdResult")]]: ...
@strawberry.type
class Query:
@strawberry.field
def some_type_queries(self, id: strawberry.ID) -> ObjectQueries[SomeType]: ...
which, now, creates a correct union type named SomeTypeByIdResult
Here's the tweet text:
π Release (next) is out! Thanks to Jacob Allen for the PR π
Get it here π https://strawberry.rocks/release/(next)
Codecov Report
Attention: Patch coverage is 93.90244% with 5 lines in your changes missing coverage. Please review.
Project coverage is 95.05%. Comparing base (
7bee2cf) to head (78cd264). Report is 1 commits behind head on main.
Additional details and impacted files
@@ Coverage Diff @@
## main #3515 +/- ##
==========================================
- Coverage 95.06% 95.05% -0.01%
==========================================
Files 501 501
Lines 32559 32632 +73
Branches 1686 1694 +8
==========================================
+ Hits 30951 31019 +68
Misses 1341 1341
- Partials 267 272 +5
:rocket: New features to boost your workflow:
- :snowflake: Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
- :package: JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.
CodSpeed Performance Report
Merging #3515 will not alter performance
Comparing enoua5:fix-3393 (78cd264) with main (7bee2cf)
Summary
β
21 untouched benchmarks
@enoua5 I pushed a fix π but I'll try to write a test as well
Thank you for the fixes, @patrick91! It's looking good, and each of the examples in #3393 are now working as expected! :tada:
It sounds like we'll need to change the release file? Does something like "Add support for generic unions" encompass the new changes? As far as examples go for the release file, would copying an example or two from the new tests suffice?
@enoua5 yes! I'll change the release file βΊοΈ it was late yesterday and I was discussing with @bellini666 if there was a way to simplify the code, I'll take another look today
Just realized we should probably also have a test to make sure unions with multiple generics work. If we think that's needed, I can add one when I get home from work today
@enoua5 yes please π
Also of note is that this is technically a breaking change. A breaking change that makes output more predictable and stable, but a breaking change nonetheless. Consider the following schema definition:
from typing import Annotated, Generic, TypeVar
import strawberry
@strawberry.type
class SomeType:
a: str
@strawberry.type
class NotFoundError:
id: strawberry.ID
message: str
T = TypeVar('T')
@strawberry.type
class ObjectQueries(Generic[T]):
...
@strawberry.field
def by_id(self, id: strawberry.ID) -> Annotated[T | NotFoundError, strawberry.union("ByIdResult")]:
...
...
@strawberry.type
class Query:
@strawberry.field
def some_type_queries(self, id: strawberry.ID) -> ObjectQueries[SomeType]:
return ObjectQueries(SomeType)
schema = strawberry.Schema(Query)
Under the current version of strawberry, this SDL is generated:
type Query {
someTypeQueries(id: ID!): SomeTypeObjectQueries!
}
type SomeTypeObjectQueries {
byId(id: ID!): SomeTypeNotFoundError!
}
union SomeTypeNotFoundError = SomeType | NotFoundError
type SomeType {
a: String!
}
type NotFoundError {
id: ID!
message: String!
}
Whereas under this PR, this is the SDL generated:
type Query {
someTypeQueries(id: ID!): SomeTypeObjectQueries!
}
type SomeTypeObjectQueries {
byId(id: ID!): SomeTypeByIdResult!
}
union SomeTypeByIdResult = SomeType | NotFoundError
type SomeType {
a: String!
}
type NotFoundError {
id: ID!
message: String!
}
For clarity, that:
6c6
< byId(id: ID!): SomeTypeNotFoundError!
---
> byId(id: ID!): SomeTypeByIdResult!
9c9
< union SomeTypeNotFoundError = SomeType | NotFoundError
---
> union SomeTypeByIdResult = SomeType | NotFoundError
While I do this this naming is better and makes it easier to write a consistent API (in the former case, adding a new error type NewError to the union would result in a type name of SomeTypeNotFoundErrorNewError, also breaking code), this could potentially break any code already using this pattern.
Didn't realize merging would list all the other commits on here :sweat_smile: how do i squash that?
Seems like something went wrong during merge, your diff has 12k lines now :(
There we go, just needed to get github to recheck the base