Skip to content

Commit d9b85de

Browse files
committed
Add skip and include directive in introspection schema
1 parent 2be6aaa commit d9b85de

File tree

4 files changed

+123
-2
lines changed

4 files changed

+123
-2
lines changed

gql/client.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@
77
ExecutionResult,
88
GraphQLSchema,
99
build_ast_schema,
10-
build_client_schema,
1110
get_introspection_query,
1211
parse,
1312
validate,
@@ -17,6 +16,7 @@
1716
from .transport.exceptions import TransportQueryError
1817
from .transport.local_schema import LocalSchemaTransport
1918
from .transport.transport import Transport
19+
from .utilities import build_client_schema
2020
from .utilities import parse_result as parse_result_fn
2121
from .utilities import serialize_variable_values
2222

gql/utilities/__init__.py

+2
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,12 @@
1+
from .build_client_schema import build_client_schema
12
from .get_introspection_query_ast import get_introspection_query_ast
23
from .parse_result import parse_result
34
from .serialize_variable_values import serialize_value, serialize_variable_values
45
from .update_schema_enum import update_schema_enum
56
from .update_schema_scalars import update_schema_scalar, update_schema_scalars
67

78
__all__ = [
9+
"build_client_schema",
810
"parse_result",
911
"get_introspection_query_ast",
1012
"serialize_variable_values",

gql/utilities/build_client_schema.py

+73
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
from typing import Dict
2+
3+
from graphql import GraphQLSchema
4+
from graphql import build_client_schema as build_client_schema_orig
5+
6+
__all__ = ["build_client_schema"]
7+
8+
9+
INCLUDE_DIRECTIVE_JSON = {
10+
"name": "include",
11+
"description": (
12+
"Directs the executor to include this field or fragment "
13+
"only when the `if` argument is true."
14+
),
15+
"locations": ["FIELD", "FRAGMENT_SPREAD", "INLINE_FRAGMENT"],
16+
"args": [
17+
{
18+
"name": "if",
19+
"description": "Included when true.",
20+
"type": {
21+
"kind": "NON_NULL",
22+
"name": "None",
23+
"ofType": {"kind": "SCALAR", "name": "Boolean", "ofType": "None"},
24+
},
25+
"defaultValue": "None",
26+
}
27+
],
28+
}
29+
30+
SKIP_DIRECTIVE_JSON = {
31+
"name": "skip",
32+
"description": (
33+
"Directs the executor to skip this field or fragment "
34+
"when the `if` argument is true."
35+
),
36+
"locations": ["FIELD", "FRAGMENT_SPREAD", "INLINE_FRAGMENT"],
37+
"args": [
38+
{
39+
"name": "if",
40+
"description": "Skipped when true.",
41+
"type": {
42+
"kind": "NON_NULL",
43+
"name": "None",
44+
"ofType": {"kind": "SCALAR", "name": "Boolean", "ofType": "None"},
45+
},
46+
"defaultValue": "None",
47+
}
48+
],
49+
}
50+
51+
52+
def build_client_schema(introspection: Dict) -> GraphQLSchema:
53+
"""This is an alternative to the graphql-core function
54+
:code:`build_client_schema` but with default include and skip directives
55+
added to the schema to fix
56+
`issue #278 <https://github.com/graphql-python/gql/issues/278>`_
57+
58+
.. warning::
59+
This function will be removed once the issue
60+
`graphql-js#3419 <https://github.com/graphql/graphql-js/issues/3419>`_
61+
has been fixed and ported to graphql-core so don't use it
62+
outside gql.
63+
"""
64+
65+
directives = introspection["__schema"]["directives"]
66+
67+
if not any(directive["name"] == "skip" for directive in directives):
68+
directives.append(SKIP_DIRECTIVE_JSON)
69+
70+
if not any(directive["name"] == "include" for directive in directives):
71+
directives.append(INCLUDE_DIRECTIVE_JSON)
72+
73+
return build_client_schema_orig(introspection, assume_valid=False)

tests/starwars/test_validation.py

+47-1
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,24 @@ def introspection_schema():
6060
return Client(introspection=StarWarsIntrospection)
6161

6262

63-
@pytest.fixture(params=["local_schema", "typedef_schema", "introspection_schema"])
63+
@pytest.fixture
64+
def introspection_schema_no_directives():
65+
introspection = StarWarsIntrospection
66+
67+
# Simulate an empty dictionary for directives
68+
introspection["__schema"]["directives"] = []
69+
70+
return Client(introspection=introspection)
71+
72+
73+
@pytest.fixture(
74+
params=[
75+
"local_schema",
76+
"typedef_schema",
77+
"introspection_schema",
78+
"introspection_schema_no_directives",
79+
]
80+
)
6481
def client(request):
6582
return request.getfixturevalue(request.param)
6683

@@ -187,3 +204,32 @@ def test_allows_object_fields_in_inline_fragments(client):
187204
}
188205
"""
189206
assert not validation_errors(client, query)
207+
208+
209+
def test_include_directive(client):
210+
query = """
211+
query fetchHero($with_friends: Boolean!) {
212+
hero {
213+
name
214+
friends @include(if: $with_friends) {
215+
name
216+
}
217+
}
218+
}
219+
"""
220+
assert not validation_errors(client, query)
221+
222+
223+
def test_skip_directive(client):
224+
query = """
225+
query fetchHero($without_friends: Boolean!) {
226+
hero {
227+
name
228+
friends @skip(if: $without_friends) {
229+
name
230+
}
231+
}
232+
}
233+
"""
234+
print(StarWarsIntrospection)
235+
assert not validation_errors(client, query)

0 commit comments

Comments
 (0)