strawberry icon indicating copy to clipboard operation
strawberry copied to clipboard

Add support for generic unions

Open enoua5 opened this issue 1 year ago β€’ 12 comments

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).

enoua5 avatar May 26 '24 19:05 enoua5

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)

botberry avatar May 26 '24 19:05 botberry

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.

codecov[bot] avatar May 26 '24 19:05 codecov[bot]

CodSpeed Performance Report

Merging #3515 will not alter performance

Comparing enoua5:fix-3393 (78cd264) with main (7bee2cf)

Summary

βœ… 21 untouched benchmarks

codspeed-hq[bot] avatar May 26 '24 20:05 codspeed-hq[bot]

@enoua5 I pushed a fix 😊 but I'll try to write a test as well

patrick91 avatar May 26 '24 20:05 patrick91

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 avatar May 27 '24 00:05 enoua5

@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

patrick91 avatar May 27 '24 08:05 patrick91

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 avatar May 30 '24 14:05 enoua5

@enoua5 yes please 😊

patrick91 avatar May 30 '24 14:05 patrick91

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.

enoua5 avatar May 31 '24 02:05 enoua5

Didn't realize merging would list all the other commits on here :sweat_smile: how do i squash that?

enoua5 avatar Sep 29 '24 03:09 enoua5

Seems like something went wrong during merge, your diff has 12k lines now :(

erikwrede avatar Sep 29 '24 04:09 erikwrede

There we go, just needed to get github to recheck the base

enoua5 avatar Sep 29 '24 05:09 enoua5