Merge branch 'graphql-python:master' into fix/inputobjecttype_undefined

This commit is contained in:
Cadu 2023-01-25 15:40:54 -03:00 committed by GitHub
commit a8cb8ca4c2
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 126 additions and 17 deletions

View File

@ -1,12 +1,6 @@
Graphene Graphene
======== ========
------------
The documentation below is for the ``dev`` (prerelease) version of Graphene. To view the documentation for the latest stable Graphene version go to the `v2 docs <https://docs.graphene-python.org/en/stable/>`_.
------------
Contents: Contents:
.. toctree:: .. toctree::

View File

@ -271,7 +271,7 @@ The following is an example for creating a DateTime scalar:
@staticmethod @staticmethod
def parse_literal(node, _variables=None): def parse_literal(node, _variables=None):
if isinstance(node, ast.StringValue): if isinstance(node, ast.StringValueNode):
return datetime.datetime.strptime( return datetime.datetime.strptime(
node.value, "%Y-%m-%dT%H:%M:%S.%f") node.value, "%Y-%m-%dT%H:%M:%S.%f")

View File

@ -46,7 +46,7 @@ from .types import (
from .utils.module_loading import lazy_import from .utils.module_loading import lazy_import
from .utils.resolve_only_args import resolve_only_args from .utils.resolve_only_args import resolve_only_args
VERSION = (3, 1, 1, "final", 0) VERSION = (3, 2, 1, "final", 0)
__version__ = get_version(VERSION) __version__ = get_version(VERSION)

View File

@ -31,18 +31,22 @@ class Argument(MountedType):
type (class for a graphene.UnmountedType): must be a class (not an instance) of an 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 unmounted graphene type (ex. scalar or object) which is used for the type of this
argument in the GraphQL schema. 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. as graphene.NonNull. Default False.
name (str): the name of the GraphQL argument. Defaults to parameter name. name (optional, str): the name of the GraphQL argument. Defaults to parameter name.
description (str): the description of the GraphQL argument in the schema. description (optional, 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 default_value (optional, Any): The value to be provided if the user does not set this argument in
the operation. 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__( def __init__(
self, self,
type_, type_,
default_value=Undefined, default_value=Undefined,
deprecation_reason=None,
description=None, description=None,
name=None, name=None,
required=False, required=False,
@ -51,12 +55,16 @@ class Argument(MountedType):
super(Argument, self).__init__(_creation_counter=_creation_counter) super(Argument, self).__init__(_creation_counter=_creation_counter)
if required: if required:
assert (
deprecation_reason is None
), f"Argument {name} is required, cannot deprecate it."
type_ = NonNull(type_) type_ = NonNull(type_)
self.name = name self.name = name
self._type = type_ self._type = type_
self.default_value = default_value self.default_value = default_value
self.description = description self.description = description
self.deprecation_reason = deprecation_reason
@property @property
def type(self): def type(self):
@ -68,6 +76,7 @@ class Argument(MountedType):
and self.type == other.type and self.type == other.type
and self.default_value == other.default_value and self.default_value == other.default_value
and self.description == other.description and self.description == other.description
and self.deprecation_reason == other.deprecation_reason
) )

View File

@ -55,11 +55,14 @@ class InputField(MountedType):
description=None, description=None,
required=False, required=False,
_creation_counter=None, _creation_counter=None,
**extra_args **extra_args,
): ):
super(InputField, self).__init__(_creation_counter=_creation_counter) super(InputField, self).__init__(_creation_counter=_creation_counter)
self.name = name self.name = name
if required: if required:
assert (
deprecation_reason is None
), f"InputField {name} is required, cannot deprecate it."
type_ = NonNull(type_) type_ = NonNull(type_)
self._type = type_ self._type = type_
self.deprecation_reason = deprecation_reason self.deprecation_reason = deprecation_reason

View File

