Skip to content

Commit 3b2c396

Browse files
authored
Fix gql to work with graphql-core 3.3.0rc0 (#597)
* Add kind to introspection query root types * Invert order or kind and name in introspection query to follow new graphql-core version * Restrict aiohttp to <= 3.13.2 for now to avoid unrelated tests break
1 parent 4300ac0 commit 3b2c396

9 files changed

Lines changed: 105 additions & 44 deletions

File tree

‎gql/dsl.py‎

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -416,7 +416,7 @@ def args(self, **kwargs: Any) -> Self:
416416
:raises graphql.error.GraphQLError:
417417
if argument doesn't exist in directive definition
418418
"""
419-
if len(self.ast_directive.arguments) > 0:
419+
if self.ast_directive.arguments and len(self.ast_directive.arguments) > 0:
420420
raise AttributeError(f"Arguments for directive @{self.name} already set.")
421421

422422
errs = []
@@ -448,7 +448,7 @@ def args(self, **kwargs: Any) -> Self:
448448
def __repr__(self) -> str:
449449
args_str = ", ".join(
450450
f"{arg.name.value}={getattr(arg.value, 'value')}"
451-
for arg in self.ast_directive.arguments
451+
for arg in (self.ast_directive.arguments or ())
452452
)
453453
return f"<DSLDirective @{self.name}({args_str})>"
454454

@@ -893,7 +893,7 @@ def default(self, default_value: Any) -> Self:
893893

894894
def is_valid_directive(self, directive: DSLDirective) -> bool:
895895
"""Check if directive is valid for Variable definitions."""
896-
for arg in directive.ast_directive.arguments:
896+
for arg in directive.ast_directive.arguments or ():
897897
if isinstance(arg.value, VariableNode):
898898
raise GraphQLError(
899899
f"Directive @{directive.name} argument value has "

‎gql/utilities/build_client_schema.py‎

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -23,10 +23,10 @@
2323
"description": "Included when true.",
2424
"type": {
2525
"kind": "NON_NULL",
26-
"name": "None",
27-
"ofType": {"kind": "SCALAR", "name": "Boolean", "ofType": "None"},
26+
"name": None,
27+
"ofType": {"kind": "SCALAR", "name": "Boolean", "ofType": None},
2828
},
29-
"defaultValue": "None",
29+
"defaultValue": None,
3030
}
3131
],
3232
}
@@ -48,10 +48,10 @@
4848
"description": "Skipped when true.",
4949
"type": {
5050
"kind": "NON_NULL",
51-
"name": "None",
52-
"ofType": {"kind": "SCALAR", "name": "Boolean", "ofType": "None"},
51+
"name": None,
52+
"ofType": {"kind": "SCALAR", "name": "Boolean", "ofType": None},
5353
},
54-
"defaultValue": "None",
54+
"defaultValue": None,
5555
}
5656
],
5757
}

‎gql/utilities/get_introspection_query_ast.py‎

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -37,9 +37,9 @@ def get_introspection_query_ast(
3737
schema.select(ds.__Schema.description)
3838

3939
schema.select(
40-
ds.__Schema.queryType.select(ds.__Type.name),
41-
ds.__Schema.mutationType.select(ds.__Type.name),
42-
ds.__Schema.subscriptionType.select(ds.__Type.name),
40+
ds.__Schema.queryType.select(ds.__Type.name, ds.__Type.kind),
41+
ds.__Schema.mutationType.select(ds.__Type.name, ds.__Type.kind),
42+
ds.__Schema.subscriptionType.select(ds.__Type.name, ds.__Type.kind),
4343
)
4444

4545
schema.select(ds.__Schema.types.select(fragment_FullType))
@@ -134,10 +134,10 @@ def get_introspection_query_ast(
134134
)
135135

136136
if type_recursion_level >= 1:
137-
current_field = ds.__Type.ofType.select(ds.__Type.kind, ds.__Type.name)
137+
current_field = ds.__Type.ofType.select(ds.__Type.name, ds.__Type.kind)
138138

139139
for _ in repeat(None, type_recursion_level - 1):
140-
parent_field = ds.__Type.ofType.select(ds.__Type.kind, ds.__Type.name)
140+
parent_field = ds.__Type.ofType.select(ds.__Type.name, ds.__Type.kind)
141141
parent_field.select(current_field)
142142
current_field = parent_field
143143

‎gql/utilities/node_tree.py‎

Lines changed: 15 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -29,22 +29,21 @@ def _node_tree_recursive(
2929
continue
3030
attr_value = getattr(obj, key, None)
3131
results.append(" " * (indent + 1) + f"{key}:")
32-
if isinstance(attr_value, Iterable) and not isinstance(
32+
if attr_value is None or (
33+
isinstance(attr_value, Sized) and len(attr_value) == 0
34+
):
35+
results.append(" " * (indent + 2) + "None")
36+
elif isinstance(attr_value, Iterable) and not isinstance(
3337
attr_value, (str, bytes)
3438
):
35-
if isinstance(attr_value, Sized) and len(attr_value) == 0:
39+
for item in attr_value:
3640
results.append(
37-
" " * (indent + 2) + f"empty {type(attr_value).__name__}"
38-
)
39-
else:
40-
for item in attr_value:
41-
results.append(
42-
_node_tree_recursive(
43-
item,
44-
indent=indent + 2,
45-
ignored_keys=ignored_keys,
46-
)
41+
_node_tree_recursive(
42+
item,
43+
indent=indent + 2,
44+
ignored_keys=ignored_keys,
4745
)
46+
)
4847
else:
4948
results.append(
5049
_node_tree_recursive(
@@ -92,4 +91,8 @@ def node_tree(
9291
# Ignore new field added in graphql-core 3.3.0a12 to keep output compatible
9392
ignored_keys.append("nullability_assertion")
9493

94+
# Ignore description field which was added to OperationDefinitionNode
95+
# in graphql-core 3.3.0b0
96+
ignored_keys.append("description")
97+
9598
return _node_tree_recursive(obj, ignored_keys=ignored_keys)

‎gql/utilities/serialize_variable_values.py‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -115,7 +115,7 @@ def serialize_variable_values(
115115
operation = _get_document_operation(document, operation_name=operation_name)
116116

117117
# Serialize every variable value defined for the operation
118-
for var_def_node in operation.variable_definitions:
118+
for var_def_node in operation.variable_definitions or ():
119119
var_name = var_def_node.variable.name.value
120120
var_type = type_from_ast(schema, var_def_node.type)
121121

‎setup.py‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@
4141
] + tests_requires
4242

4343
install_aiohttp_requires = [
44-
"aiohttp>=3.11.2,<4",
44+
"aiohttp>=3.11.2,<=3.13.2",
4545
]
4646

4747
install_requests_requires = [

‎tests/starwars/test_dsl.py‎

Lines changed: 18 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1044,6 +1044,10 @@ def test_invalid_meta_field_selection(ds):
10441044
ds.Query.hero.select(DSLMetaField("__type"))
10451045

10461046

1047+
@pytest.mark.skipif(
1048+
version.parse(graphql_version) < version.parse("3.3.0rc0"),
1049+
reason="Requires graphql-core >= 3.3.0rc0",
1050+
)
10471051
@pytest.mark.parametrize("option", [True, False])
10481052
def test_get_introspection_query_ast(option):
10491053

@@ -1089,8 +1093,8 @@ def test_get_introspection_query_ast(option):
10891093

10901094

10911095
@pytest.mark.skipif(
1092-
version.parse(graphql_version) < version.parse("3.3.0a7"),
1093-
reason="Requires graphql-core >= 3.3.0a7",
1096+
version.parse(graphql_version) < version.parse("3.3.0rc0"),
1097+
reason="Requires graphql-core >= 3.3.0rc0",
10941098
)
10951099
@pytest.mark.parametrize("option", [True, False])
10961100
def test_get_introspection_query_ast_is_one_of(option):
@@ -1165,7 +1169,7 @@ def test_node_tree_with_loc(ds):
11651169
definitions:
11661170
OperationDefinitionNode
11671171
directives:
1168-
empty tuple
1172+
None
11691173
loc:
11701174
Location
11711175
<Location 0:43>
@@ -1188,9 +1192,9 @@ def test_node_tree_with_loc(ds):
11881192
alias:
11891193
None
11901194
arguments:
1191-
empty tuple
1195+
None
11921196
directives:
1193-
empty tuple
1197+
None
11941198
loc:
11951199
Location
11961200
<Location 22:41>
@@ -1213,9 +1217,9 @@ def test_node_tree_with_loc(ds):
12131217
alias:
12141218
None
12151219
arguments:
1216-
empty tuple
1220+
None
12171221
directives:
1218-
empty tuple
1222+
None
12191223
loc:
12201224
Location
12211225
<Location 33:37>
@@ -1231,7 +1235,7 @@ def test_node_tree_with_loc(ds):
12311235
selection_set:
12321236
None
12331237
variable_definitions:
1234-
empty tuple
1238+
None
12351239
loc:
12361240
Location
12371241
<Location 0:43>
@@ -1242,7 +1246,7 @@ def test_node_tree_with_loc(ds):
12421246
definitions:
12431247
OperationDefinitionNode
12441248
directives:
1245-
empty tuple
1249+
None
12461250
loc:
12471251
Location
12481252
<Location 0:43>
@@ -1265,9 +1269,9 @@ def test_node_tree_with_loc(ds):
12651269
alias:
12661270
None
12671271
arguments:
1268-
empty tuple
1272+
None
12691273
directives:
1270-
empty tuple
1274+
None
12711275
loc:
12721276
Location
12731277
<Location 22:41>
@@ -1288,9 +1292,9 @@ def test_node_tree_with_loc(ds):
12881292
alias:
12891293
None
12901294
arguments:
1291-
empty tuple
1295+
None
12921296
directives:
1293-
empty tuple
1297+
None
12941298
loc:
12951299
Location
12961300
<Location 33:37>
@@ -1304,7 +1308,7 @@ def test_node_tree_with_loc(ds):
13041308
selection_set:
13051309
None
13061310
variable_definitions:
1307-
empty tuple
1311+
None
13081312
loc:
13091313
Location
13101314
<Location 0:43>

‎tests/test_transport.py‎

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,15 +12,42 @@
1212

1313

1414
def use_cassette(name):
15+
import json
16+
1517
import vcr
1618

19+
# method to ignore introspection changes in graphql-core 3.3.0b0
20+
def graphql_body_matcher(r1, r2):
21+
try:
22+
b1 = json.loads(r1.body)
23+
b2 = json.loads(r2.body)
24+
if isinstance(b1, dict) and isinstance(b2, dict):
25+
q1 = b1.get("query", "")
26+
q2 = b2.get("query", "")
27+
if "IntrospectionQuery" in q1 and "IntrospectionQuery" in q2:
28+
return True
29+
return b1 == b2
30+
elif isinstance(b1, list) and isinstance(b2, list) and len(b1) == len(b2):
31+
for item1, item2 in zip(b1, b2):
32+
q1 = item1.get("query", "")
33+
q2 = item2.get("query", "")
34+
if "IntrospectionQuery" in q1 and "IntrospectionQuery" in q2:
35+
continue
36+
if item1 != item2:
37+
return False
38+
return True
39+
except Exception:
40+
pass
41+
return r1.body == r2.body
42+
1743
query_vcr = vcr.VCR(
1844
cassette_library_dir=os.path.join(
1945
os.path.dirname(__file__), "fixtures", "vcr_cassettes"
2046
),
2147
record_mode="new_episodes",
22-
match_on=["uri", "method", "body"],
2348
)
49+
query_vcr.register_matcher("graphql_body", graphql_body_matcher)
50+
query_vcr.match_on = ["uri", "method", "graphql_body"]
2451

2552
return query_vcr.use_cassette(name + ".yaml")
2653

‎tests/test_transport_batch.py‎

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,15 +12,42 @@
1212

1313

1414
def use_cassette(name):
15+
import json
16+
1517
import vcr
1618

19+
# method to ignore introspection changes in graphql-core 3.3.0b0
20+
def graphql_body_matcher(r1, r2):
21+
try:
22+
b1 = json.loads(r1.body)
23+
b2 = json.loads(r2.body)
24+
if isinstance(b1, dict) and isinstance(b2, dict):
25+
q1 = b1.get("query", "")
26+
q2 = b2.get("query", "")
27+
if "IntrospectionQuery" in q1 and "IntrospectionQuery" in q2:
28+
return True
29+
return b1 == b2
30+
elif isinstance(b1, list) and isinstance(b2, list) and len(b1) == len(b2):
31+
for item1, item2 in zip(b1, b2):
32+
q1 = item1.get("query", "")
33+
q2 = item2.get("query", "")
34+
if "IntrospectionQuery" in q1 and "IntrospectionQuery" in q2:
35+
continue
36+
if item1 != item2:
37+
return False
38+
return True
39+
except Exception:
40+
pass
41+
return r1.body == r2.body
42+
1743
query_vcr = vcr.VCR(
1844
cassette_library_dir=os.path.join(
1945
os.path.dirname(__file__), "fixtures", "vcr_cassettes"
2046
),
2147
record_mode="new_episodes",
22-
match_on=["uri", "method", "body"],
2348
)
49+
query_vcr.register_matcher("graphql_body", graphql_body_matcher)
50+
query_vcr.match_on = ["uri", "method", "graphql_body"]
2451

2552
return query_vcr.use_cassette(name + ".yaml")
2653

0 commit comments

Comments
 (0)