Skip to content

Commit 44e9b3f

Browse files
committed
Add NoDeprecatedCustomRule and deprecate find_deprecated_usages
Replicates graphql/graphql-js@9735ac7
1 parent 5230569 commit 44e9b3f

File tree

8 files changed

+195
-121
lines changed

8 files changed

+195
-121
lines changed

src/graphql/__init__.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -321,6 +321,7 @@
321321
UniqueDirectiveNamesRule,
322322
PossibleTypeExtensionsRule,
323323
# Custom validation rules
324+
NoDeprecatedCustomRule,
324325
NoSchemaIntrospectionCustomRule,
325326
)
326327

@@ -398,6 +399,8 @@
398399
DangerousChangeType,
399400
find_breaking_changes,
400401
find_dangerous_changes,
402+
# Report all deprecated usages within a GraphQL document (deprecated).
403+
find_deprecated_usages,
401404
)
402405

403406
# Utilities for compatibility with the Python language.
@@ -656,6 +659,7 @@
656659
"UniqueFieldDefinitionNamesRule",
657660
"UniqueDirectiveNamesRule",
658661
"PossibleTypeExtensionsRule",
662+
"NoDeprecatedCustomRule",
659663
"NoSchemaIntrospectionCustomRule",
660664
"GraphQLError",
661665
"GraphQLSyntaxError",
@@ -691,6 +695,7 @@
691695
"is_valid_name_error",
692696
"find_breaking_changes",
693697
"find_dangerous_changes",
698+
"find_deprecated_usages",
694699
"BreakingChange",
695700
"BreakingChangeType",
696701
"DangerousChange",

src/graphql/utilities/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,7 @@
8181
find_dangerous_changes,
8282
)
8383

84-
# Report all deprecated usages within a GraphQL document.
84+
# Report all deprecated usages within a GraphQL document (deprecated).
8585
from .find_deprecated_usages import find_deprecated_usages
8686

8787
__all__ = [
Lines changed: 17 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -1,56 +1,25 @@
1-
from typing import Any, List
1+
from typing import List
22

33
from ..error import GraphQLError
4-
from ..language import DocumentNode, EnumValueNode, FieldNode, Visitor, visit
5-
from ..type import GraphQLSchema, get_named_type
6-
from .type_info import TypeInfo, TypeInfoVisitor
7-
4+
from ..language import DocumentNode
5+
from ..type import GraphQLSchema
86

97
__all__ = ["find_deprecated_usages"]
108

119

1210
def find_deprecated_usages(
1311
schema: GraphQLSchema, ast: DocumentNode
14-
) -> List[GraphQLError]:
15-
"""Get a list of GraphQLError instances describing each deprecated use."""
16-
17-
type_info = TypeInfo(schema)
18-
visitor = FindDeprecatedUsages(type_info)
19-
visit(ast, TypeInfoVisitor(type_info, visitor))
20-
return visitor.errors
21-
22-
23-
class FindDeprecatedUsages(Visitor):
24-
"""A validation rule which reports deprecated usages."""
25-
26-
type_info: TypeInfo
27-
errors: List[GraphQLError]
28-
29-
def __init__(self, type_info: TypeInfo):
30-
super().__init__()
31-
self.type_info = type_info
32-
self.errors = []
33-
34-
def enter_field(self, node: FieldNode, *_args: Any) -> None:
35-
parent_type = self.type_info.get_parent_type()
36-
field_def = self.type_info.get_field_def()
37-
if parent_type and field_def and field_def.deprecation_reason is not None:
38-
self.errors.append(
39-
GraphQLError(
40-
f"The field '{parent_type.name}.{node.name.value}'"
41-
" is deprecated. " + field_def.deprecation_reason,
42-
node,
43-
)
44-
)
45-
46-
def enter_enum_value(self, node: EnumValueNode, *_args: Any) -> None:
47-
type_ = get_named_type(self.type_info.get_input_type())
48-
enum_val = self.type_info.get_enum_value()
49-
if type_ and enum_val and enum_val.deprecation_reason is not None:
50-
self.errors.append(
51-
GraphQLError(
52-
f"The enum value '{type_.name}.{node.value}'"
53-
" is deprecated. " + enum_val.deprecation_reason,
54-
node,
55-
)
56-
)
12+
) -> List[GraphQLError]: # pragma: no cover
13+
"""Get a list of GraphQLError instances describing each deprecated use.
14+
15+
.. deprecated:: 3.1.3
16+
17+
Please use ``validate`` with ``NoDeprecatedCustomRule`` instead::
18+
19+
from graphql import validate, NoDeprecatedCustomRule
20+
21+
errors = validate(schema, document, [NoDeprecatedCustomRule])
22+
"""
23+
from ..validation import validate, NoDeprecatedCustomRule
24+
25+
return validate(schema, ast, [NoDeprecatedCustomRule])

