Skip to content
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

Support "void" functions #1648

Merged
merged 7 commits into from
Feb 17, 2022
Merged

Support "void" functions #1648

merged 7 commits into from
Feb 17, 2022

Conversation

paulo-raca
Copy link
Contributor

@paulo-raca paulo-raca commented Feb 16, 2022

Description

It might not be a best practice, but sometimes a mutation resolver just doesn't need to return anything.

Having a resolver that returns None is currently an error in strawberry.

This PR adds the scalar "Void" and uses it automatically when the type is None (or NoneType, which can be confusing 🙄)

This scalar mimics the Void type provided by GraphQL Scalars

Example:

    @strawberry.type
    class Mutation:
        @strawberry.field
        def do_something(self, arg: int) -> None:
            return

results in this schema:

   type Mutation {
       doSomething(arg: Int!): Void
    }

Changes:

  • Added a new Void scalar, and registered it to handle None and NoneType
  • Allow None type annotations
  • Merge from_optional and from_non_optional into from_maybe_optional
  • Add a special case for NoneType, to avoid wrapping it into GraphQLNonNull

Types of Changes

  • Core
  • Bugfix
  • New feature
  • Enhancement/optimization
  • Documentation

Checklist

  • My code follows the code style of this project.
  • My change requires a change to the documentation.
  • I have updated the documentation accordingly.
  • I have read the CONTRIBUTING document.
  • I have added tests to cover my changes.
  • I have tested the changes and verified that they work and don't break anything (as well as I can manage).

@botberry
Copy link
Member

botberry commented Feb 16, 2022

Thanks for adding the RELEASE.md file!

Here's a preview of the changelog:


Support "void" functions

It is now possible to have a resolver that returns "None". Strawberry will automatically assign the new Void scalar in the schema
and will always send null in the response

Exampe

@strawberry.type
class Mutation:
    @strawberry.field
    def do_something(self, arg: int) -> None:
        return

results in this schema:

type Mutation {
    doSomething(arg: Int!): Void
}

Here's the tweet text:

🆕 Release (next) is out! Thanks to Paulo Costa for the PR 👏

Get it here 👉 https://github.com/strawberry-graphql/strawberry/releases/tag/(next)

It might not conform to GraphQL's best practices, but sometimes we just don't need to return anything.

This PR adds the scalar "Void" and uses it automatically when the type is `None` (or `NoneType`, which can be confusing)

It mimics the `Void` type provided by [GraphQL Scalars](https://www.graphql-scalars.dev/docs/scalars/void)
Copy link
Member

@patrick91 patrick91 left a comment

Choose a reason for hiding this comment

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

Uhm, I'm not sure we should add a void type, I personally never needed to do something like that (mostly because I use union types for returning errors).

Maybe it would be better do add some docs about this pattern? Have you seen something like this in other libraries?

/cc @jkimbo what do you think?

Comment on lines +411 to +420
def from_maybe_optional(
self, type_: Union[StrawberryType, type]
) -> Union[GraphQLNullableType, GraphQLNonNull]:
NoneType = type(None)
if type_ is None or type_ is NoneType:
return self.from_type(type_)
elif isinstance(type_, StrawberryOptional):
return self.from_type(type_.of_type)
else:
return GraphQLNonNull(self.from_type(type_))
Copy link
Member

Choose a reason for hiding this comment

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

I like this! /cc @BryceBeagle

@codecov
Copy link

codecov bot commented Feb 16, 2022

Codecov Report

Merging #1648 (b3e9986) into main (f25ac4b) will decrease coverage by 0.02%.
The diff coverage is 93.33%.

@@            Coverage Diff             @@
##             main    #1648      +/-   ##
==========================================
- Coverage   98.16%   98.14%   -0.03%     
==========================================
  Files         129      129              
  Lines        4532     4528       -4     
  Branches      781      779       -2     
==========================================
- Hits         4449     4444       -5     
  Misses         43       43              
- Partials       40       41       +1     

@ThirVondukr
Copy link
Contributor

@patrick91 I think associating entities would be a valid use case for Void type?

type Tag {
  id: String!
  slug: String!
}

type Post {
  id: String!
  tags: [Tag!]!
}

mutation tagPost(tagId: String!, postId: String!): Void

@marcoacierno
Copy link
Member

marcoacierno commented Feb 16, 2022

@patrick91 I think associating entities would be a valid use case for Void type?

type Tag {
  id: String!
  slug: String!
}

type Post {
  id: String!
  tags: [Tag!]!
}

mutation tagPost(tagId: String!, postId: String!): Void

I think in that case you might want the mutation to return the list of updated tags so that who uses your API (e.g Website) can have the updated list of tags after the mutation without having to re-fetch (e.g update the UI of the site)

@paulo-raca
Copy link
Contributor Author

Uhm, I'm not sure we should add a void type, I personally never needed to do something like that (mostly because I use union types for returning errors).

Yes, there is a consensus that this is not the best practice 😅

However I believe strawberry should still be able to handle it anyway

Maybe it would be better do add some docs about this pattern?

I can add a line about that in the "Mutations" section

Have you seen something like this in other libraries?

TBH I'm quite new to GraphQL and I haven't seen much

However GraphQL Scalars has this and it is used in many github projects

@patrick91
Copy link
Member

Uhm, I'm not sure we should add a void type, I personally never needed to do something like that (mostly because I use union types for returning errors).

Yes, there is a consensus that this is not the best practice 😅

However I believe strawberry should still be able to handle it anyway

Maybe it would be better do add some docs about this pattern?

I can add a line about that in the "Mutations" section

Have you seen something like this in other libraries?

TBH I'm quite new to GraphQL and I haven't seen much

However GraphQL Scalars has this and it is used in many github projects

Gotcha, maybe we should add support for it, but not bind it to None 🤔

If more people ask for it to be bound to None we could have a configuration option.

What do you think?

@jkimbo
Copy link
Member

jkimbo commented Feb 16, 2022

IMO this seems like a good default. You can override the behaviour of None with the scalar overrides anyway if someone doesn't like it.

@github-actions
Copy link

Copy link
Member

@patrick91 patrick91 left a comment

Choose a reason for hiding this comment

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

Looks pretty good to me! Thanks for the PR and thanks to everyone for sharing their opinions 😊

I've left a couple of minor comments, then this is ready to be merged :)

RELEASE.md Outdated Show resolved Hide resolved
RELEASE.md Outdated Show resolved Hide resolved
docs/general/mutations.md Show resolved Hide resolved
name="Void",
serialize=_verify_void,
parse_value=_verify_void,
description="Represents NULL values",
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
description="Represents NULL values",
description="Represents None values",

maybe it is better to use None here? or Void? since it will be used in the schema :)

