Skip to content

Distinguish between python and graphql generics#3202

Merged
patrick91 merged 13 commits intomainfrom
feature/graphql-vs-python-generics
Nov 7, 2023
Merged

Distinguish between python and graphql generics#3202
patrick91 merged 13 commits intomainfrom
feature/graphql-vs-python-generics

Conversation

@patrick91
Copy link
Copy Markdown
Member

@patrick91 patrick91 commented Nov 3, 2023

So at the moment we treat any generic as a GraphQL generic, this is usually fine,
but there are some cases where this is not ideal, for example in something like this:

@strawberry.type
class Edge[T]:
    cursor: strawberry.ID
    node_field: strawberry.Private[T]

where no GraphQL field is actually generic.

This PR changes how we check for generics by checking field types and argument types.

Not 100% sure yet if the implementation is fine, we might have some weird edge cases

This should be a better approach for fixing @kkom use case in #3160

@codecov
Copy link
Copy Markdown

codecov bot commented Nov 3, 2023

Codecov Report

Merging #3202 (70bf01a) into main (dfa003b) will increase coverage by 0.00%.
The diff coverage is 94.05%.

Additional details and impacted files
@@           Coverage Diff           @@
##             main    #3202   +/-   ##
=======================================
  Coverage   96.59%   96.60%           
=======================================
  Files         481      481           
  Lines       29806    29826   +20     
  Branches     3670     3673    +3     
=======================================
+ Hits        28791    28813   +22     
+ Misses        832      830    -2     
  Partials      183      183           


# If this is a generic field, try to resolve it using its origin's
# specialized type_var_map
# TODO: should we check arguments here too?
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd leave this for the future 😊

@patrick91 patrick91 marked this pull request as ready for review November 3, 2023 22:10
@botberry
Copy link
Copy Markdown
Member

botberry commented Nov 3, 2023

Thanks for adding the RELEASE.md file!

Here's a preview of the changelog:


This release changes how we check for generic types. Previously, any type that
had a generic typevar would be considered generic for the GraphQL schema, this
would generate un-necessary types in some cases. Now, we only consider a type
generic if it has a typevar that is used as the type of a field or one of its arguments.

For example the following type:

@strawberry.type
class Edge[T]:
    cursor: strawberry.ID
    some_interna_value: strawberry.Private[T]

Will not generate a generic type in the schema, as the typevar T is not used
as the type of a field or argument.

Here's the tweet text:

🆕 Release (next) is out! Thanks to @patrick91 for the PR 👏

Get it here 👉 https://beta.strawberry.rocks/release/(next)

@codspeed-hq
Copy link
Copy Markdown

codspeed-hq bot commented Nov 3, 2023

CodSpeed Performance Report

Merging #3202 will not alter performance

Comparing feature/graphql-vs-python-generics (70bf01a) with main (dfa003b)

Summary

✅ 12 untouched benchmarks

@patrick91 patrick91 marked this pull request as draft November 3, 2023 22:16
@patrick91
Copy link
Copy Markdown
Member Author

/pre-release

@patrick91
Copy link
Copy Markdown
Member Author

this broke lazy generics, will check in the next few days :)

@botberry
Copy link
Copy Markdown
Member

botberry commented Nov 3, 2023

Pre-release

👋

Pre-release 0.212.0.dev.1699291750 [c82108e] has been released on PyPi! 🚀
You can try it by doing:

poetry add strawberry-graphql==0.212.0.dev.1699291750

@strawberry-graphql strawberry-graphql deleted a comment from botberry Nov 3, 2023
@kkom
Copy link
Copy Markdown

kkom commented Nov 4, 2023

Beautiful! I can confirm it works for my use case. Thank you! 🙏

As expected, I have to add # pyright: ignore[reportMissingTypeArgument] comments, since I'm using generic classes without providing type arguments, but this is fair game. (I'm adding it to the proposed test case below for clarity, but you may not need it given your type checker config.)

Worth adding a test case like this, if you agree this is legit usage?

@strawberry.interface
class GenericInterface[T]:
    data: strawberry.Private[T]

    @strawberry.field
    def value(self) -> str:
        raise NotImplementedError()

