Skip to content

Commit

Permalink
Batch Mutations for creating, updating, and deleting #438 (#653)
Browse files Browse the repository at this point in the history
  • Loading branch information
keithhackbarth authored Dec 8, 2024
1 parent 1c74535 commit 21c99df
Show file tree
Hide file tree
Showing 5 changed files with 156 additions and 73 deletions.
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 @@ -217,7 +217,6 @@ def arguments(self):
argument(
self.argument_name,
self.input_type,
is_list=self.is_list and isinstance(self, DjangoCreateMutation),
),
]

Expand Down Expand Up @@ -274,6 +273,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 @@ -302,13 +305,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],
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 @@ -368,7 +383,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"},
]


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.

0 comments on commit 21c99df

Please sign in to comment.