Skip to content

Commit eb2aeeb

Browse files
committed
Allow deprecating input fields and arguments
Replicates graphql/graphql-js@01bcc7d
1 parent a53ddfb commit eb2aeeb

13 files changed

+658
-65
lines changed

src/graphql/type/definition.py

+14
Original file line numberDiff line numberDiff line change
@@ -604,6 +604,7 @@ class GraphQLArgument:
604604
type: "GraphQLInputType"
605605
default_value: Any
606606
description: Optional[str]
607+
deprecation_reason: Optional[str]
607608
out_name: Optional[str] # for transforming names (extension of GraphQL.js)
608609
extensions: Optional[Dict[str, Any]]
609610
ast_node: Optional[InputValueDefinitionNode]
@@ -613,6 +614,7 @@ def __init__(
613614
type_: "GraphQLInputType",
614615
default_value: Any = Undefined,
615616
description: Optional[str] = None,
617+
deprecation_reason: Optional[str] = None,
616618
out_name: Optional[str] = None,
617619
extensions: Optional[Dict[str, Any]] = None,
618620
ast_node: Optional[InputValueDefinitionNode] = None,
@@ -621,6 +623,8 @@ def __init__(
621623
raise TypeError("Argument type must be a GraphQL input type.")
622624
if description is not None and not is_description(description):
623625
raise TypeError("Argument description must be a string.")
626+
if deprecation_reason is not None and not is_description(deprecation_reason):
627+
raise TypeError("Argument deprecation reason must be a string.")
624628
if out_name is not None and not isinstance(out_name, str):
625629
raise TypeError("Argument out name must be a string.")
626630
if extensions is not None and (
@@ -635,6 +639,7 @@ def __init__(
635639
self.type = type_
636640
self.default_value = default_value
637641
self.description = description
642+
self.deprecation_reason = deprecation_reason
638643
self.out_name = out_name
639644
self.extensions = extensions
640645
self.ast_node = ast_node
@@ -645,6 +650,7 @@ def __eq__(self, other: Any) -> bool:
645650
and self.type == other.type
646651
and self.default_value == other.default_value
647652
and self.description == other.description
653+
and self.deprecation_reason == other.deprecation_reason
648654
and self.out_name == other.out_name
649655
and self.extensions == other.extensions
650656
)
@@ -654,6 +660,7 @@ def to_kwargs(self) -> Dict[str, Any]:
654660
type_=self.type,
655661
default_value=self.default_value,
656662
description=self.description,
663+
deprecation_reason=self.deprecation_reason,
657664
out_name=self.out_name,
658665
extensions=self.extensions,
659666
ast_node=self.ast_node,
@@ -1399,6 +1406,7 @@ class GraphQLInputField:
13991406
type: "GraphQLInputType"
14001407
default_value: Any
14011408
description: Optional[str]
1409+
deprecation_reason: Optional[str]
14021410
out_name: Optional[str] # for transforming names (extension of GraphQL.js)
14031411
extensions: Optional[Dict[str, Any]]
14041412
ast_node: Optional[InputValueDefinitionNode]
@@ -1408,6 +1416,7 @@ def __init__(
14081416
type_: "GraphQLInputType",
14091417
default_value: Any = Undefined,
14101418
description: Optional[str] = None,
1419+
deprecation_reason: Optional[str] = None,
14111420
out_name: Optional[str] = None,
14121421
extensions: Optional[Dict[str, Any]] = None,
14131422
ast_node: Optional[InputValueDefinitionNode] = None,
@@ -1416,6 +1425,8 @@ def __init__(
14161425
raise TypeError("Input field type must be a GraphQL input type.")
14171426
if description is not None and not is_description(description):
14181427
raise TypeError("Input field description must be a string.")
1428+
if deprecation_reason is not None and not is_description(deprecation_reason):
1429+
raise TypeError("Input field deprecation reason must be a string.")
14191430
if out_name is not None and not isinstance(out_name, str):
14201431
raise TypeError("Input field out name must be a string.")
14211432
if extensions is not None and (
@@ -1430,6 +1441,7 @@ def __init__(
14301441
self.type = type_
14311442
self.default_value = default_value
14321443
self.description = description
1444+
self.deprecation_reason = deprecation_reason
14331445
self.out_name = out_name
14341446
self.extensions = extensions
14351447
self.ast_node = ast_node
@@ -1440,6 +1452,7 @@ def __eq__(self, other: Any) -> bool:
14401452
and self.type == other.type
14411453
and self.default_value == other.default_value
14421454
and self.description == other.description
1455+
and self.deprecation_reason == other.deprecation_reason
14431456
and self.extensions == other.extensions
14441457
and self.out_name == other.out_name
14451458
)
@@ -1449,6 +1462,7 @@ def to_kwargs(self) -> Dict[str, Any]:
14491462
type_=self.type,
14501463
default_value=self.default_value,
14511464
description=self.description,
1465+
deprecation_reason=self.deprecation_reason,
14521466
out_name=self.out_name,
14531467
extensions=self.extensions,
14541468
ast_node=self.ast_node,

src/graphql/type/directives.py

+6-1
Original file line numberDiff line numberDiff line change
@@ -185,7 +185,12 @@ def assert_directive(directive: Any) -> GraphQLDirective:
185185
# Used to declare element of a GraphQL schema as deprecated:
186186
GraphQLDeprecatedDirective = GraphQLDirective(
187187
name="deprecated",
188-
locations=[DirectiveLocation.FIELD_DEFINITION, DirectiveLocation.ENUM_VALUE],
188+
locations=[
189+
DirectiveLocation.FIELD_DEFINITION,
190+
DirectiveLocation.ARGUMENT_DEFINITION,
191+
DirectiveLocation.INPUT_FIELD_DEFINITION,
192+
DirectiveLocation.ENUM_VALUE,
193+
],
189194
args={
190195
"reason": GraphQLArgument(
191196
GraphQLString,

src/graphql/type/introspection.py

+76-15
Original file line numberDiff line numberDiff line change
@@ -243,6 +243,11 @@
243243
),
244244
"inputFields": GraphQLField(
245245
GraphQLList(GraphQLNonNull(__InputValue)),
246+
args={
247+
"includeDeprecated": GraphQLArgument(
248+
GraphQLBoolean, default_value=False
249+
)
250+
},
246251
resolve=TypeFieldResolvers.input_fields,
247252
),
248253
"ofType": GraphQLField(__Type, resolve=TypeFieldResolvers.of_type),
@@ -311,14 +316,22 @@ def possible_types(type_, info):
311316
def enum_values(type_, _info, includeDeprecated=False):
312317
if is_enum_type(type_):
313318
items = type_.values.items()
314-
if not includeDeprecated:
315-
return [item for item in items if item[1].deprecation_reason is None]
316-
return items
319+
return (
320+
items
321+
if includeDeprecated
322+
else [item for item in items if item[1].deprecation_reason is None]
323+
)
317324

325+
# noinspection PyPep8Naming
318326
@staticmethod
319-
def input_fields(type_, _info):
327+
def input_fields(type_, _info, includeDeprecated=False):
320328
if is_input_object_type(type_):
321-
return type_.fields.items()
329+
items = type_.fields.items()
330+
return (
331+
items
332+
if includeDeprecated
333+
else [item for item in items if item[1].deprecation_reason is None]
334+
)
322335

323336
@staticmethod
324337
def of_type(type_, _info):
@@ -332,29 +345,62 @@ def of_type(type_, _info):
332345
" and a return type.",
333346
fields=lambda: {
334347
"name": GraphQLField(
335-
GraphQLNonNull(GraphQLString), resolve=lambda item, _info: item[0]
336-
),
337-
"description": GraphQLField(
338-
GraphQLString, resolve=lambda item, _info: item[1].description
348+
GraphQLNonNull(GraphQLString), resolve=FieldResolvers.name
339349
),
350+
"description": GraphQLField(GraphQLString, resolve=FieldResolvers.description),
340351
"args": GraphQLField(
341352
GraphQLNonNull(GraphQLList(GraphQLNonNull(__InputValue))),
342-
resolve=lambda item, _info: item[1].args.items(),
343-
),
344-
"type": GraphQLField(
345-
GraphQLNonNull(__Type), resolve=lambda item, _info: item[1].type
353+
args={
354+
"includeDeprecated": GraphQLArgument(
355+
GraphQLBoolean, default_value=False
356+
)
357+
},
358+
resolve=FieldResolvers.args,
346359
),
360+
"type": GraphQLField(GraphQLNonNull(__Type), resolve=FieldResolvers.type),
347361
"isDeprecated": GraphQLField(
348362
GraphQLNonNull(GraphQLBoolean),
349-
resolve=lambda item, _info: item[1].deprecation_reason is not None,
363+
resolve=FieldResolvers.is_deprecated,
350364
),
351365
"deprecationReason": GraphQLField(
352-
GraphQLString, resolve=lambda item, _info: item[1].deprecation_reason
366+
GraphQLString, resolve=FieldResolvers.deprecation_reason
353367
),
354368
},
355369
)
356370

357371

372+
class FieldResolvers:
373+
@staticmethod
374+
def name(item, _info):
375+
return item[0]
376+
377+
@staticmethod
378+
def description(item, _info):
379+
return item[1].description
380+
381+
# noinspection PyPep8Naming
382+
@staticmethod
383+
def args(item, _info, includeDeprecated=False):
384+
items = item[1].args.items()
385+
return (
386+
items
387+
if includeDeprecated
388+
else [item for item in items if item[1].deprecation_reason is None]
389+
)
390+
391+
@staticmethod
392+
def type(item, _info):
393+
return item[1].type
394+
395+
@staticmethod
396+
def is_deprecated(item, _info):
397+
return item[1].deprecation_reason is not None
398+
399+
@staticmethod
400+
def deprecation_reason(item, _info):
401+
return item[1].deprecation_reason
402+
403+
358404
__InputValue: GraphQLObjectType = GraphQLObjectType(
359405
name="__InputValue",
360406
description="Arguments provided to Fields or Directives and the input"
@@ -376,6 +422,13 @@ def of_type(type_, _info):
376422
" the default value for this input value.",
377423
resolve=InputValueFieldResolvers.default_value,
378424
),
425+
"isDeprecated": GraphQLField(
426+
GraphQLNonNull(GraphQLBoolean),
427+
resolve=InputValueFieldResolvers.is_deprecated,
428+
),
429+
"deprecationReason": GraphQLField(
430+
GraphQLString, resolve=InputValueFieldResolvers.deprecation_reason
431+
),
379432
},
380433
)
381434

@@ -401,6 +454,14 @@ def default_value(item, _info):
401454
value_ast = ast_from_value(item[1].default_value, item[1].type)
402455
return print_ast(value_ast) if value_ast else None
403456

457+
@staticmethod
458+
def is_deprecated(item, _info):
459+
return item[1].deprecation_reason is not None
460+
461+
@staticmethod
462+
def deprecation_reason(item, _info):
463+
return item[1].deprecation_reason
464+
404465

405466
__EnumValue: GraphQLObjectType = GraphQLObjectType(
406467
name="__EnumValue",

src/graphql/type/validate.py

+53-2
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,14 @@
1414

1515
from ..error import GraphQLError, located_error
1616
from ..pyutils import inspect
17-
from ..language import NamedTypeNode, Node, OperationType, OperationTypeDefinitionNode
17+
from ..language import (
18+
DirectiveNode,
19+
InputValueDefinitionNode,
20+
NamedTypeNode,
21+
Node,
22+
OperationType,
23+
OperationTypeDefinitionNode,
24+
)
1825
from .definition import (
1926
GraphQLEnumType,
2027
GraphQLInputField,
@@ -32,10 +39,11 @@
3239
is_output_type,
3340
is_union_type,
3441
is_required_argument,
42+
is_required_input_field,
3543
)
3644
from ..utilities.assert_valid_name import is_valid_name_error
3745
from ..utilities.type_comparators import is_equal_type, is_type_sub_type_of
38-
from .directives import is_directive, GraphQLDirective
46+
from .directives import is_directive, GraphQLDirective, GraphQLDeprecatedDirective
3947
from .introspection import is_introspection_type
4048
from .schema import GraphQLSchema, assert_schema
4149

@@ -163,6 +171,16 @@ def validate_directives(self) -> None:
163171
arg.ast_node,
164172
)
165173

174+
if is_required_argument(arg) and arg.deprecation_reason is not None:
175+
self.report_error(
176+
f"Required argument @{directive.name}({arg_name}:)"
177+
" cannot be deprecated.",
178+
[
179+
get_deprecated_directive_node(arg.ast_node),
180+
arg.ast_node and arg.ast_node.type,
181+
],
182+
)
183+
166184
def validate_name(self, node: Any, name: Optional[str] = None) -> None:
167185
# Ensure names are valid, however introspection types opt out.
168186
try:
@@ -261,6 +279,16 @@ def validate_fields(
261279
arg.ast_node and arg.ast_node.type,
262280
)
263281

282+
if is_required_argument(arg) and arg.deprecation_reason is not None:
283+
self.report_error(
284+
f"Required argument {type_.name}.{field_name}({arg_name}:)"
285+
" cannot be deprecated.",
286+
[
287+
get_deprecated_directive_node(arg.ast_node),
288+
arg.ast_node and arg.ast_node.type,
289+
],
290+
)
291+
264292
def validate_interfaces(
265293
self, type_: Union[GraphQLObjectType, GraphQLInterfaceType]
266294
) -> None:
@@ -456,6 +484,16 @@ def validate_input_fields(self, input_obj: GraphQLInputObjectType) -> None:
456484
field.ast_node.type if field.ast_node else None,
457485
)
458486

487+
if is_required_input_field(field) and field.deprecation_reason is not None:
488+
self.report_error(
489+
f"Required input field {input_obj.name}.{field_name}"
490+
" cannot be deprecated.",
491+
[
492+
get_deprecated_directive_node(field.ast_node),
493+
field.ast_node and field.ast_node.type,
494+
],
495+
)
496+
459497

460498
def get_operation_type_node(
461499
schema: GraphQLSchema, operation: OperationType
@@ -575,3 +613,16 @@ def get_union_member_type_nodes(
575613
return [
576614
union_node for union_node in union_nodes if union_node.name.value == type_name
577615
]
616+
617+
618+
def get_deprecated_directive_node(
619+
definition_node: Optional[Union[InputValueDefinitionNode]],
620+
) -> Optional[DirectiveNode]:
621+
directives = definition_node and definition_node.directives
622+
if directives:
623+
for directive in directives:
624+
if (
625+
directive.name.value == GraphQLDeprecatedDirective.name
626+
): # pragma: no cover else
627+
return directive
628+
return None # pragma: no cover

src/graphql/utilities/extend_schema.py

+3-1
Original file line numberDiff line numberDiff line change
@@ -448,6 +448,7 @@ def build_argument_map(
448448
type_=type_,
449449
description=arg.description.value if arg.description else None,
450450
default_value=value_from_ast(arg.default_value, type_),
451+
deprecation_reason=get_deprecation_reason(arg),
451452
ast_node=arg,
452453
)
453454
return arg_map
@@ -468,6 +469,7 @@ def build_input_field_map(
468469
type_=type_,
469470
description=field.description.value if field.description else None,
470471
default_value=value_from_ast(field.default_value, type_),
472+
deprecation_reason=get_deprecation_reason(field),
471473
ast_node=field,
472474
)
473475
return input_field_map
@@ -676,7 +678,7 @@ def build_type(ast_node: TypeDefinitionNode) -> GraphQLNamedType:
676678

677679

678680
def get_deprecation_reason(
679-
node: Union[EnumValueDefinitionNode, FieldDefinitionNode]
681+
node: Union[EnumValueDefinitionNode, FieldDefinitionNode, InputValueDefinitionNode]
680682
) -> Optional[str]:
681683
"""Given a field or enum value node, get deprecation reason as string."""
682684
from ..execution import get_directive_values

0 commit comments

Comments
 (0)