Skip to content

Commit 2585715

Browse files
committed
Move validation of names into GraphQL object constructors
Replicates graphql/graphql-js@5774173
1 parent 1fc8e7e commit 2585715

14 files changed

+356
-212
lines changed

.coveragerc

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,10 @@ branch = True
33
source = src
44
omit =
55
*/conftest.py
6+
*/test_*_fuzz.py
67
*/cached_property.py
78
*/is_iterable.py
8-
*/test_*_fuzz.py
9+
*/assert_valid_name.py
910

1011
[report]
1112
exclude_lines =

src/graphql/__init__.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -132,6 +132,9 @@
132132
# Validate GraphQL schema.
133133
validate_schema,
134134
assert_valid_schema,
135+
# Uphold the spec rules about naming
136+
assert_name,
137+
assert_enum_value_name,
135138
# Types
136139
GraphQLType,
137140
GraphQLInputType,
@@ -508,6 +511,8 @@
508511
"get_named_type",
509512
"validate_schema",
510513
"assert_valid_schema",
514+
"assert_name",
515+
"assert_enum_value_name",
511516
"GraphQLType",
512517
"GraphQLInputType",
513518
"GraphQLOutputType",

src/graphql/type/__init__.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,9 @@
1414
GraphQLSchema,
1515
)
1616

