Skip to content

Commit 7bb0103

Browse files
committed
Make parse_literal optional and use wrapped parse_value by default
Replicates graphql/graphql-js@f2c3e96
1 parent b55b4f3 commit 7bb0103

File tree

4 files changed

+71
-43
lines changed

4 files changed

+71
-43
lines changed

graphql/type/definition.py

Lines changed: 51 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -304,14 +304,6 @@ def serialize_odd(value):
304304
305305
"""
306306

307-
# Serializes an internal value to include in a response.
308-
serialize: GraphQLScalarSerializer
309-
# Parses an externally provided value to use as an input.
310-
parse_value: GraphQLScalarValueParser
311-
# Parses an externally provided literal value to use as an input.
312-
# Takes a dictionary of variables as an optional second argument.
313-
parse_literal: GraphQLScalarLiteralParser
314-
315307
ast_node: Optional[ScalarTypeDefinitionNode]
316308
extension_ast_nodes: Optional[Tuple[ScalarTypeExtensionNode]]
317309

@@ -333,17 +325,19 @@ def __init__(
333325
)
334326
if serialize is not None and not callable(serialize):
335327
raise TypeError(
336-
f"{name} must provide 'serialize' function."
328+
f"{name} must provide 'serialize' as a function."
337329
" If this custom Scalar is also used as an input type,"
338330
" ensure 'parse_value' and 'parse_literal' functions"
339331
" are also provided."
340332
)
341-
if parse_value is not None or parse_literal is not None:
342-
if not callable(parse_value) or not callable(parse_literal):
343-
raise TypeError(
344-
f"{name} must provide"
345-
" both 'parse_value' and 'parse_literal' functions."
346-
)
333+
if parse_literal is not None and (
334+
not callable(parse_literal)
335+
or (parse_value is None or not callable(parse_value))
336+
):
337+
raise TypeError(
338+
f"{name} must provide"
339+
" both 'parse_value' and 'parse_literal' as functions."
340+
)
347341
if ast_node and not isinstance(ast_node, ScalarTypeDefinitionNode):
348342
raise TypeError(f"{name} AST node must be a ScalarTypeDefinitionNode.")
349343
if extension_ast_nodes and not all(
@@ -352,23 +346,59 @@ def __init__(
352346
raise TypeError(
353347
f"{name} extension AST nodes must be ScalarTypeExtensionNode."
354348
)
355-
self.serialize = serialize or identity_func # type: ignore
356-
self.parse_value = parse_value or identity_func # type: ignore
357-
self.parse_literal = parse_literal or value_from_ast_untyped # type: ignore
349+
if serialize is not None:
350+
self.serialize = serialize
351+
if parse_value is not None:
352+
self.parse_value = parse_value
353+
if parse_literal is not None:
354+
self.parse_literal = parse_literal
358355

359356
def __repr__(self):
360357
return f"<{self.__class__.__name__} {self.name!r}>"
361358

362359
def __str__(self):
363360
return self.name
364361

362+
@staticmethod
363+
def serialize(value: Any):
364+
"""Serializes an internal value to include in a response.
365+
366+
This default method just passes the value through and should be replaced
367+
with a more specific version when creating a scalar type.
368+
"""
369+
return value
370+
371+
@staticmethod
372+
def parse_value(value: Any):
373+
"""Parses an externally provided value to use as an input.
374+
375+
This default method just passes the value through and should be replaced
376+
with a more specific version when creating a scalar type.
377+
"""
378+
return value
379+
380+
def parse_literal( # type: ignore
381+
self, node: ValueNode, _variables: Dict[str, Any] = None
382+
):
383+
"""Parses an externally provided literal value to use as an input.
384+
385+
This default method uses the parse_value method and should be replaced
386+
with a more specific version when creating a scalar type.
387+
"""
388+
return self.parse_value(value_from_ast_untyped(node))
389+
365390
def to_kwargs(self) -> Dict[str, Any]:
366391
return dict(
367392
**super().to_kwargs(),
368-
serialize=None if self.serialize is identity_func else self.serialize,
369-
parse_value=None if self.parse_value is identity_func else self.parse_value,
393+
serialize=None
394+
if self.serialize is GraphQLScalarType.serialize
395+
else self.serialize,
396+
parse_value=None
397+
if self.parse_value is GraphQLScalarType.parse_value
398+
else self.parse_value,
370399
parse_literal=None
371-
if self.parse_literal is value_from_ast_untyped
400+
if getattr(self.parse_literal, "__func__")
401+
is GraphQLScalarType.parse_literal
372402
else self.parse_literal,
373403
)
374404

tests/type/test_definition.py

Lines changed: 18 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
from pytest import mark, raises
44

55
from graphql.error import INVALID
6-
from graphql.language import Node, InputValueDefinitionNode
6+
from graphql.language import parse_value, Node, InputValueDefinitionNode
77
from graphql.pyutils import identity_func
88
from graphql.type import (
99
GraphQLArgument,
@@ -21,7 +21,6 @@
2121
GraphQLString,
2222
GraphQLUnionType,
2323
)
24-
from graphql.utilities import value_from_ast_untyped
2524

2625
ScalarType = GraphQLScalarType("Scalar")
2726
ObjectType = GraphQLObjectType("Object", {})
@@ -48,43 +47,45 @@ def accepts_a_scalar_type_defining_parse_value_and_parse_literal():
4847
def provides_default_methods_if_omitted():
4948
scalar = GraphQLScalarType("Foo")
5049

51-
assert scalar.serialize is identity_func
52-
assert scalar.parse_value is identity_func
53-
assert scalar.parse_literal is value_from_ast_untyped
50+
assert scalar.serialize is GraphQLScalarType.serialize
51+
assert scalar.parse_value is GraphQLScalarType.parse_value
52+
assert scalar.parse_literal.__func__ is GraphQLScalarType.parse_literal
5453

5554
kwargs = scalar.to_kwargs()
5655
assert kwargs["serialize"] is None
5756
assert kwargs["parse_value"] is None
5857
assert kwargs["parse_literal"] is None
5958

59+
def use_parse_value_for_parsing_literals_if_parse_literal_omitted():
60+
scalar = GraphQLScalarType(
61+
"Foo", parse_value=lambda value: f"parse_value: {value!r}"
62+
)
63+
64+
assert scalar.parse_literal(parse_value("null")) == "parse_value: None"
65+
assert (
66+
scalar.parse_literal(parse_value('{foo: "bar"}'))
67+
== "parse_value: {'foo': 'bar'}"
68+
)
69+
6070
def rejects_a_scalar_type_defining_serialize_with_incorrect_type():
6171
with raises(TypeError) as exc_info:
6272
# noinspection PyTypeChecker
6373
GraphQLScalarType("SomeScalar", {})
6474
msg = str(exc_info.value)
6575
assert msg == (
66-
"SomeScalar must provide 'serialize' function."
76+
"SomeScalar must provide 'serialize' as a function."
6777
" If this custom Scalar is also used as an input type,"
6878
" ensure 'parse_value' and 'parse_literal' functions"
6979
" are also provided."
7080
)
7181

72-
def rejects_a_scalar_type_defining_parse_value_but_not_parse_literal():
73-
with raises(TypeError) as exc_info:
74-
GraphQLScalarType("SomeScalar", parse_value=lambda: None)
75-
msg = str(exc_info.value)
76-
assert msg == (
77-
"SomeScalar must provide both"
78-
" 'parse_value' and 'parse_literal' functions."
79-
)
80-
8182
def rejects_a_scalar_type_defining_parse_literal_but_not_parse_value():
8283
with raises(TypeError) as exc_info:
8384
GraphQLScalarType("SomeScalar", parse_literal=lambda: None)
8485
msg = str(exc_info.value)
8586
assert msg == (
8687
"SomeScalar must provide both"
87-
" 'parse_value' and 'parse_literal' functions."
88+
" 'parse_value' and 'parse_literal' as functions."
8889
)
8990

9091
def rejects_a_scalar_type_incorrectly_defining_parse_literal_and_value():
@@ -94,7 +95,7 @@ def rejects_a_scalar_type_incorrectly_defining_parse_literal_and_value():
9495
msg = str(exc_info.value)
9596
assert msg == (
9697
"SomeScalar must provide both"
97-
" 'parse_value' and 'parse_literal' functions."
98+
" 'parse_value' and 'parse_literal' as functions."
9899
)
99100

100101

tests/validation/harness.py

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
from typing import List, Optional, Type
22

33
from graphql.error import GraphQLError
4-
from graphql.language import parse, print_ast
4+
from graphql.language import parse
55
from graphql.type import (
66
GraphQLArgument,
77
GraphQLBoolean,
@@ -230,9 +230,6 @@ def raise_type_error(message):
230230

231231
InvalidScalar = GraphQLScalarType(
232232
name="Invalid",
233-
parse_literal=lambda value_node: raise_type_error(
234-
f"Invalid scalar is always invalid: {print_ast(value_node)}"
235-
),
236233
parse_value=lambda value: raise_type_error(
237234
f"Invalid scalar is always invalid: {value!r}"
238235
),

tests/validation/test_validation.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ def detects_bad_scalar_parse():
3838
assert errors == [
3939
{
4040
"message": 'Expected type Invalid, found "bad value";'
41-
' Invalid scalar is always invalid: "bad value"',
41+
" Invalid scalar is always invalid: 'bad value'",
4242
"locations": [(3, 31)],
4343
}
4444
]

0 commit comments

Comments
 (0)