Skip to content

Commit 3be333a

Browse files
authored
Only reuse memoized default values for the type they were coerced to (#279)
1 parent e46bc83 commit 3be333a

4 files changed

Lines changed: 50 additions & 8 deletions

File tree

‎src/graphql/type/definition.py‎

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1254,16 +1254,16 @@ class GraphQLDefaultInput: # noqa: PLW1641
12541254
literal: ConstValueNode | None
12551255
"""GraphQL literal default value, or None if a runtime value is provided instead."""
12561256

1257-
__slots__ = "_memoized_coerced_value", "literal", "value"
1257+
__slots__ = "_memoized_coercion", "literal", "value"
12581258

12591259
def __init__(
12601260
self, value: Any = Undefined, literal: ConstValueNode | None = None
12611261
) -> None:
12621262
self.value = value
12631263
self.literal = literal
1264-
# Used to memoize the result of coercing the default value (see
1265-
# coerce_default_value() in the utilities).
1266-
self._memoized_coerced_value: Any = Undefined
1264+
# Used to memoize the result of coercing the default value, together with
1265+
# the type it was coerced to (see coerce_default_value() in the utilities).
1266+
self._memoized_coercion: tuple[GraphQLInputType | None, Any] = None, Undefined
12671267

12681268
def __eq__(self, other: object) -> bool:
12691269
return self is other or (

‎src/graphql/utilities/coerce_input_value.py‎

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -355,10 +355,12 @@ def coerce_default_value(
355355
# field on the GraphQLDefaultInput object. (Contrary to GraphQL.js, which
356356
# memoizes on the input value itself, this also works for the immutable
357357
# variable signatures that reuse this function for fragment arguments.)
358+
# It is only reused for the same type, as a default can be used with
359+
# different types, e.g. after extend_schema() replaced the type.
358360
default_input = input_value.default
359361
if default_input is not None:
360-
coerced_value = default_input._memoized_coerced_value # noqa: SLF001
361-
if coerced_value is Undefined:
362+
coerced_type, coerced_value = default_input._memoized_coercion # noqa: SLF001
363+
if coerced_type is not input_value.type:
362364
coerced_value = (
363365
coerce_input_literal(default_input.literal, input_value.type)
364366
if default_input.literal is not None
@@ -375,7 +377,7 @@ def coerce_default_value(
375377
f" to be valid, found: {found}."
376378
)
377379
raise TypeError(msg)
378-
default_input._memoized_coerced_value = coerced_value # noqa: SLF001
380+
default_input._memoized_coercion = input_value.type, coerced_value # noqa: SLF001
379381
return coerced_value
380382

381383
# The deprecated internal default value is used as is.

‎tests/utilities/test_coerce_input_value.py‎

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -716,3 +716,14 @@ def coerce_input_value(value):
716716
# Call a second time
717717
assert coerce_default_value(input_value) == "hello"
718718
assert coerce_input_value_calls == ["hello"]
719+
720+
def memoizes_coercion_per_type():
721+
color_enum = GraphQLEnumType("Color", {"RED": 0, "BLUE": 1})
722+
shared_default = GraphQLDefaultInput("RED")
723+
724+
color_value = GraphQLInputField(color_enum, default=shared_default)
725+
string_value = GraphQLInputField(GraphQLString, default=shared_default)
726+
727+
assert coerce_default_value(color_value) == 0
728+
assert coerce_default_value(string_value) == "RED"
729+
assert coerce_default_value(color_value) == 0

‎tests/utilities/test_extend_schema.py‎

Lines changed: 30 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44

55
import pytest
66

7-
from graphql import graphql_sync
7+
from graphql import ExecutionResult, graphql_sync
88
from graphql.error import GraphQLSyntaxError
99
from graphql.language import parse, print_ast
1010
from graphql.type import (
@@ -383,6 +383,35 @@ def extends_inputs_by_adding_new_fields():
383383
),
384384
)
385385

386+
def coerces_default_values_with_extended_input_types():
387+
schema = build_schema(
388+
"""
389+
type Query {
390+
someInput(arg: SomeInput = {}): String
391+
}
392+
393+
input SomeInput {
394+
oldField: String
395+
}
396+
"""
397+
)
398+
extend_ast = parse(
399+
"""
400+
extend input SomeInput {
401+
newField: String = "new"
402+
}
403+
"""
404+
)
405+
extended_schema = extend_schema(schema, extend_ast)
406+
root_value = {"someInput": lambda _info, arg: str(arg)}
407+
408+
def query(schema: GraphQLSchema) -> ExecutionResult:
409+
return graphql_sync(schema, "{ someInput }", root_value)
410+
411+
assert query(schema) == ({"someInput": "{}"}, None)
412+
assert query(extended_schema) == ({"someInput": "{'newField': 'new'}"}, None)
413+
assert query(schema) == ({"someInput": "{}"}, None)
414+
386415
def extends_scalars_by_adding_new_directives():
387416
schema = build_schema(
388417
"""

0 commit comments

Comments
 (0)