Skip to content

Commit 6136f90

Browse files
author
Awais Hussain
committed
Address CR changes
1 parent 33df630 commit 6136f90

File tree

3 files changed

+59
-59
lines changed

3 files changed

+59
-59
lines changed

gql/client.py

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
from graphql.validation import validate
55

66
from .transport.local_schema import LocalSchemaTransport
7-
from .type_adaptor import TypeAdaptor
7+
from .type_adapter import TypeAdapter
88
from .exceptions import GQLServerError, GQLSyntaxError
99

1010
log = logging.getLogger(__name__)
@@ -19,7 +19,10 @@ def __init__(self, retries_count, last_exception):
1919

2020
class Client(object):
2121
def __init__(self, schema=None, introspection=None, type_def=None, transport=None,
22-
fetch_schema_from_transport=False, custom_scalars={}, retries=0):
22+
fetch_schema_from_transport=False, retries=0, custom_types={}):
23+
"""custom_types should be of type Dict[str, Any]
24+
where str is the name of the custom scalar type, and
25+
Any is a class which has a `parse_value()` function"""
2326
assert not(type_def and introspection), 'Cant provide introspection type definition at the same time'
2427
if transport and fetch_schema_from_transport:
2528
assert not schema, 'Cant fetch the schema from transport if is already provided'
@@ -38,7 +41,7 @@ def __init__(self, schema=None, introspection=None, type_def=None, transport=Non
3841
self.introspection = introspection
3942
self.transport = transport
4043
self.retries = retries
41-
self.type_adaptor = TypeAdaptor(schema, custom_scalars) if custom_scalars else None
44+
self.type_adapter = TypeAdapter(schema, custom_types) if custom_types else None
4245

4346
def validate(self, document):
4447
if not self.schema:
@@ -55,8 +58,8 @@ def execute(self, document, *args, **kwargs):
5558
if result.errors:
5659
raise GQLServerError(result.errors[0])
5760

58-
if self.type_adaptor:
59-
result.data = self.type_adaptor.apply(result.data)
61+
if self.type_adapter:
62+
result.data = self.type_adapter.convert_scalars(result.data)
6063

6164
return result.data
6265

gql/type_adaptor.py renamed to gql/type_adapter.py

Lines changed: 26 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
from graphql.type.definition import GraphQLObjectType, GraphQLField, GraphQLScalarType
55

66

7-
class TypeAdaptor(object):
7+
class TypeAdapter(object):
88
"""Substitute custom scalars in a GQL response with their decoded counterparts.
99
1010
GQL custom scalar types are defined on the GQL schema and are used to represent
@@ -16,20 +16,20 @@ class TypeAdaptor(object):
1616
the `_traverse()` function).
1717
1818
Each time we find a field which is a custom scalar (it's type name appears
19-
as a key in self.custom_scalars), we replace the value of that field with the
19+
as a key in self.custom_types), we replace the value of that field with the
2020
decoded value. All of this logic happens in `_substitute()`.
2121
2222
Public Interface:
2323
apply(): pass in a GQL response to replace all instances of custom
2424
scalar strings with their deserialized representation."""
2525

26-
def __init__(self, schema: GraphQLSchema, custom_scalars: Dict[str, Any] = {}) -> None:
26+
def __init__(self, schema: GraphQLSchema, custom_types: Dict[str, Any] = {}) -> None:
2727
""" schema: a graphQL schema in the GraphQLSchema format
28-
custom_scalars: a Dict[str, Any],
28+
custom_types: a Dict[str, Any],
2929
where str is the name of the custom scalar type, and
30-
Any is a class which has a `parse_value()` function"""
30+
Any is a class which has a `parse_value(str)` function"""
3131
self.schema = schema
32-
self.custom_scalars = custom_scalars
32+
self.custom_types = custom_types
3333

3434
def _follow_type_chain(self, node: Any) -> Any:
3535
""" Get the type of the schema node in question.
@@ -61,46 +61,49 @@ def _lookup_scalar_type(self, keys: List[str]) -> Optional[str]:
6161
If keys (e.g. ['film', 'release_date']) points to a scalar type, then
6262
this function returns the name of that type. (e.g. 'DateTime')
6363
64-
If it is not a scalar type (e..g a GraphQLObject or list), then this
64+
If it is not a scalar type (e..g a GraphQLObject), then this
6565
function returns None.
6666
6767
`keys` is a breadcrumb trail telling us where to look in the GraphQL schema.
6868
By default the root level is `schema.query`, if that fails, then we check
6969
`schema.mutation`."""
7070

71-
def iterate(node: Any, lookup: List[str]):
72-
lookup = lookup.copy()
71+
def traverse_schema(node: Any, lookup: List[str]):
7372
if not lookup:
7473
return self._get_scalar_type_name(node)
7574

7675
final_node = self._follow_type_chain(node)
77-
return iterate(final_node.fields[lookup.pop(0)], lookup)
76+
return traverse_schema(final_node.fields[lookup[0]], lookup[1:])
77+
78+
if keys[0] in self.schema.get_query_type().fields:
79+
schema_root = self.schema.get_query_type()
80+
elif keys[0] in self.schema.get_mutation_type().fields:
81+
schema_root = self.schema.get_mutation_type()
82+
else:
83+
return None
7884

7985
try:
80-
return iterate(self.schema.get_query_type(), keys)
86+
return traverse_schema(schema_root, keys)
8187
except (KeyError, AttributeError):
82-
try:
83-
return iterate(self.schema.get_mutation_type(), keys)
84-
except (KeyError, AttributeError):
85-
return None
88+
return None
8689

87-
def _substitute(self, keys: List[str], value: Any) -> Any:
90+
def _get_decoded_scalar_type(self, keys: List[str], value: Any) -> Any:
8891
"""Get the decoded value of the type identified by `keys`.
8992
9093
If the type is not a custom scalar, then return the original value.
9194
9295
If it is a custom scalar, return the deserialized value, as
9396
output by `<CustomScalarType>.parse_value()`"""
9497
scalar_type = self._lookup_scalar_type(keys)
95-
if scalar_type and scalar_type in self.custom_scalars:
96-
return self.custom_scalars[scalar_type].parse_value(value)
98+
if scalar_type and scalar_type in self.custom_types:
99+
return self.custom_types[scalar_type].parse_value(value)
97100
return value
98101

99-
def _traverse(self, response: Dict[str, Any], substitute: Callable) -> Dict[str, Any]:
102+
def convert_scalars(self, response: Dict[str, Any]) -> Dict[str, Any]:
100103
"""Recursively traverse the GQL response
101104
102-
Recursively traverses the GQL response and calls the `substitute`
103-
function on all leaf nodes. The function is called with 2 arguments:
105+
Recursively traverses the GQL response and calls _get_decoded_scalar_type()
106+
for all leaf nodes. The function is called with 2 arguments:
104107
keys: List[str] is a breadcrumb trail telling us where we are in the
105108
response, and therefore, where to look in the GQL Schema.
106109
value: Any is the value at that node in the response
@@ -109,15 +112,9 @@ def _traverse(self, response: Dict[str, Any], substitute: Callable) -> Dict[str,
109112
modified."""
110113
def iterate(node: Any, keys: List[str] = []):
111114
if isinstance(node, dict):
112-
result = {}
113-
for _key, value in node.items():
114-
result[_key] = iterate(value, keys + [_key])
115-
return result
115+
return {_key: iterate(value, keys + [_key]) for _key, value in node.items()}
116116
elif isinstance(node, list):
117117
return [(iterate(item, keys)) for item in node]
118118
else:
119-
return substitute(keys, node)
119+
return self._get_decoded_scalar_type(keys, node)
120120
return iterate(response)
121-
122-
def apply(self, response: Dict[str, Any]) -> Dict[str, Any]:
123-
return self._traverse(response, self._substitute)

tests/test_type_adaptor.py renamed to tests/test_type_adapter.py

Lines changed: 25 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
locally.
66
"""
77
import copy
8-
from gql.type_adaptor import TypeAdaptor
8+
from gql.type_adapter import TypeAdapter
99
import pytest
1010
import requests
1111
from gql import Client
@@ -16,7 +16,7 @@ class Capitalize():
1616
def parse_value(self, value: str):
1717
return value.upper();
1818

19-
@pytest.fixture
19+
@pytest.fixture(scope='session')
2020
def schema():
2121
request = requests.get('http://swapi.graphene-python.org/graphql',
2222
headers={
@@ -36,37 +36,37 @@ def schema():
3636
return client.schema
3737

3838
def test_scalar_type_name_for_scalar_field_returns_name(schema):
39-
type_adaptor = TypeAdaptor(schema)
39+
type_adapter = TypeAdapter(schema)
4040
schema_obj = schema.get_query_type().fields['film']
4141

42-
assert type_adaptor ._get_scalar_type_name(schema_obj.type.fields['releaseDate']) == 'DateTime'
42+
assert type_adapter ._get_scalar_type_name(schema_obj.type.fields['releaseDate']) == 'DateTime'
4343

4444

4545
def test_scalar_type_name_for_non_scalar_field_returns_none(schema):
46-
type_adaptor = TypeAdaptor(schema)
46+
type_adapter = TypeAdapter(schema)
4747
schema_obj = schema.get_query_type().fields['film']
4848

49-
assert type_adaptor._get_scalar_type_name(schema_obj.type.fields['species']) is None
49+
assert type_adapter._get_scalar_type_name(schema_obj.type.fields['species']) is None
5050

5151
def test_lookup_scalar_type(schema):
52-
type_adaptor = TypeAdaptor(schema)
52+
type_adapter = TypeAdapter(schema)
5353

54-
assert type_adaptor._lookup_scalar_type(["film"]) is None
55-
assert type_adaptor._lookup_scalar_type(["film", "releaseDate"]) == 'DateTime'
56-
assert type_adaptor._lookup_scalar_type(["film", "species"]) is None
54+
assert type_adapter._lookup_scalar_type(["film"]) is None
55+
assert type_adapter._lookup_scalar_type(["film", "releaseDate"]) == 'DateTime'
56+
assert type_adapter._lookup_scalar_type(["film", "species"]) is None
5757

5858
def test_lookup_scalar_type_in_mutation(schema):
59-
type_adaptor = TypeAdaptor(schema)
59+
type_adapter = TypeAdapter(schema)
6060

61-
assert type_adaptor._lookup_scalar_type(["createHero"]) is None
62-
assert type_adaptor._lookup_scalar_type(["createHero", "hero"]) is None
63-
assert type_adaptor._lookup_scalar_type(["createHero", "ok"]) == 'Boolean'
61+
assert type_adapter._lookup_scalar_type(["createHero"]) is None
62+
assert type_adapter._lookup_scalar_type(["createHero", "hero"]) is None
63+
assert type_adapter._lookup_scalar_type(["createHero", "ok"]) == 'Boolean'
6464

6565
def test_parse_response(schema):
66-
custom_scalars = {
66+
custom_types = {
6767
'DateTime': Capitalize
6868
}
69-
type_adaptor = TypeAdaptor(schema, custom_scalars)
69+
type_adapter = TypeAdapter(schema, custom_types)
7070

7171
response = {
7272
'film': {
@@ -82,14 +82,14 @@ def test_parse_response(schema):
8282
}
8383
}
8484

85-
assert type_adaptor.apply(response) == expected
85+
assert type_adapter.convert_scalars(response) == expected
8686
assert response['film']['releaseDate'] == 'some_datetime' # ensure original response is not changed
8787

8888
def test_parse_response_containing_list(schema):
89-
custom_scalars = {
89+
custom_types = {
9090
'DateTime': Capitalize
9191
}
92-
type_adaptor = TypeAdaptor(schema, custom_scalars)
92+
type_adapter = TypeAdapter(schema, custom_types)
9393

9494
response = {
9595
"allFilms": {
@@ -108,11 +108,11 @@ def test_parse_response_containing_list(schema):
108108
}
109109

110110
expected = copy.deepcopy(response)
111-
expected['allFilms']['edges'][0]['node']['releaseDate'] = "SOME_DATETIME"
112-
expected['allFilms']['edges'][1]['node']['releaseDate'] = "SOME_OTHER_DATETIME"
113-
114-
result = type_adaptor.apply(response)
111+
expected['allFilms']['edges'][0]['node']['releaseDate'] = 'SOME_DATETIME'
112+
expected['allFilms']['edges'][1]['node']['releaseDate'] = 'SOME_OTHER_DATETIME'
115113

114+
result = type_adapter.convert_scalars(response)
116115
assert result == expected
117-
expected['allFilms']['edges'][0]['node']['releaseDate'] = "some_datetime"
118-
expected['allFilms']['edges'][1]['node']['releaseDate'] = "some_other_datetime"
116+
117+
assert response['allFilms']['edges'][0]['node']['releaseDate'] == 'some_datetime' # ensure original response is not changed
118+
assert response['allFilms']['edges'][1]['node']['releaseDate'] == 'some_other_datetime' # ensure original response is not changed

0 commit comments

Comments
 (0)