Skip to content

Commit a2f327f

Browse files
authored
Adding node_tree method in utilities to debug and compare DocumentNode instances (#449)
DSL: Set variable_definitions to None for Fragments by default instead of empty tuple
1 parent 3a48a2f commit a2f327f

File tree

5 files changed

+270
-4
lines changed

5 files changed

+270
-4
lines changed

Diff for: gql/dsl.py

+17-1
Original file line numberDiff line numberDiff line change
@@ -1068,10 +1068,26 @@ def executable_ast(self) -> FragmentDefinitionNode:
10681068
"Missing type condition. Please use .on(type_condition) method"
10691069
)
10701070

1071+
fragment_variable_definitions = self.variable_definitions.get_ast_definitions()
1072+
1073+
if len(fragment_variable_definitions) == 0:
1074+
"""Fragment variable definitions are obsolete and only supported on
1075+
graphql-core if the Parser is initialized with:
1076+
allow_legacy_fragment_variables=True.
1077+
1078+
We will not provide variable_definitions instead of providing an empty
1079+
tuple to be coherent with how it works by default on graphql-core.
1080+
"""
1081+
variable_definition_kwargs = {}
1082+
else:
1083+
variable_definition_kwargs = {
1084+
"variable_definitions": fragment_variable_definitions
1085+
}
1086+
10711087
return FragmentDefinitionNode(
10721088
type_condition=NamedTypeNode(name=NameNode(value=self._type.name)),
10731089
selection_set=self.selection_set,
1074-
variable_definitions=self.variable_definitions.get_ast_definitions(),
1090+
**variable_definition_kwargs,
10751091
name=NameNode(value=self.name),
10761092
directives=(),
10771093
)

Diff for: gql/utilities/__init__.py

+2
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,14 @@
11
from .build_client_schema import build_client_schema
22
from .get_introspection_query_ast import get_introspection_query_ast
3+
from .node_tree import node_tree
34
from .parse_result import parse_result
45
from .serialize_variable_values import serialize_value, serialize_variable_values
56
from .update_schema_enum import update_schema_enum
67
from .update_schema_scalars import update_schema_scalar, update_schema_scalars
78

89
__all__ = [
910
"build_client_schema",
11+
"node_tree",
1012
"parse_result",
1113
"get_introspection_query_ast",
1214
"serialize_variable_values",

Diff for: gql/utilities/node_tree.py

+89
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
from typing import Any, Iterable, List, Optional, Sized
2+
3+
from graphql import Node
4+
5+
6+
def _node_tree_recursive(
7+
obj: Any,
8+
*,
9+
indent: int = 0,
10+
ignored_keys: List,
11+
):
12+
13+
assert ignored_keys is not None
14+
15+
results = []
16+
17+
if hasattr(obj, "__slots__"):
18+
19+
results.append(" " * indent + f"{type(obj).__name__}")
20+
21+
try:
22+
keys = obj.keys
23+
except AttributeError:
24+
# If the object has no keys attribute, print its repr and return.
25+
results.append(" " * (indent + 1) + repr(obj))
26+
else:
27+
for key in keys:
28+
if key in ignored_keys:
29+
continue
30+
attr_value = getattr(obj, key, None)
31+
results.append(" " * (indent + 1) + f"{key}:")
32+
if isinstance(attr_value, Iterable) and not isinstance(
33+
attr_value, (str, bytes)
34+
):
35+
if isinstance(attr_value, Sized) and len(attr_value) == 0:
36+
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+
)
47+
)
48+
else:
49+
results.append(
50+
_node_tree_recursive(
51+
attr_value,
52+
indent=indent + 2,
53+
ignored_keys=ignored_keys,
54+
)
55+
)
56+
else:
57+
results.append(" " * indent + repr(obj))
58+
59+
return "\n".join(results)
60+
61+
62+
def node_tree(
63+
obj: Node,
64+
*,
65+
ignore_loc: bool = True,
66+
ignore_block: bool = True,
67+
ignored_keys: Optional[List] = None,
68+
):
69+
"""Method which returns a tree of Node elements as a String.
70+
71+
Useful to debug deep DocumentNode instances created by gql or dsl_gql.
72+
73+
WARNING: the output of this method is not guaranteed and may change without notice.
74+
"""
75+
76+
assert isinstance(obj, Node)
77+
78+
if ignored_keys is None:
79+
ignored_keys = []
80+
81+
if ignore_loc:
82+
# We are ignoring loc attributes by default
83+
ignored_keys.append("loc")
84+
85+
if ignore_block:
86+
# We are ignoring block attributes by default (in StringValueNode)
87+
ignored_keys.append("block")
88+
89+
return _node_tree_recursive(obj, ignored_keys=ignored_keys)

Diff for: tests/regressions/issue_447_dsl_missing_directives/test_dsl_directives.py

+17-2
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
1-
from gql import Client
2-
from gql.dsl import DSLFragment, DSLQuery, DSLSchema, dsl_gql
1+
from gql import Client, gql
2+
from gql.dsl import DSLFragment, DSLQuery, DSLSchema, dsl_gql, print_ast
3+
from gql.utilities import node_tree
34

45
schema_str = """
56
type MonsterForm {
@@ -57,3 +58,17 @@ def test_issue_447():
5758
q = dsl_gql(sprite, copy_of, DSLQuery(query))
5859

5960
client.validate(q)
61+
62+
# Creating a tree from the DocumentNode created by dsl_gql
63+
dsl_tree = node_tree(q)
64+
65+
# Creating a tree from the DocumentNode created by gql
66+
gql_tree = node_tree(gql(print_ast(q)))
67+
68+
print("=======")
69+
print(dsl_tree)
70+
print("+++++++")
71+
print(gql_tree)
72+
print("=======")
73+
74+
assert dsl_tree == gql_tree

0 commit comments

Comments
 (0)