-
Notifications
You must be signed in to change notification settings - Fork 829
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Abstract thenables (promise, coroutine) out of relay Connections and …
…Mutations
- Loading branch information
1 parent
5777d85
commit dd973aa
Showing
9 changed files
with
305 additions
and
31 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,39 @@ | ||
""" | ||
This file is used mainly as a bridge for thenable abstractions. | ||
This includes: | ||
- Promises | ||
- Asyncio Coroutines | ||
""" | ||
|
||
try: | ||
from promise import Promise, is_thenable | ||
except ImportError: | ||
|
||
def is_thenable(obj): | ||
return False | ||
|
||
|
||
try: | ||
from inspect import isawaitable | ||
from .thenables_asyncio import await_and_execute | ||
except ImportError: | ||
|
||
def isawaitable(obj): | ||
return False | ||
|
||
|
||
def maybe_thenable(obj, on_resolve): | ||
""" | ||
Execute a on_resolve function once the thenable is resolved, | ||
returning the same type of object inputed. | ||
If the object is not thenable, it should return on_resolve(obj) | ||
""" | ||
if isawaitable(obj): | ||
return await_and_execute(obj, on_resolve) | ||
|
||
if is_thenable(obj): | ||
return Promise.resolve(obj).then(on_resolve) | ||
|
||
# If it's not awaitable not a Promise, return | ||
# the function executed over the object | ||
return on_resolve(obj) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
def await_and_execute(obj, on_resolve): | ||
async def build_resolve_async(): | ||
return on_resolve(await obj) | ||
|
||
return build_resolve_async() | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,128 @@ | ||
import pytest | ||
|
||
from collections import OrderedDict | ||
from graphql.execution.executors.asyncio import AsyncioExecutor | ||
|
||
from graphql_relay.utils import base64 | ||
|
||
from graphene.types import ObjectType, Schema, String | ||
from graphene.relay.connection import Connection, ConnectionField, PageInfo | ||
from graphene.relay.node import Node | ||
|
||
letter_chars = ["A", "B", "C", "D", "E"] | ||
|
||
|
||
class Letter(ObjectType): | ||
class Meta: | ||
interfaces = (Node,) | ||
|
||
letter = String() | ||
|
||
|
||
class LetterConnection(Connection): | ||
class Meta: | ||
node = Letter | ||
|
||
|
||
class Query(ObjectType): | ||
letters = ConnectionField(LetterConnection) | ||
connection_letters = ConnectionField(LetterConnection) | ||
promise_letters = ConnectionField(LetterConnection) | ||
|
||
node = Node.Field() | ||
|
||
def resolve_letters(self, info, **args): | ||
return list(letters.values()) | ||
|
||
async def resolve_promise_letters(self, info, **args): | ||
return list(letters.values()) | ||
|
||
def resolve_connection_letters(self, info, **args): | ||
return LetterConnection( | ||
page_info=PageInfo(has_next_page=True, has_previous_page=False), | ||
edges=[ | ||
LetterConnection.Edge(node=Letter(id=0, letter="A"), cursor="a-cursor") | ||
], | ||
) | ||
|
||
|
||
schema = Schema(Query) | ||
|
||
letters = OrderedDict() | ||
for i, letter in enumerate(letter_chars): | ||
letters[letter] = Letter(id=i, letter=letter) | ||
|
||
|
||
def edges(selected_letters): | ||
return [ | ||
{ | ||
"node": {"id": base64("Letter:%s" % l.id), "letter": l.letter}, | ||
"cursor": base64("arrayconnection:%s" % l.id), | ||
} | ||
for l in [letters[i] for i in selected_letters] | ||
] | ||
|
||
|
||
def cursor_for(ltr): | ||
letter = letters[ltr] | ||
return base64("arrayconnection:%s" % letter.id) | ||
|
||
|
||
def execute(args=""): | ||
if args: | ||
args = "(" + args + ")" | ||
|
||
return schema.execute( | ||
""" | ||
{ | ||
letters%s { | ||
edges { | ||
node { | ||
id | ||
letter | ||
} | ||
cursor | ||
} | ||
pageInfo { | ||
hasPreviousPage | ||
hasNextPage | ||
startCursor | ||
endCursor | ||
} | ||
} | ||
} | ||
""" | ||
% args | ||
) | ||
|
||
|
||
@pytest.mark.asyncio | ||
async def test_connection_promise(): | ||
result = await schema.execute( | ||
""" | ||
{ | ||
promiseLetters(first:1) { | ||
edges { | ||
node { | ||
id | ||
letter | ||
} | ||
} | ||
pageInfo { | ||
hasPreviousPage | ||
hasNextPage | ||
} | ||
} | ||
} | ||
""", | ||
executor=AsyncioExecutor(), | ||
return_promise=True, | ||
) | ||
|
||
assert not result.errors | ||
assert result.data == { | ||
"promiseLetters": { | ||
"edges": [{"node": {"id": "TGV0dGVyOjA=", "letter": "A"}}], | ||
"pageInfo": {"hasPreviousPage": False, "hasNextPage": True}, | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,100 @@ | ||
import pytest | ||
from graphql.execution.executors.asyncio import AsyncioExecutor | ||
|
||
from graphene.types import ( | ||
ID, | ||
Argument, | ||
Field, | ||
InputField, | ||
InputObjectType, | ||
NonNull, | ||
ObjectType, | ||
Schema, | ||
) | ||
from graphene.types.scalars import String | ||
from graphene.relay.mutation import ClientIDMutation | ||
|
||
|
||
class SharedFields(object): | ||
shared = String() | ||
|
||
|
||
class MyNode(ObjectType): | ||
# class Meta: | ||
# interfaces = (Node, ) | ||
id = ID() | ||
name = String() | ||
|
||
|
||
class SaySomethingAsync(ClientIDMutation): | ||
class Input: | ||
what = String() | ||
|
||
phrase = String() | ||
|
||
@staticmethod | ||
async def mutate_and_get_payload(self, info, what, client_mutation_id=None): | ||
return SaySomethingAsync(phrase=str(what)) | ||
|
||
|
||
# MyEdge = MyNode.Connection.Edge | ||
class MyEdge(ObjectType): | ||
node = Field(MyNode) | ||
cursor = String() | ||
|
||
|
||
class OtherMutation(ClientIDMutation): | ||
class Input(SharedFields): | ||
additional_field = String() | ||
|
||
name = String() | ||
my_node_edge = Field(MyEdge) | ||
|
||
@staticmethod | ||
def mutate_and_get_payload( | ||
self, info, shared="", additional_field="", client_mutation_id=None | ||
): | ||
edge_type = MyEdge | ||
return OtherMutation( | ||
name=shared + additional_field, | ||
my_node_edge=edge_type(cursor="1", node=MyNode(name="name")), | ||
) | ||
|
||
|
||
class RootQuery(ObjectType): | ||
something = String() | ||
|
||
|
||
class Mutation(ObjectType): | ||
say_promise = SaySomethingAsync.Field() | ||
other = OtherMutation.Field() | ||
|
||
|
||
schema = Schema(query=RootQuery, mutation=Mutation) | ||
|
||
|
||
@pytest.mark.asyncio | ||
async def test_node_query_promise(): | ||
executed = await schema.execute( | ||
'mutation a { sayPromise(input: {what:"hello", clientMutationId:"1"}) { phrase } }', | ||
executor=AsyncioExecutor(), | ||
return_promise=True, | ||
) | ||
assert not executed.errors | ||
assert executed.data == {"sayPromise": {"phrase": "hello"}} | ||
|
||
|
||
@pytest.mark.asyncio | ||
async def test_edge_query(): | ||
executed = await schema.execute( | ||
'mutation a { other(input: {clientMutationId:"1"}) { clientMutationId, myNodeEdge { cursor node { name }} } }', | ||
executor=AsyncioExecutor(), | ||
return_promise=True, | ||
) | ||
assert not executed.errors | ||
assert dict(executed.data) == { | ||
"other": { | ||
"clientMutationId": "1", | ||
"myNodeEdge": {"cursor": "1", "node": {"name": "name"}}, | ||
} | ||
} |
Oops, something went wrong.