Skip to content

Commit 57f3aa3

Browse files
authored
Merge pull request #1190 from graphql-python/update-dataloaderdocs
Update Dataloader docs
2 parents 80e3498 + 8e1c3d3 commit 57f3aa3

File tree

3 files changed

+98
-18
lines changed

3 files changed

+98
-18
lines changed

docs/execution/dataloader.rst

+18-18
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ Dataloader
44
DataLoader is a generic utility to be used as part of your application's
55
data fetching layer to provide a simplified and consistent API over
66
various remote data sources such as databases or web services via batching
7-
and caching.
7+
and caching. It is provided by a separate package `aiodataloader <https://pypi.org/project/aiodataloader/>`.
88

99

1010
Batching
@@ -15,31 +15,31 @@ Create loaders by providing a batch loading function.
1515

1616
.. code:: python
1717
18-
from promise import Promise
19-
from promise.dataloader import DataLoader
18+
from aiodataloader import DataLoader
2019
2120
class UserLoader(DataLoader):
22-
def batch_load_fn(self, keys):
23-
# Here we return a promise that will result on the
24-
# corresponding user for each key in keys
25-
return Promise.resolve([get_user(id=key) for key in keys])
21+
async def batch_load_fn(self, keys):
22+
# Here we call a function to return a user for each key in keys
23+
return [get_user(id=key) for key in keys]
2624
2725
28-
A batch loading function accepts a list of keys, and returns a ``Promise``
29-
which resolves to a list of ``values``.
26+
A batch loading async function accepts a list of keys, and returns a list of ``values``.
27+
3028

3129
``DataLoader`` will coalesce all individual loads which occur within a
32-
single frame of execution (executed once the wrapping promise is resolved)
30+
single frame of execution (executed once the wrapping event loop is resolved)
3331
and then call your batch function with all requested keys.
3432

3533

3634
.. code:: python
3735
3836
user_loader = UserLoader()
3937
40-
user_loader.load(1).then(lambda user: user_loader.load(user.best_friend_id))
38+
user1 = await user_loader.load(1)
39+
user1_best_friend = await user_loader.load(user1.best_friend_id))
4140
42-
user_loader.load(2).then(lambda user: user_loader.load(user.best_friend_id))
41+
user2 = await user_loader.load(2)
42+
user2_best_friend = await user_loader.load(user2.best_friend_id))
4343
4444
4545
A naive application may have issued *four* round-trips to a backend for the
@@ -53,9 +53,9 @@ make sure that you then order the query result for the results to match the keys
5353
.. code:: python
5454
5555
class UserLoader(DataLoader):
56-
def batch_load_fn(self, keys):
56+
async def batch_load_fn(self, keys):
5757
users = {user.id: user for user in User.objects.filter(id__in=keys)}
58-
return Promise.resolve([users.get(user_id) for user_id in keys])
58+
return [users.get(user_id) for user_id in keys]
5959
6060
6161
``DataLoader`` allows you to decouple unrelated parts of your application without
@@ -110,8 +110,8 @@ leaner code and at most 4 database requests, and possibly fewer if there are cac
110110
best_friend = graphene.Field(lambda: User)
111111
friends = graphene.List(lambda: User)
112112
113-
def resolve_best_friend(root, info):
114-
return user_loader.load(root.best_friend_id)
113+
async def resolve_best_friend(root, info):
114+
return await user_loader.load(root.best_friend_id)
115115
116-
def resolve_friends(root, info):
117-
return user_loader.load_many(root.friend_ids)
116+
async def resolve_friends(root, info):
117+
return await user_loader.load_many(root.friend_ids)

setup.py

+1
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@ def run_tests(self):
5353
"snapshottest>=0.6,<1",
5454
"coveralls>=3.3,<4",
5555
"promise>=2.3,<3",
56+
"aiodataloader<1",
5657
"mock>=4,<5",
5758
"pytz==2022.1",
5859
"iso8601>=1,<2",

tests_asyncio/test_dataloader.py

+79
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
from collections import namedtuple
2+
from unittest.mock import Mock
3+
from pytest import mark
4+
from aiodataloader import DataLoader
5+
6+
from graphene import ObjectType, String, Schema, Field, List
7+
8+
9+
CHARACTERS = {
10+
"1": {"name": "Luke Skywalker", "sibling": "3"},
11+
"2": {"name": "Darth Vader", "sibling": None},
12+
"3": {"name": "Leia Organa", "sibling": "1"},
13+
}
14+
15+
16+
get_character = Mock(side_effect=lambda character_id: CHARACTERS[character_id])
17+
18+
19+
class CharacterType(ObjectType):
20+
name = String()
21+
sibling = Field(lambda: CharacterType)
22+
23+
async def resolve_sibling(character, info):
24+
if character["sibling"]:
25+
return await info.context.character_loader.load(character["sibling"])
26+
return None
27+
28+
29+
class Query(ObjectType):
30+
skywalker_family = List(CharacterType)
31+
32+
async def resolve_skywalker_family(_, info):
33+
return await info.context.character_loader.load_many(["1", "2", "3"])
34+
35+
36+
mock_batch_load_fn = Mock(
37+
side_effect=lambda character_ids: [get_character(id) for id in character_ids]
38+
)
39+
40+
41+
class CharacterLoader(DataLoader):
42+
async def batch_load_fn(self, character_ids):
43+
return mock_batch_load_fn(character_ids)
44+
45+
46+
Context = namedtuple("Context", "character_loader")
47+
48+
49+
@mark.asyncio
50+
async def test_basic_dataloader():
51+
schema = Schema(query=Query)
52+
53+
character_loader = CharacterLoader()
54+
context = Context(character_loader=character_loader)
55+
56+
query = """
57+
{
58+
skywalkerFamily {
59+
name
60+
sibling {
61+
name
62+
}
63+
}
64+
}
65+
"""
66+
67+
result = await schema.execute_async(query, context=context)
68+
69+
assert not result.errors
70+
assert result.data == {
71+
"skywalkerFamily": [
72+
{"name": "Luke Skywalker", "sibling": {"name": "Leia Organa"}},
73+
{"name": "Darth Vader", "sibling": None},
74+
{"name": "Leia Organa", "sibling": {"name": "Luke Skywalker"}},
75+
]
76+
}
77+
78+
assert mock_batch_load_fn.call_count == 1
79+
assert get_character.call_count == 3

0 commit comments

Comments
 (0)