Copy link
Contributor Author

@paulo-raca paulo-raca Feb 17, 2022

Choose a reason for hiding this comment

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

I think NULL makes sense as it will be the returned value on the GraphQL protocol, while None is a Python thing.

However I don't have strong feeling about that 🤷‍♂️

For whatever it is worth, I have defined this description to be the consistent with graphql-scalar's Void

paulo-raca and others added 3 commits February 16, 2022 22:16
Co-authored-by: Patrick Arminio <[email protected]>
Co-authored-by: Patrick Arminio <[email protected]>
Co-authored-by: Patrick Arminio <[email protected]>
else:
argument_type = self.from_non_optional(argument.type)

argument_type = cast(GraphQLInputType, self.from_maybe_optional(argument.type))
Copy link
Contributor Author

Choose a reason for hiding this comment

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

I don't know why, but the cast() was necessary in a few places to make mypy happy 🤷‍♂️

name="Void",
serialize=_verify_void,
parse_value=_verify_void,
description="Represents NULL values",
Copy link
Contributor Author

@paulo-raca paulo-raca Feb 17, 2022

Choose a reason for hiding this comment

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

I think NULL makes sense as it will be the returned value on the GraphQL protocol, while None is a Python thing.

However I don't have strong feeling about that 🤷‍♂️

For whatever it is worth, I have defined this description to be the consistent with graphql-scalar's Void


<Note>

Mutations with void-result go against [GQL best practices](https://graphql-rules.com/rules/mutation-payload)
Copy link
Contributor Author

Choose a reason for hiding this comment

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

🤔 The link is broken in the preview documentation.

Any idea what is wrong? Is there a different syntax inside <Note>?

Copy link
Member

Choose a reason for hiding this comment

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

it needs the spaces, now it looks ok! https://strawberry.rocks/docs/pr/1648/general/mutations

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Thanks!

```grapqhl
type Mutation {
doSomething(arg: Int!): Void
}
Copy link
Member

Choose a reason for hiding this comment

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

I think this file got messed up because of the suggestions I made 😊 can you fix it? I'm unable to push to your branch :)

Copy link
Contributor Author

@paulo-raca paulo-raca Feb 17, 2022

Choose a reason for hiding this comment

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

Whoops, good catch!

@patrick91 patrick91 merged commit f5a2afd into strawberry-graphql:main Feb 17, 2022
@botberry
Copy link
Member

Thanks for contributing to Strawberry! 🎉 You've been invited to join
the Strawberry GraphQL organisation 😊

You can also request a free sticker by filling this form: https://forms.gle/dmnfQUPoY5gZbVT67

And don't forget to join our discord server: https://strawberry.rocks/discord 🔥

@paulo-raca
Copy link
Contributor Author

Thanks! ❤️

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

Successfully merging this pull request may close these issues.

6 participants