strawberry
strawberry copied to clipboard
Proposal: Automatic creation of Input Classes
Let me start with an example, a very standard CRUD API:
@strawberry.type
class User
name: str
birthday: date
...
@strawberry.field
async def something(self) -> Something
@strawberry.input
class UserInput
name: str = UNSET
birthday: date = UNSET
...
@strawberry.type
class Query:
@strawberry.field
get_user(id: ID) -> User:
...
@strawberry.type
class Mutation:
@strawberry.mutation
create_user(user: UserInput) -> User:
...
@strawberry.mutation
update_user(id: ID, user: UserInput) -> User:
..
The types User
and UserInput
are essentially the same thing all over again -- Except the inputs don't have resolvers
Keeping the fields and docs in sync is just boring repetitive work.
Suggestion 1
Would be great if we could reuse the same type declaration in our arguments, and strawberry would just create an input type in the schema under the hood containing only the dataclass fields
@strawberry.type
class Mutation:
@strawberry.mutation
create_user(user: User) -> User:
...
---
type Mutation {
createUser(user: UserInput!): User!
}
GraphQL-Kotlin does that
IMHO this is a simple and reasonable approach
Suggestion 2
Another suggestion is to keep the Main/Input types distinct, but generate the Type.Input automatically:
- The python class for the input would be added as a nested class
User.Input
- dataclass-style fields would be kept by default
- resolver fields are discarded by default
- default values would be set to
UNSET
or removed entirely - If the field type if a graphQL
SomeTime
, it gets replaced bySomeTypeInput
- These option could be overriden using
strawberry.field()
strawberry.field(
input_options=InputFieldOptions(
keep=True, # Keep/discard this field in the Input type,
type=DataType, # Modify the field type. e.g., a NestedObject-type field might be replaced by an ID in an input
default=DataType() # Uses this as the default value
default_factory=lambda: DataType() # Uses this as the factory
description="Same field, now in an input"
)
)
This would look like this:
@strawberry.type
class Mutation:
@strawberry.mutation
create_user(user: User.Input) -> User:
...
I also ran into this problem, it's too repetitive, is there any plan to modify this ?
I have a draft implementation of suggestion 2 by adding a @gen_strawberry_input
decorator.
Hopefully this can be useful to you
https://gist.github.com/paulo-raca/b1896a29c5029455b7290d03c4a106af
This is still a draft:
- I currently plan to store the
InputFieldOptions
using "typing.Annotated", but I need #1730 to be merged first - I expect to get more feedback on this before making a PR
In a way this is actually solved with the pydantic plugin, so strange that core library cannot do it
I think we might want to do this at some point, but we are still working on some internal refactoring 😊
Also we'd need to make a decision on this would work exactly, especially with regards to UNSET, making fields options and dealing with customisation of the type
https://gist.github.com/paulo-raca/b1896a29c5029455b7290d03c4a106af
Hello! Made several improvements to this code, added support for:
- Optional
- List
- Union
- Generics and "bad" Generics
- LazyType
The code is uploaded in the comments.
I think something that is worth thinking about doing this is that GraphQL APIs aren't really meant to be CRUD APIs, so adding something like this directly to the core of Strawberry might look like promoting creating CRUD APIs and all the problems that it brings.
Code duplication is not necessarily a bad thing if that allows you to customise your API better to your clients use-case
Yes, it's true.
But, the cases are different, it is not uncommon that such functionality would be useful to save time. If the developer understands that even in the idealized case, the output and input classes will be the same, then it would be cool to give him the opportunity to generate such an input class.
But, of course, there are big questions to ensure that this is added to the core as the main part of the API. Maybe a single function would be enough (with not full support, certain limitations).
I've added some extra implementation details between input/outputs here #2369 I basically want the same functionality mentioned in this issue.