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

Batch Mutations for creating, updating, and deleting #438 #653

Merged
merged 3 commits into from
Dec 8, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 17 additions & 0 deletions docs/guide/mutations.md
Original file line number Diff line number Diff line change
Expand Up @@ -184,3 +184,20 @@ class Mutation:

schema = strawberry.Schema(mutation=Mutation)
```

## Batching

If you need to make multiple creates, updates, or deletes as part of one atomic mutation you can use batching. Batching has a similar syntax except that the mutations take and return a list.

```python title="schema.py"
import strawberry
from strawberry_django import mutations

@strawberry.type
class Mutation:
createFruits: list[Fruit] = mutations.create(list[FruitPartialInput])
updateFruits: list[Fruit] = mutations.update(list[FruitPartialInput])
deleteFruits: list[Fruit] = mutations.delete(list[FruitPartialInput])

schema = strawberry.Schema(mutation=Mutation)
```
25 changes: 20 additions & 5 deletions strawberry_django/mutations/fields.py
Original file line number Diff line number Diff line change
Expand Up @@ -215,7 +215,6 @@ def arguments(self):
argument(
self.argument_name,
self.input_type,
is_list=self.is_list and isinstance(self, DjangoCreateMutation),
),
]

Expand Down Expand Up @@ -272,6 +271,10 @@ def create(self, data: dict[str, Any], *, info: Info):
)


def get_vdata(data: Any) -> dict[str, Any]:
return vars(data).copy() if data is not None else {}


def get_pk(
data: dict[str, Any],
*,
Expand Down Expand Up @@ -300,13 +303,25 @@ def resolver(
) -> Any:
assert info is not None

data: list[Any] | Any = kwargs.get(self.argument_name)

if isinstance(data, list):
return [self.instance_level_update(info, kwargs, d) for d in data]

return self.instance_level_update(info, kwargs, data)

def instance_level_update(
self,
info: Info,
kwargs: dict[str, Any],
keithhackbarth marked this conversation as resolved.
Show resolved Hide resolved
data: Any,
) -> Any:
model = self.django_model
assert model is not None

data: Any = kwargs.get(self.argument_name)
vdata = vars(data).copy() if data is not None else {}

vdata = get_vdata(data)
pk = get_pk(vdata, key_attr=self.key_attr)

if pk not in (None, UNSET): # noqa: PLR6201
instance = get_with_perms(
pk,
Expand Down Expand Up @@ -366,7 +381,7 @@ def resolver(
assert model is not None

data: Any = kwargs.get(self.argument_name)
vdata = vars(data).copy() if data is not None else {}
vdata = get_vdata(data)

pk = get_pk(vdata, key_attr=self.key_attr)
if pk not in (None, UNSET): # noqa: PLR6201
Expand Down
3 changes: 2 additions & 1 deletion tests/mutations/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,8 @@ class FruitFilter:
@strawberry.type
class Mutation:
create_fruit: Fruit = mutations.create(FruitInput)
create_fruits: list[Fruit] = mutations.create(FruitInput)
create_fruits: list[Fruit] = mutations.create(list[FruitInput])
patch_fruits: list[Fruit] = mutations.update(list[FruitPartialInput], key_attr="id")
update_fruits: list[Fruit] = mutations.update(
FruitPartialInput, filters=FruitFilter, key_attr="id"
)
Expand Down
117 changes: 117 additions & 0 deletions tests/mutations/test_batch_mutations.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
"""Tests for batched mutations.

Batched mutations are mutations that mutate multiple objects at once.
Mutations with a filter function or accept a list of objects that return a list.
"""


def test_batch_create(mutation, fruits):
result = mutation(
"""
mutation {
fruits: createFruits(
data: [{ name: "banana" }, { name: "cherry" }]
) {
id
name
}
}
"""
)
assert not result.errors
assert result.data["fruits"] == [
{"id": "4", "name": "banana"},
{"id": "5", "name": "cherry"},
]
keithhackbarth marked this conversation as resolved.
Show resolved Hide resolved
keithhackbarth marked this conversation as resolved.
Show resolved Hide resolved


def test_batch_delete_with_filter(mutation, fruits):
result = mutation(
"""
mutation($ids: [ID!]) {
fruits: deleteFruits(
filters: {id: {inList: $ids}}
) {
id
name
}
}
""",
{"ids": ["2"]},
)
assert not result.errors
assert result.data["fruits"] == [
{"id": "2", "name": "raspberry"},
]


def test_batch_delete_with_filter_empty_list(mutation, fruits):
result = mutation(
"""
{
fruits: deleteFruits(
filters: {id: {inList: []}}
) {
id
name
}
}
"""
)
assert not result.errors


def test_batch_update_with_filter(mutation, fruits):
result = mutation(
"""
{
fruits: updateFruits(
data: { name: "orange" }
filters: {id: {inList: [1]}}
) {
id
name
}
}
"""
)
assert not result.errors
assert result.data["fruits"] == [
{"id": "1", "name": "orange"},
]


def test_batch_update_with_filter_empty_list(mutation, fruits):
result = mutation(
"""
{
fruits: updateFruits(
data: { name: "orange" }
filters: {id: {inList: []}}
) {
id
name
}
}
"""
)
assert not result.errors


def test_batch_patch(mutation, fruits):
result = mutation(
"""
{
fruits: patchFruits(
data: [{ id: 2, name: "orange" }]
) {
id
name
}
}
"""
)
assert not result.errors
assert result.data["fruits"] == [
{"id": "2", "name": "orange"},
]
67 changes: 0 additions & 67 deletions tests/mutations/test_filter_mutations.py

This file was deleted.

Loading