Skip to content

Commit 28d6281

Browse files
committed
Implement changes for executable descriptions
Replicates graphql/graphql-js@364f17fd
1 parent 21a7e93 commit 28d6281

9 files changed

Lines changed: 313 additions & 15 deletions

File tree

src/graphql/language/ast.py

Lines changed: 19 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -242,19 +242,27 @@ class OperationType(Enum):
242242
"name": (),
243243
"document": ("definitions",),
244244
"operation_definition": (
245+
"description",
245246
"name",
246247
"variable_definitions",
247248
"directives",
248249
"selection_set",
249250
),
250-
"variable_definition": ("variable", "type", "default_value", "directives"),
251+
"variable_definition": (
252+
"description",
253+
"variable",
254+
"type",
255+
"default_value",
256+
"directives",
257+
),
251258
"variable": ("name",),
252259
"selection_set": ("selections",),
253260
"field": ("alias", "name", "arguments", "directives", "selection_set"),
254261
"argument": ("name", "value"),
255262
"fragment_spread": ("name", "directives"),
256263
"inline_fragment": ("type_condition", "directives", "selection_set"),
257264
"fragment_definition": (
265+
"description",
258266
# Note: fragment variable definitions are deprecated and will be removed in v3.3
259267
"name",
260268
"variable_definitions",
@@ -423,8 +431,15 @@ class DefinitionNode(Node):
423431

424432

425433
class ExecutableDefinitionNode(DefinitionNode):
426-
__slots__ = "name", "directives", "variable_definitions", "selection_set"
434+
__slots__ = (
435+
"description",
436+
"name",
437+
"directives",
438+
"variable_definitions",
439+
"selection_set",
440+
)
427441

442+
description: Optional["StringValueNode"]
428443
name: Optional[NameNode]
429444
directives: Tuple["DirectiveNode", ...]
430445
variable_definitions: Tuple["VariableDefinitionNode", ...]
@@ -438,8 +453,9 @@ class OperationDefinitionNode(ExecutableDefinitionNode):
438453

439454

440455
class VariableDefinitionNode(Node):
441-
__slots__ = "variable", "type", "default_value", "directives"
456+
__slots__ = "description", "variable", "type", "default_value", "directives"
442457

458+
description: Optional["StringValueNode"]
443459
variable: "VariableNode"
444460
type: "TypeNode"
445461
default_value: Optional["ConstValueNode"]

src/graphql/language/parser.py

Lines changed: 24 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -261,9 +261,12 @@ def parse_document(self) -> DocumentNode:
261261
"directive": "directive_definition",
262262
}
263263

264-
_parse_other_definition_method_names: Dict[str, str] = {
264+
_parse_executable_definition_method_names: Dict[str, str] = {
265265
**dict.fromkeys(("query", "mutation", "subscription"), "operation_definition"),
266266
"fragment": "fragment_definition",
267+
}
268+
269+
_parse_other_definition_method_names: Dict[str, str] = {
267270
"extend": "type_system_extension",
268271
}
269272

@@ -287,6 +290,14 @@ def parse_definition(self) -> DefinitionNode:
287290
self._lexer.lookahead() if has_description else self._lexer.token
288291
)
289292