17+
# Uphold the spec rules about naming.
18+
from .assert_name import assert_name, assert_enum_value_name
19+
1720
from .definition import (
1821
# Predicates
1922
is_type,
@@ -149,6 +152,8 @@
149152
__all__ = [
150153
"is_schema",
151154
"assert_schema",
155+
"assert_name",
156+
"assert_enum_value_name",
152157
"GraphQLSchema",
153158
"is_type",
154159
"is_scalar_type",

src/graphql/type/assert_name.py

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
from ..error import GraphQLError
2+
from ..language.character_classes import is_name_start, is_name_continue
3+
4+
__all__ = ["assert_name", "assert_enum_value_name"]
5+
6+
7+
def assert_name(name: str) -> str:
8+
"""Uphold the spec rules about naming.
9+
10+
.. deprecated:: 3.2
11+
Please use ``get_enter_leave_for_kind`` instead. Will be removed in v3.3.
12+
"""
13+
if name is None:
14+
raise TypeError("Must provide name.")
15+
if not isinstance(name, str):
16+
raise TypeError("Expected name to be a string.")
17+
if not name:
18+
raise GraphQLError("Expected name to be a non-empty string.")
19+
if not all(is_name_continue(char) for char in name[1:]):
20+
raise GraphQLError(
21+
f"Names must only contain [_a-zA-Z0-9] but {name!r} does not."
22+
)
23+
if not is_name_start(name[0]):
24+
raise GraphQLError(f"Names must start with [_a-zA-Z] but {name!r} does not.")
25+
return name
26+
27+
28+
def assert_enum_value_name(name: str) -> str:
29+
"""Uphold the spec rules about naming enum values."""
30+
assert_name(name)
31+
if name in {"true", "false", "null"}:
32+
raise GraphQLError(f"Enum values cannot be named: {name}.")
33+
return name

src/graphql/type/definition.py

Lines changed: 19 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@
5656
Undefined,
5757
)
5858
from ..utilities.value_from_ast_untyped import value_from_ast_untyped
59+
from .assert_name import assert_name, assert_enum_value_name
5960

6061
if TYPE_CHECKING:
6162
from .schema import GraphQLSchema # noqa: F401
@@ -208,10 +209,7 @@ def __init__(
208209
ast_node: Optional[TypeDefinitionNode] = None,
209210
extension_ast_nodes: Optional[Collection[TypeExtensionNode]] = None,
210211
) -> None:
211-
if not name:
212-
raise TypeError("Must provide name.")
213-
if not isinstance(name, str):
214-
raise TypeError("The name must be a string.")
212+
assert_name(name)
215213
if description is not None and not is_description(description):
216214
raise TypeError("The description must be a string.")
217215
if extensions is None:
@@ -465,7 +463,7 @@ def __init__(
465463
)
466464
else:
467465
args = {
468-
name: value
466+
assert_name(name): value
469467
if isinstance(value, GraphQLArgument)
470468
else GraphQLArgument(cast(GraphQLInputType, value))
471469
for name, value in args.items()
@@ -737,7 +735,8 @@ def fields(self) -> GraphQLFieldMap:
737735
try:
738736
fields = resolve_thunk(self._fields)
739737
except Exception as error:
740-
raise TypeError(f"{self.name} fields cannot be resolved. {error}")
738+
cls = GraphQLError if isinstance(error, GraphQLError) else TypeError
739+
raise cls(f"{self.name} fields cannot be resolved. {error}") from error
741740
if not isinstance(fields, Mapping) or not all(
742741
isinstance(key, str) for key in fields
743742
):
@@ -753,7 +752,7 @@ def fields(self) -> GraphQLFieldMap:
753752
f"{self.name} fields must be GraphQLField or output type objects."
754753
)
755754
return {
756-
name: value
755+
assert_name(name): value
757756
if isinstance(value, GraphQLField)
758757
else GraphQLField(value) # type: ignore
759758
for name, value in fields.items()
@@ -767,7 +766,8 @@ def interfaces(self) -> List["GraphQLInterfaceType"]:
767766
self._interfaces # type: ignore
768767
)
769768
except Exception as error:
770-
raise TypeError(f"{self.name} interfaces cannot be resolved. {error}")
769+
cls = GraphQLError if isinstance(error, GraphQLError) else TypeError
770+
raise cls(f"{self.name} interfaces cannot be resolved. {error}") from error
771771
if interfaces is None:
772772
interfaces = []
773773
elif not is_collection(interfaces) or not all(
@@ -862,7 +862,8 @@ def fields(self) -> GraphQLFieldMap:
862862
try:
863863
fields = resolve_thunk(self._fields)
864864
except Exception as error:
865-
raise TypeError(f"{self.name} fields cannot be resolved. {error}")
865+
cls = GraphQLError if isinstance(error, GraphQLError) else TypeError
866+
raise cls(f"{self.name} fields cannot be resolved. {error}") from error
866867
if not isinstance(fields, Mapping) or not all(
867868
isinstance(key, str) for key in fields
868869
):
@@ -878,7 +879,7 @@ def fields(self) -> GraphQLFieldMap:
878879
f"{self.name} fields must be GraphQLField or output type objects."
879880
)
880881
return {
881-
name: value
882+
assert_name(name): value
882883
if isinstance(value, GraphQLField)
883884
else GraphQLField(value) # type: ignore
884885
for name, value in fields.items()
@@ -892,7 +893,8 @@ def interfaces(self) -> List["GraphQLInterfaceType"]:
892893
self._interfaces # type: ignore
893894
)
894895
except Exception as error:
895-
raise TypeError(f"{self.name} interfaces cannot be resolved. {error}")
896+
cls = GraphQLError if isinstance(error, GraphQLError) else TypeError
897+
raise cls(f"{self.name} interfaces cannot be resolved. {error}") from error
896898
if interfaces is None:
897899
interfaces = []
898900
elif not is_collection(interfaces) or not all(
@@ -985,7 +987,8 @@ def types(self) -> List[GraphQLObjectType]:
985987
try:
986988
types: Collection[GraphQLObjectType] = resolve_thunk(self._types)
987989
except Exception as error:
988-
raise TypeError(f"{self.name} types cannot be resolved. {error}")
990+
cls = GraphQLError if isinstance(error, GraphQLError) else TypeError
991+
raise cls(f"{self.name} types cannot be resolved. {error}") from error
989992
if types is None:
990993
types = []
991994
elif not is_collection(types) or not all(
@@ -1081,7 +1084,7 @@ def __init__(
10811084
values = cast(Dict, values)
10821085
values = {key: value.value for key, value in values.items()}
10831086
values = {
1084-
key: value
1087+
assert_enum_value_name(key): value
10851088
if isinstance(value, GraphQLEnumValue)
10861089
else GraphQLEnumValue(value)
10871090
for key, value in values.items()
@@ -1338,7 +1341,8 @@ def fields(self) -> GraphQLInputFieldMap:
13381341
try:
13391342
fields = resolve_thunk(self._fields)
13401343
except Exception as error:
1341-
raise TypeError(f"{self.name} fields cannot be resolved. {error}")
1344+
cls = GraphQLError if isinstance(error, GraphQLError) else TypeError
1345+
raise cls(f"{self.name} fields cannot be resolved. {error}") from error
13421346
if not isinstance(fields, Mapping) or not all(
13431347
isinstance(key, str) for key in fields
13441348
):
@@ -1355,7 +1359,7 @@ def fields(self) -> GraphQLInputFieldMap:
13551359
" GraphQLInputField or input type objects."
13561360
)
13571361
return {
1358-
name: value
1362+
assert_name(name): value
13591363
if isinstance(value, GraphQLInputField)
13601364
else GraphQLInputField(value) # type: ignore
13611365
for name, value in fields.items()

src/graphql/type/directives.py

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
from ..language import ast, DirectiveLocation
44
from ..pyutils import inspect, is_description, FrozenList
5+
from .assert_name import assert_name
56
from .definition import GraphQLArgument, GraphQLInputType, GraphQLNonNull, is_input_type
67
from .scalars import GraphQLBoolean, GraphQLString
78

@@ -45,10 +46,7 @@ def __init__(
4546
extensions: Optional[Dict[str, Any]] = None,
4647
ast_node: Optional[ast.DirectiveDefinitionNode] = None,
4748
) -> None:
48-
if not name:
49-
raise TypeError("Directive must be named.")
50-
elif not isinstance(name, str):
51-
raise TypeError("The directive name must be a string.")
49+
assert_name(name)
5250
try:
5351
locations = [
5452
value
@@ -76,7 +74,7 @@ def __init__(
7674
)
7775
else:
7876
args = {
79-
name: value
77+
assert_name(name): value
8078
if isinstance(value, GraphQLArgument)
8179
else GraphQLArgument(cast(GraphQLInputType, value))
8280
for name, value in args.items()

src/graphql/type/validate.py

Lines changed: 8 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
cast,
1212
)
1313

14-
from ..error import GraphQLError, located_error
14+
from ..error import GraphQLError
1515
from ..pyutils import inspect
1616
from ..language import (
1717
DirectiveNode,
@@ -41,7 +41,6 @@
4141
is_required_argument,
4242
is_required_input_field,
4343
)
44-
from ..utilities.assert_valid_name import is_valid_name_error
4544
from ..utilities.type_comparators import is_equal_type, is_type_sub_type_of
4645
from .directives import is_directive, GraphQLDeprecatedDirective
4746
from .introspection import is_introspection_type
@@ -109,10 +108,7 @@ def report_error(
109108
if nodes and not isinstance(nodes, Node):
110109
nodes = [node for node in nodes if node]
111110
nodes = cast(Optional[Collection[Node]], nodes)
112-
self.add_error(GraphQLError(message, nodes))
113-
114-
def add_error(self, error: GraphQLError) -> None:
115-
self.errors.append(error)
111+
self.errors.append(GraphQLError(message, nodes))
116112

117113
def validate_root_types(self) -> None:
118114
schema = self.schema
@@ -191,9 +187,12 @@ def validate_name(self, node: Any, name: Optional[str] = None) -> None:
191187
except AttributeError: # pragma: no cover
192188
pass
193189
else:
194-
error = is_valid_name_error(name)
195-
if error:
196-
self.add_error(located_error(error, ast_node))
190+
if name.startswith("__"):
191+
self.report_error(
192+
f"Name {name!r} must not begin with '__',"
193+
" which is reserved by GraphQL introspection.",
194+
ast_node,
195+
)
197196

198197
def validate_types(self) -> None:
199198
validate_input_object_circular_refs = InputObjectCircularRefsValidator(self)
@@ -457,12 +456,6 @@ def validate_enum_values(self, enum_type: GraphQLEnumType) -> None:
457456
for value_name, enum_value in enum_values.items():
458457
# Ensure valid name.
459458
self.validate_name(enum_value, value_name)
460-
if value_name in ("true", "false", "null"):
461-
self.report_error(
462-
f"Enum type {enum_type.name} cannot include value:"
463-
f" {value_name}.",
464-
enum_value.ast_node,
465-
)
466459

467460
def validate_input_fields(self, input_obj: GraphQLInputObjectType) -> None:
468461
fields = input_obj.fields
Lines changed: 15 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,43 +1,38 @@
1-
import re
21
from typing import Optional
32

4-
from ..language.character_classes import is_name_start, is_name_continue
3+
from ..type.assert_name import assert_name
54
from ..error import GraphQLError
65

76
__all__ = ["assert_valid_name", "is_valid_name_error"]
87

98

10-
re_name = re.compile("^[_a-zA-Z][_a-zA-Z0-9]*$")
11-
12-
139
def assert_valid_name(name: str) -> str:
14-
"""Uphold the spec rules about naming."""
10+
"""Uphold the spec rules about naming.
11+
12+
.. deprecated:: 3.2
13+
Please use ``assert_name`` instead. Will be removed in v3.3.
14+
"""
1515
error = is_valid_name_error(name)
1616
if error:
1717
raise error
1818
return name
1919

2020

2121
def is_valid_name_error(name: str) -> Optional[GraphQLError]:
22-
"""Return an Error if a name is invalid."""
22+
"""Return an Error if a name is invalid.
23+
24+
.. deprecated:: 3.2
25+
Please use ``assert_name`` instead. Will be removed in v3.3.
26+
"""
2327
if not isinstance(name, str):
2428
raise TypeError("Expected name to be a string.")
25-
2629
if name.startswith("__"):
2730
return GraphQLError(
2831
f"Name {name!r} must not begin with '__',"
2932
" which is reserved by GraphQL introspection."
3033
)
31-
32-
if not name:
33-
return GraphQLError("Expected name to be a non-empty string.")
34-
35-
if not all(is_name_continue(char) for char in name[1:]):
36-
return GraphQLError(
37-
f"Names must only contain [_a-zA-Z0-9] but {name!r} does not."
38-
)
39-
40-
if not is_name_start(name[0]):
41-
return GraphQLError(f"Names must start with [_a-zA-Z] but {name!r} does not.")
42-
34+
try:
35+
assert_name(name)
36+
except GraphQLError as error:
37+
return error
4338
return None

tests/pyutils/test_description.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -107,15 +107,15 @@ def graphql_named_type():
107107
named_type = GraphQLNamedType(name="Foo", description="not lazy")
108108
assert named_type.name == "Foo"
109109
assert named_type.description == "not lazy"
110-
with raises(TypeError, match="The name must be a string\\."):
110+
with raises(TypeError, match="Expected name to be a string\\."):
111111
GraphQLNamedType(name=lazy_string)
112112
with raises(TypeError, match="The description must be a string\\."):
113113
GraphQLNamedType(name="Foo", description=lazy_string)
114114
with registered(LazyString):
115115
named_type = GraphQLNamedType(name="Foo", description=lazy_string)
116116
assert named_type.description is lazy_string
117117
assert str(named_type.description).endswith("lazy?")
118-
with raises(TypeError, match="The name must be a string\\."):
118+
with raises(TypeError, match="Expected name to be a string\\."):
119119
GraphQLNamedType(name=lazy_string)
120120

121121
def graphql_field():
@@ -185,15 +185,15 @@ def graphql_directive():
185185
directive = GraphQLDirective("Foo", [], description="not lazy")
186186
assert directive.name == "Foo"
187187
assert directive.description == "not lazy"
188-
with raises(TypeError, match="The directive name must be a string\\."):
188+
with raises(TypeError, match="Expected name to be a string\\."):
189189
GraphQLDirective(lazy_string, [])
190190
with raises(TypeError, match="Foo description must be a string\\."):
191191
GraphQLDirective("Foo", [], description=lazy_string)
192192
with registered(LazyString):
193193
directive = GraphQLDirective("Foo", [], description=lazy_string)
194194
assert directive.description is lazy_string
195195
assert str(directive.description).endswith("lazy?")
196-
with raises(TypeError, match="The directive name must be a string\\."):
196+
with raises(TypeError, match="Expected name to be a string\\."):
197197
GraphQLDirective(lazy_string, [])
198198

199199
def introspection():

0 commit comments

Comments
 (0)