@ -1,3 +1,4 @@
from enum import Enum as PyEnum
import inspect import inspect
from functools import partial from functools import partial
@ -169,10 +170,16 @@ class TypeMap(dict):
values = {} values = {}
for name, value in graphene_type._meta.enum.__members__.items(): for name, value in graphene_type._meta.enum.__members__.items():
description = getattr(value, "description", None) description = getattr(value, "description", None)
deprecation_reason = getattr(value, "deprecation_reason", None) # if the "description" attribute is an Enum, it is likely an enum member
# called description, not a description property
if isinstance(description, PyEnum):
description = None
if not description and callable(graphene_type._meta.description): if not description and callable(graphene_type._meta.description):
description = graphene_type._meta.description(value) description = graphene_type._meta.description(value)
deprecation_reason = getattr(value, "deprecation_reason", None)
if isinstance(deprecation_reason, PyEnum):
deprecation_reason = None
if not deprecation_reason and callable( if not deprecation_reason and callable(
graphene_type._meta.deprecation_reason graphene_type._meta.deprecation_reason
): ):
@ -309,6 +316,7 @@ class TypeMap(dict):
default_value=field.default_value, default_value=field.default_value,
out_name=name, out_name=name,
description=field.description, description=field.description,
deprecation_reason=field.deprecation_reason,
) )
else: else:
args = {} args = {}
@ -320,6 +328,7 @@ class TypeMap(dict):
out_name=arg_name, out_name=arg_name,
description=arg.description, description=arg.description,
default_value=arg.default_value, default_value=arg.default_value,
deprecation_reason=arg.deprecation_reason,
) )
subscribe = field.wrap_subscribe( subscribe = field.wrap_subscribe(
self.get_function_for_type( self.get_function_for_type(

View File

@ -18,8 +18,20 @@ def test_argument():
def test_argument_comparasion(): def test_argument_comparasion():
arg1 = Argument(String, name="Hey", description="Desc", default_value="default") arg1 = Argument(
arg2 = Argument(String, name="Hey", description="Desc", default_value="default") 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 == arg2
assert arg1 != String() 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(): def test_to_arguments_raises_if_field():
args = {"arg_string": Field(String)} args = {"arg_string": Field(String)}

View File

@ -565,3 +565,36 @@ def test_iterable_instance_creation_enum():
for c in TestEnum: for c in TestEnum:
result.append(c.name) result.append(c.name)
assert result == expected_values assert result == expected_values
# https://github.com/graphql-python/graphene/issues/1321
def test_enum_description_member_not_interpreted_as_property():
class RGB(Enum):
"""Description"""
red = "red"
green = "green"
blue = "blue"
description = "description"
deprecation_reason = "deprecation_reason"
class Query(ObjectType):
color = RGB()
def resolve_color(_, info):
return RGB.description
values = RGB._meta.enum.__members__.values()
assert sorted(v.name for v in values) == [
"blue",
"deprecation_reason",
"description",
"green",
"red",
]
schema = Schema(query=Query)
results = schema.execute("query { color }")
assert not results.errors
assert results.data["color"] == RGB.description.name

View File

@ -128,13 +128,20 @@ def test_field_name_as_argument():
def test_field_source_argument_as_kw(): def test_field_source_argument_as_kw():
MyType = object() 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 list(field.args) == ["b", "c", "a"]
assert isinstance(field.args["b"], Argument) assert isinstance(field.args["b"], Argument)
assert isinstance(field.args["b"].type, NonNull) assert isinstance(field.args["b"].type, NonNull)
assert field.args["b"].type.of_type is True assert field.args["b"].type.of_type is True
assert isinstance(field.args["c"], Argument) assert isinstance(field.args["c"], Argument)
assert field.args["c"].type is None 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"], Argument)
assert isinstance(field.args["a"].type, NonNull) assert isinstance(field.args["a"].type, NonNull)
assert field.args["a"].type.of_type is False assert field.args["a"].type.of_type is False

View File

@ -1,5 +1,7 @@
from functools import partial from functools import partial
from pytest import raises
from ..inputfield import InputField from ..inputfield import InputField
from ..structures import NonNull from ..structures import NonNull
from .utils import MyLazyType from .utils import MyLazyType
@ -12,6 +14,22 @@ def test_inputfield_required():
assert field.type.of_type == MyType 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(): def test_inputfield_with_lazy_type():
MyType = object() MyType = object()
field = InputField(lambda: MyType) field = InputField(lambda: MyType)