293+
if has_description and keyword_token.kind is TokenKind.BRACE_L:
294+
raise GraphQLSyntaxError(
295+
self._lexer.source,
296+
self._lexer.token.start,
297+
"Unexpected description,"
298+
" descriptions are not supported on shorthand queries.",
299+
)
300+
290301
if keyword_token.kind is TokenKind.NAME:
291302
token_name = cast(str, keyword_token.value)
292303
method_name = self._parse_type_system_definition_method_names.get(
@@ -295,12 +306,16 @@ def parse_definition(self) -> DefinitionNode:
295306
if method_name:
296307
return getattr(self, f"parse_{method_name}")()
297308

309+
method_name = self._parse_executable_definition_method_names.get(token_name)
310+
if method_name:
311+
return getattr(self, f"parse_{method_name}")()
312+
298313
if has_description:
299314
raise GraphQLSyntaxError(
300315
self._lexer.source,
301316
self._lexer.token.start,
302317
"Unexpected description,"
303-
" descriptions are supported only on type definitions.",
318+
" only GraphQL definitions support descriptions.",
304319
)
305320

306321
method_name = self._parse_other_definition_method_names.get(token_name)
@@ -317,16 +332,19 @@ def parse_operation_definition(self) -> OperationDefinitionNode:
317332
if self.peek(TokenKind.BRACE_L):
318333
return OperationDefinitionNode(
319334
operation=OperationType.QUERY,
335+
description=None,
320336
name=None,
321337
variable_definitions=[],
322338
directives=[],
323339
selection_set=self.parse_selection_set(),
324340
loc=self.loc(start),
325341
)
342+
description = self.parse_description()
326343
operation = self.parse_operation_type()
327344
name = self.parse_name() if self.peek(TokenKind.NAME) else None
328345
return OperationDefinitionNode(
329346
operation=operation,
347+
description=description,
330348
name=name,
331349
variable_definitions=self.parse_variable_definitions(),
332350
directives=self.parse_directives(False),
@@ -352,6 +370,7 @@ def parse_variable_definition(self) -> VariableDefinitionNode:
352370
"""VariableDefinition: Variable: Type DefaultValue? Directives[Const]?"""
353371
start = self._lexer.token
354372
return VariableDefinitionNode(
373+
description=self.parse_description(),
355374
variable=self.parse_variable(),
356375
type=self.expect_token(TokenKind.COLON) and self.parse_type_reference(),
357376
default_value=(
@@ -455,11 +474,13 @@ def parse_fragment(self) -> Union[FragmentSpreadNode, InlineFragmentNode]:
455474
def parse_fragment_definition(self) -> FragmentDefinitionNode:
456475
"""FragmentDefinition"""
457476
start = self._lexer.token
477+
description = self.parse_description()
458478
self.expect_keyword("fragment")
459479
# Legacy support for defining variables within fragments changes
460480
# the grammar of FragmentDefinition
461481
if self._allow_legacy_fragment_variables:
462482
return FragmentDefinitionNode(
483+
description=description,
463484
name=self.parse_fragment_name(),
464485
variable_definitions=self.parse_variable_definitions(),
465486
type_condition=self.parse_type_condition(),
@@ -468,6 +489,7 @@ def parse_fragment_definition(self) -> FragmentDefinitionNode:
468489
loc=self.loc(start),
469490
)
470491
return FragmentDefinitionNode(
492+
description=description,
471493
name=self.parse_fragment_name(),
472494
type_condition=self.parse_type_condition(),
473495
directives=self.parse_directives(False),

src/graphql/language/printer.py

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -66,8 +66,12 @@ def leave_document(node: PrintedNode, *_args: Any) -> str:
6666

6767
@staticmethod
6868
def leave_operation_definition(node: PrintedNode, *_args: Any) -> str:
69-
var_defs = wrap("(", join(node.variable_definitions, ", "), ")")
70-
prefix = join(
69+
var_defs = (
70+
wrap("(\n", join(node.variable_definitions, "\n"), "\n)")
71+
if has_multiline_items(node.variable_definitions)
72+
else wrap("(", join(node.variable_definitions, ", "), ")")
73+
)
74+
prefix = wrap("", node.description, "\n") + join(
7175
(
7276
node.operation.value,
7377
join((node.name, var_defs)),
@@ -82,7 +86,7 @@ def leave_operation_definition(node: PrintedNode, *_args: Any) -> str:
8286
@staticmethod
8387
def leave_variable_definition(node: PrintedNode, *_args: Any) -> str:
8488
return (
85-
f"{node.variable}: {node.type}"
89+
wrap("", node.description, "\n") + f"{node.variable}: {node.type}"
8690
f"{wrap(' = ', node.default_value)}"
8791
f"{wrap(' ', join(node.directives, ' '))}"
8892
)
@@ -127,7 +131,7 @@ def leave_inline_fragment(node: PrintedNode, *_args: Any) -> str:
127131
def leave_fragment_definition(node: PrintedNode, *_args: Any) -> str:
128132
# Note: fragment variable definitions are deprecated and will be removed in v3.3
129133
return (
130-
f"fragment {node.name}"
134+
wrap("", node.description, "\n") + f"fragment {node.name}"
131135
f"{wrap('(', join(node.variable_definitions, ', '), ')')}"
132136
f" on {node.type_condition}"
133137
f" {wrap('', join(node.directives, ' '), ' ')}"

tests/fixtures/kitchen_sink.graphql

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,9 @@
1-
query queryName($foo: ComplexType, $site: Site = MOBILE) @onQuery {
1+
"Query description"
2+
query queryName(
3+
"Very complex variable"
4+
$foo: ComplexType,
5+
$site: Site = MOBILE
6+
) @onQuery {
27
whoever123is: node(id: [123, 456]) {
38
id ,
49
... on User @onInlineFragment {
@@ -42,6 +47,9 @@ subscription StoryLikeSubscription(
4247
}
4348
}
4449

50+
"""
51+
Fragment description
52+
"""
4553
fragment frag on Friend @onFragmentDefinition {
4654
foo(size: $size, bar: $b, obj: {key: "value", block: """
4755

0 commit comments

Comments
 (0)