src/graphql/validation/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,7 @@
105105
from .rules.possible_type_extensions import PossibleTypeExtensionsRule
106106

107107
# Optional rules not defined by the GraphQL Specification
108+
from .rules.custom.no_deprecated import NoDeprecatedCustomRule
108109
from .rules.custom.no_schema_introspection import NoSchemaIntrospectionCustomRule
109110

110111
__all__ = [
@@ -149,5 +150,6 @@
149150
"UniqueFieldDefinitionNamesRule",
150151
"UniqueDirectiveNamesRule",
151152
"PossibleTypeExtensionsRule",
153+
"NoDeprecatedCustomRule",
152154
"NoSchemaIntrospectionCustomRule",
153155
]
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
from typing import Any
2+
3+
from ....error import GraphQLError
4+
from ....language import FieldNode, EnumValueNode
5+
from ....type import get_named_type
6+
from .. import ValidationRule
7+
8+
__all__ = ["NoDeprecatedCustomRule"]
9+
10+
11+
class NoDeprecatedCustomRule(ValidationRule):
12+
"""No deprecated
13+
14+
A GraphQL document is only valid if all selected fields and all used enum values
15+
have not been deprecated.
16+
17+
Note: This rule is optional and is not part of the Validation section of the GraphQL
18+
Specification. The main purpose of this rule is detection of deprecated usages and
19+
not necessarily to forbid their use when querying a service.
20+
"""
21+
22+
def enter_field(self, node: FieldNode, *_args: Any) -> None:
23+
context = self.context
24+
field_def = context.get_field_def()
25+
parent_type = context.get_parent_type()
26+
if parent_type and field_def and field_def.deprecation_reason is not None:
27+
self.report_error(
28+
GraphQLError(
29+
f"The field {parent_type.name}.{node.name.value}"
30+
f" is deprecated. {field_def.deprecation_reason}",
31+
node,
32+
)
33+
)
34+
35+
def enter_enum_value(self, node: EnumValueNode, *_args: Any) -> None:
36+
context = self.context
37+
type_ = get_named_type(context.get_input_type())
38+
enum_val = context.get_enum_value()
39+
if type_ and enum_val and enum_val.deprecation_reason is not None:
40+
self.report_error(
41+
GraphQLError(
42+
f"The enum value '{type_.name}.{node.value}'"
43+
f" is deprecated. {enum_val.deprecation_reason}",
44+
node,
45+
)
46+
)

src/graphql/validation/validation_context.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
from typing import Any, Callable, Dict, List, NamedTuple, Optional, Set, Union, cast
22

