SerializerMutation return types are incompatible with the relay updater api
Nobody has claimed this yet.
Assessment
- Difficulty
- 5/5
- Estimated time
- Over a week
- Newbie friendliness
- 35/100
- Issue type
- Feature
- Clarity
- Mostly clear
- Activity status
- Stale
- Domain
- api, backend, backend-api-design
Research direction
Start by reading SerializerMutation's init_subclass_with__meta and perform_mutate implementations, then compare their metadata and registry handling with DjangoObjectType. Review the Relay mutation response requirements described in the issue and decide whether the node-based return shape should be breaking or opt-in. Done means an agreed implementation has coverage for the generated mutation payload and Relay store updates.
Written by the indexing model from the issue text.
Description
Is your feature request related to a problem? Please describe.
In relay, mutations use a store updater that has the ability to update automatically the store, without the need to write an updater function, if the mutation object return type is provided in a shape undersood by relay. The current implementation of the SerializerMutation is incompatible with this api.
In particular, reading the latest useMutation relay hook docs (additional explanation here), I saw If updater is not provided, by default, Relay will know to automatically update the fields on the records referenced in the mutation response;. The mutation default return type automatically generated (MyMutationPayload)is not an object recognized by relay for an automatic update of the store.
Describe the solution you'd like
Provided that the SerializerMutation already implements the clientMutationId api,
the cleanest would be simply to change the in SerializerMutation the mutation return type by doing the following : first providing a registry to the __init_subclass_with__meta__ and reading from it in the _meta initialization, like this is done in the DjangoObjectType. The registry, is then used to fetch the Node corresponding to the model_class also available in the _meta defintion to create the return type. Something like :
if not _meta:
_meta = RelaySerializerMutationOptions(cls)
_meta.lookup_field = lookup_field
_meta.model_operations = model_operations
_meta.serializer_class = serializer_class
_meta.model_class = model_class
if registry:
_meta.registry = registry
node_type = registry.get_type_for_model(model_class)
_meta.node_type = node_type
new_output_fields = OrderedDict()
new_output_fields["instance"] = graphene.Field(
node_type, description="The created/updated instance."
)
_meta.fields = yank_fields_from_attrs(new_output_fields, _as=Field)
Also, this would require the following change in perform_mutate method.
@classmethod
def perform_mutate(cls, serializer, info):
obj = serializer.save()
kwargs = {}
for f, field in serializer.fields.items():
if not field.write_only:
if isinstance(field, serializers.SerializerMethodField):
kwargs[f] = field.to_representation(obj)
else:
kwargs[f] = field.get_attribute(obj)
if cls._meta.registry:
node_type = cls._meta.node_type
kwargs["id"] = to_global_id(node_type.__name__, obj.pk)
instance = node_type(**kwargs)
# without it it breaks (NodeType has no attribute pk. ).
instance.pk = obj.pk
return cls(errors=None, instance=instance)
return cls(errors=None, **kwargs)
The code above is working.
This solution however, implies a breaking change since we would not be spreading anymore the model instance alongside the clientMutationId, but in a separate relay node (instance in the snippet above).
In the two snippets, the if registry: tests are for rapid prototyping only, I would imagine an implementation where this is the default behaviour.
Describe alternatives you've considered
Other solutions would be less elegant, one would be to create a children class of SerializerMutation that implements this behaviour, this would not have the downside of being a breaking change rather than an opt in. However, it does also require passing the register to the SerializerMutation, which also needs to be changed to receive it as a param, but does not uses it itself.
Another one I considered early was to simply change the ID returned in the MutationPayload to a globalId. I managed to do the change, and I got an interesting result with useMutation. Instead of the store of relay not updating, it would update to a blank item - even if the graphql mutation payload received contained the data. This means that the automatic store updater mentioned in the introduction does indeed require a globalId as mentionned in the introduction, but also the __typename to be matching.
Additional context
Thank you for reading this ! I would enjoy reading your thoughts if you have any. Implementing this changewould ensure greater compatibility with the relay api with the downside of being a breaking change. I would be happy to provide a PR for consideration if the change appears relevant.
- Dominant language
- Python
- Stars
- 4.4k
- Forks
- 761
- PR merge metrics
- No merged PRs in 30d
Getting set up
- No Dockerfile or Docker Compose file
- No pull request template
- Read the contributing guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
More from graphql-python/graphene-django
-
Difficulty 5/5 Over a week Newbie friendliness 25/100
graphql-python/graphene-django#1561 · 1 comment ·
-
🐛bug
Difficulty 4/5 3-5 days Newbie friendliness 45/100
graphql-python/graphene-django#1558 · 1 comment ·
-
🐛bug
Difficulty 4/5 3-5 days Newbie friendliness 35/100
graphql-python/graphene-django#1551 · 1 comment ·
-
🐛bug
Difficulty 1/5 Under an hour Newbie friendliness 38/100
graphql-python/graphene-django#1547 · 1 comment ·
-
🐛bug
Difficulty 3/5 1-2 days Newbie friendliness 35/100
graphql-python/graphene-django#1542 · 3 comments ·
All issues in graphql-python/graphene-django
Similar issues
-
bug
Difficulty 2/5 1-3 hours Newbie friendliness 72/100
awslabs/visual-asset-management-system#414 ·
Maintainers usually reply within 1 day
-
bug v1 v2
Difficulty 2/5 1-3 hours Newbie friendliness 75/100
modelcontextprotocol/python-sdk#3670 · 1 comment ·
Maintainers usually reply within 1 day
-
Difficulty 2/5 1-3 hours Newbie friendliness 82/100
aicell-lab/bioengine#232 ·
Maintainers usually reply within 1 day
-
Difficulty 2/5 1-3 hours Newbie friendliness 74/100
modelscope/evalscope#1836 ·
Maintainers usually reply within 1 day
-
Difficulty 2/5 1-3 hours Newbie friendliness 72/100