strawberry-graphql / strawberry-django

Strawberry GraphQL Django extension
MIT License
391 stars 115 forks source link

Allow to return custom types on mutations without a related django model #495

Open Speedy1991 opened 3 months ago

Speedy1991 commented 3 months ago

I just chatted with @patrick91 about returning custom types in combination with strawberry_django

My mutation looks like this:

mutation UpdateBlockRichText {
  updateBlockRichText(data: {id: 5, content: "abc"}) { <<<< using "content" to update, because the db field also is content
    id
    text  <<<<< query field text, but it is hydrated from 'content'
  }
}
import strawberry
import strawberry_django
from strawberry import auto
from strawberry_django import mutations
from mypackage.models import Block

class TextBlockModel(Block):
  ...
  blockmodel_ptr_id = models.ForeignKey(...)
  content = models.TextField(...)

@strawberry.type # <<<< no strawberry_django.type and therefore no model is set
class BlockRichTextType:
  text: str

@strawberry_django.input(TextBlockModel)
class BlockRichTextInput:
    content: auto

@strawberry_django.partial(TextBlockModel)
class BlockRichTextInputPartial:
    blockmodel_ptr_id: auto = strawberry.field(name="id")
    content: auto

@strawberry.type
class Mutation:
    create_block_rich_text: BlockRichTextType = mutations.create(BlockRichTextInput)
    update_block_rich_text: BlockRichTextType = mutations.update(BlockRichTextInputPartial, key_attr="blockmodel_ptr_id")
    delete_block_rich_text: BlockRichTextType = mutations.delete(BlockRichTextInputPartial, key_attr="blockmodel_ptr_id")

But this is leading to exceptions like

 File "replaced\venv\Lib\site-packages\strawberry_django\mutations\fields.py", line 291, in resolver
    assert model is not None
           ^^^^^^^^^^^^^^^^^
AssertionError

because I use a custom strawberry.type and not a strawberry_django.type

The core problem is, that update_block_rich_text: BlockRichTextType is statically typed and there is no way to get control over the return type via instances.

A nice solution would be to define a resolver in the create/update/delete functions taking the database object, e.g.:

def resolver_helper(info: Info, instance: TextBlockModel) -> BlockRichTextType:
  return BlockRichTextType(text=instance.content)

create_block_rich_text: BlockRichTextType = mutations.create(BlockRichTextInput, resolver=resolver_helper)
update_block_rich_text: BlockRichTextType = mutations.update(BlockRichTextInputPartial, key_attr="blockmodel_ptr_id", resolver=resolver_helper)
delete_block_rich_text: BlockRichTextType = mutations.delete(BlockRichTextInputPartial, key_attr="blockmodel_ptr_id")

That would also allow to use inline lambdas:

create_block_rich_text: BlockRichTextType = mutations.create(BlockRichTextInput, resolver=lambda instance: BlockRichTextType(text=instance.content))
update_block_rich_text: BlockRichTextType = mutations.update(BlockRichTextInputPartial, key_attr="blockmodel_ptr_id", resolver=lambda instance: BlockRichTextType(text=instance.content))

Upvote & Fund

Fund with Polar