@strawberry.type
class ImplementationOne(GenericInterface[str]):
    @strawberry.field
    def value(self) -> str:
        return self.data

@strawberry.type
class ImplementationTwo(GenericInterface[bool]):
    @strawberry.field
    def value(self) -> str:
        if self.data is True:
            return "true"
        return "false"

@strawberry.type
class TestQuery:
    @strawberry.field
    @staticmethod
    async def generic_field() -> GenericInterface:  # pyright: ignore[reportMissingTypeArgument]
        return ImplementationOne(data="foo")
interface GenericInterface {
  value: String!
}

type ImplementationOne implements GenericInterface {
  value: String!
}

type ImplementationTwo implements GenericInterface {
  value: String!
}

type TestQuery {
  genericField: GenericInterface!
}

@botberry
Copy link
Copy Markdown
Member

botberry commented Nov 6, 2023

Apollo Federation Subgraph Compatibility Results

Federation 1 SupportFederation 2 Support
_service🟢
@key (single)🟢
@key (multi)🟢
@key (composite)🟢
repeatable @key🟢
@requires🟢
@provides🟢
federated tracing🔲
@link🟢
@shareable🟢
@tag🟢
@override🟢
@inaccessible🟢
@composeDirective🟢
@interfaceObject🟢

Learn more:

@patrick91
Copy link
Copy Markdown
Member Author

/pre-release

@patrick91
Copy link
Copy Markdown
Member Author

@kkom can you test this latest pre-release (once it is out)? All tests are passing now, except for when I pass a type (like your example but with GenericInterface[int|str]), but that should be easy to fix :)

@kkom
Copy link
Copy Markdown

kkom commented Nov 6, 2023

Awesome, will do later in the evening! But the test case looks exactly like what I'm after, so it should be all good :) Thanks!

@patrick91 patrick91 marked this pull request as ready for review November 6, 2023 17:28
@patrick91
Copy link
Copy Markdown
Member Author

/pre-release

@kkom
Copy link
Copy Markdown

kkom commented Nov 6, 2023

Just tested it, it works exactly as I wanted it to – thanks!

It's been a pretty simple use case so far, I'll let you know if anything more complex doesn't work – but I don't expect any issues really.

@patrick91
Copy link
Copy Markdown
Member Author

I'll merge this later today! So I can get @bellini666 to do a release for Strawberry Django 😊

@patrick91 patrick91 merged commit 35d671e into main Nov 7, 2023
@patrick91 patrick91 deleted the feature/graphql-vs-python-generics branch November 7, 2023 14:00
@AntonOfTheWoods
Copy link
Copy Markdown
Contributor

AntonOfTheWoods commented Nov 12, 2023

@patrick91 Obviously my fault but... what is the expected way to force strawberry to generate these? Previously I was getting:

LanguageclassesInputCheckpoint

Generated when I had only:

@strawberry.input
class InputCheckpoint(Generic[T]):
    id: Optional[str] = ""
    updated_at: Optional[float] = -1
...

@strawberry.type
class Languageclasses(CommonType):
    @staticmethod
    def from_model(obj):
        out = CommonType.from_model_base(obj, Languageclasses)
        return out
...

@strawberry.type
class CommonType:
    id: str
    created_by: Optional[str] = ""
 ...

    @strawberry.field
    async def pull_languageclasses(  # pylint: disable=E0213
        info: Info[Context, Any],
        limit: int,
        checkpoint: Optional[InputCheckpoint[Languageclasses]] = None,
    ) -> Return[Languageclasses]:
 ...

And now they aren't. My client (rxdb) relies on these existing - do I need to explicitly declare them somehow? If so, any pointers? Thanks!

@patrick91
Copy link
Copy Markdown
Member Author

@AntonOfTheWoods you'd need a field on the generic type to also use the type var

Other than that I don't a good workaround idea 🤔

@AntonOfTheWoods
Copy link
Copy Markdown
Contributor

Thanks... so I guess I'll just pin to < 0.212.0 or recreate the 30 or so types that this got rid of... sniff...

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants