Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions gql/dsl.py
Original file line number Diff line number Diff line change
Expand Up @@ -416,7 +416,7 @@ def args(self, **kwargs: Any) -> Self:
:raises graphql.error.GraphQLError:
if argument doesn't exist in directive definition
"""
if len(self.ast_directive.arguments) > 0:
if self.ast_directive.arguments and len(self.ast_directive.arguments) > 0:
raise AttributeError(f"Arguments for directive @{self.name} already set.")

errs = []
Expand Down Expand Up @@ -448,7 +448,7 @@ def args(self, **kwargs: Any) -> Self:
def __repr__(self) -> str:
args_str = ", ".join(
f"{arg.name.value}={getattr(arg.value, 'value')}"
for arg in self.ast_directive.arguments
for arg in (self.ast_directive.arguments or ())
)
return f"<DSLDirective @{self.name}({args_str})>"

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

def is_valid_directive(self, directive: DSLDirective) -> bool:
"""Check if directive is valid for Variable definitions."""
for arg in directive.ast_directive.arguments:
for arg in directive.ast_directive.arguments or ():
if isinstance(arg.value, VariableNode):
raise GraphQLError(
f"Directive @{directive.name} argument value has "
Expand Down
12 changes: 6 additions & 6 deletions gql/utilities/build_client_schema.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,10 +23,10 @@
"description": "Included when true.",
"type": {
"kind": "NON_NULL",
"name": "None",
"ofType": {"kind": "SCALAR", "name": "Boolean", "ofType": "None"},
"name": None,
"ofType": {"kind": "SCALAR", "name": "Boolean", "ofType": None},
},
"defaultValue": "None",
"defaultValue": None,
}
],
}
Expand All @@ -48,10 +48,10 @@
"description": "Skipped when true.",
"type": {
"kind": "NON_NULL",
"name": "None",
"ofType": {"kind": "SCALAR", "name": "Boolean", "ofType": "None"},
"name": None,
"ofType": {"kind": "SCALAR", "name": "Boolean", "ofType": None},
},
"defaultValue": "None",
"defaultValue": None,
}
],
}
Expand Down
10 changes: 5 additions & 5 deletions gql/utilities/get_introspection_query_ast.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,9 +37,9 @@ def get_introspection_query_ast(
schema.select(ds.__Schema.description)

schema.select(
ds.__Schema.queryType.select(ds.__Type.name),
ds.__Schema.mutationType.select(ds.__Type.name),
ds.__Schema.subscriptionType.select(ds.__Type.name),
ds.__Schema.queryType.select(ds.__Type.name, ds.__Type.kind),
ds.__Schema.mutationType.select(ds.__Type.name, ds.__Type.kind),
ds.__Schema.subscriptionType.select(ds.__Type.name, ds.__Type.kind),
)

schema.select(ds.__Schema.types.select(fragment_FullType))
Expand Down Expand Up @@ -134,10 +134,10 @@ def get_introspection_query_ast(
)

if type_recursion_level >= 1:
current_field = ds.__Type.ofType.select(ds.__Type.kind, ds.__Type.name)
current_field = ds.__Type.ofType.select(ds.__Type.name, ds.__Type.kind)

for _ in repeat(None, type_recursion_level - 1):
parent_field = ds.__Type.ofType.select(ds.__Type.kind, ds.__Type.name)
parent_field = ds.__Type.ofType.select(ds.__Type.name, ds.__Type.kind)
parent_field.select(current_field)
current_field = parent_field

Expand Down
27 changes: 15 additions & 12 deletions gql/utilities/node_tree.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,22 +29,21 @@ def _node_tree_recursive(
continue
attr_value = getattr(obj, key, None)
results.append(" " * (indent + 1) + f"{key}:")
if isinstance(attr_value, Iterable) and not isinstance(
if attr_value is None or (
isinstance(attr_value, Sized) and len(attr_value) == 0
):
results.append(" " * (indent + 2) + "None")
elif isinstance(attr_value, Iterable) and not isinstance(
attr_value, (str, bytes)
):
if isinstance(attr_value, Sized) and len(attr_value) == 0:
for item in attr_value:
results.append(
" " * (indent + 2) + f"empty {type(attr_value).__name__}"
)
else:
for item in attr_value:
results.append(
_node_tree_recursive(
item,
indent=indent + 2,
ignored_keys=ignored_keys,
)
_node_tree_recursive(
item,
indent=indent + 2,
ignored_keys=ignored_keys,
)
)
else:
results.append(
_node_tree_recursive(
Expand Down Expand Up @@ -92,4 +91,8 @@ def node_tree(
# Ignore new field added in graphql-core 3.3.0a12 to keep output compatible
ignored_keys.append("nullability_assertion")

# Ignore description field which was added to OperationDefinitionNode
# in graphql-core 3.3.0b0
ignored_keys.append("description")

return _node_tree_recursive(obj, ignored_keys=ignored_keys)
2 changes: 1 addition & 1 deletion gql/utilities/serialize_variable_values.py
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,7 @@ def serialize_variable_values(
operation = _get_document_operation(document, operation_name=operation_name)

# Serialize every variable value defined for the operation
for var_def_node in operation.variable_definitions:
for var_def_node in operation.variable_definitions or ():
var_name = var_def_node.variable.name.value
var_type = type_from_ast(schema, var_def_node.type)

Expand Down
2 changes: 1 addition & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@
] + tests_requires

install_aiohttp_requires = [
"aiohttp>=3.11.2,<4",
"aiohttp>=3.11.2,<=3.13.2",
]

install_requests_requires = [
Expand Down
32 changes: 18 additions & 14 deletions tests/starwars/test_dsl.py
Original file line number Diff line number Diff line change
Expand Up @@ -1044,6 +1044,10 @@ def test_invalid_meta_field_selection(ds):
ds.Query.hero.select(DSLMetaField("__type"))


@pytest.mark.skipif(
version.parse(graphql_version) < version.parse("3.3.0rc0"),
reason="Requires graphql-core >= 3.3.0rc0",
)
@pytest.mark.parametrize("option", [True, False])
def test_get_introspection_query_ast(option):

Expand Down Expand Up @@ -1089,8 +1093,8 @@ def test_get_introspection_query_ast(option):


@pytest.mark.skipif(
version.parse(graphql_version) < version.parse("3.3.0a7"),
reason="Requires graphql-core >= 3.3.0a7",
version.parse(graphql_version) < version.parse("3.3.0rc0"),
reason="Requires graphql-core >= 3.3.0rc0",
)
@pytest.mark.parametrize("option", [True, False])
def test_get_introspection_query_ast_is_one_of(option):
Expand Down Expand Up @@ -1165,7 +1169,7 @@ def test_node_tree_with_loc(ds):
definitions:
OperationDefinitionNode
directives:
empty tuple
None
loc:
Location
<Location 0:43>
Expand All @@ -1188,9 +1192,9 @@ def test_node_tree_with_loc(ds):
alias:
None
arguments:
empty tuple
None
directives:
empty tuple
None
loc:
Location
<Location 22:41>
Expand All @@ -1213,9 +1217,9 @@ def test_node_tree_with_loc(ds):
alias:
None
arguments:
empty tuple
None
directives:
empty tuple
None
loc:
Location
<Location 33:37>
Expand All @@ -1231,7 +1235,7 @@ def test_node_tree_with_loc(ds):
selection_set:
None
variable_definitions:
empty tuple
None
loc:
Location
<Location 0:43>
Expand All @@ -1242,7 +1246,7 @@ def test_node_tree_with_loc(ds):
definitions:
OperationDefinitionNode
directives:
empty tuple
None
loc:
Location
<Location 0:43>
Expand All @@ -1265,9 +1269,9 @@ def test_node_tree_with_loc(ds):
alias:
None
arguments:
empty tuple
None
directives:
empty tuple
None
loc:
Location
<Location 22:41>
Expand All @@ -1288,9 +1292,9 @@ def test_node_tree_with_loc(ds):
alias:
None
arguments:
empty tuple
None
directives:
empty tuple
None
loc:
Location
<Location 33:37>
Expand All @@ -1304,7 +1308,7 @@ def test_node_tree_with_loc(ds):
selection_set:
None
variable_definitions:
empty tuple
None
loc:
Location
<Location 0:43>
Expand Down
29 changes: 28 additions & 1 deletion tests/test_transport.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,15 +12,42 @@


def use_cassette(name):
import json

import vcr

# method to ignore introspection changes in graphql-core 3.3.0b0
def graphql_body_matcher(r1, r2):
try:
b1 = json.loads(r1.body)
b2 = json.loads(r2.body)
if isinstance(b1, dict) and isinstance(b2, dict):
q1 = b1.get("query", "")
q2 = b2.get("query", "")
if "IntrospectionQuery" in q1 and "IntrospectionQuery" in q2:
return True
return b1 == b2
elif isinstance(b1, list) and isinstance(b2, list) and len(b1) == len(b2):
for item1, item2 in zip(b1, b2):
q1 = item1.get("query", "")
q2 = item2.get("query", "")
if "IntrospectionQuery" in q1 and "IntrospectionQuery" in q2:
continue
if item1 != item2:
return False
return True
except Exception:
pass
return r1.body == r2.body

query_vcr = vcr.VCR(
cassette_library_dir=os.path.join(
os.path.dirname(__file__), "fixtures", "vcr_cassettes"
),
record_mode="new_episodes",
match_on=["uri", "method", "body"],
)
query_vcr.register_matcher("graphql_body", graphql_body_matcher)
query_vcr.match_on = ["uri", "method", "graphql_body"]

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

Expand Down
29 changes: 28 additions & 1 deletion tests/test_transport_batch.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,15 +12,42 @@


def use_cassette(name):
import json

import vcr

# method to ignore introspection changes in graphql-core 3.3.0b0
def graphql_body_matcher(r1, r2):
try:
b1 = json.loads(r1.body)
b2 = json.loads(r2.body)
if isinstance(b1, dict) and isinstance(b2, dict):
q1 = b1.get("query", "")
q2 = b2.get("query", "")
if "IntrospectionQuery" in q1 and "IntrospectionQuery" in q2:
return True
return b1 == b2
elif isinstance(b1, list) and isinstance(b2, list) and len(b1) == len(b2):
for item1, item2 in zip(b1, b2):
q1 = item1.get("query", "")
q2 = item2.get("query", "")
if "IntrospectionQuery" in q1 and "IntrospectionQuery" in q2:
continue
if item1 != item2:
return False
return True
except Exception:
pass
return r1.body == r2.body

query_vcr = vcr.VCR(
cassette_library_dir=os.path.join(
os.path.dirname(__file__), "fixtures", "vcr_cassettes"
),
record_mode="new_episodes",
match_on=["uri", "method", "body"],
)
query_vcr.register_matcher("graphql_body", graphql_body_matcher)
query_vcr.match_on = ["uri", "method", "graphql_body"]

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

Expand Down
Loading