Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions src/graphql/type/definition.py
Original file line number Diff line number Diff line change
Expand Up @@ -1254,16 +1254,16 @@ class GraphQLDefaultInput: # noqa: PLW1641
literal: ConstValueNode | None
"""GraphQL literal default value, or None if a runtime value is provided instead."""

__slots__ = "_memoized_coerced_value", "literal", "value"
__slots__ = "_memoized_coercion", "literal", "value"

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

def __eq__(self, other: object) -> bool:
return self is other or (
Expand Down
8 changes: 5 additions & 3 deletions src/graphql/utilities/coerce_input_value.py
Original file line number Diff line number Diff line change
Expand Up @@ -355,10 +355,12 @@ def coerce_default_value(
# field on the GraphQLDefaultInput object. (Contrary to GraphQL.js, which
# memoizes on the input value itself, this also works for the immutable
# variable signatures that reuse this function for fragment arguments.)
# It is only reused for the same type, as a default can be used with
# different types, e.g. after extend_schema() replaced the type.
default_input = input_value.default
if default_input is not None:
coerced_value = default_input._memoized_coerced_value # noqa: SLF001
if coerced_value is Undefined:
coerced_type, coerced_value = default_input._memoized_coercion # noqa: SLF001
if coerced_type is not input_value.type:
coerced_value = (
coerce_input_literal(default_input.literal, input_value.type)
if default_input.literal is not None
Expand All @@ -375,7 +377,7 @@ def coerce_default_value(
f" to be valid, found: {found}."
)
raise TypeError(msg)
default_input._memoized_coerced_value = coerced_value # noqa: SLF001
default_input._memoized_coercion = input_value.type, coerced_value # noqa: SLF001
return coerced_value

# The deprecated internal default value is used as is.
Expand Down
11 changes: 11 additions & 0 deletions tests/utilities/test_coerce_input_value.py
Original file line number Diff line number Diff line change
Expand Up @@ -716,3 +716,14 @@ def coerce_input_value(value):
# Call a second time
assert coerce_default_value(input_value) == "hello"
assert coerce_input_value_calls == ["hello"]

def memoizes_coercion_per_type():
color_enum = GraphQLEnumType("Color", {"RED": 0, "BLUE": 1})
shared_default = GraphQLDefaultInput("RED")

color_value = GraphQLInputField(color_enum, default=shared_default)
string_value = GraphQLInputField(GraphQLString, default=shared_default)

assert coerce_default_value(color_value) == 0
assert coerce_default_value(string_value) == "RED"
assert coerce_default_value(color_value) == 0
31 changes: 30 additions & 1 deletion tests/utilities/test_extend_schema.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

import pytest

from graphql import graphql_sync
from graphql import ExecutionResult, graphql_sync
from graphql.error import GraphQLSyntaxError
from graphql.language import parse, print_ast
from graphql.type import (
Expand Down Expand Up @@ -383,6 +383,35 @@ def extends_inputs_by_adding_new_fields():
),
)

def coerces_default_values_with_extended_input_types():
schema = build_schema(
"""
type Query {
someInput(arg: SomeInput = {}): String
}

input SomeInput {
oldField: String
}
"""
)
extend_ast = parse(
"""
extend input SomeInput {
newField: String = "new"
}
"""
)
extended_schema = extend_schema(schema, extend_ast)
root_value = {"someInput": lambda _info, arg: str(arg)}

def query(schema: GraphQLSchema) -> ExecutionResult:
return graphql_sync(schema, "{ someInput }", root_value)

assert query(schema) == ({"someInput": "{}"}, None)
assert query(extended_schema) == ({"someInput": "{'newField': 'new'}"}, None)
assert query(schema) == ({"someInput": "{}"}, None)

def extends_scalars_by_adding_new_directives():
schema = build_schema(
"""
Expand Down
Loading