diff --git a/graphene/types/argument.py b/graphene/types/argument.py index f9dc843bf..d9283c416 100644 --- a/graphene/types/argument.py +++ b/graphene/types/argument.py @@ -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, @@ -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): @@ -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 ) diff --git a/graphene/types/field.py b/graphene/types/field.py index dafb04b53..563707f58 100644 --- a/graphene/types/field.py +++ b/graphene/types/field.py @@ -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 diff --git a/graphene/types/inputfield.py b/graphene/types/inputfield.py index 791ca6a48..e7ededb0b 100644 --- a/graphene/types/inputfield.py +++ b/graphene/types/inputfield.py @@ -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 diff --git a/graphene/types/mountedtype.py b/graphene/types/mountedtype.py index c42383e24..fbf88f74b 100644 --- a/graphene/types/mountedtype.py +++ b/graphene/types/mountedtype.py @@ -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)}" diff --git a/graphene/types/schema.py b/graphene/types/schema.py index 1a33a93d3..368876392 100644 --- a/graphene/types/schema.py +++ b/graphene/types/schema.py @@ -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 = {} @@ -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( diff --git a/graphene/types/tests/test_argument.py b/graphene/types/tests/test_argument.py index db4d6c242..c5521b6c2 100644 --- a/graphene/types/tests/test_argument.py +++ b/graphene/types/tests/test_argument.py @@ -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() @@ -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)} diff --git a/graphene/types/tests/test_field.py b/graphene/types/tests/test_field.py index 669ada4f8..7c4eabb88 100644 --- a/graphene/types/tests/test_field.py +++ b/graphene/types/tests/test_field.py @@ -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: @@ -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 diff --git a/graphene/types/tests/test_inputfield.py b/graphene/types/tests/test_inputfield.py index bfedfb057..9b1001286 100644 --- a/graphene/types/tests/test_inputfield.py +++ b/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 @@ -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) diff --git a/graphene/types/tests/test_mutation.py b/graphene/types/tests/test_mutation.py index 4a7ad3c7c..21d62274b 100644 --- a/graphene/types/tests/test_mutation.py +++ b/graphene/types/tests/test_mutation.py @@ -6,7 +6,6 @@ from ..objecttype import ObjectType from ..scalars import String from ..schema import Schema -from ..structures import NonNull from ..interface import Interface @@ -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(): diff --git a/graphene/types/unmountedtype.py b/graphene/types/unmountedtype.py index 83a6afefc..f4189d58f 100644 --- a/graphene/types/unmountedtype.py +++ b/graphene/types/unmountedtype.py @@ -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. """ @@ -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() diff --git a/graphene/validation/depth_limit.py b/graphene/validation/depth_limit.py index 5be852c7b..b4599e660 100644 --- a/graphene/validation/depth_limit.py +++ b/graphene/validation/depth_limit.py @@ -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):