Skip to content

Commit 629257c

Browse files
committed
Refactor the LocalSchemaTransport into an AsyncTransport
1 parent e7c9fa5 commit 629257c

File tree

5 files changed

+99
-37
lines changed

5 files changed

+99
-37
lines changed

gql/client.py

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
import asyncio
2-
from inspect import isawaitable
32
from typing import Any, AsyncGenerator, Dict, Generator, Optional, Union, cast
43

54
from graphql import (
@@ -116,9 +115,6 @@ def execute(self, document: DocumentNode, *args, **kwargs) -> Dict:
116115

117116
result = self.transport.execute(document, *args, **kwargs)
118117

119-
assert not isawaitable(result), "Transport returned an awaitable result."
120-
result = cast(ExecutionResult, result)
121-
122118
if result.errors:
123119
raise TransportQueryError(str(result.errors[0]))
124120

gql/transport/local_schema.py

Lines changed: 59 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,12 @@
1-
from typing import Awaitable, Union
1+
from inspect import isawaitable
2+
from typing import Any, AsyncGenerator, AsyncIterator, Awaitable, Coroutine, cast
23

3-
from graphql import DocumentNode, ExecutionResult, GraphQLSchema, execute
4+
from graphql import DocumentNode, ExecutionResult, GraphQLSchema, execute, subscribe
45

5-
from gql.transport import Transport
6+
from gql.transport import AsyncTransport
67

78

8-
class LocalSchemaTransport(Transport):
9+
class LocalSchemaTransport(AsyncTransport):
910
"""A transport for executing GraphQL queries against a local schema."""
1011

1112
def __init__(
@@ -17,14 +18,59 @@ def __init__(
1718
"""
1819
self.schema = schema
1920

20-
def execute(
21-
self, document: DocumentNode, *args, **kwargs
22-
) -> Union[ExecutionResult, Awaitable[ExecutionResult]]:
23-
"""Execute the given document against the configured local schema.
21+
async def connect(self):
22+
"""No connection needed on local transport
23+
"""
24+
pass
25+
26+
async def close(self):
27+
"""No close needed on local transport
28+
"""
29+
pass
2430

25-
:param document: GraphQL query as AST Node object.
26-
:param args: Positional options for execute method from graphql-core.
27-
:param kwargs: Keyword options passed to execute method from graphql-core.
28-
:return: ExecutionResult (either as value or awaitable)
31+
async def execute(
32+
self, document: DocumentNode, *args, **kwargs,
33+
) -> ExecutionResult:
34+
"""Execute the provided document AST for on a local GraphQL Schema.
2935
"""
30-
return execute(self.schema, document, *args, **kwargs)
36+
37+
result_or_awaitable = execute(self.schema, document, *args, **kwargs)
38+
39+
execution_result: ExecutionResult
40+
41+
if isawaitable(result_or_awaitable):
42+
result_or_awaitable = cast(Awaitable[ExecutionResult], result_or_awaitable)
43+
execution_result = await result_or_awaitable
44+
else:
45+
result_or_awaitable = cast(ExecutionResult, result_or_awaitable)
46+
execution_result = result_or_awaitable
47+
48+
return execution_result
49+
50+
async def subscribe(
51+
self, document: DocumentNode, *args, **kwargs,
52+
) -> AsyncGenerator[ExecutionResult, None]:
53+
"""Send a query and receive the results using an async generator
54+
55+
The query can be a graphql query, mutation or subscription
56+
57+
The results are sent as an ExecutionResult object
58+
"""
59+
60+
subscribe_result = subscribe(self.schema, document, *args, **kwargs)
61+
62+
if isinstance(subscribe_result, ExecutionResult):
63+
yield ExecutionResult
64+
65+
else:
66+
# if we don't get an ExecutionResult, then we should receive
67+
# a Coroutine returning an AsyncIterator[ExecutionResult]
68+
69+
subscribe_coro = cast(
70+
Coroutine[Any, Any, AsyncIterator[ExecutionResult]], subscribe_result
71+
)
72+
73+
subscribe_generator = await subscribe_coro
74+
75+
async for result in subscribe_generator:
76+
yield result

gql/transport/transport.py

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,17 @@
11
import abc
2-
from typing import Awaitable, Union
32

43
from graphql import DocumentNode, ExecutionResult
54

65

76
class Transport:
87
@abc.abstractmethod
9-
def execute(
10-
self, document: DocumentNode, *args, **kwargs
11-
) -> Union[ExecutionResult, Awaitable[ExecutionResult]]:
8+
def execute(self, document: DocumentNode, *args, **kwargs) -> ExecutionResult:
129
"""Execute GraphQL query.
1310
1411
Execute the provided document AST for either a remote or local GraphQL Schema.
1512
1613
:param document: GraphQL query as AST Node or Document object.
17-
:return: ExecutionResult (either as value or awaitable)
14+
:return: ExecutionResult
1815
"""
1916
raise NotImplementedError(
2017
"Any Transport subclass must implement execute method"
@@ -27,4 +24,4 @@ def close(self):
2724
from it. This is currently used by the RequestsHTTPTransport transport to close
2825
the session's connection pool.
2926
"""
30-
pass
27+
pass # pragma: no cover

tests/starwars/test_subscription.py

Lines changed: 35 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,21 @@
11
import pytest
22
from graphql import subscribe
33

4-
from gql import gql
4+
from gql import Client, gql
55

66
from .fixtures import reviews
77
from .schema import StarWarsSchema
88

9+
subscription_str = """
10+
subscription ListenEpisodeReviews($ep: Episode!) {
11+
reviewAdded(episode: $ep) {
12+
stars,
13+
commentary,
14+
episode
15+
}
16+
}
17+
"""
18+
919

1020
@pytest.mark.asyncio
1121
async def test_subscription_support():
@@ -15,17 +25,8 @@ async def test_subscription_support():
1525
{"stars": 5, "commentary": "This is a great movie!", "episode": 6},
1626
]
1727

18-
subs = gql(
19-
"""
20-
subscription ListenEpisodeReviews($ep: Episode!) {
21-
reviewAdded(episode: $ep) {
22-
stars,
23-
commentary,
24-
episode
25-
}
26-
}
27-
"""
28-
)
28+
subs = gql(subscription_str)
29+
2930
params = {"ep": "JEDI"}
3031
expected = [{**review, "episode": "JEDI"} for review in reviews[6]]
3132

@@ -34,3 +35,25 @@ async def test_subscription_support():
3435
result = [result.data["reviewAdded"] async for result in ai]
3536

3637
assert result == expected
38+
39+
40+
@pytest.mark.asyncio
41+
async def test_subscription_support_using_client():
42+
# reset review data for this test
43+
reviews[6] = [
44+
{"stars": 3, "commentary": "Was expecting more stuff", "episode": 6},
45+
{"stars": 5, "commentary": "This is a great movie!", "episode": 6},
46+
]
47+
48+
subs = gql(subscription_str)
49+
50+
params = {"ep": "JEDI"}
51+
expected = [{**review, "episode": "JEDI"} for review in reviews[6]]
52+
53+
results = []
54+
55+
async with Client(schema=StarWarsSchema) as session:
56+
async for result in session.subscribe(subs, variable_values=params):
57+
results.append(result["reviewAdded"])
58+
59+
assert results == expected

tests/test_client.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -171,6 +171,6 @@ def test_gql():
171171
"""
172172
)
173173

174-
with Client(schema=schema) as client:
175-
result = client.execute(query)
174+
client = Client(schema=schema)
175+
result = client.execute(query)
176176
assert result["user"] is None

0 commit comments

Comments
 (0)