3-
from .. import GraphQLDirective
43
from ..error import GraphQLError
54
from ..language import (
65
DocumentNode,
@@ -16,6 +15,8 @@
1615
from ..type import (
1716
GraphQLArgument,
1817
GraphQLCompositeType,
18+
GraphQLDirective,
19+
GraphQLEnumValue,
1920
GraphQLField,
2021
GraphQLInputType,
2122
GraphQLOutputType,
@@ -241,3 +242,6 @@ def get_directive(self) -> Optional[GraphQLDirective]:
241242

242243
def get_argument(self) -> Optional[GraphQLArgument]:
243244
return self._type_info.get_argument()
245+
246+
def get_enum_value(self) -> Optional[GraphQLEnumValue]:
247+
return self._type_info.get_enum_value()

tests/utilities/test_find_deprecated_usages.py

Lines changed: 0 additions & 71 deletions
This file was deleted.
Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
1+
from functools import partial
2+
3+
from graphql.utilities import build_schema
4+
from graphql.validation import NoDeprecatedCustomRule
5+
6+
from .harness import assert_validation_errors
7+
8+
schema = build_schema(
9+
"""
10+
enum EnumType {
11+
NORMAL_VALUE
12+
DEPRECATED_VALUE @deprecated(reason: "Some enum reason.")
13+
DEPRECATED_VALUE_WITH_NO_REASON @deprecated
14+
}
15+
16+
type Query {
17+
normalField(enumArg: [EnumType]): String
18+
deprecatedField: String @deprecated(reason: "Some field reason.")
19+
deprecatedFieldWithNoReason: String @deprecated
20+
}
21+
"""
22+
)
23+
24+
assert_errors = partial(assert_validation_errors, NoDeprecatedCustomRule, schema=schema)
25+
26+
assert_valid = partial(assert_errors, errors=[])
27+
28+
29+
def describe_validate_no_deprecated():
30+
def ignores_fields_and_enum_values_that_are_not_deprecated():
31+
assert_valid(
32+
"""
33+
{
34+
normalField(enumArg: [NORMAL_VALUE])
35+
}
36+
"""
37+
)
38+
39+
def ignores_unknown_fields_and_enum_values():
40+
assert_valid(
41+
"""
42+
fragment UnknownFragment on UnknownType {
43+
unknownField(unknownArg: UNKNOWN_VALUE)
44+
}
45+
46+
fragment QueryFragment on Query {
47+
unknownField(unknownArg: UNKNOWN_VALUE)
48+
normalField(enumArg: UNKNOWN_VALUE)
49+
}
50+
"""
51+
)
52+
53+
def reports_error_when_a_deprecated_field_is_selected():
54+
assert_errors(
55+
"""
56+
{
57+
normalField
58+
deprecatedField
59+
deprecatedFieldWithNoReason
60+
}
61+
""",
62+
[
63+
{
64+
"message": "The field Query.deprecatedField is deprecated."
65+
" Some field reason.",
66+
"locations": [(4, 15)],
67+
},
68+
{
69+
"message": "The field Query.deprecatedFieldWithNoReason"
70+
" is deprecated. No longer supported",
71+
"locations": [(5, 15)],
72+
},
73+
],
74+
)
75+
76+
def reports_error_when_a_deprecated_enum_value_is_used():
77+
assert_errors(
78+
"""
79+
{
80+
normalField(enumArg: [NORMAL_VALUE, DEPRECATED_VALUE])
81+
normalField(enumArg: [DEPRECATED_VALUE_WITH_NO_REASON])
82+
}
83+
""",
84+
[
85+
{
86+
"message": "The enum value 'EnumType.DEPRECATED_VALUE'"
87+
" is deprecated. Some enum reason.",
88+
"locations": [(3, 51)],
89+
},
90+
{
91+
"message": "The enum value"
92+
" 'EnumType.DEPRECATED_VALUE_WITH_NO_REASON'"
93+
" is deprecated. No longer supported",
94+
"locations": [(4, 37)],
95+
},
96+
],
97+
)
98+
99+
def reports_error_when_deprecated_field_or_enum_value_is_used_inside_a_fragment():
100+
assert_errors(
101+
"""
102+
fragment QueryFragment on Query {
103+
deprecatedField
104+
normalField(enumArg: [NORMAL_VALUE, DEPRECATED_VALUE])
105+
}
106+
""",
107+
[
108+
{
109+
"message": "The field Query.deprecatedField is deprecated."
110+
" Some field reason.",
111+
"locations": [(3, 15)],
112+
},
113+
{
114+
"message": "The enum value 'EnumType.DEPRECATED_VALUE'"
115+
" is deprecated. Some enum reason.",
116+
"locations": [(4, 51)],
117+
},
118+
],
119+
)

0 commit comments

Comments
 (0)