Skip to content

Commit 7942946

Browse files
committed
Introspection: Add support for repeatable directives
Replicates graphql/graphql-js@cc4c363
1 parent cb8d351 commit 7942946

File tree

8 files changed

+75
-14
lines changed

8 files changed

+75
-14
lines changed

src/graphql/type/introspection.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,10 @@
8888
"description": GraphQLField(
8989
GraphQLString, resolve=lambda obj, _info: obj.description
9090
),
91+
"isRepeatable": GraphQLField(
92+
GraphQLNonNull(GraphQLBoolean),
93+
resolve=lambda obj, _info: obj.is_repeatable,
94+
),
9195
"locations": GraphQLField(
9296
GraphQLNonNull(GraphQLList(GraphQLNonNull(__DirectiveLocation))),
9397
resolve=lambda obj, _info: obj.locations,

src/graphql/utilities/build_client_schema.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -313,6 +313,7 @@ def build_directive(directive_introspection: Dict) -> GraphQLDirective:
313313
return GraphQLDirective(
314314
name=directive_introspection["name"],
315315
description=directive_introspection.get("description"),
316+
is_repeatable=directive_introspection.get("isRepeatable", False),
316317
locations=list(
317318
cast(
318319
Collection[DirectiveLocation],

src/graphql/utilities/get_introspection_query.py

Lines changed: 13 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,13 @@
33
__all__ = ["get_introspection_query"]
44

55

6-
def get_introspection_query(descriptions=True) -> str:
7-
"""Get a query for introspection, optionally without descriptions."""
6+
def get_introspection_query(descriptions=True, directive_is_repeatable=False) -> str:
7+
"""Get a query for introspection.
8+
9+
Optionally, you can exclude descriptions and include repeatability of directives.
10+
"""
11+
maybe_description = "description" if descriptions else ""
12+
maybe_directive_is_repeatable = "isRepeatable" if directive_is_repeatable else ""
813
return dedent(
914
f"""
1015
query IntrospectionQuery {{
@@ -17,7 +22,8 @@ def get_introspection_query(descriptions=True) -> str:
1722
}}
1823
directives {{
1924
name
20-
{'description' if descriptions else ''}
25+
{maybe_description}
26+
{maybe_directive_is_repeatable}
2127
locations
2228
args {{
2329
...InputValue
@@ -29,10 +35,10 @@ def get_introspection_query(descriptions=True) -> str:
2935
fragment FullType on __Type {{
3036
kind
3137
name
32-
{'description' if descriptions else ''}
38+
{maybe_description}
3339
fields(includeDeprecated: true) {{
3440
name
35-
{'description' if descriptions else ''}
41+
{maybe_description}
3642
args {{
3743
...InputValue
3844
}}
@@ -50,7 +56,7 @@ def get_introspection_query(descriptions=True) -> str:
5056
}}
5157
enumValues(includeDeprecated: true) {{
5258
name
53-
{'description' if descriptions else ''}
59+
{maybe_description}
5460
isDeprecated
5561
deprecationReason
5662
}}
@@ -61,7 +67,7 @@ def get_introspection_query(descriptions=True) -> str:
6167
6268
fragment InputValue on __InputValue {{
6369
name
64-
{'description' if descriptions else ''}
70+
{maybe_description}
6571
type {{ ...TypeRef }}
6672
defaultValue
6773
}}

src/graphql/utilities/introspection_from_schema.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,9 @@
1212

1313

1414
def introspection_from_schema(
15-
schema: GraphQLSchema, descriptions: bool = True
15+
schema: GraphQLSchema,
16+
descriptions: bool = True,
17+
directive_is_repeatable: bool = True,
1618
) -> IntrospectionSchema:
1719
"""Build an IntrospectionQuery from a GraphQLSchema
1820
@@ -22,7 +24,7 @@ def introspection_from_schema(
2224
This is the inverse of build_client_schema. The primary use case is outside of the
2325
server context, for instance when doing schema comparisons.
2426
"""
25-
document = parse(get_introspection_query(descriptions))
27+
document = parse(get_introspection_query(descriptions, directive_is_repeatable))
2628

2729
from ..execution.execute import execute, ExecutionResult
2830

tests/type/test_introspection.py

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,9 @@ def executes_an_introspection_query():
1919
schema = GraphQLSchema(
2020
GraphQLObjectType("QueryRoot", {"onlyField": GraphQLField(GraphQLString)})
2121
)
22-
source = get_introspection_query(descriptions=False)
22+
source = get_introspection_query(
23+
descriptions=False, directive_is_repeatable=True
24+
)
2325

2426
result = graphql_sync(schema=schema, source=source)
2527
assert result.errors is None
@@ -638,6 +640,21 @@ def executes_an_introspection_query():
638640
"isDeprecated": False,
639641
"deprecationReason": None,
640642
},
643+
{
644+
"name": "isRepeatable",
645+
"args": [],
646+
"type": {
647+
"kind": "NON_NULL",
648+
"name": None,
649+
"ofType": {
650+
"kind": "SCALAR",
651+
"name": "Boolean",
652+
"ofType": None,
653+
},
654+
},
655+
"isDeprecated": False,
656+
"deprecationReason": None,
657+
},
641658
{
642659
"name": "locations",
643660
"args": [],
@@ -799,6 +816,7 @@ def executes_an_introspection_query():
799816
"directives": [
800817
{
801818
"name": "include",
819+
"isRepeatable": False,
802820
"locations": ["FIELD", "FRAGMENT_SPREAD", "INLINE_FRAGMENT"],
803821
"args": [
804822
{
@@ -818,6 +836,7 @@ def executes_an_introspection_query():
818836
},
819837
{
820838
"name": "skip",
839+
"isRepeatable": False,
821840
"locations": ["FIELD", "FRAGMENT_SPREAD", "INLINE_FRAGMENT"],
822841
"args": [
823842
{
@@ -837,6 +856,7 @@ def executes_an_introspection_query():
837856
},
838857
{
839858
"name": "deprecated",
859+
"isRepeatable": False,
840860
"locations": ["FIELD_DEFINITION", "ENUM_VALUE"],
841861
"args": [
842862
{
@@ -1329,7 +1349,7 @@ def executes_introspection_query_without_calling_global_field_resolver():
13291349
)
13301350

13311351
schema = GraphQLSchema(query_root)
1332-
source = get_introspection_query()
1352+
source = get_introspection_query(directive_is_repeatable=True)
13331353

13341354
def field_resolver(_obj, info):
13351355
assert False, f"Called on {info.parent_type.name}.{info.field_name}"

tests/utilities/test_build_client_schema.py

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -31,12 +31,14 @@ def cycle_introspection(sdl_string):
3131
build in-memory GraphQLSchema from it, produce a client-side representation of the
3232
schema by using "build_client_schema" and then return that schema printed as SDL.
3333
"""
34+
options = dict(directive_is_repeatable=True)
35+
3436
server_schema = build_schema(sdl_string)
35-
initial_introspection = introspection_from_schema(server_schema)
37+
initial_introspection = introspection_from_schema(server_schema, **options)
3638
client_schema = build_client_schema(initial_introspection)
3739
# If the client then runs the introspection query against the client-side schema,
3840
# it should get a result identical to what was returned by the server
39-
second_introspection = introspection_from_schema(client_schema)
41+
second_introspection = introspection_from_schema(client_schema, **options)
4042

4143
# If the client then runs the introspection query against the client-side
4244
# schema, it should get a result identical to what was returned by the server.
@@ -460,7 +462,7 @@ def builds_a_schema_with_custom_directives():
460462
sdl = dedent(
461463
'''
462464
"""This is a custom directive"""
463-
directive @customDirective on FIELD
465+
directive @customDirective repeatable on FIELD
464466
465467
type Query {
466468
string: String
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
import re
2+
3+
from graphql.utilities import get_introspection_query
4+
5+
6+
def describe_get_introspection_query():
7+
def skips_all_description_fields():
8+
has_descriptions = re.compile(r"\bdescription\b").search
9+
10+
assert has_descriptions(get_introspection_query())
11+
12+
assert has_descriptions(get_introspection_query(descriptions=True))
13+
14+
assert not has_descriptions(get_introspection_query(descriptions=False))
15+
16+
def includes_is_repeatable_field():
17+
has_repeatability = re.compile(r"\bisRepeatable\b").search
18+
19+
assert not has_repeatability(get_introspection_query())
20+
21+
assert has_repeatability(get_introspection_query(directive_is_repeatable=True))
22+
23+
assert not has_repeatability(
24+
get_introspection_query(directive_is_repeatable=False)
25+
)

tests/utilities/test_schema_printer.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -744,6 +744,7 @@ def prints_introspection_schema():
744744
type __Directive {
745745
name: String!
746746
description: String
747+
isRepeatable: Boolean!
747748
locations: [__DirectiveLocation!]!
748749
args: [__InputValue!]!
749750
}

0 commit comments

Comments
 (0)