Skip to content

Commit

Permalink
Pass deprecation reason to graphql core schema construction
Browse files Browse the repository at this point in the history
  • Loading branch information
vhutov committed Nov 27, 2022
1 parent ccdd35b commit f6fc761
Show file tree
Hide file tree
Showing 11 changed files with 108 additions and 12 deletions.
17 changes: 13 additions & 4 deletions graphene/types/argument.py
Expand Up @@ -31,18 +31,22 @@ class Argument(MountedType):
type (class for a graphene.UnmountedType): must be a class (not an instance) of an
unmounted graphene type (ex. scalar or object) which is used for the type of this
argument in the GraphQL schema.
required (bool): indicates this argument as not null in the graphql schema. Same behavior
required (optional, bool): indicates this argument as not null in the graphql schema. Same behavior
as graphene.NonNull. Default False.
name (str): the name of the GraphQL argument. Defaults to parameter name.
description (str): the description of the GraphQL argument in the schema.
default_value (Any): The value to be provided if the user does not set this argument in
name (optional, str): the name of the GraphQL argument. Defaults to parameter name.
description (optional, str): the description of the GraphQL argument in the schema.
default_value (optional, Any): The value to be provided if the user does not set this argument in
the operation.
deprecation_reason (optional, str): Setting this value indicates that the argument is
depreciated and may provide instruction or reason on how for clients to proceed. Cannot be
set if the argument is required (see spec).
"""

def __init__(
self,
type_,
default_value=Undefined,
deprecation_reason=None,
description=None,
name=None,
required=False,
Expand All @@ -51,12 +55,16 @@ def __init__(
super(Argument, self).__init__(_creation_counter=_creation_counter)

if required:
assert (
deprecation_reason is None
), f"Argument {name} is required, cannot deprecate it."
type_ = NonNull(type_)

self.name = name
self._type = type_
self.default_value = default_value
self.description = description
self.deprecation_reason = deprecation_reason

@property
def type(self):
Expand All @@ -68,6 +76,7 @@ def __eq__(self, other):
and self.type == other.type
and self.default_value == other.default_value
and self.description == other.description
and self.deprecation_reason == other.deprecation_reason
)


Expand Down
3 changes: 3 additions & 0 deletions graphene/types/field.py
Expand Up @@ -89,6 +89,9 @@ def __init__(
), f'The default value can not be a function but received "{base_type(default_value)}".'

if required:
assert (
deprecation_reason is None
), f"Field {name} is required, cannot deprecate it."
type_ = NonNull(type_)

# Check if name is actually an argument of the field
Expand Down
5 changes: 4 additions & 1 deletion graphene/types/inputfield.py
Expand Up @@ -55,11 +55,14 @@ def __init__(
description=None,
required=False,
_creation_counter=None,
**extra_args
**extra_args,
):
super(InputField, self).__init__(_creation_counter=_creation_counter)
self.name = name
if required:
assert (
deprecation_reason is None
), f"InputField {name} is required, cannot deprecate it."
type_ = NonNull(type_)
self._type = type_
self.deprecation_reason = deprecation_reason
Expand Down
1 change: 1 addition & 0 deletions graphene/types/mountedtype.py
Expand Up @@ -8,6 +8,7 @@ def mounted(cls, unmounted): # noqa: N802
"""
Mount the UnmountedType instance
"""

assert isinstance(
unmounted, UnmountedType
), f"{cls.__name__} can't mount {repr(unmounted)}"
Expand Down
2 changes: 2 additions & 0 deletions graphene/types/schema.py
Expand Up @@ -309,6 +309,7 @@ def create_fields_for_type(self, graphene_type, is_input_type=False):
default_value=field.default_value,
out_name=name,
description=field.description,
deprecation_reason=field.deprecation_reason,
)
else:
args = {}
Expand All @@ -320,6 +321,7 @@ def create_fields_for_type(self, graphene_type, is_input_type=False):
out_name=arg_name,
description=arg.description,
default_value=arg.default_value,
deprecation_reason=arg.deprecation_reason,
)
subscribe = field.wrap_subscribe(
self.get_function_for_type(
Expand Down
40 changes: 38 additions & 2 deletions graphene/types/tests/test_argument.py
Expand Up @@ -18,8 +18,20 @@ def test_argument():


def test_argument_comparasion():
arg1 = Argument(String, name="Hey", description="Desc", default_value="default")
arg2 = Argument(String, name="Hey", description="Desc", default_value="default")
arg1 = Argument(
String,
name="Hey",
description="Desc",
default_value="default",
deprecation_reason="deprecated",
)
arg2 = Argument(
String,
name="Hey",
description="Desc",
default_value="default",
deprecation_reason="deprecated",
)

assert arg1 == arg2
assert arg1 != String()
Expand All @@ -40,6 +52,30 @@ def test_to_arguments():
}


def test_to_arguments_deprecated():
args = {"unmounted_arg": String(required=False, deprecation_reason="deprecated")}

my_args = to_arguments(args)
assert my_args == {
"unmounted_arg": Argument(
String, required=False, deprecation_reason="deprecated"
),
}


def test_to_arguments_required_deprecated():
args = {
"unmounted_arg": String(
required=True, name="arg", deprecation_reason="deprecated"
)
}

with raises(AssertionError) as exc_info:
to_arguments(args)

assert str(exc_info.value) == "Argument arg is required, cannot deprecate it."


def test_to_arguments_raises_if_field():
args = {"arg_string": Field(String)}

Expand Down
17 changes: 16 additions & 1 deletion graphene/types/tests/test_field.py
Expand Up @@ -51,6 +51,14 @@ def test_field_required():
assert field.type.of_type == MyType


def test_field_required_deprecated():
MyType = object()
with raises(AssertionError) as exc_info:
Field(MyType, name="field", required=True, deprecation_reason="deprecated")

assert str(exc_info.value) == "Field field is required, cannot deprecate it."


def test_field_default_value_not_callable():
MyType = object()
try:
Expand Down Expand Up @@ -128,13 +136,20 @@ def test_field_name_as_argument():

def test_field_source_argument_as_kw():
MyType = object()
field = Field(MyType, b=NonNull(True), c=Argument(None), a=NonNull(False))
deprecation_reason = "deprecated"
field = Field(
MyType,
b=NonNull(True),
c=Argument(None, deprecation_reason=deprecation_reason),
a=NonNull(False),
)
assert list(field.args) == ["b", "c", "a"]
assert isinstance(field.args["b"], Argument)
assert isinstance(field.args["b"].type, NonNull)
assert field.args["b"].type.of_type is True
assert isinstance(field.args["c"], Argument)
assert field.args["c"].type is None
assert field.args["c"].deprecation_reason == deprecation_reason
assert isinstance(field.args["a"], Argument)
assert isinstance(field.args["a"].type, NonNull)
assert field.args["a"].type.of_type is False
18 changes: 18 additions & 0 deletions graphene/types/tests/test_inputfield.py
@@ -1,5 +1,7 @@
from functools import partial

from pytest import raises

from ..inputfield import InputField
from ..structures import NonNull
from .utils import MyLazyType
Expand All @@ -12,6 +14,22 @@ def test_inputfield_required():
assert field.type.of_type == MyType


def test_inputfield_deprecated():
MyType = object()
deprecation_reason = "deprecated"
field = InputField(MyType, required=False, deprecation_reason=deprecation_reason)
assert isinstance(field.type, type(MyType))
assert field.deprecation_reason == deprecation_reason


def test_inputfield_required_deprecated():
MyType = object()
with raises(AssertionError) as exc_info:
InputField(MyType, name="input", required=True, deprecation_reason="deprecated")

assert str(exc_info.value) == "InputField input is required, cannot deprecate it."


def test_inputfield_with_lazy_type():
MyType = object()
field = InputField(lambda: MyType)
Expand Down
5 changes: 2 additions & 3 deletions graphene/types/tests/test_mutation.py
Expand Up @@ -6,7 +6,6 @@
from ..objecttype import ObjectType
from ..scalars import String
from ..schema import Schema
from ..structures import NonNull
from ..interface import Interface


Expand Down Expand Up @@ -149,14 +148,14 @@ class MyMutation(ObjectType):
name="createUser",
description="Create a user",
deprecation_reason="Is deprecated",
required=True,
required=False,
)

field = MyMutation._meta.fields["create_user"]
assert field.name == "createUser"
assert field.description == "Create a user"
assert field.deprecation_reason == "Is deprecated"
assert field.type == NonNull(CreateUser)
assert field.type == CreateUser


def test_mutation_default_args_output():
Expand Down
10 changes: 10 additions & 0 deletions graphene/types/unmountedtype.py
Expand Up @@ -35,6 +35,8 @@ class MyObjectType(ObjectType):
An unmounted type will accept arguments based upon its context (ObjectType, Field or
InputObjectType) and pass it on to the appropriate MountedType (Field, Argument or InputField).
Unmounted types which inherit SubclassWithMeta also pass some Meta values as arguments.
Precendence is kwargs > meta_args.
See each Mounted type reference for more information about valid parameters.
"""
Expand Down Expand Up @@ -85,3 +87,11 @@ def __eq__(self, other):
and self.args == other.args
and self.kwargs == other.kwargs
)

@classmethod
def mountable_meta(cls):
"""
Classes can use this method to select which meta fields
can be inherited when mounting.
"""
return tuple()
2 changes: 1 addition & 1 deletion graphene/validation/depth_limit.py
Expand Up @@ -53,7 +53,7 @@
def depth_limit_validator(
max_depth: int,
ignore: Optional[List[IgnoreType]] = None,
callback: Callable[[Dict[str, int]], None] = None,
callback: Optional[Callable[[Dict[str, int]], None]] = None,
):
class DepthLimitValidator(ValidationRule):
def __init__(self, validation_context: ValidationContext):
Expand Down

0 comments on commit f6fc761

Please sign in to comment.