From e23184fe13ff11d9a5326766de070e2ee56e6f0e Mon Sep 17 00:00:00 2001 From: Eran Kampf Date: Sat, 1 Jun 2019 22:01:10 -0700 Subject: [PATCH 01/26] Changed dependencies to core-next --- setup.py | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/setup.py b/setup.py index b57f7be24..548fe86d9 100644 --- a/setup.py +++ b/setup.py @@ -79,16 +79,12 @@ def run_tests(self): ], keywords="api graphql protocol rest relay graphene", packages=find_packages(exclude=["tests", "tests.*", "examples"]), - install_requires=[ - "graphql-core>=2.1,<3", - "graphql-relay>=0.4.5,<1", - "aniso8601>=3,<=6.0.*", - ], + install_requires=["graphql-core-next~=1.0.5", "aniso8601~=6.0.0"], tests_require=tests_require, extras_require={ "test": tests_require, - "django": ["graphene-django"], - "sqlalchemy": ["graphene-sqlalchemy"], + # "django": ["graphene-django"], + # "sqlalchemy": ["graphene-sqlalchemy"], }, cmdclass={"test": PyTest}, ) From a0428d749f786f83f206d835cd412d4e451ab860 Mon Sep 17 00:00:00 2001 From: Eran Kampf Date: Sat, 1 Jun 2019 22:15:39 -0700 Subject: [PATCH 02/26] Converted Scalars --- graphene/types/datetime.py | 8 ++++---- graphene/types/decimal.py | 4 ++-- graphene/types/generic.py | 22 +++++++++++----------- graphene/types/json.py | 4 ++-- graphene/types/scalars.py | 17 +++++++++++------ graphene/types/uuid.py | 4 ++-- 6 files changed, 32 insertions(+), 27 deletions(-) diff --git a/graphene/types/datetime.py b/graphene/types/datetime.py index ca96547fe..fecdd88a9 100644 --- a/graphene/types/datetime.py +++ b/graphene/types/datetime.py @@ -3,7 +3,7 @@ import datetime from aniso8601 import parse_date, parse_datetime, parse_time -from graphql.language import ast +from graphql.language.ast import StringValueNode from .scalars import Scalar @@ -26,7 +26,7 @@ def serialize(date): @classmethod def parse_literal(cls, node): - if isinstance(node, ast.StringValue): + if isinstance(node, StringValueNode): return cls.parse_value(node.value) @staticmethod @@ -56,7 +56,7 @@ def serialize(dt): @classmethod def parse_literal(cls, node): - if isinstance(node, ast.StringValue): + if isinstance(node, StringValueNode): return cls.parse_value(node.value) @staticmethod @@ -86,7 +86,7 @@ def serialize(time): @classmethod def parse_literal(cls, node): - if isinstance(node, ast.StringValue): + if isinstance(node, StringValueNode): return cls.parse_value(node.value) @classmethod diff --git a/graphene/types/decimal.py b/graphene/types/decimal.py index 2f99134d0..10a2609a9 100644 --- a/graphene/types/decimal.py +++ b/graphene/types/decimal.py @@ -2,7 +2,7 @@ from decimal import Decimal as _Decimal -from graphql.language import ast +from graphql.language.ast import StringValueNode from .scalars import Scalar @@ -23,7 +23,7 @@ def serialize(dec): @classmethod def parse_literal(cls, node): - if isinstance(node, ast.StringValue): + if isinstance(node, StringValueNode): return cls.parse_value(node.value) @staticmethod diff --git a/graphene/types/generic.py b/graphene/types/generic.py index e5470dd97..5d1a6c4b6 100644 --- a/graphene/types/generic.py +++ b/graphene/types/generic.py @@ -1,12 +1,12 @@ from __future__ import unicode_literals from graphql.language.ast import ( - BooleanValue, - FloatValue, - IntValue, - ListValue, - ObjectValue, - StringValue, + BooleanValueNode, + FloatValueNode, + IntValueNode, + ListValueNode, + ObjectValueNode, + StringValueNode, ) from graphene.types.scalars import MAX_INT, MIN_INT @@ -30,17 +30,17 @@ def identity(value): @staticmethod def parse_literal(ast): - if isinstance(ast, (StringValue, BooleanValue)): + if isinstance(ast, (StringValueNode, BooleanValueNode)): return ast.value - elif isinstance(ast, IntValue): + elif isinstance(ast, IntValueNode): num = int(ast.value) if MIN_INT <= num <= MAX_INT: return num - elif isinstance(ast, FloatValue): + elif isinstance(ast, FloatValueNode): return float(ast.value) - elif isinstance(ast, ListValue): + elif isinstance(ast, ListValueNode): return [GenericScalar.parse_literal(value) for value in ast.values] - elif isinstance(ast, ObjectValue): + elif isinstance(ast, ObjectValueNode): return { field.name.value: GenericScalar.parse_literal(field.value) for field in ast.fields diff --git a/graphene/types/json.py b/graphene/types/json.py index 495943a92..f21f92cec 100644 --- a/graphene/types/json.py +++ b/graphene/types/json.py @@ -2,7 +2,7 @@ import json -from graphql.language import ast +from graphql.language.ast import StringValueNode from .scalars import Scalar @@ -16,7 +16,7 @@ def serialize(dt): @staticmethod def parse_literal(node): - if isinstance(node, ast.StringValue): + if isinstance(node, StringValueNode): return json.loads(node.value) @staticmethod diff --git a/graphene/types/scalars.py b/graphene/types/scalars.py index b9cb1aa0e..245fa570b 100644 --- a/graphene/types/scalars.py +++ b/graphene/types/scalars.py @@ -1,6 +1,11 @@ from typing import Any -from graphql.language.ast import BooleanValue, FloatValue, IntValue, StringValue +from graphql.language.ast import ( + BooleanValueNode, + FloatValueNode, + IntValueNode, + StringValueNode, +) from .base import BaseOptions, BaseType from .unmountedtype import UnmountedType @@ -71,7 +76,7 @@ def coerce_int(value): @staticmethod def parse_literal(ast): - if isinstance(ast, IntValue): + if isinstance(ast, IntValueNode): num = int(ast.value) if MIN_INT <= num <= MAX_INT: return num @@ -97,7 +102,7 @@ def coerce_float(value): @staticmethod def parse_literal(ast): - if isinstance(ast, (FloatValue, IntValue)): + if isinstance(ast, (FloatValueNode, IntValueNode)): return float(ast.value) @@ -119,7 +124,7 @@ def coerce_string(value): @staticmethod def parse_literal(ast): - if isinstance(ast, StringValue): + if isinstance(ast, StringValueNode): return ast.value @@ -133,7 +138,7 @@ class Boolean(Scalar): @staticmethod def parse_literal(ast): - if isinstance(ast, BooleanValue): + if isinstance(ast, BooleanValueNode): return ast.value @@ -151,5 +156,5 @@ class ID(Scalar): @staticmethod def parse_literal(ast): - if isinstance(ast, (StringValue, IntValue)): + if isinstance(ast, (StringValueNode, IntValueNode)): return ast.value diff --git a/graphene/types/uuid.py b/graphene/types/uuid.py index f55e7a859..c31b0a8cf 100644 --- a/graphene/types/uuid.py +++ b/graphene/types/uuid.py @@ -1,7 +1,7 @@ from __future__ import absolute_import from uuid import UUID as _UUID -from graphql.language import ast +from graphql.language.ast import StringValueNode from .scalars import Scalar @@ -21,7 +21,7 @@ def serialize(uuid): @staticmethod def parse_literal(node): - if isinstance(node, ast.StringValue): + if isinstance(node, StringValueNode): return _UUID(node.value) @staticmethod From 0e31eec81e034a3f900bcb88128f707201b6d5fe Mon Sep 17 00:00:00 2001 From: Eran Kampf Date: Sat, 1 Jun 2019 22:28:59 -0700 Subject: [PATCH 03/26] ResolveInfo name change --- graphene/types/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/graphene/types/__init__.py b/graphene/types/__init__.py index 965916059..292db235b 100644 --- a/graphene/types/__init__.py +++ b/graphene/types/__init__.py @@ -1,5 +1,5 @@ # flake8: noqa -from graphql import ResolveInfo +from graphql import GraphQLResolveInfo as ResolveInfo from .objecttype import ObjectType from .interface import Interface From 2986658470ff22ee7f50a584980ac98786ddbb51 Mon Sep 17 00:00:00 2001 From: Eran Kampf Date: Sat, 29 Jun 2019 10:00:18 -0700 Subject: [PATCH 04/26] Ignore .venv --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 0e3fcd9ed..9b3eefe9f 100644 --- a/.gitignore +++ b/.gitignore @@ -82,3 +82,4 @@ target/ *.sqlite3 .vscode .mypy_cache +/.venv From 6a0afb08a1bf95a55add1d17968e8f1c47e30629 Mon Sep 17 00:00:00 2001 From: Christoph Zwerschke Date: Sun, 30 Jun 2019 02:30:51 +0200 Subject: [PATCH 05/26] Make Schema compatible with GraphQL-core-next --- graphene/pyutils/compat.py | 2 - graphene/types/definitions.py | 2 +- graphene/types/enum.py | 3 +- graphene/types/schema.py | 510 +++++++++++++++--- graphene/types/tests/test_definition.py | 28 +- graphene/types/tests/test_inputobjecttype.py | 1 + graphene/types/tests/test_query.py | 10 +- graphene/types/tests/test_schema.py | 34 +- .../{test_typemap.py => test_type_map.py} | 103 ++-- graphene/types/typemap.py | 337 ------------ 10 files changed, 541 insertions(+), 489 deletions(-) rename graphene/types/tests/{test_typemap.py => test_type_map.py} (73%) delete mode 100644 graphene/types/typemap.py diff --git a/graphene/pyutils/compat.py b/graphene/pyutils/compat.py index d6d24358a..ade0399bb 100644 --- a/graphene/pyutils/compat.py +++ b/graphene/pyutils/compat.py @@ -1,7 +1,5 @@ from __future__ import absolute_import -from graphql.pyutils.compat import Enum - try: from inspect import signature except ImportError: diff --git a/graphene/types/definitions.py b/graphene/types/definitions.py index a914008c5..009169201 100644 --- a/graphene/types/definitions.py +++ b/graphene/types/definitions.py @@ -8,7 +8,7 @@ ) -class GrapheneGraphQLType(object): +class GrapheneGraphQLType: """ A class for extending the base GraphQLType with the related graphene_type diff --git a/graphene/types/enum.py b/graphene/types/enum.py index 997cc0aec..79f2f636f 100644 --- a/graphene/types/enum.py +++ b/graphene/types/enum.py @@ -1,7 +1,8 @@ from collections import OrderedDict +from enum import Enum as PyEnum + from graphene.utils.subclass_with_meta import SubclassWithMeta_Meta -from ..pyutils.compat import Enum as PyEnum from .base import BaseOptions, BaseType from .unmountedtype import UnmountedType diff --git a/graphene/types/schema.py b/graphene/types/schema.py index a885c88a6..c1c8e825b 100644 --- a/graphene/types/schema.py +++ b/graphene/types/schema.py @@ -1,74 +1,444 @@ import inspect +from functools import partial -from graphql import GraphQLObjectType, GraphQLSchema, graphql, is_type -from graphql.type.directives import ( - GraphQLDirective, - GraphQLIncludeDirective, - GraphQLSkipDirective, +from graphql import ( + default_type_resolver, + get_introspection_query, + graphql, + graphql_sync, + introspection_types, + is_type, + print_schema, + GraphQLArgument, + GraphQLBoolean, + GraphQLEnumValue, + GraphQLField, + GraphQLFloat, + GraphQLID, + GraphQLInputField, + GraphQLInt, + GraphQLList, + GraphQLNonNull, + GraphQLObjectType, + GraphQLSchema, + GraphQLString, + INVALID, ) -from graphql.type.introspection import IntrospectionSchema -from graphql.utils.introspection_query import introspection_query -from graphql.utils.schema_printer import print_schema -from .definitions import GrapheneGraphQLType +from ..utils.str_converters import to_camel_case +from ..utils.get_unbound_function import get_unbound_function +from .definitions import ( + GrapheneEnumType, + GrapheneGraphQLType, + GrapheneInputObjectType, + GrapheneInterfaceType, + GrapheneObjectType, + GrapheneScalarType, + GrapheneUnionType, +) +from .dynamic import Dynamic +from .enum import Enum +from .field import Field +from .inputobjecttype import InputObjectType +from .interface import Interface from .objecttype import ObjectType -from .typemap import TypeMap, is_graphene_type +from .resolver import get_default_resolver +from .scalars import ID, Boolean, Float, Int, Scalar, String +from .structures import List, NonNull +from .union import Union +from .utils import get_field_as + +introspection_query = get_introspection_query() +IntrospectionSchema = introspection_types["__Schema"] -def assert_valid_root_type(_type): - if _type is None: +def assert_valid_root_type(type_): + if type_ is None: return - is_graphene_objecttype = inspect.isclass(_type) and issubclass(_type, ObjectType) - is_graphql_objecttype = isinstance(_type, GraphQLObjectType) + is_graphene_objecttype = inspect.isclass(type_) and issubclass(type_, ObjectType) + is_graphql_objecttype = isinstance(type_, GraphQLObjectType) assert is_graphene_objecttype or is_graphql_objecttype, ( "Type {} is not a valid ObjectType." - ).format(_type) + ).format(type_) -class Schema(GraphQLSchema): - """ - Schema Definition +def is_graphene_type(type_): + if isinstance(type_, (List, NonNull)): + return True + if inspect.isclass(type_) and issubclass( + type_, (ObjectType, InputObjectType, Scalar, Interface, Union, Enum) + ): + return True + + +def resolve_type(resolve_type_func, map_, type_name, root, info, _type): + type_ = resolve_type_func(root, info) + + if not type_: + return_type = map_[type_name] + return default_type_resolver(root, info, return_type) + + if inspect.isclass(type_) and issubclass(type_, ObjectType): + graphql_type = map_.get(type_._meta.name) + assert graphql_type, "Can't find type {} in schema".format(type_._meta.name) + assert graphql_type.graphene_type == type_, ( + "The type {} does not match with the associated graphene type {}." + ).format(type_, graphql_type.graphene_type) + return graphql_type + + return type_ + + +def is_type_of_from_possible_types(possible_types, root, _info): + return isinstance(root, possible_types) - A Schema is created by supplying the root types of each type of operation, - query and mutation (optional). - """ + +class GrapheneGraphQLSchema(GraphQLSchema): + """A GraphQLSchema that can deal with Graphene types as well.""" def __init__( self, query=None, mutation=None, subscription=None, - directives=None, types=None, + directives=None, auto_camelcase=True, ): assert_valid_root_type(query) assert_valid_root_type(mutation) assert_valid_root_type(subscription) - self._query = query - self._mutation = mutation - self._subscription = subscription - self.types = types + self.auto_camelcase = auto_camelcase - if directives is None: - directives = [GraphQLIncludeDirective, GraphQLSkipDirective] + super().__init__(query, mutation, subscription, types, directives) + + if query: + self.query_type = self.get_type( + query.name if isinstance(query, GraphQLObjectType) else query._meta.name + ) + if mutation: + self.mutation_type = self.get_type( + mutation.name + if isinstance(mutation, GraphQLObjectType) + else mutation._meta.name + ) + if subscription: + self.subscription_type = self.get_type( + subscription.name + if isinstance(subscription, GraphQLObjectType) + else subscription._meta.name + ) + + def get_graphql_type(self, _type): + if not _type: + return _type + if is_type(_type): + return _type + if is_graphene_type(_type): + graphql_type = self.get_type(_type._meta.name) + assert graphql_type, "Type {} not found in this schema.".format( + _type._meta.name + ) + assert graphql_type.graphene_type == _type + return graphql_type + raise Exception("{} is not a valid GraphQL type.".format(_type)) - assert all( - isinstance(d, GraphQLDirective) for d in directives - ), "Schema directives must be List[GraphQLDirective] if provided but got: {}.".format( - directives + # noinspection PyMethodOverriding + def type_map_reducer(self, map_, type_): + if not type_: + return map_ + if inspect.isfunction(type_): + type_ = type_() + if is_graphene_type(type_): + return self.graphene_reducer(map_, type_) + return super().type_map_reducer(map_, type_) + + def graphene_reducer(self, map_, type_): + if isinstance(type_, (List, NonNull)): + return self.type_map_reducer(map_, type_.of_type) + if type_._meta.name in map_: + _type = map_[type_._meta.name] + if isinstance(_type, GrapheneGraphQLType): + assert _type.graphene_type == type_, ( + "Found different types with the same name in the schema: {}, {}." + ).format(_type.graphene_type, type_) + return map_ + + if issubclass(type_, ObjectType): + internal_type = self.construct_objecttype(map_, type_) + elif issubclass(type_, InputObjectType): + internal_type = self.construct_inputobjecttype(map_, type_) + elif issubclass(type_, Interface): + internal_type = self.construct_interface(map_, type_) + elif issubclass(type_, Scalar): + internal_type = self.construct_scalar(type_) + elif issubclass(type_, Enum): + internal_type = self.construct_enum(type_) + elif issubclass(type_, Union): + internal_type = self.construct_union(map_, type_) + else: + raise Exception("Expected Graphene type, but received: {}.".format(type_)) + + return super().type_map_reducer(map_, internal_type) + + @staticmethod + def construct_scalar(type_): + # We have a mapping to the original GraphQL types + # so there are no collisions. + _scalars = { + String: GraphQLString, + Int: GraphQLInt, + Float: GraphQLFloat, + Boolean: GraphQLBoolean, + ID: GraphQLID, + } + if type_ in _scalars: + return _scalars[type_] + + return GrapheneScalarType( + graphene_type=type_, + name=type_._meta.name, + description=type_._meta.description, + serialize=getattr(type_, "serialize", None), + parse_value=getattr(type_, "parse_value", None), + parse_literal=getattr(type_, "parse_literal", None), ) - self._directives = directives - self.build_typemap() - def get_query_type(self): - return self.get_graphql_type(self._query) + @staticmethod + def construct_enum(type_): + values = {} + for name, value in type_._meta.enum.__members__.items(): + description = getattr(value, "description", None) + deprecation_reason = getattr(value, "deprecation_reason", None) + if not description and callable(type_._meta.description): + description = type_._meta.description(value) - def get_mutation_type(self): - return self.get_graphql_type(self._mutation) + if not deprecation_reason and callable(type_._meta.deprecation_reason): + deprecation_reason = type_._meta.deprecation_reason(value) - def get_subscription_type(self): - return self.get_graphql_type(self._subscription) + values[name] = GraphQLEnumValue( + value=value.value, + description=description, + deprecation_reason=deprecation_reason, + ) + + type_description = ( + type_._meta.description(None) + if callable(type_._meta.description) + else type_._meta.description + ) + + return GrapheneEnumType( + graphene_type=type_, + values=values, + name=type_._meta.name, + description=type_description, + ) + + def construct_objecttype(self, map_, type_): + if type_._meta.name in map_: + _type = map_[type_._meta.name] + if isinstance(_type, GrapheneGraphQLType): + assert _type.graphene_type == type_, ( + "Found different types with the same name in the schema: {}, {}." + ).format(_type.graphene_type, type_) + return _type + + def interfaces(): + interfaces = [] + for interface in type_._meta.interfaces: + self.graphene_reducer(map_, interface) + internal_type = map_[interface._meta.name] + assert internal_type.graphene_type == interface + interfaces.append(internal_type) + return interfaces + + if type_._meta.possible_types: + is_type_of = partial( + is_type_of_from_possible_types, type_._meta.possible_types + ) + else: + is_type_of = type_.is_type_of + + return GrapheneObjectType( + graphene_type=type_, + name=type_._meta.name, + description=type_._meta.description, + fields=partial(self.construct_fields_for_type, map_, type_), + is_type_of=is_type_of, + interfaces=interfaces, + ) + + def construct_interface(self, map_, type_): + if type_._meta.name in map_: + _type = map_[type_._meta.name] + if isinstance(_type, GrapheneInterfaceType): + assert _type.graphene_type == type_, ( + "Found different types with the same name in the schema: {}, {}." + ).format(_type.graphene_type, type_) + return _type + + _resolve_type = None + if type_.resolve_type: + _resolve_type = partial( + resolve_type, type_.resolve_type, map_, type_._meta.name + ) + return GrapheneInterfaceType( + graphene_type=type_, + name=type_._meta.name, + description=type_._meta.description, + fields=partial(self.construct_fields_for_type, map_, type_), + resolve_type=_resolve_type, + ) + + def construct_inputobjecttype(self, map_, type_): + return GrapheneInputObjectType( + graphene_type=type_, + name=type_._meta.name, + description=type_._meta.description, + # TODO: container_type not supported by core-next (is this needed?) + # container_type=type_._meta.container, + fields=partial( + self.construct_fields_for_type, map_, type_, is_input_type=True + ), + ) + + def construct_union(self, map_, type_): + _resolve_type = None + if type_.resolve_type: + _resolve_type = partial( + resolve_type, type_.resolve_type, map_, type_._meta.name + ) + + def types(): + union_types = [] + for objecttype in type_._meta.types: + self.graphene_reducer(map_, objecttype) + internal_type = map_[objecttype._meta.name] + assert internal_type.graphene_type == objecttype + union_types.append(internal_type) + return union_types + + return GrapheneUnionType( + graphene_type=type_, + name=type_._meta.name, + description=type_._meta.description, + types=types, + resolve_type=_resolve_type, + ) + + def get_name(self, name): + if self.auto_camelcase: + return to_camel_case(name) + return name + + def construct_fields_for_type(self, map_, type_, is_input_type=False): + fields = {} + for name, field in type_._meta.fields.items(): + if isinstance(field, Dynamic): + field = get_field_as(field.get_type(self), _as=Field) + if not field: + continue + map_ = self.type_map_reducer(map_, field.type) + field_type = self.get_field_type(map_, field.type) + if is_input_type: + _field = GraphQLInputField( + field_type, + default_value=field.default_value, + # TODO: out_name not (yet) supported by core-next + # out_name=name, + description=field.description, + ) + else: + args = {} + for arg_name, arg in field.args.items(): + map_ = self.type_map_reducer(map_, arg.type) + arg_type = self.get_field_type(map_, arg.type) + processed_arg_name = arg.name or self.get_name(arg_name) + args[processed_arg_name] = GraphQLArgument( + arg_type, + # TODO: out_name not (yet) supported by core-next + # out_name=arg_name, + description=arg.description, + default_value=INVALID + if isinstance(arg.type, NonNull) + else arg.default_value, + ) + _field = GraphQLField( + field_type, + args=args, + resolve=field.get_resolver( + self.get_resolver_for_type(type_, name, field.default_value) + ), + deprecation_reason=field.deprecation_reason, + description=field.description, + ) + field_name = field.name or self.get_name(name) + fields[field_name] = _field + return fields + + def get_resolver_for_type(self, type_, name, default_value): + if not issubclass(type_, ObjectType): + return + resolver = getattr(type_, "resolve_{}".format(name), None) + if not resolver: + # If we don't find the resolver in the ObjectType class, then try to + # find it in each of the interfaces + interface_resolver = None + for interface in type_._meta.interfaces: + if name not in interface._meta.fields: + continue + interface_resolver = getattr(interface, "resolve_{}".format(name), None) + if interface_resolver: + break + resolver = interface_resolver + + # Only if is not decorated with classmethod + if resolver: + return get_unbound_function(resolver) + + default_resolver = type_._meta.default_resolver or get_default_resolver() + return partial(default_resolver, name, default_value) + + def get_field_type(self, map_, type_): + if isinstance(type_, List): + return GraphQLList(self.get_field_type(map_, type_.of_type)) + if isinstance(type_, NonNull): + return GraphQLNonNull(self.get_field_type(map_, type_.of_type)) + return map_.get(type_._meta.name) + + +class Schema: + """ + Schema Definition + + A Schema is created by supplying the root types of each type of operation, + query and mutation (optional). + """ + + def __init__( + self, + query=None, + mutation=None, + subscription=None, + types=None, + directives=None, + auto_camelcase=True, + ): + self.query = query + self.mutation = mutation + self.subscription = subscription + self.graphql_schema = GrapheneGraphQLSchema( + query, + mutation, + subscription, + types, + directives, + auto_camelcase=auto_camelcase, + ) + + def __str__(self): + return print_schema(self.graphql_schema) def __getattr__(self, type_name): """ @@ -77,51 +447,39 @@ def __getattr__(self, type_name): Example: using schema.Query for accessing the "Query" type in the Schema """ - _type = super(Schema, self).get_type(type_name) + _type = self.graphql_schema.get_type(type_name) if _type is None: raise AttributeError('Type "{}" not found in the Schema'.format(type_name)) if isinstance(_type, GrapheneGraphQLType): return _type.graphene_type return _type - def get_graphql_type(self, _type): - if not _type: - return _type - if is_type(_type): - return _type - if is_graphene_type(_type): - graphql_type = self.get_type(_type._meta.name) - assert graphql_type, "Type {} not found in this schema.".format( - _type._meta.name - ) - assert graphql_type.graphene_type == _type - return graphql_type - raise Exception("{} is not a valid GraphQL type.".format(_type)) + def lazy(self, _type): + return lambda: self.get_type(_type) + + async def async_execute(self, *args, **kwargs): + return graphql(self.graphql_schema, *args, **normalize_execute_kwargs(kwargs)) def execute(self, *args, **kwargs): - return graphql(self, *args, **kwargs) + return graphql_sync( + self.graphql_schema, *args, **normalize_execute_kwargs(kwargs) + ) def introspect(self): - instrospection = self.execute(introspection_query) - if instrospection.errors: - raise instrospection.errors[0] - return instrospection.data + introspection = self.execute(introspection_query) + if introspection.errors: + raise introspection.errors[0] + return introspection.data - def __str__(self): - return print_schema(self) - def lazy(self, _type): - return lambda: self.get_type(_type) - - def build_typemap(self): - initial_types = [ - self._query, - self._mutation, - self._subscription, - IntrospectionSchema, - ] - if self.types: - initial_types += self.types - self._type_map = TypeMap( - initial_types, auto_camelcase=self.auto_camelcase, schema=self - ) +def normalize_execute_kwargs(kwargs): + """Replace alias names in keyword arguments for graphql()""" + if "root" in kwargs and "root_value" not in kwargs: + kwargs["root_value"] = kwargs.pop("root") + if "context" in kwargs and "context_value" not in kwargs: + kwargs["context_value"] = kwargs.pop("context") + if "variables" in kwargs and "variable_values" not in kwargs: + kwargs["variable_values"] = kwargs.pop("variables") + if "operation" in kwargs and "operation_name" not in kwargs: + kwargs["operation_name"] = kwargs.pop("operation") + return kwargs diff --git a/graphene/types/tests/test_definition.py b/graphene/types/tests/test_definition.py index 549847d5a..57656ccf2 100644 --- a/graphene/types/tests/test_definition.py +++ b/graphene/types/tests/test_definition.py @@ -69,7 +69,8 @@ class MyInputObjectType(InputObjectType): def test_defines_a_query_only_schema(): blog_schema = Schema(Query) - assert blog_schema.get_query_type().graphene_type == Query + assert blog_schema.query == Query + assert blog_schema.graphql_schema.query_type.graphene_type == Query article_field = Query._meta.fields["article"] assert article_field.type == Article @@ -95,7 +96,8 @@ def test_defines_a_query_only_schema(): def test_defines_a_mutation_schema(): blog_schema = Schema(Query, mutation=Mutation) - assert blog_schema.get_mutation_type().graphene_type == Mutation + assert blog_schema.mutation == Mutation + assert blog_schema.graphql_schema.mutation_type.graphene_type == Mutation write_mutation = Mutation._meta.fields["write_article"] assert write_mutation.type == Article @@ -105,7 +107,8 @@ def test_defines_a_mutation_schema(): def test_defines_a_subscription_schema(): blog_schema = Schema(Query, subscription=Subscription) - assert blog_schema.get_subscription_type().graphene_type == Subscription + assert blog_schema.subscription == Subscription + assert blog_schema.graphql_schema.subscription_type.graphene_type == Subscription subscription = Subscription._meta.fields["article_subscribe"] assert subscription.type == Article @@ -126,8 +129,9 @@ class SomeSubscription(Mutation): subscribe_to_something = Field(Article, input=Argument(SomeInputObject)) schema = Schema(query=Query, mutation=SomeMutation, subscription=SomeSubscription) + type_map = schema.graphql_schema.type_map - assert schema.get_type_map()["NestedInputObject"].graphene_type is NestedInputObject + assert type_map["NestedInputObject"].graphene_type is NestedInputObject def test_includes_interfaces_thunk_subtypes_in_the_type_map(): @@ -142,8 +146,9 @@ class Query(ObjectType): iface = Field(lambda: SomeInterface) schema = Schema(query=Query, types=[SomeSubtype]) + type_map = schema.graphql_schema.type_map - assert schema.get_type_map()["SomeSubtype"].graphene_type is SomeSubtype + assert type_map["SomeSubtype"].graphene_type is SomeSubtype def test_includes_types_in_union(): @@ -161,9 +166,10 @@ class Query(ObjectType): union = Field(MyUnion) schema = Schema(query=Query) + type_map = schema.graphql_schema.type_map - assert schema.get_type_map()["OtherType"].graphene_type is OtherType - assert schema.get_type_map()["SomeType"].graphene_type is SomeType + assert type_map["OtherType"].graphene_type is OtherType + assert type_map["SomeType"].graphene_type is SomeType def test_maps_enum(): @@ -181,9 +187,10 @@ class Query(ObjectType): union = Field(MyUnion) schema = Schema(query=Query) + type_map = schema.graphql_schema.type_map - assert schema.get_type_map()["OtherType"].graphene_type is OtherType - assert schema.get_type_map()["SomeType"].graphene_type is SomeType + assert type_map["OtherType"].graphene_type is OtherType + assert type_map["SomeType"].graphene_type is SomeType def test_includes_interfaces_subtypes_in_the_type_map(): @@ -198,8 +205,9 @@ class Query(ObjectType): iface = Field(SomeInterface) schema = Schema(query=Query, types=[SomeSubtype]) + type_map = schema.graphql_schema.type_map - assert schema.get_type_map()["SomeSubtype"].graphene_type is SomeSubtype + assert type_map["SomeSubtype"].graphene_type is SomeSubtype def test_stringifies_simple_types(): diff --git a/graphene/types/tests/test_inputobjecttype.py b/graphene/types/tests/test_inputobjecttype.py index dc557b943..a88953bfa 100644 --- a/graphene/types/tests/test_inputobjecttype.py +++ b/graphene/types/tests/test_inputobjecttype.py @@ -105,6 +105,7 @@ class MyInputObjectType(MyAbstractType, InputObjectType): ] +# TOOD: I think this fails because container_type is not supported in core-next def test_inputobjecttype_of_input(): class Child(InputObjectType): first_name = String() diff --git a/graphene/types/tests/test_query.py b/graphene/types/tests/test_query.py index 8681e4628..f23747c08 100644 --- a/graphene/types/tests/test_query.py +++ b/graphene/types/tests/test_query.py @@ -1,7 +1,13 @@ import json from functools import partial -from graphql import GraphQLError, ResolveInfo, Source, execute, parse +from graphql import ( + GraphQLError, + GraphQLResolveInfo as ResolveInfo, + Source, + execute, + parse, +) from ..context import Context from ..dynamic import Dynamic @@ -175,7 +181,7 @@ class Query(ObjectType): assert len(executed.errors) == 1 assert ( executed.errors[0].message - == GraphQLError('Expected value of type "MyType" but got: str.').message + == GraphQLError("Expected value of type 'MyType' but got: 'hello'.").message ) assert executed.data == {"hello": None} diff --git a/graphene/types/tests/test_schema.py b/graphene/types/tests/test_schema.py index d4f2e33e3..acd6f9721 100644 --- a/graphene/types/tests/test_schema.py +++ b/graphene/types/tests/test_schema.py @@ -1,5 +1,7 @@ import pytest +from graphql.pyutils import dedent + from ..field import Field from ..objecttype import ObjectType from ..scalars import String @@ -15,8 +17,8 @@ class Query(ObjectType): def test_schema(): - schema = Schema(Query) - assert schema.get_query_type() == schema.get_graphql_type(Query) + schema = Schema(Query).graphql_schema + assert schema.query_type == schema.get_graphql_type(Query) def test_schema_get_type(): @@ -35,23 +37,23 @@ def test_schema_get_type_error(): def test_schema_str(): schema = Schema(Query) - assert ( - str(schema) - == """schema { - query: Query -} - -type MyOtherType { - field: String -} - -type Query { - inner: MyOtherType -} -""" + assert str(schema) == dedent( + """ + type MyOtherType { + field: String + } + + type Query { + inner: MyOtherType + } + """ ) def test_schema_introspect(): schema = Schema(Query) + print() + print(schema) + print() + print(schema.introspect()) assert "__schema" in schema.introspect() diff --git a/graphene/types/tests/test_typemap.py b/graphene/types/tests/test_type_map.py similarity index 73% rename from graphene/types/tests/test_typemap.py rename to graphene/types/tests/test_type_map.py index f713726fc..b4d7d6ad3 100644 --- a/graphene/types/tests/test_typemap.py +++ b/graphene/types/tests/test_type_map.py @@ -4,7 +4,7 @@ GraphQLEnumType, GraphQLEnumValue, GraphQLField, - GraphQLInputObjectField, + GraphQLInputField, GraphQLInputObjectType, GraphQLInterfaceType, GraphQLObjectType, @@ -20,7 +20,13 @@ from ..objecttype import ObjectType from ..scalars import Int, String from ..structures import List, NonNull -from ..typemap import TypeMap, resolve_type +from ..schema import GrapheneGraphQLSchema, resolve_type + + +def create_type_map(types, auto_camelcase=True): + query = GraphQLObjectType("Query", {}) + schema = GrapheneGraphQLSchema(query, types=types, auto_camelcase=auto_camelcase) + return schema.type_map def test_enum(): @@ -39,22 +45,18 @@ def deprecation_reason(self): if self == MyEnum.foo: return "Is deprecated" - typemap = TypeMap([MyEnum]) - assert "MyEnum" in typemap - graphql_enum = typemap["MyEnum"] + type_map = create_type_map([MyEnum]) + assert "MyEnum" in type_map + graphql_enum = type_map["MyEnum"] assert isinstance(graphql_enum, GraphQLEnumType) assert graphql_enum.name == "MyEnum" assert graphql_enum.description == "Description" - values = graphql_enum.values - assert values == [ - GraphQLEnumValue( - name="foo", - value=1, - description="Description foo=1", - deprecation_reason="Is deprecated", + assert graphql_enum.values == { + 'foo': GraphQLEnumValue( + value=1, description="Description foo=1", deprecation_reason="Is deprecated" ), - GraphQLEnumValue(name="bar", value=2, description="Description bar=2"), - ] + 'bar': GraphQLEnumValue(value=2, description="Description bar=2"), + } def test_objecttype(): @@ -70,9 +72,9 @@ class MyObjectType(ObjectType): def resolve_foo(self, bar): return bar - typemap = TypeMap([MyObjectType]) - assert "MyObjectType" in typemap - graphql_type = typemap["MyObjectType"] + type_map = create_type_map([MyObjectType]) + assert "MyObjectType" in type_map + graphql_type = type_map["MyObjectType"] assert isinstance(graphql_type, GraphQLObjectType) assert graphql_type.name == "MyObjectType" assert graphql_type.description == "Description" @@ -88,7 +90,8 @@ def resolve_foo(self, bar): GraphQLString, description="Argument description", default_value="x", - out_name="bar", + # TODO: out_name not (yet) supported by core-next + # out_name="bar", ) } @@ -100,10 +103,10 @@ class MyObjectType(ObjectType): bar = Dynamic(lambda: Field(String)) own = Field(lambda: MyObjectType) - typemap = TypeMap([MyObjectType]) - assert "MyObjectType" in typemap + type_map = create_type_map([MyObjectType]) + assert "MyObjectType" in type_map assert list(MyObjectType._meta.fields.keys()) == ["bar", "own"] - graphql_type = typemap["MyObjectType"] + graphql_type = type_map["MyObjectType"] fields = graphql_type.fields assert list(fields.keys()) == ["bar", "own"] @@ -125,9 +128,9 @@ class MyInterface(Interface): def resolve_foo(self, args, info): return args.get("bar") - typemap = TypeMap([MyInterface]) - assert "MyInterface" in typemap - graphql_type = typemap["MyInterface"] + type_map = create_type_map([MyInterface]) + assert "MyInterface" in type_map + graphql_type = type_map["MyInterface"] assert isinstance(graphql_type, GraphQLInterfaceType) assert graphql_type.name == "MyInterface" assert graphql_type.description == "Description" @@ -139,13 +142,14 @@ def resolve_foo(self, args, info): foo_field = fields["foo"] assert isinstance(foo_field, GraphQLField) assert foo_field.description == "Field description" - assert not foo_field.resolver # Resolver not attached in interfaces + assert not foo_field.resolve # Resolver not attached in interfaces assert foo_field.args == { "bar": GraphQLArgument( GraphQLString, description="Argument description", default_value="x", - out_name="bar", + # TODO: out_name not (yet) supported by core-next + # out_name="bar", ) } @@ -169,15 +173,16 @@ class MyInputObjectType(InputObjectType): def resolve_foo_bar(self, args, info): return args.get("bar") - typemap = TypeMap([MyInputObjectType]) - assert "MyInputObjectType" in typemap - graphql_type = typemap["MyInputObjectType"] + type_map = create_type_map([MyInputObjectType]) + assert "MyInputObjectType" in type_map + graphql_type = type_map["MyInputObjectType"] assert isinstance(graphql_type, GraphQLInputObjectType) assert graphql_type.name == "MyInputObjectType" assert graphql_type.description == "Description" - other_graphql_type = typemap["OtherObjectType"] - inner_graphql_type = typemap["MyInnerObjectType"] + other_graphql_type = type_map["OtherObjectType"] + inner_graphql_type = type_map["MyInnerObjectType"] + # TODO: create_container not supported by core-next container = graphql_type.create_container( { "bar": "oh!", @@ -205,7 +210,7 @@ def resolve_foo_bar(self, args, info): own_field = fields["own"] assert own_field.type == graphql_type foo_field = fields["fooBar"] - assert isinstance(foo_field, GraphQLInputObjectField) + assert isinstance(foo_field, GraphQLInputField) assert foo_field.description == "Field description" @@ -215,9 +220,9 @@ class MyObjectType(ObjectType): foo_bar = String(bar_foo=String()) - typemap = TypeMap([MyObjectType]) - assert "MyObjectType" in typemap - graphql_type = typemap["MyObjectType"] + type_map = create_type_map([MyObjectType]) + assert "MyObjectType" in type_map + graphql_type = type_map["MyObjectType"] assert isinstance(graphql_type, GraphQLObjectType) assert graphql_type.name == "MyObjectType" assert graphql_type.description == "Description" @@ -227,7 +232,12 @@ class MyObjectType(ObjectType): foo_field = fields["fooBar"] assert isinstance(foo_field, GraphQLField) assert foo_field.args == { - "barFoo": GraphQLArgument(GraphQLString, out_name="bar_foo") + "barFoo": GraphQLArgument( + GraphQLString, + default_value=None, + # TODO: out_name not (yet) supported by core-next + # out_name="bar_foo" + ) } @@ -237,9 +247,9 @@ class MyObjectType(ObjectType): foo_bar = String(bar_foo=String()) - typemap = TypeMap([MyObjectType], auto_camelcase=False) - assert "MyObjectType" in typemap - graphql_type = typemap["MyObjectType"] + type_map = create_type_map([MyObjectType], auto_camelcase=False) + assert "MyObjectType" in type_map + graphql_type = type_map["MyObjectType"] assert isinstance(graphql_type, GraphQLObjectType) assert graphql_type.name == "MyObjectType" assert graphql_type.description == "Description" @@ -249,7 +259,12 @@ class MyObjectType(ObjectType): foo_field = fields["foo_bar"] assert isinstance(foo_field, GraphQLField) assert foo_field.args == { - "bar_foo": GraphQLArgument(GraphQLString, out_name="bar_foo") + "bar_foo": GraphQLArgument( + GraphQLString, + default_value=None, + # TODO: out_name not (yet) supported by core-next + # out_name="bar_foo" + ) } @@ -262,8 +277,8 @@ class Meta: foo_bar = String() - typemap = TypeMap([MyObjectType]) - graphql_type = typemap["MyObjectType"] + type_map = create_type_map([MyObjectType]) + graphql_type = type_map["MyObjectType"] assert graphql_type.is_type_of assert graphql_type.is_type_of({}, None) is True assert graphql_type.is_type_of(MyObjectType(), None) is False @@ -279,8 +294,8 @@ class MyOtherObjectType(ObjectType): def resolve_type_func(root, info): return MyOtherObjectType - typemap = TypeMap([MyObjectType]) + type_map = create_type_map([MyObjectType]) with pytest.raises(AssertionError) as excinfo: - resolve_type(resolve_type_func, typemap, "MyOtherObjectType", {}, {}) + resolve_type(resolve_type_func, type_map, "MyOtherObjectType", {}, {}, None) assert "MyOtherObjectTyp" in str(excinfo.value) diff --git a/graphene/types/typemap.py b/graphene/types/typemap.py deleted file mode 100644 index 9edb85181..000000000 --- a/graphene/types/typemap.py +++ /dev/null @@ -1,337 +0,0 @@ -import inspect -from collections import OrderedDict -from functools import partial - -from graphql import ( - GraphQLArgument, - GraphQLBoolean, - GraphQLField, - GraphQLFloat, - GraphQLID, - GraphQLInputObjectField, - GraphQLInt, - GraphQLList, - GraphQLNonNull, - GraphQLString, -) -from graphql.execution.executor import get_default_resolve_type_fn -from graphql.type import GraphQLEnumValue -from graphql.type.typemap import GraphQLTypeMap - -from ..utils.get_unbound_function import get_unbound_function -from ..utils.str_converters import to_camel_case -from .definitions import ( - GrapheneEnumType, - GrapheneGraphQLType, - GrapheneInputObjectType, - GrapheneInterfaceType, - GrapheneObjectType, - GrapheneScalarType, - GrapheneUnionType, -) -from .dynamic import Dynamic -from .enum import Enum -from .field import Field -from .inputobjecttype import InputObjectType -from .interface import Interface -from .objecttype import ObjectType -from .resolver import get_default_resolver -from .scalars import ID, Boolean, Float, Int, Scalar, String -from .structures import List, NonNull -from .union import Union -from .utils import get_field_as - - -def is_graphene_type(_type): - if isinstance(_type, (List, NonNull)): - return True - if inspect.isclass(_type) and issubclass( - _type, (ObjectType, InputObjectType, Scalar, Interface, Union, Enum) - ): - return True - - -def resolve_type(resolve_type_func, map, type_name, root, info): - _type = resolve_type_func(root, info) - - if not _type: - return_type = map[type_name] - return get_default_resolve_type_fn(root, info, return_type) - - if inspect.isclass(_type) and issubclass(_type, ObjectType): - graphql_type = map.get(_type._meta.name) - assert graphql_type, "Can't find type {} in schema".format(_type._meta.name) - assert graphql_type.graphene_type == _type, ( - "The type {} does not match with the associated graphene type {}." - ).format(_type, graphql_type.graphene_type) - return graphql_type - - return _type - - -def is_type_of_from_possible_types(possible_types, root, info): - return isinstance(root, possible_types) - - -class TypeMap(GraphQLTypeMap): - def __init__(self, types, auto_camelcase=True, schema=None): - self.auto_camelcase = auto_camelcase - self.schema = schema - super(TypeMap, self).__init__(types) - - def reducer(self, map, type): - if not type: - return map - if inspect.isfunction(type): - type = type() - if is_graphene_type(type): - return self.graphene_reducer(map, type) - return GraphQLTypeMap.reducer(map, type) - - def graphene_reducer(self, map, type): - if isinstance(type, (List, NonNull)): - return self.reducer(map, type.of_type) - if type._meta.name in map: - _type = map[type._meta.name] - if isinstance(_type, GrapheneGraphQLType): - assert _type.graphene_type == type, ( - "Found different types with the same name in the schema: {}, {}." - ).format(_type.graphene_type, type) - return map - - if issubclass(type, ObjectType): - internal_type = self.construct_objecttype(map, type) - elif issubclass(type, InputObjectType): - internal_type = self.construct_inputobjecttype(map, type) - elif issubclass(type, Interface): - internal_type = self.construct_interface(map, type) - elif issubclass(type, Scalar): - internal_type = self.construct_scalar(map, type) - elif issubclass(type, Enum): - internal_type = self.construct_enum(map, type) - elif issubclass(type, Union): - internal_type = self.construct_union(map, type) - else: - raise Exception("Expected Graphene type, but received: {}.".format(type)) - - return GraphQLTypeMap.reducer(map, internal_type) - - def construct_scalar(self, map, type): - # We have a mapping to the original GraphQL types - # so there are no collisions. - _scalars = { - String: GraphQLString, - Int: GraphQLInt, - Float: GraphQLFloat, - Boolean: GraphQLBoolean, - ID: GraphQLID, - } - if type in _scalars: - return _scalars[type] - - return GrapheneScalarType( - graphene_type=type, - name=type._meta.name, - description=type._meta.description, - serialize=getattr(type, "serialize", None), - parse_value=getattr(type, "parse_value", None), - parse_literal=getattr(type, "parse_literal", None), - ) - - def construct_enum(self, map, type): - values = OrderedDict() - for name, value in type._meta.enum.__members__.items(): - description = getattr(value, "description", None) - deprecation_reason = getattr(value, "deprecation_reason", None) - if not description and callable(type._meta.description): - description = type._meta.description(value) - - if not deprecation_reason and callable(type._meta.deprecation_reason): - deprecation_reason = type._meta.deprecation_reason(value) - - values[name] = GraphQLEnumValue( - name=name, - value=value.value, - description=description, - deprecation_reason=deprecation_reason, - ) - - type_description = ( - type._meta.description(None) - if callable(type._meta.description) - else type._meta.description - ) - - return GrapheneEnumType( - graphene_type=type, - values=values, - name=type._meta.name, - description=type_description, - ) - - def construct_objecttype(self, map, type): - if type._meta.name in map: - _type = map[type._meta.name] - if isinstance(_type, GrapheneGraphQLType): - assert _type.graphene_type == type, ( - "Found different types with the same name in the schema: {}, {}." - ).format(_type.graphene_type, type) - return _type - - def interfaces(): - interfaces = [] - for interface in type._meta.interfaces: - self.graphene_reducer(map, interface) - internal_type = map[interface._meta.name] - assert internal_type.graphene_type == interface - interfaces.append(internal_type) - return interfaces - - if type._meta.possible_types: - is_type_of = partial( - is_type_of_from_possible_types, type._meta.possible_types - ) - else: - is_type_of = type.is_type_of - - return GrapheneObjectType( - graphene_type=type, - name=type._meta.name, - description=type._meta.description, - fields=partial(self.construct_fields_for_type, map, type), - is_type_of=is_type_of, - interfaces=interfaces, - ) - - def construct_interface(self, map, type): - if type._meta.name in map: - _type = map[type._meta.name] - if isinstance(_type, GrapheneInterfaceType): - assert _type.graphene_type == type, ( - "Found different types with the same name in the schema: {}, {}." - ).format(_type.graphene_type, type) - return _type - - _resolve_type = None - if type.resolve_type: - _resolve_type = partial( - resolve_type, type.resolve_type, map, type._meta.name - ) - return GrapheneInterfaceType( - graphene_type=type, - name=type._meta.name, - description=type._meta.description, - fields=partial(self.construct_fields_for_type, map, type), - resolve_type=_resolve_type, - ) - - def construct_inputobjecttype(self, map, type): - return GrapheneInputObjectType( - graphene_type=type, - name=type._meta.name, - description=type._meta.description, - container_type=type._meta.container, - fields=partial( - self.construct_fields_for_type, map, type, is_input_type=True - ), - ) - - def construct_union(self, map, type): - _resolve_type = None - if type.resolve_type: - _resolve_type = partial( - resolve_type, type.resolve_type, map, type._meta.name - ) - - def types(): - union_types = [] - for objecttype in type._meta.types: - self.graphene_reducer(map, objecttype) - internal_type = map[objecttype._meta.name] - assert internal_type.graphene_type == objecttype - union_types.append(internal_type) - return union_types - - return GrapheneUnionType( - graphene_type=type, - name=type._meta.name, - description=type._meta.description, - types=types, - resolve_type=_resolve_type, - ) - - def get_name(self, name): - if self.auto_camelcase: - return to_camel_case(name) - return name - - def construct_fields_for_type(self, map, type, is_input_type=False): - fields = OrderedDict() - for name, field in type._meta.fields.items(): - if isinstance(field, Dynamic): - field = get_field_as(field.get_type(self.schema), _as=Field) - if not field: - continue - map = self.reducer(map, field.type) - field_type = self.get_field_type(map, field.type) - if is_input_type: - _field = GraphQLInputObjectField( - field_type, - default_value=field.default_value, - out_name=name, - description=field.description, - ) - else: - args = OrderedDict() - for arg_name, arg in field.args.items(): - map = self.reducer(map, arg.type) - arg_type = self.get_field_type(map, arg.type) - processed_arg_name = arg.name or self.get_name(arg_name) - args[processed_arg_name] = GraphQLArgument( - arg_type, - out_name=arg_name, - description=arg.description, - default_value=arg.default_value, - ) - _field = GraphQLField( - field_type, - args=args, - resolver=field.get_resolver( - self.get_resolver_for_type(type, name, field.default_value) - ), - deprecation_reason=field.deprecation_reason, - description=field.description, - ) - field_name = field.name or self.get_name(name) - fields[field_name] = _field - return fields - - def get_resolver_for_type(self, type, name, default_value): - if not issubclass(type, ObjectType): - return - resolver = getattr(type, "resolve_{}".format(name), None) - if not resolver: - # If we don't find the resolver in the ObjectType class, then try to - # find it in each of the interfaces - interface_resolver = None - for interface in type._meta.interfaces: - if name not in interface._meta.fields: - continue - interface_resolver = getattr(interface, "resolve_{}".format(name), None) - if interface_resolver: - break - resolver = interface_resolver - - # Only if is not decorated with classmethod - if resolver: - return get_unbound_function(resolver) - - default_resolver = type._meta.default_resolver or get_default_resolver() - return partial(default_resolver, name, default_value) - - def get_field_type(self, map, type): - if isinstance(type, List): - return GraphQLList(self.get_field_type(map, type.of_type)) - if isinstance(type, NonNull): - return GraphQLNonNull(self.get_field_type(map, type.of_type)) - return map.get(type._meta.name) From ff83b72b6867e39a6b0d9a4665b9b1fcc9e633c6 Mon Sep 17 00:00:00 2001 From: Christoph Zwerschke Date: Sun, 30 Jun 2019 02:47:57 +0200 Subject: [PATCH 06/26] Ignore more venv names and mypy and pytest caches --- .gitignore | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/.gitignore b/.gitignore index 9b3eefe9f..9148845fa 100644 --- a/.gitignore +++ b/.gitignore @@ -10,7 +10,6 @@ __pycache__/ # Distribution / packaging .Python -env/ build/ develop-eggs/ dist/ @@ -45,7 +44,8 @@ htmlcov/ .pytest_cache nosetests.xml coverage.xml -*,cover +*.cover +.pytest_cache/ # Translations *.mo @@ -60,6 +60,14 @@ docs/_build/ # PyBuilder target/ +# VirtualEnv +.env +.venv +env/ +venv/ + +# Typing +.mypy_cache/ /tests/django.sqlite @@ -82,4 +90,3 @@ target/ *.sqlite3 .vscode .mypy_cache -/.venv From 5991c6efca7c6a669dfe04cee193c8112ce5fce6 Mon Sep 17 00:00:00 2001 From: Christoph Zwerschke Date: Sun, 30 Jun 2019 12:03:20 +0200 Subject: [PATCH 07/26] Remove print statements for debugging in schema test --- graphene/types/tests/test_schema.py | 4 ---- 1 file changed, 4 deletions(-) diff --git a/graphene/types/tests/test_schema.py b/graphene/types/tests/test_schema.py index acd6f9721..18f102a05 100644 --- a/graphene/types/tests/test_schema.py +++ b/graphene/types/tests/test_schema.py @@ -52,8 +52,4 @@ def test_schema_str(): def test_schema_introspect(): schema = Schema(Query) - print() - print(schema) - print() - print(schema.introspect()) assert "__schema" in schema.introspect() From d259bf611321eebd4932e5ff5acd417d2d7a74ab Mon Sep 17 00:00:00 2001 From: Christoph Zwerschke Date: Sun, 30 Jun 2019 21:31:49 +0200 Subject: [PATCH 08/26] core-next now provides out_type and out_name --- graphene/types/schema.py | 9 +++------ graphene/types/tests/test_inputobjecttype.py | 2 +- graphene/types/tests/test_type_map.py | 21 ++++++++------------ 3 files changed, 12 insertions(+), 20 deletions(-) diff --git a/graphene/types/schema.py b/graphene/types/schema.py index c1c8e825b..be40bd6ae 100644 --- a/graphene/types/schema.py +++ b/graphene/types/schema.py @@ -296,8 +296,7 @@ def construct_inputobjecttype(self, map_, type_): graphene_type=type_, name=type_._meta.name, description=type_._meta.description, - # TODO: container_type not supported by core-next (is this needed?) - # container_type=type_._meta.container, + out_type=type_._meta.container, fields=partial( self.construct_fields_for_type, map_, type_, is_input_type=True ), @@ -345,8 +344,7 @@ def construct_fields_for_type(self, map_, type_, is_input_type=False): _field = GraphQLInputField( field_type, default_value=field.default_value, - # TODO: out_name not (yet) supported by core-next - # out_name=name, + out_name=name, description=field.description, ) else: @@ -357,8 +355,7 @@ def construct_fields_for_type(self, map_, type_, is_input_type=False): processed_arg_name = arg.name or self.get_name(arg_name) args[processed_arg_name] = GraphQLArgument( arg_type, - # TODO: out_name not (yet) supported by core-next - # out_name=arg_name, + out_name=arg_name, description=arg.description, default_value=INVALID if isinstance(arg.type, NonNull) diff --git a/graphene/types/tests/test_inputobjecttype.py b/graphene/types/tests/test_inputobjecttype.py index a88953bfa..de42b66e4 100644 --- a/graphene/types/tests/test_inputobjecttype.py +++ b/graphene/types/tests/test_inputobjecttype.py @@ -105,7 +105,6 @@ class MyInputObjectType(MyAbstractType, InputObjectType): ] -# TOOD: I think this fails because container_type is not supported in core-next def test_inputobjecttype_of_input(): class Child(InputObjectType): first_name = String() @@ -134,5 +133,6 @@ def resolve_is_child(self, info, parent): } """ ) + assert not result.errors assert result.data == {"isChild": True} diff --git a/graphene/types/tests/test_type_map.py b/graphene/types/tests/test_type_map.py index b4d7d6ad3..9c4d95fb0 100644 --- a/graphene/types/tests/test_type_map.py +++ b/graphene/types/tests/test_type_map.py @@ -90,8 +90,7 @@ def resolve_foo(self, bar): GraphQLString, description="Argument description", default_value="x", - # TODO: out_name not (yet) supported by core-next - # out_name="bar", + out_name="bar", ) } @@ -148,8 +147,7 @@ def resolve_foo(self, args, info): GraphQLString, description="Argument description", default_value="x", - # TODO: out_name not (yet) supported by core-next - # out_name="bar", + out_name="bar", ) } @@ -182,15 +180,14 @@ def resolve_foo_bar(self, args, info): other_graphql_type = type_map["OtherObjectType"] inner_graphql_type = type_map["MyInnerObjectType"] - # TODO: create_container not supported by core-next - container = graphql_type.create_container( + container = graphql_type.out_type( { "bar": "oh!", - "baz": inner_graphql_type.create_container( + "baz": inner_graphql_type.out_type( { "some_other_field": [ - other_graphql_type.create_container({"thingy": 1}), - other_graphql_type.create_container({"thingy": 2}), + other_graphql_type.out_type({"thingy": 1}), + other_graphql_type.out_type({"thingy": 2}), ] } ), @@ -235,8 +232,7 @@ class MyObjectType(ObjectType): "barFoo": GraphQLArgument( GraphQLString, default_value=None, - # TODO: out_name not (yet) supported by core-next - # out_name="bar_foo" + out_name="bar_foo" ) } @@ -262,8 +258,7 @@ class MyObjectType(ObjectType): "bar_foo": GraphQLArgument( GraphQLString, default_value=None, - # TODO: out_name not (yet) supported by core-next - # out_name="bar_foo" + out_name="bar_foo" ) } From 34936092cadf16a300e734691609dbf74c53aa40 Mon Sep 17 00:00:00 2001 From: Christoph Zwerschke Date: Sun, 30 Jun 2019 21:32:46 +0200 Subject: [PATCH 09/26] Adapt date and time scalar types to core-next --- graphene/types/datetime.py | 9 +++++---- graphene/types/tests/test_datetime.py | 25 ++++++++++++++++--------- 2 files changed, 21 insertions(+), 13 deletions(-) diff --git a/graphene/types/datetime.py b/graphene/types/datetime.py index fecdd88a9..c533d23e4 100644 --- a/graphene/types/datetime.py +++ b/graphene/types/datetime.py @@ -3,7 +3,8 @@ import datetime from aniso8601 import parse_date, parse_datetime, parse_time -from graphql.language.ast import StringValueNode +from graphql.error import INVALID +from graphql.language import StringValueNode from .scalars import Scalar @@ -37,7 +38,7 @@ def parse_value(value): elif isinstance(value, str): return parse_date(value) except ValueError: - return None + return INVALID class DateTime(Scalar): @@ -67,7 +68,7 @@ def parse_value(value): elif isinstance(value, str): return parse_datetime(value) except ValueError: - return None + return INVALID class Time(Scalar): @@ -97,4 +98,4 @@ def parse_value(cls, value): elif isinstance(value, str): return parse_time(value) except ValueError: - return None + return INVALID diff --git a/graphene/types/tests/test_datetime.py b/graphene/types/tests/test_datetime.py index bb6f212c9..c08af7e7a 100644 --- a/graphene/types/tests/test_datetime.py +++ b/graphene/types/tests/test_datetime.py @@ -76,12 +76,15 @@ def test_time_query(sample_time): def test_bad_datetime_query(): - not_a_date = "Some string that's not a date" + not_a_date = "Some string that's not a datetime" result = schema.execute("""{ datetime(in: "%s") }""" % not_a_date) - assert len(result.errors) == 1 - assert isinstance(result.errors[0], GraphQLError) + assert result.errors and len(result.errors) == 1 + error = result.errors[0] + assert isinstance(error, GraphQLError) + assert error.message == ( + "Expected type DateTime, found \"Some string that's not a datetime\".") assert result.data is None @@ -90,18 +93,22 @@ def test_bad_date_query(): result = schema.execute("""{ date(in: "%s") }""" % not_a_date) - assert len(result.errors) == 1 - assert isinstance(result.errors[0], GraphQLError) + error = result.errors[0] + assert isinstance(error, GraphQLError) + assert error.message == ( + "Expected type Date, found \"Some string that's not a date\".") assert result.data is None def test_bad_time_query(): - not_a_date = "Some string that's not a date" + not_a_date = "Some string that's not a time" result = schema.execute("""{ time(at: "%s") }""" % not_a_date) - assert len(result.errors) == 1 - assert isinstance(result.errors[0], GraphQLError) + error = result.errors[0] + assert isinstance(error, GraphQLError) + assert error.message == ( + "Expected type Time, found \"Some string that's not a time\".") assert result.data is None @@ -174,7 +181,7 @@ def _test_bad_variables(type_, input_): ), variables={"input": input_}, ) - assert len(result.errors) == 1 + assert result.errors and len(result.errors) == 1 # when `input` is not JSON serializable formatting the error message in # `graphql.utils.is_valid_value` line 79 fails with a TypeError assert isinstance(result.errors[0], GraphQLError) From 0d9ba1c0d96aac510907cf33244f22d171f38c29 Mon Sep 17 00:00:00 2001 From: Christoph Zwerschke Date: Sun, 30 Jun 2019 21:47:18 +0200 Subject: [PATCH 10/26] Ignore the non-standard result.invalid flag --- graphene/test/__init__.py | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/graphene/test/__init__.py b/graphene/test/__init__.py index 4d2e4df0c..fd6a2126c 100644 --- a/graphene/test/__init__.py +++ b/graphene/test/__init__.py @@ -8,20 +8,15 @@ def default_format_error(error): if isinstance(error, GraphQLError): return format_graphql_error(error) - return {"message": str(error)} def format_execution_result(execution_result, format_error): if execution_result: response = {} - if execution_result.errors: response["errors"] = [format_error(e) for e in execution_result.errors] - - if not execution_result.invalid: - response["data"] = execution_result.data - + response["data"] = execution_result.data return response From 8106fff522cfe8ceb1ed388841cfd3999a53f379 Mon Sep 17 00:00:00 2001 From: Christoph Zwerschke Date: Sun, 30 Jun 2019 21:51:48 +0200 Subject: [PATCH 11/26] Results are named tuples in core-next (immutable) --- graphene/utils/tests/test_deduplicator.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/graphene/utils/tests/test_deduplicator.py b/graphene/utils/tests/test_deduplicator.py index 604ae4388..b845caf19 100644 --- a/graphene/utils/tests/test_deduplicator.py +++ b/graphene/utils/tests/test_deduplicator.py @@ -150,8 +150,8 @@ def resolve_events(_, info): result = schema.execute(query) assert not result.errors - result.data = deflate(result.data) - assert result.data == { + data = deflate(result.data) + assert data == { "events": [ { "__typename": "Event", From c8adab675a84057da9c85efc040e2a85c78b6a96 Mon Sep 17 00:00:00 2001 From: Christoph Zwerschke Date: Sun, 30 Jun 2019 22:02:50 +0200 Subject: [PATCH 12/26] Enum values are returned as dict in core-next --- graphene/types/tests/test_enum.py | 35 +++++++------------------------ 1 file changed, 7 insertions(+), 28 deletions(-) diff --git a/graphene/types/tests/test_enum.py b/graphene/types/tests/test_enum.py index e09379920..cf7227e46 100644 --- a/graphene/types/tests/test_enum.py +++ b/graphene/types/tests/test_enum.py @@ -80,36 +80,15 @@ def custom_deprecation_reason(value): class Query(ObjectType): foo = Episode() - schema = Schema(query=Query) + schema = Schema(query=Query).graphql_schema - GraphQLPyEpisode = schema._type_map["PyEpisode"].values + episode = schema.get_type("PyEpisode") - assert schema._type_map["PyEpisode"].description == "StarWars Episodes" - assert ( - GraphQLPyEpisode[0].name == "NEWHOPE" - and GraphQLPyEpisode[0].description == "New Hope Episode" - ) - assert ( - GraphQLPyEpisode[1].name == "EMPIRE" - and GraphQLPyEpisode[1].description == "Other" - ) - assert ( - GraphQLPyEpisode[2].name == "JEDI" - and GraphQLPyEpisode[2].description == "Other" - ) - - assert ( - GraphQLPyEpisode[0].name == "NEWHOPE" - and GraphQLPyEpisode[0].deprecation_reason == "meh" - ) - assert ( - GraphQLPyEpisode[1].name == "EMPIRE" - and GraphQLPyEpisode[1].deprecation_reason is None - ) - assert ( - GraphQLPyEpisode[2].name == "JEDI" - and GraphQLPyEpisode[2].deprecation_reason is None - ) + assert episode.description == "StarWars Episodes" + assert [(name, value.description, value.deprecation_reason) + for name, value in episode.values.items()] == [ + ('NEWHOPE', 'New Hope Episode', 'meh'), + ('EMPIRE', 'Other', None), ('JEDI', 'Other', None)] def test_enum_from_python3_enum_uses_enum_doc(): From f1ae6e4cd734772a76f97252671aafeef8f52ed5 Mon Sep 17 00:00:00 2001 From: Christoph Zwerschke Date: Sun, 30 Jun 2019 22:27:33 +0200 Subject: [PATCH 13/26] Fix mutation tests with promises --- graphene/relay/tests/test_mutation.py | 22 +++++++++++----------- graphene/types/schema.py | 10 +++++----- tests_asyncio/test_relay_mutation.py | 15 +++++---------- 3 files changed, 21 insertions(+), 26 deletions(-) diff --git a/graphene/relay/tests/test_mutation.py b/graphene/relay/tests/test_mutation.py index b58a3ddfc..96ddcf0c9 100644 --- a/graphene/relay/tests/test_mutation.py +++ b/graphene/relay/tests/test_mutation.py @@ -1,5 +1,4 @@ -import pytest -from promise import Promise +from pytest import mark, raises from ...types import ( ID, @@ -55,15 +54,15 @@ def mutate_and_get_payload(self, info, what, client_mutation_id=None): return FixedSaySomething(phrase=str(what)) -class SaySomethingPromise(ClientIDMutation): +class SaySomethingAsync(ClientIDMutation): class Input: what = String() phrase = String() @staticmethod - def mutate_and_get_payload(self, info, what, client_mutation_id=None): - return Promise.resolve(SaySomething(phrase=str(what))) + async def mutate_and_get_payload(self, info, what, client_mutation_id=None): + return SaySomething(phrase=str(what)) # MyEdge = MyNode.Connection.Edge @@ -97,7 +96,7 @@ class RootQuery(ObjectType): class Mutation(ObjectType): say = SaySomething.Field() say_fixed = SaySomethingFixed.Field() - say_promise = SaySomethingPromise.Field() + say_async = SaySomethingAsync.Field() other = OtherMutation.Field() @@ -105,7 +104,7 @@ class Mutation(ObjectType): def test_no_mutate_and_get_payload(): - with pytest.raises(AssertionError) as excinfo: + with raises(AssertionError) as excinfo: class MyMutation(ClientIDMutation): pass @@ -185,12 +184,13 @@ def test_node_query_fixed(): ) -def test_node_query_promise(): - executed = schema.execute( - 'mutation a { sayPromise(input: {what:"hello", clientMutationId:"1"}) { phrase } }' +@mark.asyncio +async def test_node_query_async(): + executed = await schema.execute_async( + 'mutation a { sayAsync(input: {what:"hello", clientMutationId:"1"}) { phrase } }' ) assert not executed.errors - assert executed.data == {"sayPromise": {"phrase": "hello"}} + assert executed.data == {"sayAsync": {"phrase": "hello"}} def test_edge_query(): diff --git a/graphene/types/schema.py b/graphene/types/schema.py index be40bd6ae..1d84dea09 100644 --- a/graphene/types/schema.py +++ b/graphene/types/schema.py @@ -454,13 +454,13 @@ def __getattr__(self, type_name): def lazy(self, _type): return lambda: self.get_type(_type) - async def async_execute(self, *args, **kwargs): - return graphql(self.graphql_schema, *args, **normalize_execute_kwargs(kwargs)) + async def execute_async(self, *args, **kwargs): + kwargs = normalize_execute_kwargs(kwargs) + return await graphql(self.graphql_schema, *args, **kwargs) def execute(self, *args, **kwargs): - return graphql_sync( - self.graphql_schema, *args, **normalize_execute_kwargs(kwargs) - ) + kwargs = normalize_execute_kwargs(kwargs) + return graphql_sync(self.graphql_schema, *args, **kwargs) def introspect(self): introspection = self.execute(introspection_query) diff --git a/tests_asyncio/test_relay_mutation.py b/tests_asyncio/test_relay_mutation.py index 42ea5fc77..f63ae1931 100644 --- a/tests_asyncio/test_relay_mutation.py +++ b/tests_asyncio/test_relay_mutation.py @@ -1,5 +1,4 @@ -import pytest -from graphql.execution.executors.asyncio import AsyncioExecutor +from pytest import mark from graphene.types import ID, Field, ObjectType, Schema from graphene.types.scalars import String @@ -64,23 +63,19 @@ class Mutation(ObjectType): schema = Schema(query=RootQuery, mutation=Mutation) -@pytest.mark.asyncio +@mark.asyncio async def test_node_query_promise(): - executed = await schema.execute( + executed = await schema.execute_async( 'mutation a { sayPromise(input: {what:"hello", clientMutationId:"1"}) { phrase } }', - executor=AsyncioExecutor(), - return_promise=True, ) assert not executed.errors assert executed.data == {"sayPromise": {"phrase": "hello"}} -@pytest.mark.asyncio +@mark.asyncio async def test_edge_query(): - executed = await schema.execute( + executed = await schema.execute_async( 'mutation a { other(input: {clientMutationId:"1"}) { clientMutationId, myNodeEdge { cursor node { name }} } }', - executor=AsyncioExecutor(), - return_promise=True, ) assert not executed.errors assert dict(executed.data) == { From c4a850f2ea81458ab81f9d7b7a6ba3f26c90be66 Mon Sep 17 00:00:00 2001 From: Christoph Zwerschke Date: Sun, 30 Jun 2019 23:56:23 +0200 Subject: [PATCH 14/26] Make all 345 tests pass with graphql-core-next --- .../snap_test_objectidentification.py | 42 ++++-- graphene/relay/connection.py | 4 +- graphene/relay/node.py | 2 +- graphene/relay/tests/test_connection_query.py | 130 ++++++++++-------- graphene/relay/tests/test_mutation.py | 4 +- graphene/relay/tests/test_node.py | 93 +++++++------ graphene/relay/tests/test_node_custom.py | 95 +++++++------ graphene/tests/issues/test_356.py | 5 +- graphene/types/tests/test_datetime.py | 1 - graphene/types/tests/test_query.py | 18 +-- graphene/utils/thenables.py | 31 +---- graphene/utils/thenables_asyncio.py | 5 - tests_asyncio/test_relay_connection.py | 25 ++-- tests_asyncio/test_relay_mutation.py | 4 +- 14 files changed, 246 insertions(+), 213 deletions(-) delete mode 100644 graphene/utils/thenables_asyncio.py diff --git a/examples/starwars_relay/tests/snapshots/snap_test_objectidentification.py b/examples/starwars_relay/tests/snapshots/snap_test_objectidentification.py index 2d13cba3a..cb57709ae 100644 --- a/examples/starwars_relay/tests/snapshots/snap_test_objectidentification.py +++ b/examples/starwars_relay/tests/snapshots/snap_test_objectidentification.py @@ -30,21 +30,22 @@ snapshots[ "test_str_schema 1" -] = """schema { - query: Query - mutation: Mutation -} - +] = '''"""A faction in the Star Wars saga""" type Faction implements Node { + """The ID of the object""" id: ID! + + """The name of the faction.""" name: String - ships(before: String, after: String, first: Int, last: Int): ShipConnection + + """The ships used by the faction.""" + ships(before: String = null, after: String = null, first: Int = null, last: Int = null): ShipConnection } input IntroduceShipInput { shipName: String! factionId: String! - clientMutationId: String + clientMutationId: String = null } type IntroduceShipPayload { @@ -57,35 +58,60 @@ introduceShip(input: IntroduceShipInput!): IntroduceShipPayload } +"""An object with an ID""" interface Node { + """The ID of the object""" id: ID! } +""" +The Relay compliant `PageInfo` type, containing data necessary to paginate this connection. +""" type PageInfo { + """When paginating forwards, are there more items?""" hasNextPage: Boolean! + + """When paginating backwards, are there more items?""" hasPreviousPage: Boolean! + + """When paginating backwards, the cursor to continue.""" startCursor: String + + """When paginating forwards, the cursor to continue.""" endCursor: String } type Query { rebels: Faction empire: Faction + + """The ID of the object""" node(id: ID!): Node } +"""A ship in the Star Wars saga""" type Ship implements Node { + """The ID of the object""" id: ID! + + """The name of the ship.""" name: String } type ShipConnection { + """Pagination data for this connection.""" pageInfo: PageInfo! + + """Contains the nodes in this connection.""" edges: [ShipEdge]! } +"""A Relay edge containing a `Ship` and its cursor.""" type ShipEdge { + """The item at the end of the edge""" node: Ship + + """A cursor for use in pagination""" cursor: String! } -""" +''' diff --git a/graphene/relay/connection.py b/graphene/relay/connection.py index 047f2b4de..24804dbdd 100644 --- a/graphene/relay/connection.py +++ b/graphene/relay/connection.py @@ -133,7 +133,7 @@ def type(self): ) assert issubclass(connection_type, Connection), ( - '{} type have to be a subclass of Connection. Received "{}".' + '{} type has to be a subclass of Connection. Received "{}".' ).format(self.__class__.__name__, connection_type) return type @@ -143,7 +143,7 @@ def resolve_connection(cls, connection_type, args, resolved): return resolved assert isinstance(resolved, Iterable), ( - "Resolved value from the connection field have to be iterable or instance of {}. " + "Resolved value from the connection field has to be iterable or instance of {}. " 'Received "{}"' ).format(connection_type, resolved) connection = connection_from_list( diff --git a/graphene/relay/node.py b/graphene/relay/node.py index d9c4c0f6c..f80db13e9 100644 --- a/graphene/relay/node.py +++ b/graphene/relay/node.py @@ -73,7 +73,7 @@ class Meta: def __init_subclass_with_meta__(cls, **options): _meta = InterfaceOptions(cls) _meta.fields = OrderedDict( - id=GlobalID(cls, description="The ID of the object.") + id=GlobalID(cls, description="The ID of the object") ) super(AbstractNode, cls).__init_subclass_with_meta__(_meta=_meta, **options) diff --git a/graphene/relay/tests/test_connection_query.py b/graphene/relay/tests/test_connection_query.py index be6ee8c74..7e6f2c4d5 100644 --- a/graphene/relay/tests/test_connection_query.py +++ b/graphene/relay/tests/test_connection_query.py @@ -1,7 +1,6 @@ -from collections import OrderedDict +from pytest import mark from graphql_relay.utils import base64 -from promise import Promise from ...types import ObjectType, Schema, String from ..connection import Connection, ConnectionField, PageInfo @@ -25,15 +24,15 @@ class Meta: class Query(ObjectType): letters = ConnectionField(LetterConnection) connection_letters = ConnectionField(LetterConnection) - promise_letters = ConnectionField(LetterConnection) + async_letters = ConnectionField(LetterConnection) node = Node.Field() def resolve_letters(self, info, **args): return list(letters.values()) - def resolve_promise_letters(self, info, **args): - return Promise.resolve(list(letters.values())) + async def resolve_async_letters(self, info, **args): + return list(letters.values()) def resolve_connection_letters(self, info, **args): return LetterConnection( @@ -46,9 +45,7 @@ def resolve_connection_letters(self, info, **args): schema = Schema(Query) -letters = OrderedDict() -for i, letter in enumerate(letter_chars): - letters[letter] = Letter(id=i, letter=letter) +letters = {letter: Letter(id=i, letter=letter) for i, letter in enumerate(letter_chars)} def edges(selected_letters): @@ -66,11 +63,11 @@ def cursor_for(ltr): return base64("arrayconnection:%s" % letter.id) -def execute(args=""): +async def execute(args=""): if args: args = "(" + args + ")" - return schema.execute( + return await schema.execute_async( """ { letters%s { @@ -94,8 +91,8 @@ def execute(args=""): ) -def check(args, letters, has_previous_page=False, has_next_page=False): - result = execute(args) +async def check(args, letters, has_previous_page=False, has_next_page=False): + result = await execute(args) expected_edges = edges(letters) expected_page_info = { "hasPreviousPage": has_previous_page, @@ -110,96 +107,114 @@ def check(args, letters, has_previous_page=False, has_next_page=False): } -def test_returns_all_elements_without_filters(): - check("", "ABCDE") +@mark.asyncio +async def test_returns_all_elements_without_filters(): + await check("", "ABCDE") -def test_respects_a_smaller_first(): - check("first: 2", "AB", has_next_page=True) +@mark.asyncio +async def test_respects_a_smaller_first(): + await check("first: 2", "AB", has_next_page=True) -def test_respects_an_overly_large_first(): - check("first: 10", "ABCDE") +@mark.asyncio +async def test_respects_an_overly_large_first(): + await check("first: 10", "ABCDE") -def test_respects_a_smaller_last(): - check("last: 2", "DE", has_previous_page=True) +@mark.asyncio +async def test_respects_a_smaller_last(): + await check("last: 2", "DE", has_previous_page=True) -def test_respects_an_overly_large_last(): - check("last: 10", "ABCDE") +@mark.asyncio +async def test_respects_an_overly_large_last(): + await check("last: 10", "ABCDE") -def test_respects_first_and_after(): - check('first: 2, after: "{}"'.format(cursor_for("B")), "CD", has_next_page=True) +@mark.asyncio +async def test_respects_first_and_after(): + await check('first: 2, after: "{}"'.format(cursor_for("B")), "CD", has_next_page=True) -def test_respects_first_and_after_with_long_first(): - check('first: 10, after: "{}"'.format(cursor_for("B")), "CDE") +@mark.asyncio +async def test_respects_first_and_after_with_long_first(): + await check('first: 10, after: "{}"'.format(cursor_for("B")), "CDE") -def test_respects_last_and_before(): - check('last: 2, before: "{}"'.format(cursor_for("D")), "BC", has_previous_page=True) +@mark.asyncio +async def test_respects_last_and_before(): + await check('last: 2, before: "{}"'.format(cursor_for("D")), "BC", has_previous_page=True) -def test_respects_last_and_before_with_long_last(): - check('last: 10, before: "{}"'.format(cursor_for("D")), "ABC") +@mark.asyncio +async def test_respects_last_and_before_with_long_last(): + await check('last: 10, before: "{}"'.format(cursor_for("D")), "ABC") -def test_respects_first_and_after_and_before_too_few(): - check( +@mark.asyncio +async def test_respects_first_and_after_and_before_too_few(): + await check( 'first: 2, after: "{}", before: "{}"'.format(cursor_for("A"), cursor_for("E")), "BC", has_next_page=True, ) -def test_respects_first_and_after_and_before_too_many(): - check( +@mark.asyncio +async def test_respects_first_and_after_and_before_too_many(): + await check( 'first: 4, after: "{}", before: "{}"'.format(cursor_for("A"), cursor_for("E")), "BCD", ) -def test_respects_first_and_after_and_before_exactly_right(): - check( +@mark.asyncio +async def test_respects_first_and_after_and_before_exactly_right(): + await check( 'first: 3, after: "{}", before: "{}"'.format(cursor_for("A"), cursor_for("E")), "BCD", ) -def test_respects_last_and_after_and_before_too_few(): - check( +@mark.asyncio +async def test_respects_last_and_after_and_before_too_few(): + await check( 'last: 2, after: "{}", before: "{}"'.format(cursor_for("A"), cursor_for("E")), "CD", has_previous_page=True, ) -def test_respects_last_and_after_and_before_too_many(): - check( +@mark.asyncio +async def test_respects_last_and_after_and_before_too_many(): + await check( 'last: 4, after: "{}", before: "{}"'.format(cursor_for("A"), cursor_for("E")), "BCD", ) -def test_respects_last_and_after_and_before_exactly_right(): - check( +@mark.asyncio +async def test_respects_last_and_after_and_before_exactly_right(): + await check( 'last: 3, after: "{}", before: "{}"'.format(cursor_for("A"), cursor_for("E")), "BCD", ) -def test_returns_no_elements_if_first_is_0(): - check("first: 0", "", has_next_page=True) +@mark.asyncio +async def test_returns_no_elements_if_first_is_0(): + await check("first: 0", "", has_next_page=True) -def test_returns_all_elements_if_cursors_are_invalid(): - check('before: "invalid" after: "invalid"', "ABCDE") +@mark.asyncio +async def test_returns_all_elements_if_cursors_are_invalid(): + await check('before: "invalid" after: "invalid"', "ABCDE") -def test_returns_all_elements_if_cursors_are_on_the_outside(): - check( +@mark.asyncio +async def test_returns_all_elements_if_cursors_are_on_the_outside(): + await check( 'before: "{}" after: "{}"'.format( base64("arrayconnection:%s" % 6), base64("arrayconnection:%s" % -1) ), @@ -207,8 +222,9 @@ def test_returns_all_elements_if_cursors_are_on_the_outside(): ) -def test_returns_no_elements_if_cursors_cross(): - check( +@mark.asyncio +async def test_returns_no_elements_if_cursors_cross(): + await check( 'before: "{}" after: "{}"'.format( base64("arrayconnection:%s" % 2), base64("arrayconnection:%s" % 4) ), @@ -216,8 +232,9 @@ def test_returns_no_elements_if_cursors_cross(): ) -def test_connection_type_nodes(): - result = schema.execute( +@mark.asyncio +async def test_connection_type_nodes(): + result = await schema.execute_async( """ { connectionLetters { @@ -248,11 +265,12 @@ def test_connection_type_nodes(): } -def test_connection_promise(): - result = schema.execute( +@mark.asyncio +async def test_connection_async(): + result = await schema.execute_async( """ { - promiseLetters(first:1) { + asyncLetters(first:1) { edges { node { id @@ -270,7 +288,7 @@ def test_connection_promise(): assert not result.errors assert result.data == { - "promiseLetters": { + "asyncLetters": { "edges": [{"node": {"id": "TGV0dGVyOjA=", "letter": "A"}}], "pageInfo": {"hasPreviousPage": False, "hasNextPage": True}, } diff --git a/graphene/relay/tests/test_mutation.py b/graphene/relay/tests/test_mutation.py index 96ddcf0c9..dcafbd80e 100644 --- a/graphene/relay/tests/test_mutation.py +++ b/graphene/relay/tests/test_mutation.py @@ -80,11 +80,11 @@ class Input(SharedFields): @staticmethod def mutate_and_get_payload( - self, info, shared="", additional_field="", client_mutation_id=None + self, info, shared, additional_field, client_mutation_id=None ): edge_type = MyEdge return OtherMutation( - name=shared + additional_field, + name=(shared or "") + (additional_field or ""), my_node_edge=edge_type(cursor="1", node=MyNode(name="name")), ) diff --git a/graphene/relay/tests/test_node.py b/graphene/relay/tests/test_node.py index fbce1d547..be6cdc85d 100644 --- a/graphene/relay/tests/test_node.py +++ b/graphene/relay/tests/test_node.py @@ -1,7 +1,7 @@ -from collections import OrderedDict - from graphql_relay import to_global_id +from graphql.pyutils import dedent + from ...types import ObjectType, Schema, String from ..node import Node, is_node @@ -70,17 +70,13 @@ def test_subclassed_node_query(): % to_global_id("MyOtherNode", 1) ) assert not executed.errors - assert executed.data == OrderedDict( - { - "node": OrderedDict( - [ - ("shared", "1"), - ("extraField", "extra field info."), - ("somethingElse", "----"), - ] - ) + assert executed.data == { + "node": { + "shared": "1", + "extraField": "extra field info.", + "somethingElse": "----" } - ) + } def test_node_requesting_non_node(): @@ -124,7 +120,7 @@ def test_node_field_only_type_wrong(): % Node.to_global_id("MyOtherNode", 1) ) assert len(executed.errors) == 1 - assert str(executed.errors[0]) == "Must receive a MyNode id." + assert str(executed.errors[0]).startswith("Must receive a MyNode id.") assert executed.data == {"onlyNode": None} @@ -143,39 +139,48 @@ def test_node_field_only_lazy_type_wrong(): % Node.to_global_id("MyOtherNode", 1) ) assert len(executed.errors) == 1 - assert str(executed.errors[0]) == "Must receive a MyNode id." + assert str(executed.errors[0]).startswith("Must receive a MyNode id.") assert executed.data == {"onlyNodeLazy": None} def test_str_schema(): - assert ( - str(schema) - == """ -schema { - query: RootQuery -} - -type MyNode implements Node { - id: ID! - name: String -} - -type MyOtherNode implements Node { - id: ID! - shared: String - somethingElse: String - extraField: String -} - -interface Node { - id: ID! -} - -type RootQuery { - first: String - node(id: ID!): Node - onlyNode(id: ID!): MyNode - onlyNodeLazy(id: ID!): MyNode -} -""".lstrip() + assert (str(schema) == dedent( + ''' + schema { + query: RootQuery + } + + type MyNode implements Node { + """The ID of the object""" + id: ID! + name: String + } + + type MyOtherNode implements Node { + """The ID of the object""" + id: ID! + shared: String + somethingElse: String + extraField: String + } + + """An object with an ID""" + interface Node { + """The ID of the object""" + id: ID! + } + + type RootQuery { + first: String + + """The ID of the object""" + node(id: ID!): Node + + """The ID of the object""" + onlyNode(id: ID!): MyNode + + """The ID of the object""" + onlyNodeLazy(id: ID!): MyNode + } + ''') ) diff --git a/graphene/relay/tests/test_node_custom.py b/graphene/relay/tests/test_node_custom.py index 07e50a1bc..967bbead0 100644 --- a/graphene/relay/tests/test_node_custom.py +++ b/graphene/relay/tests/test_node_custom.py @@ -1,4 +1,5 @@ -from graphql import graphql +from graphql import graphql_sync +from graphql.pyutils import dedent from ...types import Interface, ObjectType, Schema from ...types.scalars import Int, String @@ -15,7 +16,7 @@ def to_global_id(type, id): @staticmethod def get_node_from_global_id(info, id, only_type=None): - assert info.schema == schema + assert info.schema is graphql_schema if id in user_data: return user_data.get(id) else: @@ -23,14 +24,14 @@ def get_node_from_global_id(info, id, only_type=None): class BasePhoto(Interface): - width = Int() + width = Int(description="The width of the photo in pixels") class User(ObjectType): class Meta: interfaces = [CustomNode] - name = String() + name = String(description="The full name of the user") class Photo(ObjectType): @@ -48,37 +49,47 @@ class RootQuery(ObjectType): schema = Schema(query=RootQuery, types=[User, Photo]) +graphql_schema = schema.graphql_schema def test_str_schema_correct(): - assert ( - str(schema) - == """schema { - query: RootQuery -} - -interface BasePhoto { - width: Int -} - -interface Node { - id: ID! -} - -type Photo implements Node, BasePhoto { - id: ID! - width: Int -} - -type RootQuery { - node(id: ID!): Node -} - -type User implements Node { - id: ID! - name: String -} -""" + assert (str(schema) == dedent( + ''' + schema { + query: RootQuery + } + + interface BasePhoto { + """The width of the photo in pixels""" + width: Int + } + + interface Node { + """The ID of the object""" + id: ID! + } + + type Photo implements Node & BasePhoto { + """The ID of the object""" + id: ID! + + """The width of the photo in pixels""" + width: Int + } + + type RootQuery { + """The ID of the object""" + node(id: ID!): Node + } + + type User implements Node { + """The ID of the object""" + id: ID! + + """The full name of the user""" + name: String + } + ''') ) @@ -91,7 +102,7 @@ def test_gets_the_correct_id_for_users(): } """ expected = {"node": {"id": "1"}} - result = graphql(schema, query) + result = graphql_sync(graphql_schema, query) assert not result.errors assert result.data == expected @@ -105,7 +116,7 @@ def test_gets_the_correct_id_for_photos(): } """ expected = {"node": {"id": "4"}} - result = graphql(schema, query) + result = graphql_sync(graphql_schema, query) assert not result.errors assert result.data == expected @@ -122,7 +133,7 @@ def test_gets_the_correct_name_for_users(): } """ expected = {"node": {"id": "1", "name": "John Doe"}} - result = graphql(schema, query) + result = graphql_sync(graphql_schema, query) assert not result.errors assert result.data == expected @@ -139,7 +150,7 @@ def test_gets_the_correct_width_for_photos(): } """ expected = {"node": {"id": "4", "width": 400}} - result = graphql(schema, query) + result = graphql_sync(graphql_schema, query) assert not result.errors assert result.data == expected @@ -154,7 +165,7 @@ def test_gets_the_correct_typename_for_users(): } """ expected = {"node": {"id": "1", "__typename": "User"}} - result = graphql(schema, query) + result = graphql_sync(graphql_schema, query) assert not result.errors assert result.data == expected @@ -169,7 +180,7 @@ def test_gets_the_correct_typename_for_photos(): } """ expected = {"node": {"id": "4", "__typename": "Photo"}} - result = graphql(schema, query) + result = graphql_sync(graphql_schema, query) assert not result.errors assert result.data == expected @@ -186,7 +197,7 @@ def test_ignores_photo_fragments_on_user(): } """ expected = {"node": {"id": "1"}} - result = graphql(schema, query) + result = graphql_sync(graphql_schema, query) assert not result.errors assert result.data == expected @@ -200,7 +211,7 @@ def test_returns_null_for_bad_ids(): } """ expected = {"node": None} - result = graphql(schema, query) + result = graphql_sync(graphql_schema, query) assert not result.errors assert result.data == expected @@ -239,7 +250,7 @@ def test_have_correct_node_interface(): ], } } - result = graphql(schema, query) + result = graphql_sync(graphql_schema, query) assert not result.errors assert result.data == expected @@ -291,6 +302,6 @@ def test_has_correct_node_root_field(): } } } - result = graphql(schema, query) + result = graphql_sync(graphql_schema, query) assert not result.errors assert result.data == expected diff --git a/graphene/tests/issues/test_356.py b/graphene/tests/issues/test_356.py index 4571aad8e..4f3e10667 100644 --- a/graphene/tests/issues/test_356.py +++ b/graphene/tests/issues/test_356.py @@ -27,6 +27,7 @@ class Query(graphene.ObjectType): graphene.Schema(query=Query) assert str(exc_info.value) == ( - "IterableConnectionField type have to be a subclass of Connection. " - 'Received "MyUnion".' + "Query fields cannot be resolved:" + " IterableConnectionField type has to be a subclass of Connection." + ' Received "MyUnion".' ) diff --git a/graphene/types/tests/test_datetime.py b/graphene/types/tests/test_datetime.py index c08af7e7a..1fd21c24e 100644 --- a/graphene/types/tests/test_datetime.py +++ b/graphene/types/tests/test_datetime.py @@ -185,7 +185,6 @@ def _test_bad_variables(type_, input_): # when `input` is not JSON serializable formatting the error message in # `graphql.utils.is_valid_value` line 79 fails with a TypeError assert isinstance(result.errors[0], GraphQLError) - print(result.errors[0]) assert result.data is None not_a_date = dict() diff --git a/graphene/types/tests/test_query.py b/graphene/types/tests/test_query.py index f23747c08..211c2ec93 100644 --- a/graphene/types/tests/test_query.py +++ b/graphene/types/tests/test_query.py @@ -229,11 +229,11 @@ def resolve_test(self, info, **args): result = test_schema.execute("{ test }", None) assert not result.errors - assert result.data == {"test": "[null,{}]"} + assert result.data == {"test": '[null,{"a_str":null,"a_int":null}]'} result = test_schema.execute('{ test(aStr: "String!") }', "Source!") assert not result.errors - assert result.data == {"test": '["Source!",{"a_str":"String!"}]'} + assert result.data == {"test": '["Source!",{"a_str":"String!","a_int":null}]'} result = test_schema.execute('{ test(aInt: -123, aStr: "String!") }', "Source!") assert not result.errors @@ -258,18 +258,20 @@ def resolve_test(self, info, **args): result = test_schema.execute("{ test }", None) assert not result.errors - assert result.data == {"test": "[null,{}]"} + assert result.data == {"test": '[null,{"a_input":null}]'} result = test_schema.execute('{ test(aInput: {aField: "String!"} ) }', "Source!") assert not result.errors - assert result.data == {"test": '["Source!",{"a_input":{"a_field":"String!"}}]'} + assert result.data == { + "test": '["Source!",{"a_input":{"a_field":"String!","recursive_field":null}}]'} result = test_schema.execute( '{ test(aInput: {recursiveField: {aField: "String!"}}) }', "Source!" ) assert not result.errors assert result.data == { - "test": '["Source!",{"a_input":{"recursive_field":{"a_field":"String!"}}}]' + "test": '["Source!",{"a_input":{"a_field":null,"recursive_field":' + '{"a_field":"String!","recursive_field":null}}}]' } @@ -285,8 +287,7 @@ def resolve_other(self, info): return "other" def reversed_middleware(next, *args, **kwargs): - p = next(*args, **kwargs) - return p.then(lambda x: x[::-1]) + return next(*args, **kwargs)[::-1] hello_schema = Schema(Query) @@ -348,10 +349,11 @@ def resolve_all_ints(self, info): return big_list hello_schema = Schema(Query) + graphql_schema = hello_schema.graphql_schema source = Source("{ allInts }") query_ast = parse(source) - big_list_query = partial(execute, hello_schema, query_ast) + big_list_query = partial(execute, graphql_schema, query_ast) result = benchmark(big_list_query) assert not result.errors assert result.data == {"allInts": list(big_list)} diff --git a/graphene/utils/thenables.py b/graphene/utils/thenables.py index a3089595f..96286992e 100644 --- a/graphene/utils/thenables.py +++ b/graphene/utils/thenables.py @@ -1,28 +1,15 @@ """ This file is used mainly as a bridge for thenable abstractions. -This includes: -- Promises -- Asyncio Coroutines """ -try: - from promise import Promise, is_thenable # type: ignore -except ImportError: +from inspect import isawaitable - class Promise(object): # type: ignore - pass - def is_thenable(obj): # type: ignore - return False +def await_and_execute(obj, on_resolve): + async def build_resolve_async(): + return on_resolve(await obj) - -try: - from inspect import isawaitable - from .thenables_asyncio import await_and_execute -except ImportError: - - def isawaitable(obj): # type: ignore - return False + return build_resolve_async() def maybe_thenable(obj, on_resolve): @@ -31,12 +18,8 @@ def maybe_thenable(obj, on_resolve): returning the same type of object inputed. If the object is not thenable, it should return on_resolve(obj) """ - if isawaitable(obj) and not isinstance(obj, Promise): + if isawaitable(obj): return await_and_execute(obj, on_resolve) - if is_thenable(obj): - return Promise.resolve(obj).then(on_resolve) - - # If it's not awaitable not a Promise, return - # the function executed over the object + # If it's not awaitable, return the function executed over the object return on_resolve(obj) diff --git a/graphene/utils/thenables_asyncio.py b/graphene/utils/thenables_asyncio.py deleted file mode 100644 index d5f93182e..000000000 --- a/graphene/utils/thenables_asyncio.py +++ /dev/null @@ -1,5 +0,0 @@ -def await_and_execute(obj, on_resolve): - async def build_resolve_async(): - return on_resolve(await obj) - - return build_resolve_async() diff --git a/tests_asyncio/test_relay_connection.py b/tests_asyncio/test_relay_connection.py index ec86fef66..67ee2ee4e 100644 --- a/tests_asyncio/test_relay_connection.py +++ b/tests_asyncio/test_relay_connection.py @@ -1,7 +1,4 @@ -import pytest - -from collections import OrderedDict -from graphql.execution.executors.asyncio import AsyncioExecutor +from pytest import mark from graphql_relay.utils import base64 @@ -27,14 +24,14 @@ class Meta: class Query(ObjectType): letters = ConnectionField(LetterConnection) connection_letters = ConnectionField(LetterConnection) - promise_letters = ConnectionField(LetterConnection) + async_letters = ConnectionField(LetterConnection) node = Node.Field() def resolve_letters(self, info, **args): return list(letters.values()) - async def resolve_promise_letters(self, info, **args): + async def resolve_async_letters(self, info, **args): return list(letters.values()) def resolve_connection_letters(self, info, **args): @@ -48,9 +45,7 @@ def resolve_connection_letters(self, info, **args): schema = Schema(Query) -letters = OrderedDict() -for i, letter in enumerate(letter_chars): - letters[letter] = Letter(id=i, letter=letter) +letters = {letter: Letter(id=i, letter=letter) for i, letter in enumerate(letter_chars)} def edges(selected_letters): @@ -96,12 +91,12 @@ def execute(args=""): ) -@pytest.mark.asyncio -async def test_connection_promise(): - result = await schema.execute( +@mark.asyncio +async def test_connection_async(): + result = await schema.execute_async( """ { - promiseLetters(first:1) { + asyncLetters(first:1) { edges { node { id @@ -115,13 +110,11 @@ async def test_connection_promise(): } } """, - executor=AsyncioExecutor(), - return_promise=True, ) assert not result.errors assert result.data == { - "promiseLetters": { + "asyncLetters": { "edges": [{"node": {"id": "TGV0dGVyOjA=", "letter": "A"}}], "pageInfo": {"hasPreviousPage": False, "hasNextPage": True}, } diff --git a/tests_asyncio/test_relay_mutation.py b/tests_asyncio/test_relay_mutation.py index f63ae1931..011557d51 100644 --- a/tests_asyncio/test_relay_mutation.py +++ b/tests_asyncio/test_relay_mutation.py @@ -42,11 +42,11 @@ class Input(SharedFields): @staticmethod def mutate_and_get_payload( - self, info, shared="", additional_field="", client_mutation_id=None + self, info, shared, additional_field, client_mutation_id=None ): edge_type = MyEdge return OtherMutation( - name=shared + additional_field, + name=(shared or "") + (additional_field or ""), my_node_edge=edge_type(cursor="1", node=MyNode(name="name")), ) From a18c226155119ffbb9b224d02ee64a621842dce6 Mon Sep 17 00:00:00 2001 From: Christoph Zwerschke Date: Mon, 1 Jul 2019 00:33:33 +0200 Subject: [PATCH 15/26] Remove the compat module which was only needed for older Py version --- graphene/pyutils/compat.py | 6 - graphene/pyutils/signature.py | 850 ---------------------------------- 2 files changed, 856 deletions(-) delete mode 100644 graphene/pyutils/compat.py delete mode 100644 graphene/pyutils/signature.py diff --git a/graphene/pyutils/compat.py b/graphene/pyutils/compat.py deleted file mode 100644 index ade0399bb..000000000 --- a/graphene/pyutils/compat.py +++ /dev/null @@ -1,6 +0,0 @@ -from __future__ import absolute_import - -try: - from inspect import signature -except ImportError: - from .signature import signature diff --git a/graphene/pyutils/signature.py b/graphene/pyutils/signature.py deleted file mode 100644 index 7757d9d01..000000000 --- a/graphene/pyutils/signature.py +++ /dev/null @@ -1,850 +0,0 @@ -# Copyright 2001-2013 Python Software Foundation; All Rights Reserved -"""Function signature objects for callables -Back port of Python 3.3's function signature tools from the inspect module, -modified to be compatible with Python 2.7 and 3.2+. -""" -from __future__ import absolute_import, division, print_function - -import functools -import itertools -import re -import types -from collections import OrderedDict - -__version__ = "0.4" - -__all__ = ["BoundArguments", "Parameter", "Signature", "signature"] - - -_WrapperDescriptor = type(type.__call__) -_MethodWrapper = type(all.__call__) - -_NonUserDefinedCallables = ( - _WrapperDescriptor, - _MethodWrapper, - types.BuiltinFunctionType, -) - - -def formatannotation(annotation, base_module=None): - if isinstance(annotation, type): - if annotation.__module__ in ("builtins", "__builtin__", base_module): - return annotation.__name__ - return annotation.__module__ + "." + annotation.__name__ - return repr(annotation) - - -def _get_user_defined_method(cls, method_name, *nested): - try: - if cls is type: - return - meth = getattr(cls, method_name) - for name in nested: - meth = getattr(meth, name, meth) - except AttributeError: - return - else: - if not isinstance(meth, _NonUserDefinedCallables): - # Once '__signature__' will be added to 'C'-level - # callables, this check won't be necessary - return meth - - -def signature(obj): - """Get a signature object for the passed callable.""" - - if not callable(obj): - raise TypeError("{!r} is not a callable object".format(obj)) - - if isinstance(obj, types.MethodType): - sig = signature(obj.__func__) - if obj.__self__ is None: - # Unbound method: the first parameter becomes positional-only - if sig.parameters: - first = sig.parameters.values()[0].replace(kind=_POSITIONAL_ONLY) - return sig.replace( - parameters=(first,) + tuple(sig.parameters.values())[1:] - ) - else: - return sig - else: - # In this case we skip the first parameter of the underlying - # function (usually `self` or `cls`). - return sig.replace(parameters=tuple(sig.parameters.values())[1:]) - - try: - sig = obj.__signature__ - except AttributeError: - pass - else: - if sig is not None: - return sig - - try: - # Was this function wrapped by a decorator? - wrapped = obj.__wrapped__ - except AttributeError: - pass - else: - return signature(wrapped) - - if isinstance(obj, types.FunctionType): - return Signature.from_function(obj) - - if isinstance(obj, functools.partial): - sig = signature(obj.func) - - new_params = OrderedDict(sig.parameters.items()) - - partial_args = obj.args or () - partial_keywords = obj.keywords or {} - try: - ba = sig.bind_partial(*partial_args, **partial_keywords) - except TypeError as ex: - msg = "partial object {!r} has incorrect arguments".format(obj) - raise ValueError(msg) - - for arg_name, arg_value in ba.arguments.items(): - param = new_params[arg_name] - if arg_name in partial_keywords: - # We set a new default value, because the following code - # is correct: - # - # >>> def foo(a): print(a) - # >>> print(partial(partial(foo, a=10), a=20)()) - # 20 - # >>> print(partial(partial(foo, a=10), a=20)(a=30)) - # 30 - # - # So, with 'partial' objects, passing a keyword argument is - # like setting a new default value for the corresponding - # parameter - # - # We also mark this parameter with '_partial_kwarg' - # flag. Later, in '_bind', the 'default' value of this - # parameter will be added to 'kwargs', to simulate - # the 'functools.partial' real call. - new_params[arg_name] = param.replace( - default=arg_value, _partial_kwarg=True - ) - - elif ( - param.kind not in (_VAR_KEYWORD, _VAR_POSITIONAL) - and not param._partial_kwarg - ): - new_params.pop(arg_name) - - return sig.replace(parameters=new_params.values()) - - sig = None - if isinstance(obj, type): - # obj is a class or a metaclass - - # First, let's see if it has an overloaded __call__ defined - # in its metaclass - call = _get_user_defined_method(type(obj), "__call__") - if call is not None: - sig = signature(call) - else: - # Now we check if the 'obj' class has a '__new__' method - new = _get_user_defined_method(obj, "__new__") - if new is not None: - sig = signature(new) - else: - # Finally, we should have at least __init__ implemented - init = _get_user_defined_method(obj, "__init__") - if init is not None: - sig = signature(init) - elif not isinstance(obj, _NonUserDefinedCallables): - # An object with __call__ - # We also check that the 'obj' is not an instance of - # _WrapperDescriptor or _MethodWrapper to avoid - # infinite recursion (and even potential segfault) - call = _get_user_defined_method(type(obj), "__call__", "im_func") - if call is not None: - sig = signature(call) - - if sig is not None: - # For classes and objects we skip the first parameter of their - # __call__, __new__, or __init__ methods - return sig.replace(parameters=tuple(sig.parameters.values())[1:]) - - if isinstance(obj, types.BuiltinFunctionType): - # Raise a nicer error message for builtins - msg = "no signature found for builtin function {!r}".format(obj) - raise ValueError(msg) - - raise ValueError("callable {!r} is not supported by signature".format(obj)) - - -class _void(object): - """A private marker - used in Parameter & Signature""" - - -class _empty(object): - pass - - -class _ParameterKind(int): - def __new__(self, *args, **kwargs): - obj = int.__new__(self, *args) - obj._name = kwargs["name"] - return obj - - def __str__(self): - return self._name - - def __repr__(self): - return "<_ParameterKind: {!r}>".format(self._name) - - -_POSITIONAL_ONLY = _ParameterKind(0, name="POSITIONAL_ONLY") -_POSITIONAL_OR_KEYWORD = _ParameterKind(1, name="POSITIONAL_OR_KEYWORD") -_VAR_POSITIONAL = _ParameterKind(2, name="VAR_POSITIONAL") -_KEYWORD_ONLY = _ParameterKind(3, name="KEYWORD_ONLY") -_VAR_KEYWORD = _ParameterKind(4, name="VAR_KEYWORD") - - -class Parameter(object): - """Represents a parameter in a function signature. - Has the following public attributes: - * name : str - The name of the parameter as a string. - * default : object - The default value for the parameter if specified. If the - parameter has no default value, this attribute is not set. - * annotation - The annotation for the parameter if specified. If the - parameter has no annotation, this attribute is not set. - * kind : str - Describes how argument values are bound to the parameter. - Possible values: `Parameter.POSITIONAL_ONLY`, - `Parameter.POSITIONAL_OR_KEYWORD`, `Parameter.VAR_POSITIONAL`, - `Parameter.KEYWORD_ONLY`, `Parameter.VAR_KEYWORD`. - """ - - __slots__ = ("_name", "_kind", "_default", "_annotation", "_partial_kwarg") - - POSITIONAL_ONLY = _POSITIONAL_ONLY - POSITIONAL_OR_KEYWORD = _POSITIONAL_OR_KEYWORD - VAR_POSITIONAL = _VAR_POSITIONAL - KEYWORD_ONLY = _KEYWORD_ONLY - VAR_KEYWORD = _VAR_KEYWORD - - empty = _empty - - def __init__( - self, name, kind, default=_empty, annotation=_empty, _partial_kwarg=False - ): - - if kind not in ( - _POSITIONAL_ONLY, - _POSITIONAL_OR_KEYWORD, - _VAR_POSITIONAL, - _KEYWORD_ONLY, - _VAR_KEYWORD, - ): - raise ValueError("invalid value for 'Parameter.kind' attribute") - self._kind = kind - - if default is not _empty: - if kind in (_VAR_POSITIONAL, _VAR_KEYWORD): - msg = "{} parameters cannot have default values".format(kind) - raise ValueError(msg) - self._default = default - self._annotation = annotation - - if name is None: - if kind != _POSITIONAL_ONLY: - raise ValueError( - "None is not a valid name for a " "non-positional-only parameter" - ) - self._name = name - else: - name = str(name) - if kind != _POSITIONAL_ONLY and not re.match(r"[a-z_]\w*$", name, re.I): - msg = "{!r} is not a valid parameter name".format(name) - raise ValueError(msg) - self._name = name - - self._partial_kwarg = _partial_kwarg - - @property - def name(self): - return self._name - - @property - def default(self): - return self._default - - @property - def annotation(self): - return self._annotation - - @property - def kind(self): - return self._kind - - def replace( - self, - name=_void, - kind=_void, - annotation=_void, - default=_void, - _partial_kwarg=_void, - ): - """Creates a customized copy of the Parameter.""" - - if name is _void: - name = self._name - - if kind is _void: - kind = self._kind - - if annotation is _void: - annotation = self._annotation - - if default is _void: - default = self._default - - if _partial_kwarg is _void: - _partial_kwarg = self._partial_kwarg - - return type(self)( - name, - kind, - default=default, - annotation=annotation, - _partial_kwarg=_partial_kwarg, - ) - - def __str__(self): - kind = self.kind - - formatted = self._name - if kind == _POSITIONAL_ONLY: - if formatted is None: - formatted = "" - formatted = "<{}>".format(formatted) - - # Add annotation and default value - if self._annotation is not _empty: - formatted = "{}:{}".format(formatted, formatannotation(self._annotation)) - - if self._default is not _empty: - formatted = "{}={}".format(formatted, repr(self._default)) - - if kind == _VAR_POSITIONAL: - formatted = "*" + formatted - elif kind == _VAR_KEYWORD: - formatted = "**" + formatted - - return formatted - - def __repr__(self): - return "<{} at {:#x} {!r}>".format(self.__class__.__name__, id(self), self.name) - - def __hash__(self): - msg = "unhashable type: '{}'".format(self.__class__.__name__) - raise TypeError(msg) - - def __eq__(self, other): - return ( - issubclass(other.__class__, Parameter) - and self._name == other._name - and self._kind == other._kind - and self._default == other._default - and self._annotation == other._annotation - ) - - def __ne__(self, other): - return not self.__eq__(other) - - -class BoundArguments(object): - """Result of `Signature.bind` call. Holds the mapping of arguments - to the function's parameters. - Has the following public attributes: - * arguments : OrderedDict - An ordered mutable mapping of parameters' names to arguments' values. - Does not contain arguments' default values. - * signature : Signature - The Signature object that created this instance. - * args : tuple - Tuple of positional arguments values. - * kwargs : dict - Dict of keyword arguments values. - """ - - def __init__(self, signature, arguments): - self.arguments = arguments - self._signature = signature - - @property - def signature(self): - return self._signature - - @property - def args(self): - args = [] - for param_name, param in self._signature.parameters.items(): - if param.kind in (_VAR_KEYWORD, _KEYWORD_ONLY) or param._partial_kwarg: - # Keyword arguments mapped by 'functools.partial' - # (Parameter._partial_kwarg is True) are mapped - # in 'BoundArguments.kwargs', along with VAR_KEYWORD & - # KEYWORD_ONLY - break - - try: - arg = self.arguments[param_name] - except KeyError: - # We're done here. Other arguments - # will be mapped in 'BoundArguments.kwargs' - break - else: - if param.kind == _VAR_POSITIONAL: - # *args - args.extend(arg) - else: - # plain argument - args.append(arg) - - return tuple(args) - - @property - def kwargs(self): - kwargs = {} - kwargs_started = False - for param_name, param in self._signature.parameters.items(): - if not kwargs_started: - if param.kind in (_VAR_KEYWORD, _KEYWORD_ONLY) or param._partial_kwarg: - kwargs_started = True - else: - if param_name not in self.arguments: - kwargs_started = True - continue - - if not kwargs_started: - continue - - try: - arg = self.arguments[param_name] - except KeyError: - pass - else: - if param.kind == _VAR_KEYWORD: - # **kwargs - kwargs.update(arg) - else: - # plain keyword argument - kwargs[param_name] = arg - - return kwargs - - def __hash__(self): - msg = "unhashable type: '{}'".format(self.__class__.__name__) - raise TypeError(msg) - - def __eq__(self, other): - return ( - issubclass(other.__class__, BoundArguments) - and self.signature == other.signature - and self.arguments == other.arguments - ) - - def __ne__(self, other): - return not self.__eq__(other) - - -class Signature(object): - """A Signature object represents the overall signature of a function. - It stores a Parameter object for each parameter accepted by the - function, as well as information specific to the function itself. - A Signature object has the following public attributes and methods: - * parameters : OrderedDict - An ordered mapping of parameters' names to the corresponding - Parameter objects (keyword-only arguments are in the same order - as listed in `code.co_varnames`). - * return_annotation : object - The annotation for the return type of the function if specified. - If the function has no annotation for its return type, this - attribute is not set. - * bind(*args, **kwargs) -> BoundArguments - Creates a mapping from positional and keyword arguments to - parameters. - * bind_partial(*args, **kwargs) -> BoundArguments - Creates a partial mapping from positional and keyword arguments - to parameters (simulating 'functools.partial' behavior.) - """ - - __slots__ = ("_return_annotation", "_parameters") - - _parameter_cls = Parameter - _bound_arguments_cls = BoundArguments - - empty = _empty - - def __init__( - self, parameters=None, return_annotation=_empty, __validate_parameters__=True - ): - """Constructs Signature from the given list of Parameter - objects and 'return_annotation'. All arguments are optional. - """ - - if parameters is None: - params = OrderedDict() - else: - if __validate_parameters__: - params = OrderedDict() - top_kind = _POSITIONAL_ONLY - - for idx, param in enumerate(parameters): - kind = param.kind - if kind < top_kind: - msg = "wrong parameter order: {0} before {1}" - msg = msg.format(top_kind, param.kind) - raise ValueError(msg) - else: - top_kind = kind - - name = param.name - if name is None: - name = str(idx) - param = param.replace(name=name) - - if name in params: - msg = "duplicate parameter name: {!r}".format(name) - raise ValueError(msg) - params[name] = param - else: - params = OrderedDict(((param.name, param) for param in parameters)) - - self._parameters = params - self._return_annotation = return_annotation - - @classmethod - def from_function(cls, func): - """Constructs Signature for the given python function""" - - if not isinstance(func, types.FunctionType): - raise TypeError("{!r} is not a Python function".format(func)) - - Parameter = cls._parameter_cls - - # Parameter information. - func_code = func.__code__ - pos_count = func_code.co_argcount - arg_names = func_code.co_varnames - positional = tuple(arg_names[:pos_count]) - keyword_only_count = getattr(func_code, "co_kwonlyargcount", 0) - keyword_only = arg_names[pos_count : (pos_count + keyword_only_count)] - annotations = getattr(func, "__annotations__", {}) - defaults = func.__defaults__ - kwdefaults = getattr(func, "__kwdefaults__", None) - - if defaults: - pos_default_count = len(defaults) - else: - pos_default_count = 0 - - parameters = [] - - # Non-keyword-only parameters w/o defaults. - non_default_count = pos_count - pos_default_count - for name in positional[:non_default_count]: - annotation = annotations.get(name, _empty) - parameters.append( - Parameter(name, annotation=annotation, kind=_POSITIONAL_OR_KEYWORD) - ) - - # ... w/ defaults. - for offset, name in enumerate(positional[non_default_count:]): - annotation = annotations.get(name, _empty) - parameters.append( - Parameter( - name, - annotation=annotation, - kind=_POSITIONAL_OR_KEYWORD, - default=defaults[offset], - ) - ) - - # *args - if func_code.co_flags & 0x04: - name = arg_names[pos_count + keyword_only_count] - annotation = annotations.get(name, _empty) - parameters.append( - Parameter(name, annotation=annotation, kind=_VAR_POSITIONAL) - ) - - # Keyword-only parameters. - for name in keyword_only: - default = _empty - if kwdefaults is not None: - default = kwdefaults.get(name, _empty) - - annotation = annotations.get(name, _empty) - parameters.append( - Parameter( - name, annotation=annotation, kind=_KEYWORD_ONLY, default=default - ) - ) - # **kwargs - if func_code.co_flags & 0x08: - index = pos_count + keyword_only_count - if func_code.co_flags & 0x04: - index += 1 - - name = arg_names[index] - annotation = annotations.get(name, _empty) - parameters.append(Parameter(name, annotation=annotation, kind=_VAR_KEYWORD)) - - return cls( - parameters, - return_annotation=annotations.get("return", _empty), - __validate_parameters__=False, - ) - - @property - def parameters(self): - try: - return types.MappingProxyType(self._parameters) - except AttributeError: - return OrderedDict(self._parameters.items()) - - @property - def return_annotation(self): - return self._return_annotation - - def replace(self, parameters=_void, return_annotation=_void): - """Creates a customized copy of the Signature. - Pass 'parameters' and/or 'return_annotation' arguments - to override them in the new copy. - """ - - if parameters is _void: - parameters = self.parameters.values() - - if return_annotation is _void: - return_annotation = self._return_annotation - - return type(self)(parameters, return_annotation=return_annotation) - - def __hash__(self): - msg = "unhashable type: '{}'".format(self.__class__.__name__) - raise TypeError(msg) - - def __eq__(self, other): - if ( - not issubclass(type(other), Signature) - or self.return_annotation != other.return_annotation - or len(self.parameters) != len(other.parameters) - ): - return False - - other_positions = { - param: idx for idx, param in enumerate(other.parameters.keys()) - } - - for idx, (param_name, param) in enumerate(self.parameters.items()): - if param.kind == _KEYWORD_ONLY: - try: - other_param = other.parameters[param_name] - except KeyError: - return False - else: - if param != other_param: - return False - else: - try: - other_idx = other_positions[param_name] - except KeyError: - return False - else: - if idx != other_idx or param != other.parameters[param_name]: - return False - - return True - - def __ne__(self, other): - return not self.__eq__(other) - - def _bind(self, args, kwargs, partial=False): - """Private method. Don't use directly.""" - - arguments = OrderedDict() - - parameters = iter(self.parameters.values()) - parameters_ex = () - arg_vals = iter(args) - - if partial: - # Support for binding arguments to 'functools.partial' objects. - # See 'functools.partial' case in 'signature()' implementation - # for details. - for param_name, param in self.parameters.items(): - if param._partial_kwarg and param_name not in kwargs: - # Simulating 'functools.partial' behavior - kwargs[param_name] = param.default - - while True: - # Let's iterate through the positional arguments and corresponding - # parameters - try: - arg_val = next(arg_vals) - except StopIteration: - # No more positional arguments - try: - param = next(parameters) - except StopIteration: - # No more parameters. That's it. Just need to check that - # we have no `kwargs` after this while loop - break - else: - if param.kind == _VAR_POSITIONAL: - # That's OK, just empty *args. Let's start parsing - # kwargs - break - elif param.name in kwargs: - if param.kind == _POSITIONAL_ONLY: - msg = ( - "{arg!r} parameter is positional only, " - "but was passed as a keyword" - ) - msg = msg.format(arg=param.name) - raise TypeError(msg) - parameters_ex = (param,) - break - elif param.kind == _VAR_KEYWORD or param.default is not _empty: - # That's fine too - we have a default value for this - # parameter. So, lets start parsing `kwargs`, starting - # with the current parameter - parameters_ex = (param,) - break - else: - if partial: - parameters_ex = (param,) - break - else: - msg = "{arg!r} parameter lacking default value" - msg = msg.format(arg=param.name) - raise TypeError(msg) - else: - # We have a positional argument to process - try: - param = next(parameters) - except StopIteration: - raise TypeError("too many positional arguments") - else: - if param.kind in (_VAR_KEYWORD, _KEYWORD_ONLY): - # Looks like we have no parameter for this positional - # argument - raise TypeError("too many positional arguments") - - if param.kind == _VAR_POSITIONAL: - # We have an '*args'-like argument, let's fill it with - # all positional arguments we have left and move on to - # the next phase - values = [arg_val] - values.extend(arg_vals) - arguments[param.name] = tuple(values) - break - - if param.name in kwargs: - raise TypeError( - "multiple values for argument " - "{arg!r}".format(arg=param.name) - ) - - arguments[param.name] = arg_val - - # Now, we iterate through the remaining parameters to process - # keyword arguments - kwargs_param = None - for param in itertools.chain(parameters_ex, parameters): - if param.kind == _POSITIONAL_ONLY: - # This should never happen in case of a properly built - # Signature object (but let's have this check here - # to ensure correct behaviour just in case) - raise TypeError( - "{arg!r} parameter is positional only, " - "but was passed as a keyword".format(arg=param.name) - ) - - if param.kind == _VAR_KEYWORD: - # Memorize that we have a '**kwargs'-like parameter - kwargs_param = param - continue - - param_name = param.name - try: - arg_val = kwargs.pop(param_name) - except KeyError: - # We have no value for this parameter. It's fine though, - # if it has a default value, or it is an '*args'-like - # parameter, left alone by the processing of positional - # arguments. - if ( - not partial - and param.kind != _VAR_POSITIONAL - and param.default is _empty - ): - raise TypeError( - "{arg!r} parameter lacking default value".format(arg=param_name) - ) - - else: - arguments[param_name] = arg_val - - if kwargs: - if kwargs_param is not None: - # Process our '**kwargs'-like parameter - arguments[kwargs_param.name] = kwargs - else: - raise TypeError("too many keyword arguments") - - return self._bound_arguments_cls(self, arguments) - - def bind(self, *args, **kwargs): - """Get a BoundArguments object, that maps the passed `args` - and `kwargs` to the function's signature. Raises `TypeError` - if the passed arguments can not be bound. - """ - return self._bind(args, kwargs) - - def bind_partial(self, *args, **kwargs): - """Get a BoundArguments object, that partially maps the - passed `args` and `kwargs` to the function's signature. - Raises `TypeError` if the passed arguments can not be bound. - """ - return self._bind(args, kwargs, partial=True) - - def __str__(self): - result = [] - render_kw_only_separator = True - for idx, param in enumerate(self.parameters.values()): - formatted = str(param) - - kind = param.kind - if kind == _VAR_POSITIONAL: - # OK, we have an '*args'-like parameter, so we won't need - # a '*' to separate keyword-only arguments - render_kw_only_separator = False - elif kind == _KEYWORD_ONLY and render_kw_only_separator: - # We have a keyword-only parameter to render and we haven't - # rendered an '*args'-like parameter before, so add a '*' - # separator to the parameters list ("foo(arg1, *, arg2)" case) - result.append("*") - # This condition should be only triggered once, so - # reset the flag - render_kw_only_separator = False - - result.append(formatted) - - rendered = "({})".format(", ".join(result)) - - if self.return_annotation is not _empty: - anno = formatannotation(self.return_annotation) - rendered += " -> {}".format(anno) - - return rendered From 029dde32a26c2b1ab7852f8031639553737d03e6 Mon Sep 17 00:00:00 2001 From: Christoph Zwerschke Date: Mon, 1 Jul 2019 00:36:46 +0200 Subject: [PATCH 16/26] Remove object as base class (not needed in Py 3) --- graphene/relay/connection.py | 2 +- graphene/relay/tests/test_connection.py | 6 +++--- graphene/relay/tests/test_global_id.py | 2 +- graphene/relay/tests/test_mutation.py | 4 ++-- graphene/relay/tests/test_node.py | 2 +- graphene/test/__init__.py | 2 +- graphene/types/base.py | 2 +- graphene/types/context.py | 2 +- graphene/types/tests/test_definition.py | 4 ++-- graphene/types/tests/test_field.py | 2 +- graphene/types/tests/test_inputobjecttype.py | 6 +++--- graphene/types/tests/test_interface.py | 6 +++--- graphene/types/tests/test_objecttype.py | 4 ++-- graphene/types/tests/test_query.py | 10 +++++----- graphene/types/tests/test_resolver.py | 2 +- graphene/utils/orderedtype.py | 2 +- graphene/utils/props.py | 2 +- graphene/utils/tests/test_trim_docstring.py | 4 ++-- 18 files changed, 32 insertions(+), 32 deletions(-) diff --git a/graphene/relay/connection.py b/graphene/relay/connection.py index 24804dbdd..87054e913 100644 --- a/graphene/relay/connection.py +++ b/graphene/relay/connection.py @@ -66,7 +66,7 @@ def __init_subclass_with_meta__(cls, node=None, name=None, **options): edge_class = getattr(cls, "Edge", None) _node = node - class EdgeBase(object): + class EdgeBase: node = Field(_node, description="The item at the end of the edge") cursor = String(required=True, description="A cursor for use in pagination") diff --git a/graphene/relay/tests/test_connection.py b/graphene/relay/tests/test_connection.py index 6686f9640..35bfde1f1 100644 --- a/graphene/relay/tests/test_connection.py +++ b/graphene/relay/tests/test_connection.py @@ -39,7 +39,7 @@ class Edge: def test_connection_inherit_abstracttype(): - class BaseConnection(object): + class BaseConnection: extra = String() class MyObjectConnection(BaseConnection, Connection): @@ -54,7 +54,7 @@ class Meta: def test_connection_name(): custom_name = "MyObjectCustomNameConnection" - class BaseConnection(object): + class BaseConnection: extra = String() class MyObjectConnection(BaseConnection, Connection): @@ -86,7 +86,7 @@ class Edge: def test_edge_with_bases(): - class BaseEdge(object): + class BaseEdge: extra = String() class MyObjectConnection(Connection): diff --git a/graphene/relay/tests/test_global_id.py b/graphene/relay/tests/test_global_id.py index 6d328f23d..2fe813008 100644 --- a/graphene/relay/tests/test_global_id.py +++ b/graphene/relay/tests/test_global_id.py @@ -17,7 +17,7 @@ class Meta: name = String() -class Info(object): +class Info: def __init__(self, parent_type): self.parent_type = GrapheneObjectType( graphene_type=parent_type, diff --git a/graphene/relay/tests/test_mutation.py b/graphene/relay/tests/test_mutation.py index dcafbd80e..151a8d6ed 100644 --- a/graphene/relay/tests/test_mutation.py +++ b/graphene/relay/tests/test_mutation.py @@ -14,7 +14,7 @@ from ..mutation import ClientIDMutation -class SharedFields(object): +class SharedFields: shared = String() @@ -36,7 +36,7 @@ def mutate_and_get_payload(self, info, what, client_mutation_id=None): return SaySomething(phrase=str(what)) -class FixedSaySomething(object): +class FixedSaySomething: __slots__ = ("phrase",) def __init__(self, phrase): diff --git a/graphene/relay/tests/test_node.py b/graphene/relay/tests/test_node.py index be6cdc85d..66ad12b43 100644 --- a/graphene/relay/tests/test_node.py +++ b/graphene/relay/tests/test_node.py @@ -6,7 +6,7 @@ from ..node import Node, is_node -class SharedNodeFields(object): +class SharedNodeFields: shared = String() something_else = String() diff --git a/graphene/test/__init__.py b/graphene/test/__init__.py index fd6a2126c..8591dc066 100644 --- a/graphene/test/__init__.py +++ b/graphene/test/__init__.py @@ -20,7 +20,7 @@ def format_execution_result(execution_result, format_error): return response -class Client(object): +class Client: def __init__(self, schema, format_error=None, **execute_options): assert isinstance(schema, Schema) self.schema = schema diff --git a/graphene/types/base.py b/graphene/types/base.py index bab78a490..79907b4d9 100644 --- a/graphene/types/base.py +++ b/graphene/types/base.py @@ -4,7 +4,7 @@ from ..utils.trim_docstring import trim_docstring -class BaseOptions(object): +class BaseOptions: name = None # type: str description = None # type: str diff --git a/graphene/types/context.py b/graphene/types/context.py index bac2073c5..3394e0048 100644 --- a/graphene/types/context.py +++ b/graphene/types/context.py @@ -1,4 +1,4 @@ -class Context(object): +class Context: def __init__(self, **params): for key, value in params.items(): setattr(self, key, value) diff --git a/graphene/types/tests/test_definition.py b/graphene/types/tests/test_definition.py index 57656ccf2..b3b480af0 100644 --- a/graphene/types/tests/test_definition.py +++ b/graphene/types/tests/test_definition.py @@ -289,7 +289,7 @@ def test_stringifies_simple_types(): def test_does_not_mutate_passed_field_definitions(): - class CommonFields(object): + class CommonFields: field1 = String() field2 = String(id=String()) @@ -301,7 +301,7 @@ class TestObject2(CommonFields, ObjectType): assert TestObject1._meta.fields == TestObject2._meta.fields - class CommonFields(object): + class CommonFields: field1 = String() field2 = String() diff --git a/graphene/types/tests/test_field.py b/graphene/types/tests/test_field.py index 13c755fca..60bf3684c 100644 --- a/graphene/types/tests/test_field.py +++ b/graphene/types/tests/test_field.py @@ -9,7 +9,7 @@ from .utils import MyLazyType -class MyInstance(object): +class MyInstance: value = "value" value_func = staticmethod(lambda: "value_func") diff --git a/graphene/types/tests/test_inputobjecttype.py b/graphene/types/tests/test_inputobjecttype.py index de42b66e4..f2b6cf881 100644 --- a/graphene/types/tests/test_inputobjecttype.py +++ b/graphene/types/tests/test_inputobjecttype.py @@ -8,7 +8,7 @@ from ..unmountedtype import UnmountedType -class MyType(object): +class MyType: pass @@ -78,7 +78,7 @@ class MyObjectType(ObjectType): def test_generate_inputobjecttype_inherit_abstracttype(): - class MyAbstractType(object): + class MyAbstractType: field1 = MyScalar(MyType) class MyInputObjectType(InputObjectType, MyAbstractType): @@ -92,7 +92,7 @@ class MyInputObjectType(InputObjectType, MyAbstractType): def test_generate_inputobjecttype_inherit_abstracttype_reversed(): - class MyAbstractType(object): + class MyAbstractType: field1 = MyScalar(MyType) class MyInputObjectType(MyAbstractType, InputObjectType): diff --git a/graphene/types/tests/test_interface.py b/graphene/types/tests/test_interface.py index b524296b6..df85472fb 100644 --- a/graphene/types/tests/test_interface.py +++ b/graphene/types/tests/test_interface.py @@ -3,7 +3,7 @@ from ..unmountedtype import UnmountedType -class MyType(object): +class MyType: pass @@ -57,7 +57,7 @@ class MyInterface(Interface): def test_generate_interface_inherit_abstracttype(): - class MyAbstractType(object): + class MyAbstractType: field1 = MyScalar() class MyInterface(Interface, MyAbstractType): @@ -80,7 +80,7 @@ class MyInterface(MyBaseInterface): def test_generate_interface_inherit_abstracttype_reversed(): - class MyAbstractType(object): + class MyAbstractType: field1 = MyScalar() class MyInterface(MyAbstractType, Interface): diff --git a/graphene/types/tests/test_objecttype.py b/graphene/types/tests/test_objecttype.py index 68b8dd8a8..505dc9f76 100644 --- a/graphene/types/tests/test_objecttype.py +++ b/graphene/types/tests/test_objecttype.py @@ -106,7 +106,7 @@ class MyObjectType(ObjectType): def test_generate_objecttype_inherit_abstracttype(): - class MyAbstractType(object): + class MyAbstractType: field1 = MyScalar() class MyObjectType(ObjectType, MyAbstractType): @@ -120,7 +120,7 @@ class MyObjectType(ObjectType, MyAbstractType): def test_generate_objecttype_inherit_abstracttype_reversed(): - class MyAbstractType(object): + class MyAbstractType: field1 = MyScalar() class MyObjectType(MyAbstractType, ObjectType): diff --git a/graphene/types/tests/test_query.py b/graphene/types/tests/test_query.py index 211c2ec93..259e6b027 100644 --- a/graphene/types/tests/test_query.py +++ b/graphene/types/tests/test_query.py @@ -34,7 +34,7 @@ class Query(ObjectType): def test_query_source(): - class Root(object): + class Root: _hello = "World" def hello(self): @@ -51,10 +51,10 @@ class Query(ObjectType): def test_query_union(): - class one_object(object): + class one_object: pass - class two_object(object): + class two_object: pass class One(ObjectType): @@ -89,10 +89,10 @@ def resolve_unions(self, info): def test_query_interface(): - class one_object(object): + class one_object: pass - class two_object(object): + class two_object: pass class MyInterface(Interface): diff --git a/graphene/types/tests/test_resolver.py b/graphene/types/tests/test_resolver.py index a03cf187d..dcadb6d8d 100644 --- a/graphene/types/tests/test_resolver.py +++ b/graphene/types/tests/test_resolver.py @@ -13,7 +13,7 @@ demo_dict = {"attr": "value"} -class demo_obj(object): +class demo_obj: attr = "value" diff --git a/graphene/utils/orderedtype.py b/graphene/utils/orderedtype.py index a58f4d08a..fb8783d27 100644 --- a/graphene/utils/orderedtype.py +++ b/graphene/utils/orderedtype.py @@ -2,7 +2,7 @@ @total_ordering -class OrderedType(object): +class OrderedType: creation_counter = 1 def __init__(self, _creation_counter=None): diff --git a/graphene/utils/props.py b/graphene/utils/props.py index 5ef3ba0a0..26c697eca 100644 --- a/graphene/utils/props.py +++ b/graphene/utils/props.py @@ -2,7 +2,7 @@ class _OldClass: pass -class _NewClass(object): +class _NewClass: pass diff --git a/graphene/utils/tests/test_trim_docstring.py b/graphene/utils/tests/test_trim_docstring.py index 704d39977..232836d1f 100644 --- a/graphene/utils/tests/test_trim_docstring.py +++ b/graphene/utils/tests/test_trim_docstring.py @@ -2,7 +2,7 @@ def test_trim_docstring(): - class WellDocumentedObject(object): + class WellDocumentedObject: """ This object is very well-documented. It has multiple lines in its description. @@ -16,7 +16,7 @@ class WellDocumentedObject(object): "description.\n\nMultiple paragraphs too" ) - class UndocumentedObject(object): + class UndocumentedObject: pass assert trim_docstring(UndocumentedObject.__doc__) is None From 5752b48a0bf4ac3497a44da6150c1987eaa0e1d8 Mon Sep 17 00:00:00 2001 From: Christoph Zwerschke Date: Mon, 1 Jul 2019 00:50:02 +0200 Subject: [PATCH 17/26] We can assume that dicts are ordered in Py 3.6+ --- graphene/relay/connection.py | 34 +++++++++++------------------ graphene/relay/mutation.py | 5 ++--- graphene/relay/node.py | 7 +++--- graphene/types/argument.py | 3 +-- graphene/types/enum.py | 7 +++--- graphene/types/inputobjecttype.py | 4 +--- graphene/types/interface.py | 4 +--- graphene/types/mutation.py | 4 +--- graphene/types/objecttype.py | 4 +--- graphene/types/utils.py | 3 +-- graphene/utils/deduplicator.py | 7 +++--- graphene/utils/tests/test_crunch.py | 19 +++++++--------- 12 files changed, 38 insertions(+), 63 deletions(-) diff --git a/graphene/relay/connection.py b/graphene/relay/connection.py index 87054e913..c27b3f49a 100644 --- a/graphene/relay/connection.py +++ b/graphene/relay/connection.py @@ -1,5 +1,5 @@ import re -from collections import Iterable, OrderedDict +from collections import Iterable from functools import partial from graphql_relay import connection_from_list @@ -86,26 +86,18 @@ class EdgeMeta: options["name"] = name _meta.node = node - _meta.fields = OrderedDict( - [ - ( - "page_info", - Field( - PageInfo, - name="pageInfo", - required=True, - description="Pagination data for this connection.", - ), - ), - ( - "edges", - Field( - NonNull(List(edge)), - description="Contains the nodes in this connection.", - ), - ), - ] - ) + _meta.fields = { + "page_info": Field( + PageInfo, + name="pageInfo", + required=True, + description="Pagination data for this connection.", + ), + "edges": Field( + NonNull(List(edge)), + description="Contains the nodes in this connection.", + ), + } return super(Connection, cls).__init_subclass_with_meta__( _meta=_meta, **options ) diff --git a/graphene/relay/mutation.py b/graphene/relay/mutation.py index ee758e78c..bb27855fc 100644 --- a/graphene/relay/mutation.py +++ b/graphene/relay/mutation.py @@ -1,5 +1,4 @@ import re -from collections import OrderedDict from ..types import Field, InputObjectType, String from ..types.mutation import Mutation @@ -30,12 +29,12 @@ def __init_subclass_with_meta__( cls.Input = type( "{}Input".format(base_name), bases, - OrderedDict( + dict( input_fields, client_mutation_id=String(name="clientMutationId") ), ) - arguments = OrderedDict( + arguments = dict( input=cls.Input(required=True) # 'client_mutation_id': String(name='clientMutationId') ) diff --git a/graphene/relay/node.py b/graphene/relay/node.py index f80db13e9..79c34ffcb 100644 --- a/graphene/relay/node.py +++ b/graphene/relay/node.py @@ -1,4 +1,3 @@ -from collections import OrderedDict from functools import partial from inspect import isclass @@ -72,9 +71,9 @@ class Meta: @classmethod def __init_subclass_with_meta__(cls, **options): _meta = InterfaceOptions(cls) - _meta.fields = OrderedDict( - id=GlobalID(cls, description="The ID of the object") - ) + _meta.fields = { + 'id': GlobalID(cls, description="The ID of the object") + } super(AbstractNode, cls).__init_subclass_with_meta__(_meta=_meta, **options) diff --git a/graphene/types/argument.py b/graphene/types/argument.py index bf3046082..b59ecc44a 100644 --- a/graphene/types/argument.py +++ b/graphene/types/argument.py @@ -1,4 +1,3 @@ -from collections import OrderedDict from itertools import chain from .dynamic import Dynamic @@ -50,7 +49,7 @@ def to_arguments(args, extra_args=None): else: extra_args = [] iter_arguments = chain(args.items(), extra_args) - arguments = OrderedDict() + arguments = {} for default_name, arg in iter_arguments: if isinstance(arg, Dynamic): arg = arg.get_type() diff --git a/graphene/types/enum.py b/graphene/types/enum.py index 79f2f636f..fd286eefd 100644 --- a/graphene/types/enum.py +++ b/graphene/types/enum.py @@ -1,4 +1,3 @@ -from collections import OrderedDict from enum import Enum as PyEnum from graphene.utils.subclass_with_meta import SubclassWithMeta_Meta @@ -23,13 +22,13 @@ class EnumOptions(BaseOptions): class EnumMeta(SubclassWithMeta_Meta): def __new__(cls, name, bases, classdict, **options): - enum_members = OrderedDict(classdict, __eq__=eq_enum) + enum_members = dict(classdict, __eq__=eq_enum) # We remove the Meta attribute from the class to not collide # with the enum values. enum_members.pop("Meta", None) enum = PyEnum(cls.__name__, enum_members) return SubclassWithMeta_Meta.__new__( - cls, name, bases, OrderedDict(classdict, __enum__=enum), **options + cls, name, bases, dict(classdict, __enum__=enum), **options ) def get(cls, value): @@ -39,7 +38,7 @@ def __getitem__(cls, value): return cls._meta.enum[value] def __prepare__(name, bases, **kwargs): # noqa: N805 - return OrderedDict() + return {} def __call__(cls, *args, **kwargs): # noqa: N805 if cls is Enum: diff --git a/graphene/types/inputobjecttype.py b/graphene/types/inputobjecttype.py index 8116a8274..d75f639ae 100644 --- a/graphene/types/inputobjecttype.py +++ b/graphene/types/inputobjecttype.py @@ -1,5 +1,3 @@ -from collections import OrderedDict - from .base import BaseOptions, BaseType from .inputfield import InputField from .unmountedtype import UnmountedType @@ -44,7 +42,7 @@ def __init_subclass_with_meta__(cls, container=None, _meta=None, **options): if not _meta: _meta = InputObjectTypeOptions(cls) - fields = OrderedDict() + fields = {} for base in reversed(cls.__mro__): fields.update(yank_fields_from_attrs(base.__dict__, _as=InputField)) diff --git a/graphene/types/interface.py b/graphene/types/interface.py index f2c9749c4..8173f4c23 100644 --- a/graphene/types/interface.py +++ b/graphene/types/interface.py @@ -1,5 +1,3 @@ -from collections import OrderedDict - from .base import BaseOptions, BaseType from .field import Field from .utils import yank_fields_from_attrs @@ -29,7 +27,7 @@ def __init_subclass_with_meta__(cls, _meta=None, **options): if not _meta: _meta = InterfaceOptions(cls) - fields = OrderedDict() + fields = {} for base in reversed(cls.__mro__): fields.update(yank_fields_from_attrs(base.__dict__, _as=Field)) diff --git a/graphene/types/mutation.py b/graphene/types/mutation.py index dd09e4614..fb139d78e 100644 --- a/graphene/types/mutation.py +++ b/graphene/types/mutation.py @@ -1,5 +1,3 @@ -from collections import OrderedDict - from ..utils.deprecated import warn_deprecation from ..utils.get_unbound_function import get_unbound_function from ..utils.props import props @@ -36,7 +34,7 @@ def __init_subclass_with_meta__( fields = {} if not output: # If output is defined, we don't need to get the fields - fields = OrderedDict() + fields = {} for base in reversed(cls.__mro__): fields.update(yank_fields_from_attrs(base.__dict__, _as=Field)) output = cls diff --git a/graphene/types/objecttype.py b/graphene/types/objecttype.py index be4addb26..d622c5986 100644 --- a/graphene/types/objecttype.py +++ b/graphene/types/objecttype.py @@ -1,5 +1,3 @@ -from collections import OrderedDict - from .base import BaseOptions, BaseType from .field import Field from .interface import Interface @@ -36,7 +34,7 @@ def __init_subclass_with_meta__( if not _meta: _meta = ObjectTypeOptions(cls) - fields = OrderedDict() + fields = {} for interface in interfaces: assert issubclass(interface, Interface), ( diff --git a/graphene/types/utils.py b/graphene/types/utils.py index a7e23572c..3b195d692 100644 --- a/graphene/types/utils.py +++ b/graphene/types/utils.py @@ -1,5 +1,4 @@ import inspect -from collections import OrderedDict from functools import partial from ..utils.module_loading import import_string @@ -33,7 +32,7 @@ def yank_fields_from_attrs(attrs, _as=None, sort=True): if sort: fields_with_names = sorted(fields_with_names, key=lambda f: f[1]) - return OrderedDict(fields_with_names) + return dict(fields_with_names) def get_type(_type): diff --git a/graphene/utils/deduplicator.py b/graphene/utils/deduplicator.py index 13c1cb163..32f6dcbe3 100644 --- a/graphene/utils/deduplicator.py +++ b/graphene/utils/deduplicator.py @@ -1,4 +1,4 @@ -from collections import Mapping, OrderedDict +from collections import Mapping def deflate(node, index=None, path=None): @@ -16,10 +16,9 @@ def deflate(node, index=None, path=None): else: index[cache_key] = True - field_names = node.keys() - result = OrderedDict() + result = {} - for field_name in field_names: + for field_name in node: value = node[field_name] new_path = path + [field_name] diff --git a/graphene/utils/tests/test_crunch.py b/graphene/utils/tests/test_crunch.py index 9646a2572..bbcf37dd4 100644 --- a/graphene/utils/tests/test_crunch.py +++ b/graphene/utils/tests/test_crunch.py @@ -1,5 +1,4 @@ import pytest -from collections import OrderedDict from ..crunch import crunch @@ -28,28 +27,26 @@ ["single-item object", {"a": None}, [None, {"a": 0}]], [ "multi-item all distinct object", - OrderedDict([("a", None), ("b", 0), ("c", True), ("d", "string")]), + {"a": None, "b": 0, "c": True, "d": "string"}, [None, 0, True, "string", {"a": 0, "b": 1, "c": 2, "d": 3}], ], [ "multi-item repeated object", - OrderedDict([("a", True), ("b", True), ("c", True), ("d", True)]), + {"a": True, "b": True, "c": True, "d": True}, [True, {"a": 0, "b": 0, "c": 0, "d": 0}], ], [ "complex array", - [OrderedDict([("a", True), ("b", [1, 2, 3])]), [1, 2, 3]], + [{"a": True, "b": [1, 2, 3]}, [1, 2, 3]], [True, 1, 2, 3, [1, 2, 3], {"a": 0, "b": 4}, [5, 4]], ], [ "complex object", - OrderedDict( - [ - ("a", True), - ("b", [1, 2, 3]), - ("c", OrderedDict([("a", True), ("b", [1, 2, 3])])), - ] - ), + { + "a": True, + "b": [1, 2, 3], + "c": {"a": True, "b": [1, 2, 3]}, + }, [True, 1, 2, 3, [1, 2, 3], {"a": 0, "b": 4}, {"a": 0, "b": 4, "c": 5}], ], ], From df0657b816a6828207c102fae35e81bc17e97456 Mon Sep 17 00:00:00 2001 From: Christoph Zwerschke Date: Mon, 1 Jul 2019 00:52:57 +0200 Subject: [PATCH 18/26] Make use of the fact that dicts are iterable --- graphene/relay/tests/test_connection.py | 10 +++++----- graphene/relay/tests/test_mutation.py | 12 ++++++------ graphene/types/inputobjecttype.py | 2 +- graphene/types/tests/test_abstracttype.py | 2 +- graphene/types/tests/test_field.py | 2 +- graphene/types/tests/test_inputobjecttype.py | 6 +++--- graphene/types/tests/test_interface.py | 8 ++++---- graphene/types/tests/test_objecttype.py | 10 +++++----- graphene/types/tests/test_type_map.py | 16 ++++++++-------- graphene/utils/subclass_with_meta.py | 2 +- 10 files changed, 35 insertions(+), 35 deletions(-) diff --git a/graphene/relay/tests/test_connection.py b/graphene/relay/tests/test_connection.py index 35bfde1f1..c9bfb3ea7 100644 --- a/graphene/relay/tests/test_connection.py +++ b/graphene/relay/tests/test_connection.py @@ -24,7 +24,7 @@ class Edge: assert MyObjectConnection._meta.name == "MyObjectConnection" fields = MyObjectConnection._meta.fields - assert list(fields.keys()) == ["page_info", "edges", "extra"] + assert list(fields) == ["page_info", "edges", "extra"] edge_field = fields["edges"] pageinfo_field = fields["page_info"] @@ -48,7 +48,7 @@ class Meta: assert MyObjectConnection._meta.name == "MyObjectConnection" fields = MyObjectConnection._meta.fields - assert list(fields.keys()) == ["page_info", "edges", "extra"] + assert list(fields) == ["page_info", "edges", "extra"] def test_connection_name(): @@ -76,7 +76,7 @@ class Edge: Edge = MyObjectConnection.Edge assert Edge._meta.name == "MyObjectEdge" edge_fields = Edge._meta.fields - assert list(edge_fields.keys()) == ["node", "cursor", "other"] + assert list(edge_fields) == ["node", "cursor", "other"] assert isinstance(edge_fields["node"], Field) assert edge_fields["node"].type == MyObject @@ -99,7 +99,7 @@ class Edge(BaseEdge): Edge = MyObjectConnection.Edge assert Edge._meta.name == "MyObjectEdge" edge_fields = Edge._meta.fields - assert list(edge_fields.keys()) == ["node", "cursor", "extra", "other"] + assert list(edge_fields) == ["node", "cursor", "extra", "other"] assert isinstance(edge_fields["node"], Field) assert edge_fields["node"].type == MyObject @@ -122,7 +122,7 @@ class Meta: def test_pageinfo(): assert PageInfo._meta.name == "PageInfo" fields = PageInfo._meta.fields - assert list(fields.keys()) == [ + assert list(fields) == [ "has_next_page", "has_previous_page", "start_cursor", diff --git a/graphene/relay/tests/test_mutation.py b/graphene/relay/tests/test_mutation.py index 151a8d6ed..5fb1c4687 100644 --- a/graphene/relay/tests/test_mutation.py +++ b/graphene/relay/tests/test_mutation.py @@ -117,12 +117,12 @@ class MyMutation(ClientIDMutation): def test_mutation(): fields = SaySomething._meta.fields - assert list(fields.keys()) == ["phrase", "client_mutation_id"] + assert list(fields) == ["phrase", "client_mutation_id"] assert SaySomething._meta.name == "SaySomethingPayload" assert isinstance(fields["phrase"], Field) field = SaySomething.Field() assert field.type == SaySomething - assert list(field.args.keys()) == ["input"] + assert list(field.args) == ["input"] assert isinstance(field.args["input"], Argument) assert isinstance(field.args["input"].type, NonNull) assert field.args["input"].type.of_type == SaySomething.Input @@ -135,7 +135,7 @@ def test_mutation_input(): Input = SaySomething.Input assert issubclass(Input, InputObjectType) fields = Input._meta.fields - assert list(fields.keys()) == ["what", "client_mutation_id"] + assert list(fields) == ["what", "client_mutation_id"] assert isinstance(fields["what"], InputField) assert fields["what"].type == String assert isinstance(fields["client_mutation_id"], InputField) @@ -144,11 +144,11 @@ def test_mutation_input(): def test_subclassed_mutation(): fields = OtherMutation._meta.fields - assert list(fields.keys()) == ["name", "my_node_edge", "client_mutation_id"] + assert list(fields) == ["name", "my_node_edge", "client_mutation_id"] assert isinstance(fields["name"], Field) field = OtherMutation.Field() assert field.type == OtherMutation - assert list(field.args.keys()) == ["input"] + assert list(field.args) == ["input"] assert isinstance(field.args["input"], Argument) assert isinstance(field.args["input"].type, NonNull) assert field.args["input"].type.of_type == OtherMutation.Input @@ -158,7 +158,7 @@ def test_subclassed_mutation_input(): Input = OtherMutation.Input assert issubclass(Input, InputObjectType) fields = Input._meta.fields - assert list(fields.keys()) == ["shared", "additional_field", "client_mutation_id"] + assert list(fields) == ["shared", "additional_field", "client_mutation_id"] assert isinstance(fields["shared"], InputField) assert fields["shared"].type == String assert isinstance(fields["additional_field"], InputField) diff --git a/graphene/types/inputobjecttype.py b/graphene/types/inputobjecttype.py index d75f639ae..cfb0cc2e0 100644 --- a/graphene/types/inputobjecttype.py +++ b/graphene/types/inputobjecttype.py @@ -20,7 +20,7 @@ class Meta: def __init__(self, *args, **kwargs): dict.__init__(self, *args, **kwargs) - for key in self._meta.fields.keys(): + for key in self._meta.fields: setattr(self, key, self.get(key, None)) def __init_subclass__(cls, *args, **kwargs): diff --git a/graphene/types/tests/test_abstracttype.py b/graphene/types/tests/test_abstracttype.py index 414703834..eb11cb799 100644 --- a/graphene/types/tests/test_abstracttype.py +++ b/graphene/types/tests/test_abstracttype.py @@ -33,5 +33,5 @@ class MyObjectType(ObjectType, MyAbstractType): assert MyObjectType._meta.description is None assert MyObjectType._meta.interfaces == () assert MyObjectType._meta.name == "MyObjectType" - assert list(MyObjectType._meta.fields.keys()) == ["field1", "field2"] + assert list(MyObjectType._meta.fields) == ["field1", "field2"] assert list(map(type, MyObjectType._meta.fields.values())) == [Field, Field] diff --git a/graphene/types/tests/test_field.py b/graphene/types/tests/test_field.py index 60bf3684c..0b9f58a1d 100644 --- a/graphene/types/tests/test_field.py +++ b/graphene/types/tests/test_field.py @@ -122,7 +122,7 @@ 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)) - assert list(field.args.keys()) == ["b", "c", "a"] + 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 diff --git a/graphene/types/tests/test_inputobjecttype.py b/graphene/types/tests/test_inputobjecttype.py index f2b6cf881..e11823823 100644 --- a/graphene/types/tests/test_inputobjecttype.py +++ b/graphene/types/tests/test_inputobjecttype.py @@ -50,7 +50,7 @@ class MyInputObjectType(InputObjectType): field = MyScalar() asa = InputField(MyType) - assert list(MyInputObjectType._meta.fields.keys()) == ["b", "a", "field", "asa"] + assert list(MyInputObjectType._meta.fields) == ["b", "a", "field", "asa"] def test_generate_inputobjecttype_unmountedtype(): @@ -84,7 +84,7 @@ class MyAbstractType: class MyInputObjectType(InputObjectType, MyAbstractType): field2 = MyScalar(MyType) - assert list(MyInputObjectType._meta.fields.keys()) == ["field1", "field2"] + assert list(MyInputObjectType._meta.fields) == ["field1", "field2"] assert [type(x) for x in MyInputObjectType._meta.fields.values()] == [ InputField, InputField, @@ -98,7 +98,7 @@ class MyAbstractType: class MyInputObjectType(MyAbstractType, InputObjectType): field2 = MyScalar(MyType) - assert list(MyInputObjectType._meta.fields.keys()) == ["field1", "field2"] + assert list(MyInputObjectType._meta.fields) == ["field1", "field2"] assert [type(x) for x in MyInputObjectType._meta.fields.values()] == [ InputField, InputField, diff --git a/graphene/types/tests/test_interface.py b/graphene/types/tests/test_interface.py index df85472fb..d551f2384 100644 --- a/graphene/types/tests/test_interface.py +++ b/graphene/types/tests/test_interface.py @@ -45,7 +45,7 @@ class MyInterface(Interface): field = MyScalar() asa = Field(MyType) - assert list(MyInterface._meta.fields.keys()) == ["b", "a", "field", "asa"] + assert list(MyInterface._meta.fields) == ["b", "a", "field", "asa"] def test_generate_interface_unmountedtype(): @@ -63,7 +63,7 @@ class MyAbstractType: class MyInterface(Interface, MyAbstractType): field2 = MyScalar() - assert list(MyInterface._meta.fields.keys()) == ["field1", "field2"] + assert list(MyInterface._meta.fields) == ["field1", "field2"] assert [type(x) for x in MyInterface._meta.fields.values()] == [Field, Field] @@ -75,7 +75,7 @@ class MyInterface(MyBaseInterface): field2 = MyScalar() assert MyInterface._meta.name == "MyInterface" - assert list(MyInterface._meta.fields.keys()) == ["field1", "field2"] + assert list(MyInterface._meta.fields) == ["field1", "field2"] assert [type(x) for x in MyInterface._meta.fields.values()] == [Field, Field] @@ -86,5 +86,5 @@ class MyAbstractType: class MyInterface(MyAbstractType, Interface): field2 = MyScalar() - assert list(MyInterface._meta.fields.keys()) == ["field1", "field2"] + assert list(MyInterface._meta.fields) == ["field1", "field2"] assert [type(x) for x in MyInterface._meta.fields.values()] == [Field, Field] diff --git a/graphene/types/tests/test_objecttype.py b/graphene/types/tests/test_objecttype.py index 505dc9f76..4cfcc898a 100644 --- a/graphene/types/tests/test_objecttype.py +++ b/graphene/types/tests/test_objecttype.py @@ -102,7 +102,7 @@ class MyObjectType(ObjectType): field = MyScalar() asa = Field(MyType) - assert list(MyObjectType._meta.fields.keys()) == ["b", "a", "field", "asa"] + assert list(MyObjectType._meta.fields) == ["b", "a", "field", "asa"] def test_generate_objecttype_inherit_abstracttype(): @@ -115,7 +115,7 @@ class MyObjectType(ObjectType, MyAbstractType): assert MyObjectType._meta.description is None assert MyObjectType._meta.interfaces == () assert MyObjectType._meta.name == "MyObjectType" - assert list(MyObjectType._meta.fields.keys()) == ["field1", "field2"] + assert list(MyObjectType._meta.fields) == ["field1", "field2"] assert list(map(type, MyObjectType._meta.fields.values())) == [Field, Field] @@ -129,7 +129,7 @@ class MyObjectType(MyAbstractType, ObjectType): assert MyObjectType._meta.description is None assert MyObjectType._meta.interfaces == () assert MyObjectType._meta.name == "MyObjectType" - assert list(MyObjectType._meta.fields.keys()) == ["field1", "field2"] + assert list(MyObjectType._meta.fields) == ["field1", "field2"] assert list(map(type, MyObjectType._meta.fields.values())) == [Field, Field] @@ -142,11 +142,11 @@ class MyObjectType(ObjectType): def test_parent_container_get_fields(): - assert list(Container._meta.fields.keys()) == ["field1", "field2"] + assert list(Container._meta.fields) == ["field1", "field2"] def test_parent_container_interface_get_fields(): - assert list(ContainerWithInterface._meta.fields.keys()) == [ + assert list(ContainerWithInterface._meta.fields) == [ "ifield", "field1", "field2", diff --git a/graphene/types/tests/test_type_map.py b/graphene/types/tests/test_type_map.py index 9c4d95fb0..53eec7552 100644 --- a/graphene/types/tests/test_type_map.py +++ b/graphene/types/tests/test_type_map.py @@ -80,7 +80,7 @@ def resolve_foo(self, bar): assert graphql_type.description == "Description" fields = graphql_type.fields - assert list(fields.keys()) == ["foo", "gizmo"] + assert list(fields) == ["foo", "gizmo"] foo_field = fields["foo"] assert isinstance(foo_field, GraphQLField) assert foo_field.description == "Field description" @@ -104,11 +104,11 @@ class MyObjectType(ObjectType): type_map = create_type_map([MyObjectType]) assert "MyObjectType" in type_map - assert list(MyObjectType._meta.fields.keys()) == ["bar", "own"] + assert list(MyObjectType._meta.fields) == ["bar", "own"] graphql_type = type_map["MyObjectType"] fields = graphql_type.fields - assert list(fields.keys()) == ["bar", "own"] + assert list(fields) == ["bar", "own"] assert fields["bar"].type == GraphQLString assert fields["own"].type == graphql_type @@ -135,9 +135,9 @@ def resolve_foo(self, args, info): assert graphql_type.description == "Description" fields = graphql_type.fields - assert list(fields.keys()) == ["foo", "gizmo", "own"] + assert list(fields) == ["foo", "gizmo", "own"] assert fields["own"].type == graphql_type - assert list(fields["gizmo"].args.keys()) == ["firstArg", "oth_arg"] + assert list(fields["gizmo"].args) == ["firstArg", "oth_arg"] foo_field = fields["foo"] assert isinstance(foo_field, GraphQLField) assert foo_field.description == "Field description" @@ -203,7 +203,7 @@ def resolve_foo_bar(self, args, info): assert container.baz.some_other_field[1].thingy == 2 fields = graphql_type.fields - assert list(fields.keys()) == ["fooBar", "gizmo", "baz", "own"] + assert list(fields) == ["fooBar", "gizmo", "baz", "own"] own_field = fields["own"] assert own_field.type == graphql_type foo_field = fields["fooBar"] @@ -225,7 +225,7 @@ class MyObjectType(ObjectType): assert graphql_type.description == "Description" fields = graphql_type.fields - assert list(fields.keys()) == ["fooBar"] + assert list(fields) == ["fooBar"] foo_field = fields["fooBar"] assert isinstance(foo_field, GraphQLField) assert foo_field.args == { @@ -251,7 +251,7 @@ class MyObjectType(ObjectType): assert graphql_type.description == "Description" fields = graphql_type.fields - assert list(fields.keys()) == ["foo_bar"] + assert list(fields) == ["foo_bar"] foo_field = fields["foo_bar"] assert isinstance(foo_field, GraphQLField) assert foo_field.args == { diff --git a/graphene/utils/subclass_with_meta.py b/graphene/utils/subclass_with_meta.py index 600de3d85..c6ba2d3fb 100644 --- a/graphene/utils/subclass_with_meta.py +++ b/graphene/utils/subclass_with_meta.py @@ -43,7 +43,7 @@ def __init_subclass__(cls, **meta_options): assert not options, ( "Abstract types can only contain the abstract attribute. " "Received: abstract, {option_keys}" - ).format(option_keys=", ".join(options.keys())) + ).format(option_keys=", ".join(options)) else: super_class = super(cls, cls) if hasattr(super_class, "__init_subclass_with_meta__"): From 9594bc616c9f6b12344de0cd3da7eeb8a546c6ca Mon Sep 17 00:00:00 2001 From: Christoph Zwerschke Date: Mon, 1 Jul 2019 00:58:43 +0200 Subject: [PATCH 19/26] Use consistent style of importing from pytest --- graphene/relay/tests/test_connection.py | 4 ++-- graphene/tests/issues/test_356.py | 4 ++-- graphene/types/tests/test_argument.py | 6 +++--- graphene/types/tests/test_datetime.py | 11 ++++++----- graphene/types/tests/test_field.py | 4 ++-- graphene/types/tests/test_mutation.py | 4 ++-- graphene/types/tests/test_objecttype.py | 10 +++++----- graphene/types/tests/test_schema.py | 4 ++-- graphene/types/tests/test_structures.py | 8 ++++---- graphene/types/tests/test_type_map.py | 5 +++-- graphene/types/tests/test_union.py | 4 ++-- graphene/utils/tests/test_crunch.py | 4 ++-- graphene/utils/tests/test_deprecated.py | 4 ++-- 13 files changed, 37 insertions(+), 35 deletions(-) diff --git a/graphene/relay/tests/test_connection.py b/graphene/relay/tests/test_connection.py index c9bfb3ea7..4015f4b43 100644 --- a/graphene/relay/tests/test_connection.py +++ b/graphene/relay/tests/test_connection.py @@ -1,4 +1,4 @@ -import pytest +from pytest import raises from ...types import Argument, Field, Int, List, NonNull, ObjectType, Schema, String from ..connection import Connection, ConnectionField, PageInfo @@ -146,7 +146,7 @@ class Meta: def test_connectionfield_node_deprecated(): field = ConnectionField(MyObject) - with pytest.raises(Exception) as exc_info: + with raises(Exception) as exc_info: field.type assert "ConnectionFields now need a explicit ConnectionType for Nodes." in str( diff --git a/graphene/tests/issues/test_356.py b/graphene/tests/issues/test_356.py index 4f3e10667..0e7daa094 100644 --- a/graphene/tests/issues/test_356.py +++ b/graphene/tests/issues/test_356.py @@ -1,6 +1,6 @@ # https://github.com/graphql-python/graphene/issues/356 -import pytest +from pytest import raises import graphene from graphene import relay @@ -23,7 +23,7 @@ def test_issue(): class Query(graphene.ObjectType): things = relay.ConnectionField(MyUnion) - with pytest.raises(Exception) as exc_info: + with raises(Exception) as exc_info: graphene.Schema(query=Query) assert str(exc_info.value) == ( diff --git a/graphene/types/tests/test_argument.py b/graphene/types/tests/test_argument.py index b8f49a47b..eee508923 100644 --- a/graphene/types/tests/test_argument.py +++ b/graphene/types/tests/test_argument.py @@ -1,6 +1,6 @@ from functools import partial -import pytest +from pytest import raises from ..argument import Argument, to_arguments from ..field import Field @@ -43,7 +43,7 @@ def test_to_arguments(): def test_to_arguments_raises_if_field(): args = {"arg_string": Field(String)} - with pytest.raises(ValueError) as exc_info: + with raises(ValueError) as exc_info: to_arguments(args) assert str(exc_info.value) == ( @@ -55,7 +55,7 @@ def test_to_arguments_raises_if_field(): def test_to_arguments_raises_if_inputfield(): args = {"arg_string": InputField(String)} - with pytest.raises(ValueError) as exc_info: + with raises(ValueError) as exc_info: to_arguments(args) assert str(exc_info.value) == ( diff --git a/graphene/types/tests/test_datetime.py b/graphene/types/tests/test_datetime.py index 1fd21c24e..357548079 100644 --- a/graphene/types/tests/test_datetime.py +++ b/graphene/types/tests/test_datetime.py @@ -2,7 +2,8 @@ import pytz from graphql import GraphQLError -import pytest + +from pytest import fixture, mark from ..datetime import Date, DateTime, Time from ..objecttype import ObjectType @@ -27,13 +28,13 @@ def resolve_time(self, info, _at=None): schema = Schema(query=Query) -@pytest.fixture +@fixture def sample_datetime(): utc_datetime = datetime.datetime(2019, 5, 25, 5, 30, 15, 10, pytz.utc) return utc_datetime -@pytest.fixture +@fixture def sample_time(sample_datetime): time = datetime.time( sample_datetime.hour, @@ -45,7 +46,7 @@ def sample_time(sample_datetime): return time -@pytest.fixture +@fixture def sample_date(sample_datetime): date = sample_datetime.date() return date @@ -170,7 +171,7 @@ def test_time_query_variable(sample_time): assert result.data == {"time": isoformat} -@pytest.mark.xfail( +@mark.xfail( reason="creating the error message fails when un-parsable object is not JSON serializable." ) def test_bad_variables(sample_date, sample_datetime, sample_time): diff --git a/graphene/types/tests/test_field.py b/graphene/types/tests/test_field.py index 0b9f58a1d..70ac09109 100644 --- a/graphene/types/tests/test_field.py +++ b/graphene/types/tests/test_field.py @@ -1,6 +1,6 @@ from functools import partial -import pytest +from pytest import raises from ..argument import Argument from ..field import Field @@ -85,7 +85,7 @@ def test_field_with_string_type(): def test_field_not_source_and_resolver(): MyType = object() - with pytest.raises(Exception) as exc_info: + with raises(Exception) as exc_info: Field(MyType, source="value", resolver=lambda: None) assert ( str(exc_info.value) diff --git a/graphene/types/tests/test_mutation.py b/graphene/types/tests/test_mutation.py index 8558dc7a1..bad2fe90e 100644 --- a/graphene/types/tests/test_mutation.py +++ b/graphene/types/tests/test_mutation.py @@ -1,4 +1,4 @@ -import pytest +from pytest import raises from ..argument import Argument from ..dynamic import Dynamic @@ -39,7 +39,7 @@ def mutate(self, info, **args): def test_mutation_raises_exception_if_no_mutate(): - with pytest.raises(AssertionError) as excinfo: + with raises(AssertionError) as excinfo: class MyMutation(Mutation): pass diff --git a/graphene/types/tests/test_objecttype.py b/graphene/types/tests/test_objecttype.py index 4cfcc898a..a3f331fe2 100644 --- a/graphene/types/tests/test_objecttype.py +++ b/graphene/types/tests/test_objecttype.py @@ -1,4 +1,4 @@ -import pytest +from pytest import raises from ..field import Field from ..interface import Interface @@ -91,7 +91,7 @@ class MyObjectType(ObjectType): m = MyObjectType(_private_state="custom") assert m._private_state == "custom" - with pytest.raises(TypeError): + with raises(TypeError): MyObjectType(_other_private_state="Wrong") @@ -177,14 +177,14 @@ def test_objecttype_as_container_all_kwargs(): def test_objecttype_as_container_extra_args(): - with pytest.raises(IndexError) as excinfo: + with raises(IndexError) as excinfo: Container("1", "2", "3") assert "Number of args exceeds number of fields" == str(excinfo.value) def test_objecttype_as_container_invalid_kwargs(): - with pytest.raises(TypeError) as excinfo: + with raises(TypeError) as excinfo: Container(unexisting_field="3") assert "'unexisting_field' is an invalid keyword argument for Container" == str( @@ -218,7 +218,7 @@ class Meta: def test_objecttype_with_possible_types_and_is_type_of_should_raise(): - with pytest.raises(AssertionError) as excinfo: + with raises(AssertionError) as excinfo: class MyObjectType(ObjectType): class Meta: diff --git a/graphene/types/tests/test_schema.py b/graphene/types/tests/test_schema.py index 18f102a05..d0990e61f 100644 --- a/graphene/types/tests/test_schema.py +++ b/graphene/types/tests/test_schema.py @@ -1,4 +1,4 @@ -import pytest +from pytest import raises from graphql.pyutils import dedent @@ -29,7 +29,7 @@ def test_schema_get_type(): def test_schema_get_type_error(): schema = Schema(Query) - with pytest.raises(AttributeError) as exc_info: + with raises(AttributeError) as exc_info: schema.X assert str(exc_info.value) == 'Type "X" not found in the Schema' diff --git a/graphene/types/tests/test_structures.py b/graphene/types/tests/test_structures.py index 5359278f5..88f3ff1da 100644 --- a/graphene/types/tests/test_structures.py +++ b/graphene/types/tests/test_structures.py @@ -1,6 +1,6 @@ from functools import partial -import pytest +from pytest import raises from ..scalars import String from ..structures import List, NonNull @@ -14,7 +14,7 @@ def test_list(): def test_list_with_unmounted_type(): - with pytest.raises(Exception) as exc_info: + with raises(Exception) as exc_info: List(String()) assert ( @@ -82,7 +82,7 @@ def test_nonnull_inherited_works_list(): def test_nonnull_inherited_dont_work_nonnull(): - with pytest.raises(Exception) as exc_info: + with raises(Exception) as exc_info: NonNull(NonNull(String)) assert ( @@ -92,7 +92,7 @@ def test_nonnull_inherited_dont_work_nonnull(): def test_nonnull_with_unmounted_type(): - with pytest.raises(Exception) as exc_info: + with raises(Exception) as exc_info: NonNull(String()) assert ( diff --git a/graphene/types/tests/test_type_map.py b/graphene/types/tests/test_type_map.py index 53eec7552..ba7e2072e 100644 --- a/graphene/types/tests/test_type_map.py +++ b/graphene/types/tests/test_type_map.py @@ -1,4 +1,5 @@ -import pytest +from pytest import raises + from graphql.type import ( GraphQLArgument, GraphQLEnumType, @@ -290,7 +291,7 @@ def resolve_type_func(root, info): return MyOtherObjectType type_map = create_type_map([MyObjectType]) - with pytest.raises(AssertionError) as excinfo: + with raises(AssertionError) as excinfo: resolve_type(resolve_type_func, type_map, "MyOtherObjectType", {}, {}, None) assert "MyOtherObjectTyp" in str(excinfo.value) diff --git a/graphene/types/tests/test_union.py b/graphene/types/tests/test_union.py index 256c7d95b..4d642d6f5 100644 --- a/graphene/types/tests/test_union.py +++ b/graphene/types/tests/test_union.py @@ -1,4 +1,4 @@ -import pytest +from pytest import raises from ..field import Field from ..objecttype import ObjectType @@ -38,7 +38,7 @@ class Meta: def test_generate_union_with_no_types(): - with pytest.raises(Exception) as exc_info: + with raises(Exception) as exc_info: class MyUnion(Union): pass diff --git a/graphene/utils/tests/test_crunch.py b/graphene/utils/tests/test_crunch.py index bbcf37dd4..2a4a0ce4b 100644 --- a/graphene/utils/tests/test_crunch.py +++ b/graphene/utils/tests/test_crunch.py @@ -1,9 +1,9 @@ -import pytest +from pytest import mark from ..crunch import crunch -@pytest.mark.parametrize( +@mark.parametrize( "description,uncrunched,crunched", [ ["number primitive", 0, [0]], diff --git a/graphene/utils/tests/test_deprecated.py b/graphene/utils/tests/test_deprecated.py index 7d407548c..8a14434b6 100644 --- a/graphene/utils/tests/test_deprecated.py +++ b/graphene/utils/tests/test_deprecated.py @@ -1,4 +1,4 @@ -import pytest +from pytest import raises from .. import deprecated from ..deprecated import deprecated as deprecated_decorator @@ -71,5 +71,5 @@ class X: def test_deprecated_other_object(mocker): mocker.patch.object(deprecated, "warn_deprecation") - with pytest.raises(TypeError): + with raises(TypeError): deprecated_decorator({}) From 8b07dbac9beb9098d6f612a710630be7045475cc Mon Sep 17 00:00:00 2001 From: Christoph Zwerschke Date: Fri, 12 Jul 2019 21:02:00 +0200 Subject: [PATCH 20/26] Restore compatibility with graphql-relay-py v3 Add adpaters for the PageInfo and Connection args. --- graphene/relay/connection.py | 22 ++++++++++++++++++---- setup.py | 6 +++++- 2 files changed, 23 insertions(+), 5 deletions(-) diff --git a/graphene/relay/connection.py b/graphene/relay/connection.py index c27b3f49a..4b8408e58 100644 --- a/graphene/relay/connection.py +++ b/graphene/relay/connection.py @@ -2,7 +2,7 @@ from collections import Iterable from functools import partial -from graphql_relay import connection_from_list +from graphql_relay import connection_from_array from ..types import Boolean, Enum, Int, Interface, List, NonNull, Scalar, String, Union from ..types.field import Field @@ -41,6 +41,14 @@ class Meta: ) +# noinspection PyPep8Naming +def page_info_adapter(startCursor, endCursor, hasPreviousPage, hasNextPage): + """Adapter for creating PageInfo instances""" + return PageInfo( + start_cursor=startCursor, end_cursor=endCursor, + has_previous_page=hasPreviousPage, has_next_page=hasNextPage) + + class ConnectionOptions(ObjectTypeOptions): node = None @@ -103,6 +111,12 @@ class EdgeMeta: ) +# noinspection PyPep8Naming +def connection_adapter(cls, edges, pageInfo): + """Adapter for creating Connection instances""" + return cls(edges=edges, page_info=pageInfo) + + class IterableConnectionField(Field): def __init__(self, type, *args, **kwargs): kwargs.setdefault("before", String()) @@ -138,12 +152,12 @@ def resolve_connection(cls, connection_type, args, resolved): "Resolved value from the connection field has to be iterable or instance of {}. " 'Received "{}"' ).format(connection_type, resolved) - connection = connection_from_list( + connection = connection_from_array( resolved, args, - connection_type=connection_type, + connection_type=partial(connection_adapter, connection_type), edge_type=connection_type.Edge, - pageinfo_type=PageInfo, + page_info_type=page_info_adapter, ) connection.iterable = resolved return connection diff --git a/setup.py b/setup.py index 548fe86d9..54bed3f12 100644 --- a/setup.py +++ b/setup.py @@ -79,7 +79,11 @@ def run_tests(self): ], keywords="api graphql protocol rest relay graphene", packages=find_packages(exclude=["tests", "tests.*", "examples"]), - install_requires=["graphql-core-next~=1.0.5", "aniso8601~=6.0.0"], + install_requires=[ + "graphql-core-next>=1.1.0b0,<2", + "graphql-relay>=3.0.0a0,<4", + "aniso8601~=6.0.0" + ], tests_require=tests_require, extras_require={ "test": tests_require, From a02ea47681d7de630d7f31f9341df8acab940ba7 Mon Sep 17 00:00:00 2001 From: Christoph Zwerschke Date: Fri, 12 Jul 2019 21:29:20 +0200 Subject: [PATCH 21/26] Avoid various deprecation warnings --- graphene/relay/connection.py | 2 +- graphene/tests/issues/test_313.py | 2 +- graphene/types/field.py | 4 ++-- graphene/types/mutation.py | 2 +- graphene/types/tests/test_abstracttype.py | 24 ++++++++++++----------- graphene/types/tests/test_datetime.py | 3 ++- graphene/utils/crunch.py | 2 +- graphene/utils/deduplicator.py | 2 +- 8 files changed, 22 insertions(+), 19 deletions(-) diff --git a/graphene/relay/connection.py b/graphene/relay/connection.py index 4b8408e58..464c68af1 100644 --- a/graphene/relay/connection.py +++ b/graphene/relay/connection.py @@ -1,5 +1,5 @@ import re -from collections import Iterable +from collections.abc import Iterable from functools import partial from graphql_relay import connection_from_array diff --git a/graphene/tests/issues/test_313.py b/graphene/tests/issues/test_313.py index 34dfef1ab..8082677a1 100644 --- a/graphene/tests/issues/test_313.py +++ b/graphene/tests/issues/test_313.py @@ -21,7 +21,7 @@ class Meta: class CreatePost(graphene.Mutation): - class Input: + class Arguments: text = graphene.String(required=True) result = graphene.Field(CreatePostResult) diff --git a/graphene/types/field.py b/graphene/types/field.py index a14286328..213a0f656 100644 --- a/graphene/types/field.py +++ b/graphene/types/field.py @@ -1,5 +1,5 @@ import inspect -from collections import Mapping, OrderedDict +from collections.abc import Mapping from functools import partial from .argument import Argument, to_arguments @@ -59,7 +59,7 @@ def __init__( self.name = name self._type = type - self.args = to_arguments(args or OrderedDict(), extra_args) + self.args = to_arguments(args or {}, extra_args) if source: resolver = partial(source_resolver, source) self.resolver = resolver diff --git a/graphene/types/mutation.py b/graphene/types/mutation.py index fb139d78e..2017c4d5f 100644 --- a/graphene/types/mutation.py +++ b/graphene/types/mutation.py @@ -47,7 +47,7 @@ def __init_subclass_with_meta__( warn_deprecation( ( "Please use {name}.Arguments instead of {name}.Input." - "Input is now only used in ClientMutationID.\n" + " Input is now only used in ClientMutationID.\n" "Read more:" " https://github.com/graphql-python/graphene/blob/v2.0.0/UPGRADE-v2.0.md#mutation-input" ).format(name=cls.__name__) diff --git a/graphene/types/tests/test_abstracttype.py b/graphene/types/tests/test_abstracttype.py index eb11cb799..a50c87571 100644 --- a/graphene/types/tests/test_abstracttype.py +++ b/graphene/types/tests/test_abstracttype.py @@ -1,4 +1,5 @@ -from .. import abstracttype +from pytest import deprecated_call + from ..abstracttype import AbstractType from ..field import Field from ..objecttype import ObjectType @@ -14,21 +15,22 @@ def get_type(self): return MyType -def test_abstract_objecttype_warn_deprecation(mocker): - mocker.patch.object(abstracttype, "warn_deprecation") - - class MyAbstractType(AbstractType): - field1 = MyScalar() +def test_abstract_objecttype_warn_deprecation(): + with deprecated_call(): - assert abstracttype.warn_deprecation.called + # noinspection PyUnusedLocal + class MyAbstractType(AbstractType): + field1 = MyScalar() def test_generate_objecttype_inherit_abstracttype(): - class MyAbstractType(AbstractType): - field1 = MyScalar() + with deprecated_call(): + + class MyAbstractType(AbstractType): + field1 = MyScalar() - class MyObjectType(ObjectType, MyAbstractType): - field2 = MyScalar() + class MyObjectType(ObjectType, MyAbstractType): + field2 = MyScalar() assert MyObjectType._meta.description is None assert MyObjectType._meta.interfaces == () diff --git a/graphene/types/tests/test_datetime.py b/graphene/types/tests/test_datetime.py index 357548079..4caa79cf8 100644 --- a/graphene/types/tests/test_datetime.py +++ b/graphene/types/tests/test_datetime.py @@ -182,9 +182,10 @@ def _test_bad_variables(type_, input_): ), variables={"input": input_}, ) - assert result.errors and len(result.errors) == 1 # when `input` is not JSON serializable formatting the error message in # `graphql.utils.is_valid_value` line 79 fails with a TypeError + assert isinstance(result.errors, list) + assert len(result.errors) == 1 assert isinstance(result.errors[0], GraphQLError) assert result.data is None diff --git a/graphene/utils/crunch.py b/graphene/utils/crunch.py index 57fcb77fe..b27d3718e 100644 --- a/graphene/utils/crunch.py +++ b/graphene/utils/crunch.py @@ -1,5 +1,5 @@ import json -from collections import Mapping +from collections.abc import Mapping def to_key(value): diff --git a/graphene/utils/deduplicator.py b/graphene/utils/deduplicator.py index 32f6dcbe3..3fbf139d1 100644 --- a/graphene/utils/deduplicator.py +++ b/graphene/utils/deduplicator.py @@ -1,4 +1,4 @@ -from collections import Mapping +from collections.abc import Mapping def deflate(node, index=None, path=None): From b3b9b4fc9e03d03150210bb2f13292b73875b4b0 Mon Sep 17 00:00:00 2001 From: Christoph Zwerschke Date: Thu, 1 Aug 2019 19:23:38 +0200 Subject: [PATCH 22/26] Use graphql-core 3 instead of graphql-core-next --- setup.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/setup.py b/setup.py index 54bed3f12..344d6d6e7 100644 --- a/setup.py +++ b/setup.py @@ -80,9 +80,9 @@ def run_tests(self): keywords="api graphql protocol rest relay graphene", packages=find_packages(exclude=["tests", "tests.*", "examples"]), install_requires=[ - "graphql-core-next>=1.1.0b0,<2", + "graphql-core>=3.0.0a0,<4", "graphql-relay>=3.0.0a0,<4", - "aniso8601~=6.0.0" + "aniso8601~=6.0.0", ], tests_require=tests_require, extras_require={ From 55c7a67275f88ed3f284bb632d32849076d06b78 Mon Sep 17 00:00:00 2001 From: Christoph Zwerschke Date: Thu, 1 Aug 2019 19:24:52 +0200 Subject: [PATCH 23/26] Update dependencies, reformat changes with black --- .pre-commit-config.yaml | 2 +- graphene/relay/connection.py | 7 ++++-- graphene/relay/mutation.py | 4 +--- graphene/relay/node.py | 4 +--- graphene/relay/tests/test_connection_query.py | 8 +++++-- graphene/relay/tests/test_node.py | 24 +++++++++---------- graphene/relay/tests/test_node_custom.py | 18 +++++++------- graphene/types/tests/test_argument.py | 2 +- graphene/types/tests/test_datetime.py | 9 ++++--- graphene/types/tests/test_enum.py | 12 ++++++---- graphene/types/tests/test_objecttype.py | 6 +---- graphene/types/tests/test_query.py | 5 ++-- graphene/types/tests/test_schema.py | 2 +- graphene/types/tests/test_type_map.py | 14 ++++------- graphene/utils/tests/test_crunch.py | 6 +---- tests_asyncio/test_relay_connection.py | 2 +- tests_asyncio/test_relay_mutation.py | 4 ++-- tox.ini | 8 ++++--- 18 files changed, 68 insertions(+), 69 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 227da33cb..7aa720015 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -23,6 +23,6 @@ repos: - id: black language_version: python3 - repo: https://github.com/PyCQA/flake8 - rev: 3.7.7 + rev: 3.7.8 hooks: - id: flake8 diff --git a/graphene/relay/connection.py b/graphene/relay/connection.py index 464c68af1..f6d3296da 100644 --- a/graphene/relay/connection.py +++ b/graphene/relay/connection.py @@ -45,8 +45,11 @@ class Meta: def page_info_adapter(startCursor, endCursor, hasPreviousPage, hasNextPage): """Adapter for creating PageInfo instances""" return PageInfo( - start_cursor=startCursor, end_cursor=endCursor, - has_previous_page=hasPreviousPage, has_next_page=hasNextPage) + start_cursor=startCursor, + end_cursor=endCursor, + has_previous_page=hasPreviousPage, + has_next_page=hasNextPage, + ) class ConnectionOptions(ObjectTypeOptions): diff --git a/graphene/relay/mutation.py b/graphene/relay/mutation.py index bb27855fc..fce0c5982 100644 --- a/graphene/relay/mutation.py +++ b/graphene/relay/mutation.py @@ -29,9 +29,7 @@ def __init_subclass_with_meta__( cls.Input = type( "{}Input".format(base_name), bases, - dict( - input_fields, client_mutation_id=String(name="clientMutationId") - ), + dict(input_fields, client_mutation_id=String(name="clientMutationId")), ) arguments = dict( diff --git a/graphene/relay/node.py b/graphene/relay/node.py index 79c34ffcb..54423bbba 100644 --- a/graphene/relay/node.py +++ b/graphene/relay/node.py @@ -71,9 +71,7 @@ class Meta: @classmethod def __init_subclass_with_meta__(cls, **options): _meta = InterfaceOptions(cls) - _meta.fields = { - 'id': GlobalID(cls, description="The ID of the object") - } + _meta.fields = {"id": GlobalID(cls, description="The ID of the object")} super(AbstractNode, cls).__init_subclass_with_meta__(_meta=_meta, **options) diff --git a/graphene/relay/tests/test_connection_query.py b/graphene/relay/tests/test_connection_query.py index 7e6f2c4d5..e109067ba 100644 --- a/graphene/relay/tests/test_connection_query.py +++ b/graphene/relay/tests/test_connection_query.py @@ -134,7 +134,9 @@ async def test_respects_an_overly_large_last(): @mark.asyncio async def test_respects_first_and_after(): - await check('first: 2, after: "{}"'.format(cursor_for("B")), "CD", has_next_page=True) + await check( + 'first: 2, after: "{}"'.format(cursor_for("B")), "CD", has_next_page=True + ) @mark.asyncio @@ -144,7 +146,9 @@ async def test_respects_first_and_after_with_long_first(): @mark.asyncio async def test_respects_last_and_before(): - await check('last: 2, before: "{}"'.format(cursor_for("D")), "BC", has_previous_page=True) + await check( + 'last: 2, before: "{}"'.format(cursor_for("D")), "BC", has_previous_page=True + ) @mark.asyncio diff --git a/graphene/relay/tests/test_node.py b/graphene/relay/tests/test_node.py index 66ad12b43..c43ee1edc 100644 --- a/graphene/relay/tests/test_node.py +++ b/graphene/relay/tests/test_node.py @@ -72,9 +72,9 @@ def test_subclassed_node_query(): assert not executed.errors assert executed.data == { "node": { - "shared": "1", - "extraField": "extra field info.", - "somethingElse": "----" + "shared": "1", + "extraField": "extra field info.", + "somethingElse": "----", } } @@ -144,18 +144,18 @@ def test_node_field_only_lazy_type_wrong(): def test_str_schema(): - assert (str(schema) == dedent( + assert str(schema) == dedent( ''' schema { query: RootQuery } - + type MyNode implements Node { """The ID of the object""" id: ID! name: String } - + type MyOtherNode implements Node { """The ID of the object""" id: ID! @@ -163,24 +163,24 @@ def test_str_schema(): somethingElse: String extraField: String } - + """An object with an ID""" interface Node { """The ID of the object""" id: ID! } - + type RootQuery { first: String - + """The ID of the object""" node(id: ID!): Node - + """The ID of the object""" onlyNode(id: ID!): MyNode - + """The ID of the object""" onlyNodeLazy(id: ID!): MyNode } - ''') + ''' ) diff --git a/graphene/relay/tests/test_node_custom.py b/graphene/relay/tests/test_node_custom.py index 967bbead0..773be48f3 100644 --- a/graphene/relay/tests/test_node_custom.py +++ b/graphene/relay/tests/test_node_custom.py @@ -53,43 +53,43 @@ class RootQuery(ObjectType): def test_str_schema_correct(): - assert (str(schema) == dedent( + assert str(schema) == dedent( ''' schema { query: RootQuery } - + interface BasePhoto { """The width of the photo in pixels""" width: Int } - + interface Node { """The ID of the object""" id: ID! } - + type Photo implements Node & BasePhoto { """The ID of the object""" id: ID! - + """The width of the photo in pixels""" width: Int } - + type RootQuery { """The ID of the object""" node(id: ID!): Node } - + type User implements Node { """The ID of the object""" id: ID! - + """The full name of the user""" name: String } - ''') + ''' ) diff --git a/graphene/types/tests/test_argument.py b/graphene/types/tests/test_argument.py index eee508923..db4d6c242 100644 --- a/graphene/types/tests/test_argument.py +++ b/graphene/types/tests/test_argument.py @@ -1,6 +1,6 @@ from functools import partial -from pytest import raises +from pytest import raises from ..argument import Argument, to_arguments from ..field import Field diff --git a/graphene/types/tests/test_datetime.py b/graphene/types/tests/test_datetime.py index 4caa79cf8..bfd56c6c0 100644 --- a/graphene/types/tests/test_datetime.py +++ b/graphene/types/tests/test_datetime.py @@ -85,7 +85,8 @@ def test_bad_datetime_query(): error = result.errors[0] assert isinstance(error, GraphQLError) assert error.message == ( - "Expected type DateTime, found \"Some string that's not a datetime\".") + 'Expected type DateTime, found "Some string that\'s not a datetime".' + ) assert result.data is None @@ -97,7 +98,8 @@ def test_bad_date_query(): error = result.errors[0] assert isinstance(error, GraphQLError) assert error.message == ( - "Expected type Date, found \"Some string that's not a date\".") + 'Expected type Date, found "Some string that\'s not a date".' + ) assert result.data is None @@ -109,7 +111,8 @@ def test_bad_time_query(): error = result.errors[0] assert isinstance(error, GraphQLError) assert error.message == ( - "Expected type Time, found \"Some string that's not a time\".") + 'Expected type Time, found "Some string that\'s not a time".' + ) assert result.data is None diff --git a/graphene/types/tests/test_enum.py b/graphene/types/tests/test_enum.py index cf7227e46..1c5bdb383 100644 --- a/graphene/types/tests/test_enum.py +++ b/graphene/types/tests/test_enum.py @@ -85,10 +85,14 @@ class Query(ObjectType): episode = schema.get_type("PyEpisode") assert episode.description == "StarWars Episodes" - assert [(name, value.description, value.deprecation_reason) - for name, value in episode.values.items()] == [ - ('NEWHOPE', 'New Hope Episode', 'meh'), - ('EMPIRE', 'Other', None), ('JEDI', 'Other', None)] + assert [ + (name, value.description, value.deprecation_reason) + for name, value in episode.values.items() + ] == [ + ("NEWHOPE", "New Hope Episode", "meh"), + ("EMPIRE", "Other", None), + ("JEDI", "Other", None), + ] def test_enum_from_python3_enum_uses_enum_doc(): diff --git a/graphene/types/tests/test_objecttype.py b/graphene/types/tests/test_objecttype.py index a3f331fe2..25025e4d1 100644 --- a/graphene/types/tests/test_objecttype.py +++ b/graphene/types/tests/test_objecttype.py @@ -146,11 +146,7 @@ def test_parent_container_get_fields(): def test_parent_container_interface_get_fields(): - assert list(ContainerWithInterface._meta.fields) == [ - "ifield", - "field1", - "field2", - ] + assert list(ContainerWithInterface._meta.fields) == ["ifield", "field1", "field2"] def test_objecttype_as_container_only_args(): diff --git a/graphene/types/tests/test_query.py b/graphene/types/tests/test_query.py index 259e6b027..004d53c8b 100644 --- a/graphene/types/tests/test_query.py +++ b/graphene/types/tests/test_query.py @@ -263,7 +263,8 @@ def resolve_test(self, info, **args): result = test_schema.execute('{ test(aInput: {aField: "String!"} ) }', "Source!") assert not result.errors assert result.data == { - "test": '["Source!",{"a_input":{"a_field":"String!","recursive_field":null}}]'} + "test": '["Source!",{"a_input":{"a_field":"String!","recursive_field":null}}]' + } result = test_schema.execute( '{ test(aInput: {recursiveField: {aField: "String!"}}) }', "Source!" @@ -271,7 +272,7 @@ def resolve_test(self, info, **args): assert not result.errors assert result.data == { "test": '["Source!",{"a_input":{"a_field":null,"recursive_field":' - '{"a_field":"String!","recursive_field":null}}}]' + '{"a_field":"String!","recursive_field":null}}}]' } diff --git a/graphene/types/tests/test_schema.py b/graphene/types/tests/test_schema.py index d0990e61f..29581122e 100644 --- a/graphene/types/tests/test_schema.py +++ b/graphene/types/tests/test_schema.py @@ -42,7 +42,7 @@ def test_schema_str(): type MyOtherType { field: String } - + type Query { inner: MyOtherType } diff --git a/graphene/types/tests/test_type_map.py b/graphene/types/tests/test_type_map.py index ba7e2072e..0ef3af1be 100644 --- a/graphene/types/tests/test_type_map.py +++ b/graphene/types/tests/test_type_map.py @@ -53,10 +53,10 @@ def deprecation_reason(self): assert graphql_enum.name == "MyEnum" assert graphql_enum.description == "Description" assert graphql_enum.values == { - 'foo': GraphQLEnumValue( + "foo": GraphQLEnumValue( value=1, description="Description foo=1", deprecation_reason="Is deprecated" ), - 'bar': GraphQLEnumValue(value=2, description="Description bar=2"), + "bar": GraphQLEnumValue(value=2, description="Description bar=2"), } @@ -230,11 +230,7 @@ class MyObjectType(ObjectType): foo_field = fields["fooBar"] assert isinstance(foo_field, GraphQLField) assert foo_field.args == { - "barFoo": GraphQLArgument( - GraphQLString, - default_value=None, - out_name="bar_foo" - ) + "barFoo": GraphQLArgument(GraphQLString, default_value=None, out_name="bar_foo") } @@ -257,9 +253,7 @@ class MyObjectType(ObjectType): assert isinstance(foo_field, GraphQLField) assert foo_field.args == { "bar_foo": GraphQLArgument( - GraphQLString, - default_value=None, - out_name="bar_foo" + GraphQLString, default_value=None, out_name="bar_foo" ) } diff --git a/graphene/utils/tests/test_crunch.py b/graphene/utils/tests/test_crunch.py index 2a4a0ce4b..92d0b1b04 100644 --- a/graphene/utils/tests/test_crunch.py +++ b/graphene/utils/tests/test_crunch.py @@ -42,11 +42,7 @@ ], [ "complex object", - { - "a": True, - "b": [1, 2, 3], - "c": {"a": True, "b": [1, 2, 3]}, - }, + {"a": True, "b": [1, 2, 3], "c": {"a": True, "b": [1, 2, 3]}}, [True, 1, 2, 3, [1, 2, 3], {"a": 0, "b": 4}, {"a": 0, "b": 4, "c": 5}], ], ], diff --git a/tests_asyncio/test_relay_connection.py b/tests_asyncio/test_relay_connection.py index 67ee2ee4e..b139f6a39 100644 --- a/tests_asyncio/test_relay_connection.py +++ b/tests_asyncio/test_relay_connection.py @@ -109,7 +109,7 @@ async def test_connection_async(): } } } - """, + """ ) assert not result.errors diff --git a/tests_asyncio/test_relay_mutation.py b/tests_asyncio/test_relay_mutation.py index 011557d51..7b083dbf9 100644 --- a/tests_asyncio/test_relay_mutation.py +++ b/tests_asyncio/test_relay_mutation.py @@ -66,7 +66,7 @@ class Mutation(ObjectType): @mark.asyncio async def test_node_query_promise(): executed = await schema.execute_async( - 'mutation a { sayPromise(input: {what:"hello", clientMutationId:"1"}) { phrase } }', + 'mutation a { sayPromise(input: {what:"hello", clientMutationId:"1"}) { phrase } }' ) assert not executed.errors assert executed.data == {"sayPromise": {"phrase": "hello"}} @@ -75,7 +75,7 @@ async def test_node_query_promise(): @mark.asyncio async def test_edge_query(): executed = await schema.execute_async( - 'mutation a { other(input: {clientMutationId:"1"}) { clientMutationId, myNodeEdge { cursor node { name }} } }', + 'mutation a { other(input: {clientMutationId:"1"}) { clientMutationId, myNodeEdge { cursor node { name }} } }' ) assert not executed.errors assert dict(executed.data) == { diff --git a/tox.ini b/tox.ini index 5529dc23e..dad55b879 100644 --- a/tox.ini +++ b/tox.ini @@ -22,14 +22,16 @@ commands = [testenv:mypy] basepython=python3.6 deps = - mypy + mypy>=0.720 commands = mypy graphene [testenv:flake8] -deps = flake8 +basepython=python3.6 +deps = + flake8>=3.7,<4 commands = - pip install -e . + pip install --pre -e . flake8 graphene [pytest] From 4b0cac5fb53f0fedbfac1a4bc0c89e1d8d887ce1 Mon Sep 17 00:00:00 2001 From: Mel van Londen Date: Sat, 10 Aug 2019 11:06:59 -0400 Subject: [PATCH 24/26] Update graphene/relay/connection.py Co-Authored-By: Jonathan Kim --- graphene/relay/connection.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/graphene/relay/connection.py b/graphene/relay/connection.py index f6d3296da..8581a4b5e 100644 --- a/graphene/relay/connection.py +++ b/graphene/relay/connection.py @@ -152,7 +152,7 @@ def resolve_connection(cls, connection_type, args, resolved): return resolved assert isinstance(resolved, Iterable), ( - "Resolved value from the connection field has to be iterable or instance of {}. " + "Resolved value from the connection field has to be an iterable or instance of {}. " 'Received "{}"' ).format(connection_type, resolved) connection = connection_from_array( From 03bf75d722c6546488a8bbb65edd485411839d19 Mon Sep 17 00:00:00 2001 From: Christoph Zwerschke Date: Sun, 11 Aug 2019 16:50:28 +0200 Subject: [PATCH 25/26] Run black on setup.py --- setup.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/setup.py b/setup.py index 27452b97a..3e22b7c43 100644 --- a/setup.py +++ b/setup.py @@ -82,11 +82,9 @@ def run_tests(self): install_requires=[ "graphql-core>=3.0.0a0,<4", "graphql-relay>=3.0.0a0,<4", - "aniso8601>=6,<8" + "aniso8601>=6,<8", ], tests_require=tests_require, - extras_require={ - "test": tests_require, - }, + extras_require={"test": tests_require}, cmdclass={"test": PyTest}, ) From 8961b413f3dbb464fcd841210f349ee8250c852d Mon Sep 17 00:00:00 2001 From: Christoph Zwerschke Date: Sun, 11 Aug 2019 17:07:52 +0200 Subject: [PATCH 26/26] Remove trailing whitespace --- graphene/types/schema.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/graphene/types/schema.py b/graphene/types/schema.py index c312884fa..bf8c469a4 100644 --- a/graphene/types/schema.py +++ b/graphene/types/schema.py @@ -407,7 +407,7 @@ def get_field_type(self, map_, type_): class Schema: """Schema Definition. - + A Graphene Schema can execute operations (query, mutation, subscription) against the defined types. For advanced purposes, the schema can be used to lookup type definitions and answer questions about the types through introspection. @@ -471,7 +471,7 @@ def lazy(self, _type): def execute(self, *args, **kwargs): """Execute a GraphQL query on the schema. - + Use the `graphql_sync` function from `graphql-core` to provide the result for a query string. Most of the time this method will be called by one of the Graphene :ref:`Integrations` via a web request. @@ -499,7 +499,7 @@ def execute(self, *args, **kwargs): async def execute_async(self, *args, **kwargs): """Execute a GraphQL query on the schema asynchronously. - + Same as `execute`, but uses `graphql` instead of `graphql_sync`. """ kwargs = normalize_execute_kwargs(kwargs)