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

Update Dataloader docs [Waiting for Graphene v3 to be live] #1190

Merged
merged 5 commits into from
Aug 13, 2022
Merged
Show file tree
Hide file tree
Changes from 3 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
35 changes: 17 additions & 18 deletions docs/execution/dataloader.rst
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ Dataloader
DataLoader is a generic utility to be used as part of your application's
data fetching layer to provide a simplified and consistent API over
various remote data sources such as databases or web services via batching
and caching.
and caching. It is provided by a seperate package `aiodataloader <https://pypi.org/project/aiodataloader/>`.
erikwrede marked this conversation as resolved.
Show resolved Hide resolved


Batching
Expand All @@ -15,32 +15,31 @@ Create loaders by providing a batch loading function.

.. code:: python

from promise import Promise
from promise.dataloader import DataLoader
from aiodataloader import DataLoader

class UserLoader(DataLoader):
def batch_load_fn(self, keys):
# Here we return a promise that will result on the
# corresponding user for each key in keys
return Promise.resolve([get_user(id=key) for key in keys])
async def batch_load_fn(self, keys):
# Here we call a function to return a user for each key in keys
return [get_user(id=key) for key in keys]


A batch loading function accepts a list of keys, and returns a ``Promise``
which resolves to a list of ``values``.
A batch loading async function accepts a list of keys, and returns a list of ``values``.

Then load individual values from the loader. ``DataLoader`` will coalesce all
individual loads which occur within a single frame of execution (executed once
the wrapping promise is resolved) and then call your batch function with all
the wrapping event loop is resolved) and then call your batch function with all
requested keys.


.. code:: python

user_loader = UserLoader()

user_loader.load(1).then(lambda user: user_loader.load(user.best_friend_id))
user1 = await user_loader.load(1)
user1_best_friend = await user_loader.load(user1.best_friend_id))

user_loader.load(2).then(lambda user: user_loader.load(user.best_friend_id))
user2 = await user_loader.load(2)
user2_best_friend = await user_loader.load(user2.best_friend_id))


A naive application may have issued *four* round-trips to a backend for the
Expand All @@ -54,9 +53,9 @@ make sure that you then order the query result for the results to match the keys
.. code:: python

class UserLoader(DataLoader):
def batch_load_fn(self, keys):
async def batch_load_fn(self, keys):
users = {user.id: user for user in User.objects.filter(id__in=keys)}
return Promise.resolve([users.get(user_id) for user_id in keys])
return [users.get(user_id) for user_id in keys]


``DataLoader`` allows you to decouple unrelated parts of your application without
Expand Down Expand Up @@ -111,8 +110,8 @@ leaner code and at most 4 database requests, and possibly fewer if there are cac
best_friend = graphene.Field(lambda: User)
friends = graphene.List(lambda: User)

def resolve_best_friend(root, info):
return user_loader.load(root.best_friend_id)
async def resolve_best_friend(root, info):
return await user_loader.load(root.best_friend_id)

def resolve_friends(root, info):
return user_loader.load_many(root.friend_ids)
async def resolve_friends(root, info):
return await user_loader.load_many(root.friend_ids)
1 change: 1 addition & 0 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ def run_tests(self):
"mock>=4.0,<5",
"pytz==2019.3",
"iso8601>=0.1,<2",
"aiodataloader<1",
]

dev_requires = ["black==19.10b0", "flake8>=3.7,<4"] + tests_require
Expand Down
79 changes: 79 additions & 0 deletions tests_asyncio/test_dataloader.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
from collections import namedtuple
from unittest.mock import Mock
from pytest import mark
from aiodataloader import DataLoader

from graphene import ObjectType, String, Schema, Field, List


CHARACTERS = {
"1": {"name": "Luke Skywalker", "sibling": "3"},
"2": {"name": "Darth Vader", "sibling": None},
"3": {"name": "Leia Organa", "sibling": "1"},
}


get_character = Mock(side_effect=lambda character_id: CHARACTERS[character_id])


class CharacterType(ObjectType):
name = String()
sibling = Field(lambda: CharacterType)

async def resolve_sibling(character, info):
if character["sibling"]:
return await info.context.character_loader.load(character["sibling"])
return None


class Query(ObjectType):
skywalker_family = List(CharacterType)

async def resolve_skywalker_family(_, info):
return await info.context.character_loader.load_many(["1", "2", "3"])


mock_batch_load_fn = Mock(
side_effect=lambda character_ids: [get_character(id) for id in character_ids]
)


class CharacterLoader(DataLoader):
async def batch_load_fn(self, character_ids):
return mock_batch_load_fn(character_ids)


Context = namedtuple("Context", "character_loader")


@mark.asyncio
async def test_basic_dataloader():
schema = Schema(query=Query)

character_loader = CharacterLoader()
context = Context(character_loader=character_loader)

query = """
{
skywalkerFamily {
name
sibling {
name
}
}
}
"""

result = await schema.execute_async(query, context=context)

assert not result.errors
assert result.data == {
"skywalkerFamily": [
{"name": "Luke Skywalker", "sibling": {"name": "Leia Organa"}},
{"name": "Darth Vader", "sibling": None},
{"name": "Leia Organa", "sibling": {"name": "Luke Skywalker"}},
]
}

assert mock_batch_load_fn.call_count == 1
assert get_character.call_count == 3