graphql-python / graphql-python/graphql-core

Discussion: decoupling async data loading from async graph resolution

Đang mở
#171 0 bình luận 0 reaction 0 người được giao Xem trên GitHub
Ngôn ngữ chính
Python
Star
531
Fork
146
Chỉ số merge pull request
Không có pull request nào được merge trong 30 ngày

Mô tả

TLDR: I would like graph-core to tell me what "leaf" nodes from my query need returned. Allow me to fetch the data through whatever method I deem efficient. Then I would like graph-core to take care of returning the data. The current asyncio implementation, seems to fundamentally not work for this.

I've been digging in deep on a graphql-core build recently and have stumbled across this interesting problem. If I've missed a key feature of the library here, than please point it out.

To me, the ideal way to use the core library is to:
1. Use graph-core to decide __what__ data to retrieve.
2. Use a separate data loading engine to __read__ the data.
3. Use graph-core to __return__ the data.

Its interesting to see where this problem fits in as either a graphe-core-3 issue, needing a feature, or a graphql issue. The essential catch is this, it's very hard to determine __when__ the graph-core resolution has finished deciding __what__ leaves from the graph need fetched. Here's an example to illustrate the point.

```
##########################################################
## TEST GRAPHE SCHEMA BASED ON ASYNCIO ######################
##########################################################
from graphql import (
GraphQLBoolean, graphql, GraphQLSchema, GraphQLObjectType, GraphQLField, GraphQLString)
import logging
import asyncio

_logger = logging.getLogger('GrapheneDeferralTest')
_logger.setLevel('DEBUG')

query = """
{
ioHMIControls {
EStopHMI,
JogHMI,
}
}
"""

async def resolve_EStopHMI(parent, info):
_id = '_EStopHMI_id'
info.context['node_ids'][_id] = None
await info.context['awaitable']
return info.context['node_ids'][_id]
EStopHMI = GraphQLField(
GraphQLBoolean,
resolve=resolve_EStopHMI
)

async def resolve_JogHMI(parent, info):
_id = '_JogHMI_id'
info.context['node_ids'][_id] = None
await info.context['awaitable']
return info.context['node_ids'][_id]
JogHMI = GraphQLField(
GraphQLBoolean,
resolve=resolve_EStopHMI
)

def resolve_ioHMIControls(parent, info):
return ioHMIControls
ioHMIControls = GraphQLObjectType(
name='ioHMIControls',
fields={
'EStopHMI': EStopHMI,
'JogHMI':JogHMI,
}
)

def resolve_GlobalVars(parent, info):
return GlobalVars
GlobalVars = GraphQLObjectType(
name='GlobalVars',
fields={
'ioHMIControls': GraphQLField(ioHMIControls, resolve=resolve_ioHMIControls)
}
)

async def simulate_fetch_data(_ids):
print(_ids)
await asyncio.sleep(1)
return {k:True for k in _ids.keys()}

async def main():
# Objective:
# 1. Have graph determine what data I need by partially resolving
# 2. Pause graph resolution.
# 3. Collect data into a `data_loader` object.
# 4. Retrieve data via `data_loader` object.
# 5. Resume graph resolution with loaded data.

# 3. collect ids of data fields into a dict
_ids = {}

#2. pause graph resolution by awaitn a future
future = asyncio.Future()
context = {
'node_ids': _ids,
'awaitable': future,
}
schema = GraphQLSchema(query=GlobalVars)

# 1. Determine WHAT data to return
resove_graph_task = asyncio.create_task(graphql(schema, query, context_value=context))

# ?
# There is no way to detect that resolve_graph_task
# has finished fillin _ids dict with id values.

# 4. Fetch the data
fetch_data_task = asyncio.create_task(simulate_fetch_data(_ids))

# ?
# This await doesn't work in this order or any order
# becaus of the interdependancy of both tasks, coupled with
# the mechanics of asyncio.
await fetch_data_task

# 5. Resume graph resolution with retrieved data.
future.set_result(0)

# ?
# return the data from the graph, as a graph result.
# problem, is that the data is not there due to
# interdependancy between await tasks.
result = await resove_graph_task
print(result)

if __name__ == '__main__':
asyncio.run(main())
```

Results
```
{}
ExecutionResult(data={'ioHMIControls': {'EStopHMI': None, 'JogHMI': None}}, errors=None)
```

The example is a little long, but I wanted it to be sufficiently complex. The gist is that there is no way in the current asyncio implementation to determine that: **all resolvers have been reached.**

Looking at the implementations we could use some advanced event systems to manage this, but it would be a bit of work. Another possible solution could be to allow resolvers to return coroutines and put off type checking till those coroutines are themselves resolved. I think, this may be the most elegant method.

Thoughts?

Hướng dẫn đóng góp

Chưa lập chỉ mục được hướng dẫn đóng góp cho kho mã nguồn này

Đánh giá

Issue này chưa được đánh giá.

Nhận issue mới trong hộp thư của bạn

Bản tóm tắt ngắn những issue GitHub phù hợp với người mới.