diff --git a/README.md b/README.md index 9635a190..80c3d9c1 100644 --- a/README.md +++ b/README.md @@ -48,7 +48,7 @@ Here is one example for you to get started: class Query(graphene.ObjectType): hello = graphene.String(description='A typical hello world') - def resolve_hello(self): + def resolve_hello(self, info): return 'World' schema = graphene.Schema(query=Query) diff --git a/README.rst b/README.rst index b31335f2..bea6c4d4 100644 --- a/README.rst +++ b/README.rst @@ -65,7 +65,7 @@ Here is one example for you to get started: class Query(graphene.ObjectType): hello = graphene.String(description='A typical hello world') - def resolve_hello(self): + def resolve_hello(self, info): return 'World' schema = graphene.Schema(query=Query) diff --git a/UPGRADE-v2.0.md b/UPGRADE-v2.0.md index ab8b997b..8c96c0b2 100644 --- a/UPGRADE-v2.0.md +++ b/UPGRADE-v2.0.md @@ -25,6 +25,42 @@ developer have to write to use them. ## Deprecations +### Simpler resolvers + +All the resolvers in graphene have been simplified. If before resolvers must had received +four arguments `root`, `args`, `context` and `info`, now the `args` are passed as keyword arguments +and `context` and `info` will only be passed if the function is annotated with it. + +Before: + +```python +my_field = graphene.String(my_arg=graphene.String()) + +def resolve_my_field(self, args, context, info): + my_arg = args.get('my_arg') + return ... +``` + +With 2.0: + +```python +my_field = graphene.String(my_arg=graphene.String()) + +def resolve_my_field(self, info, my_arg): + return ... +``` + +And, if you need the context in the resolver, you can use `info.context`: + +```python +my_field = graphene.String(my_arg=graphene.String()) + +def resolve_my_field(self, info, my_arg): + context = info.context + return ... +``` + + ### AbstractType deprecated AbstractType is deprecated in graphene 2.0, you can now use normal inheritance instead. @@ -51,7 +87,7 @@ class Pet(CommonFields, Interface): ### resolve\_only\_args -`resolve_only_args` is now deprecated in favor of type annotations (using the polyfill `@graphene.annotate` in Python 2 in case is necessary for accessing `context` or `info`). +`resolve_only_args` is now deprecated as the resolver API has been simplified. Before: @@ -70,7 +106,7 @@ With 2.0: class User(ObjectType): name = String() - def resolve_name(self): + def resolve_name(self, info): return self.name ``` @@ -174,6 +210,42 @@ class Query(ObjectType): user_connection = relay.ConnectionField(UserConnection) ``` +## Node.get_node + +The method `get_node` in `ObjectTypes` that have `Node` as interface, changes it's api. +From `def get_node(cls, id, context, info)` to `def get_node(cls, info, id)`. + +```python +class MyObject(ObjectType): + class Meta: + interfaces = (Node, ) + + @classmethod + def get_node(cls, id, context, info): + return ... +``` + +To: +```python +class MyObject(ObjectType): + class Meta: + interfaces = (Node, ) + + @classmethod + def get_node(cls, info, id): + return ... +``` + +## Mutation.mutate + +Now only receives (`root`, `info`, `**args`) + + +## ClientIDMutation.mutate_and_get_payload + +Now only receives (`root`, `info`, `**input`) + + ## New Features ### InputObjectType @@ -216,10 +288,9 @@ class UserInput(InputObjectType): class Query(ObjectType): user = graphene.Field(User, input=UserInput()) - def resolve_user(self, input): + def resolve_user(self, info, input): if input.is_valid: return get_user(input.id) - ``` @@ -255,7 +326,7 @@ class Base(ObjectType): id = ID() - def resolve_id(self): + def resolve_id(self, info): return "{type}_{id}".format( type=self.__class__.__name__, id=self.id diff --git a/docs/execution/dataloader.rst b/docs/execution/dataloader.rst index 17374534..3322acfd 100644 --- a/docs/execution/dataloader.rst +++ b/docs/execution/dataloader.rst @@ -99,8 +99,8 @@ leaner code and at most 4 database requests, and possibly fewer if there are cac best_friend = graphene.Field(lambda: User) friends = graphene.List(lambda: User) - def resolve_best_friend(self): + def resolve_best_friend(self, info): return user_loader.load(self.best_friend_id) - def resolve_friends(self): + def resolve_friends(self, info): return user_loader.load_many(self.friend_ids) diff --git a/docs/execution/execute.rst b/docs/execution/execute.rst index 50e54e14..327ce230 100644 --- a/docs/execution/execute.rst +++ b/docs/execution/execute.rst @@ -24,9 +24,8 @@ You can pass context to a query via ``context_value``. class Query(graphene.ObjectType): name = graphene.String() - @graphene.annotate(context=graphene.Context) - def resolve_name(self, context): - return context.get('name') + def resolve_name(self, info): + return info.context.get('name') schema = graphene.Schema(Query) result = schema.execute('{ name }', context_value={'name': 'Syrus'}) @@ -44,8 +43,8 @@ You can pass variables to a query via ``variable_values``. class Query(graphene.ObjectType): user = graphene.Field(User) - def resolve_user(self, args, context, info): - return context.get('user') + def resolve_user(self, info): + return info.context.get('user') schema = graphene.Schema(Query) result = schema.execute( diff --git a/docs/execution/middleware.rst b/docs/execution/middleware.rst index 3303ed41..c5e11aa7 100644 --- a/docs/execution/middleware.rst +++ b/docs/execution/middleware.rst @@ -31,10 +31,10 @@ This middleware only continues evaluation if the ``field_name`` is not ``'user'` .. code:: python class AuthorizationMiddleware(object): - def resolve(self, next, root, args, context, info): + def resolve(self, next, root, info, **args): if info.field_name == 'user': return None - return next(root, args, context, info) + return next(root, info, **args) And then execute it with: diff --git a/docs/quickstart.rst b/docs/quickstart.rst index 4192fada..dde79c3b 100644 --- a/docs/quickstart.rst +++ b/docs/quickstart.rst @@ -39,7 +39,7 @@ one field: ``hello`` and an input name. And when we query it, it should return ` class Query(graphene.ObjectType): hello = graphene.String(name=graphene.String(default_value="stranger")) - def resolve_hello(self, name): + def resolve_hello(self, info, name): return 'Hello ' + name schema = graphene.Schema(query=Query) diff --git a/docs/relay/connection.rst b/docs/relay/connection.rst index 898d627d..c2379cbc 100644 --- a/docs/relay/connection.rst +++ b/docs/relay/connection.rst @@ -41,5 +41,5 @@ that implements ``Node`` will have a default Connection. name = graphene.String() ships = relay.ConnectionField(ShipConnection) - def resolve_ships(self): + def resolve_ships(self, info): return [] diff --git a/docs/relay/mutations.rst b/docs/relay/mutations.rst index 25c2fd54..89bf89b3 100644 --- a/docs/relay/mutations.rst +++ b/docs/relay/mutations.rst @@ -21,7 +21,7 @@ subclass of ``relay.ClientIDMutation``. faction = graphene.Field(Faction) @classmethod - def mutate_and_get_payload(cls, input, context, info): + def mutate_and_get_payload(cls, root, info, **input): ship_name = input.ship_name faction_id = input.faction_id ship = create_ship(ship_name, faction_id) @@ -46,7 +46,7 @@ Mutations can also accept files, that's how it will work with different integrat success = graphene.String() @classmethod - def mutate_and_get_payload(cls, input, context, info): + def mutate_and_get_payload(cls, root, info, **input): # When using it in Django, context will be the request files = context.FILES # Or, if used in Flask, context will be the flask global request diff --git a/docs/relay/nodes.rst b/docs/relay/nodes.rst index 5f470055..74f42094 100644 --- a/docs/relay/nodes.rst +++ b/docs/relay/nodes.rst @@ -22,7 +22,7 @@ Example usage (taken from the `Starwars Relay example`_): name = graphene.String(description='The name of the ship.') @classmethod - def get_node(cls, id, context, info): + def get_node(cls, info, id): return get_ship(id) The ``id`` returned by the ``Ship`` type when you query it will be a @@ -55,7 +55,7 @@ Example of a custom node: return '{}:{}'.format(type, id) @staticmethod - def get_node_from_global_id(global_id, context, info, only_type=None): + def get_node_from_global_id(info global_id, only_type=None): type, id = global_id.split(':') if only_node: # We assure that the node type that we want to retrieve diff --git a/docs/types/objecttypes.rst b/docs/types/objecttypes.rst index 409db58c..091617ce 100644 --- a/docs/types/objecttypes.rst +++ b/docs/types/objecttypes.rst @@ -25,7 +25,7 @@ This example model defines a Person, with a first and a last name: last_name = graphene.String() full_name = graphene.String() - def resolve_full_name(self): + def resolve_full_name(self, info): return '{} {}'.format(self.first_name, self.last_name) **first\_name** and **last\_name** are fields of the ObjectType. Each @@ -71,7 +71,7 @@ method in the class. class Query(graphene.ObjectType): reverse = graphene.String(word=graphene.String()) - def resolve_reverse(self, word): + def resolve_reverse(self, info, word): return word[::-1] Resolvers outside the class @@ -83,7 +83,7 @@ A field can use a custom resolver from outside the class: import graphene - def reverse(root, word): + def reverse(root, info, word): return word[::-1] class Query(graphene.ObjectType): diff --git a/examples/complex_example.py b/examples/complex_example.py index f6f80f81..492e6a19 100644 --- a/examples/complex_example.py +++ b/examples/complex_example.py @@ -11,10 +11,9 @@ class Address(graphene.ObjectType): class Query(graphene.ObjectType): - address = graphene.Field(Address, geo=GeoInput()) + address = graphene.Field(Address, geo=GeoInput(required=True)) - def resolve_address(self, args, context, info): - geo = args.get('geo') + def resolve_address(self, info, geo): return Address(latlng="({},{})".format(geo.get('lat'), geo.get('lng'))) diff --git a/examples/context_example.py b/examples/context_example.py index 2477a9bf..dfcf3548 100644 --- a/examples/context_example.py +++ b/examples/context_example.py @@ -9,9 +9,8 @@ class User(graphene.ObjectType): class Query(graphene.ObjectType): me = graphene.Field(User) - @graphene.annotate(context=graphene.Context) - def resolve_me(self, context): - return context['user'] + def resolve_me(self, info): + return info.context['user'] schema = graphene.Schema(query=Query) diff --git a/examples/simple_example.py b/examples/simple_example.py index 58fd07d1..927e0962 100644 --- a/examples/simple_example.py +++ b/examples/simple_example.py @@ -11,7 +11,7 @@ class Query(graphene.ObjectType): patron = graphene.Field(Patron) - def resolve_patron(self): + def resolve_patron(self, info): return Patron(id=1, name='Syrus', age=27) diff --git a/examples/starwars/schema.py b/examples/starwars/schema.py index cc36c75f..a19a7b31 100644 --- a/examples/starwars/schema.py +++ b/examples/starwars/schema.py @@ -15,7 +15,7 @@ class Character(graphene.Interface): friends = graphene.List(lambda: Character) appears_in = graphene.List(Episode) - def resolve_friends(self): + def resolve_friends(self, info): # The character friends is a list of strings return [get_character(f) for f in self.friends] @@ -45,13 +45,13 @@ class Query(graphene.ObjectType): id=graphene.String() ) - def resolve_hero(self, episode=None): + def resolve_hero(self, info, episode=None): return get_hero(episode) - def resolve_human(self, id): + def resolve_human(self, info, id): return get_human(id) - def resolve_droid(self, id): + def resolve_droid(self, info, id): return get_droid(id) diff --git a/examples/starwars_relay/schema.py b/examples/starwars_relay/schema.py index f2a6c6f3..beb291c3 100644 --- a/examples/starwars_relay/schema.py +++ b/examples/starwars_relay/schema.py @@ -1,5 +1,5 @@ import graphene -from graphene import annotate, relay, annotate +from graphene import relay from .data import create_ship, get_empire, get_faction, get_rebels, get_ship @@ -13,7 +13,7 @@ class Ship(graphene.ObjectType): name = graphene.String(description='The name of the ship.') @classmethod - def get_node(cls, id, context, info): + def get_node(cls, info, id): return get_ship(id) @@ -32,12 +32,12 @@ class Faction(graphene.ObjectType): name = graphene.String(description='The name of the faction.') ships = relay.ConnectionField(ShipConnection, description='The ships used by the faction.') - def resolve_ships(self, **args): + def resolve_ships(self, info, **args): # Transform the instance ship_ids into real instances return [get_ship(ship_id) for ship_id in self.ships] @classmethod - def get_node(cls, id, context, info): + def get_node(cls, info, id): return get_faction(id) @@ -51,9 +51,7 @@ class IntroduceShip(relay.ClientIDMutation): faction = graphene.Field(Faction) @classmethod - def mutate_and_get_payload(cls, input, context, info): - ship_name = input.get('ship_name') - faction_id = input.get('faction_id') + def mutate_and_get_payload(cls, root, info, ship_name, faction_id, client_mutation_id=None): ship = create_ship(ship_name, faction_id) faction = get_faction(faction_id) return IntroduceShip(ship=ship, faction=faction) @@ -64,10 +62,10 @@ class Query(graphene.ObjectType): empire = graphene.Field(Faction) node = relay.Node.Field() - def resolve_rebels(self): + def resolve_rebels(self, info): return get_rebels() - def resolve_empire(self): + def resolve_empire(self, info): return get_empire() diff --git a/graphene/__init__.py b/graphene/__init__.py index aae2a3ae..72eae8e5 100644 --- a/graphene/__init__.py +++ b/graphene/__init__.py @@ -48,8 +48,6 @@ if not __SETUP__: ) from .utils.resolve_only_args import resolve_only_args from .utils.module_loading import lazy_import - from .utils.annotate import annotate - from .utils.auto_resolver import final_resolver, is_final_resolver __all__ = [ 'ObjectType', @@ -82,11 +80,8 @@ if not __SETUP__: 'ConnectionField', 'PageInfo', 'lazy_import', - 'annotate', 'Context', 'ResolveInfo', - 'final_resolver', - 'is_final_resolver', # Deprecated 'AbstractType', diff --git a/graphene/relay/connection.py b/graphene/relay/connection.py index 5906ec06..e480f036 100644 --- a/graphene/relay/connection.py +++ b/graphene/relay/connection.py @@ -10,7 +10,6 @@ from ..types import (Boolean, Enum, Int, Interface, List, NonNull, Scalar, from ..types.field import Field from ..types.objecttype import ObjectType, ObjectTypeOptions from ..utils.deprecated import warn_deprecation -from ..utils.auto_resolver import final_resolver from .node import is_node @@ -132,8 +131,8 @@ class IterableConnectionField(Field): return connection @classmethod - def connection_resolver(cls, resolver, connection_type, root, args, context, info): - resolved = resolver(root, args, context, info) + def connection_resolver(cls, resolver, connection_type, root, info, **args): + resolved = resolver(root, info, **args) on_resolve = partial(cls.resolve_connection, connection_type, args) if is_thenable(resolved): @@ -143,7 +142,7 @@ class IterableConnectionField(Field): def get_resolver(self, parent_resolver): resolver = super(IterableConnectionField, self).get_resolver(parent_resolver) - return final_resolver(partial(self.connection_resolver, resolver, self.type)) + return partial(self.connection_resolver, resolver, self.type) ConnectionField = IterableConnectionField diff --git a/graphene/relay/mutation.py b/graphene/relay/mutation.py index 84e1e87b..7daccbf5 100644 --- a/graphene/relay/mutation.py +++ b/graphene/relay/mutation.py @@ -3,9 +3,8 @@ from collections import OrderedDict from promise import Promise, is_thenable -from ..types import Field, InputObjectType, String, Context, ResolveInfo +from ..types import Field, InputObjectType, String from ..types.mutation import Mutation -from ..utils.annotate import annotate class ClientIDMutation(Mutation): @@ -58,18 +57,17 @@ class ClientIDMutation(Mutation): ) @classmethod - @annotate(context=Context, info=ResolveInfo, _trigger_warning=False) - def mutate(cls, root, input, context, info): + def mutate(cls, root, info, input): def on_resolve(payload): try: - payload.client_mutation_id = input.get('clientMutationId') + payload.client_mutation_id = input.get('client_mutation_id') except: raise Exception( ('Cannot set client_mutation_id in the payload object {}' ).format(repr(payload))) return payload - result = cls.mutate_and_get_payload(input, context, info) + result = cls.mutate_and_get_payload(root, info, **input) if is_thenable(result): return Promise.resolve(result).then(on_resolve) diff --git a/graphene/relay/node.py b/graphene/relay/node.py index 8b89f8f3..d762a110 100644 --- a/graphene/relay/node.py +++ b/graphene/relay/node.py @@ -3,11 +3,9 @@ from functools import partial from graphql_relay import from_global_id, to_global_id -from ..types import ID, Field, Interface, ObjectType, Context, ResolveInfo +from ..types import ID, Field, Interface, ObjectType from ..types.interface import InterfaceOptions from ..types.utils import get_type -from ..utils.annotate import annotate -from ..utils.auto_resolver import final_resolver def is_node(objecttype): @@ -31,15 +29,15 @@ class GlobalID(Field): self.parent_type_name = parent_type._meta.name if parent_type else None @staticmethod - def id_resolver(parent_resolver, node, root, args, context, info, parent_type_name=None): - type_id = parent_resolver(root, args, context, info) + def id_resolver(parent_resolver, node, root, info, parent_type_name=None, **args): + type_id = parent_resolver(root, info, **args) parent_type_name = parent_type_name or info.parent_type.name return node.to_global_id(parent_type_name, type_id) # root._meta.name def get_resolver(self, parent_resolver): - return final_resolver(partial( + return partial( self.id_resolver, parent_resolver, self.node, parent_type_name=self.parent_type_name - )) + ) class NodeField(Field): @@ -58,7 +56,7 @@ class NodeField(Field): ) def get_resolver(self, parent_resolver): - return partial(self.node_type.node_resolver, only_type=get_type(self.field_type)) + return partial(self.node_type.node_resolver, get_type(self.field_type)) class AbstractNode(Interface): @@ -83,12 +81,11 @@ class Node(AbstractNode): return NodeField(cls, *args, **kwargs) @classmethod - @annotate(context=Context, info=ResolveInfo, _trigger_warning=False) - def node_resolver(cls, root, id, context, info, only_type=None): - return cls.get_node_from_global_id(id, context, info, only_type) + def node_resolver(cls, only_type, root, info, id): + return cls.get_node_from_global_id(info, id, only_type=only_type) @classmethod - def get_node_from_global_id(cls, global_id, context, info, only_type=None): + def get_node_from_global_id(cls, info, global_id, only_type=None): try: _type, _id = cls.from_global_id(global_id) graphene_type = info.schema.get_type(_type).graphene_type @@ -106,7 +103,7 @@ class Node(AbstractNode): get_node = getattr(graphene_type, 'get_node', None) if get_node: - return get_node(_id, context, info) + return get_node(info, _id) @classmethod def from_global_id(cls, global_id): diff --git a/graphene/relay/tests/test_connection_query.py b/graphene/relay/tests/test_connection_query.py index 370db36e..b8150e64 100644 --- a/graphene/relay/tests/test_connection_query.py +++ b/graphene/relay/tests/test_connection_query.py @@ -31,13 +31,13 @@ class Query(ObjectType): node = Node.Field() - def resolve_letters(self, **args): + def resolve_letters(self, info, **args): return list(letters.values()) - def resolve_promise_letters(self, **args): + def resolve_promise_letters(self, info, **args): return Promise.resolve(list(letters.values())) - def resolve_connection_letters(self, **args): + def resolve_connection_letters(self, info, **args): return LetterConnection( page_info=PageInfo( has_next_page=True, diff --git a/graphene/relay/tests/test_global_id.py b/graphene/relay/tests/test_global_id.py index 6e53bccc..6301f954 100644 --- a/graphene/relay/tests/test_global_id.py +++ b/graphene/relay/tests/test_global_id.py @@ -48,7 +48,7 @@ def test_global_id_defaults_to_info_parent_type(): my_id = '1' gid = GlobalID() id_resolver = gid.get_resolver(lambda *_: my_id) - my_global_id = id_resolver(None, None, None, Info(User)) + my_global_id = id_resolver(None, Info(User)) assert my_global_id == to_global_id(User._meta.name, my_id) @@ -56,5 +56,5 @@ def test_global_id_allows_setting_customer_parent_type(): my_id = '1' gid = GlobalID(parent_type=User) id_resolver = gid.get_resolver(lambda *_: my_id) - my_global_id = id_resolver(None, None, None, None) + my_global_id = id_resolver(None, None) assert my_global_id == to_global_id(User._meta.name, my_id) diff --git a/graphene/relay/tests/test_mutation.py b/graphene/relay/tests/test_mutation.py index 32ff07b8..d37f8047 100644 --- a/graphene/relay/tests/test_mutation.py +++ b/graphene/relay/tests/test_mutation.py @@ -27,8 +27,7 @@ class SaySomething(ClientIDMutation): phrase = String() @staticmethod - def mutate_and_get_payload(args, context, info): - what = args.get('what') + def mutate_and_get_payload(self, info, what, client_mutation_id=None): return SaySomething(phrase=str(what)) @@ -40,8 +39,7 @@ class SaySomethingPromise(ClientIDMutation): phrase = String() @staticmethod - def mutate_and_get_payload(args, context, info): - what = args.get('what') + def mutate_and_get_payload(self, info, what, client_mutation_id=None): return Promise.resolve(SaySomething(phrase=str(what))) @@ -59,13 +57,11 @@ class OtherMutation(ClientIDMutation): name = String() my_node_edge = Field(MyEdge) - @classmethod - def mutate_and_get_payload(cls, args, context, info): - shared = args.get('shared', '') - additionalField = args.get('additionalField', '') + @staticmethod + def mutate_and_get_payload(self, info, shared='', additional_field='', client_mutation_id=None): edge_type = MyEdge return OtherMutation( - name=shared + additionalField, + name=shared + additional_field, 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 e0f65cdd..315f2e39 100644 --- a/graphene/relay/tests/test_node.py +++ b/graphene/relay/tests/test_node.py @@ -22,7 +22,7 @@ class MyNode(ObjectType): name = String() @staticmethod - def get_node(id, *_): + def get_node(info, id): return MyNode(name=str(id)) @@ -36,7 +36,7 @@ class MyOtherNode(SharedNodeFields, ObjectType): return 'extra field info.' @staticmethod - def get_node(id, *_): + def get_node(info, id): return MyOtherNode(shared=str(id)) diff --git a/graphene/relay/tests/test_node_custom.py b/graphene/relay/tests/test_node_custom.py index ba34c401..cc4e910c 100644 --- a/graphene/relay/tests/test_node_custom.py +++ b/graphene/relay/tests/test_node_custom.py @@ -15,7 +15,7 @@ class CustomNode(Node): return id @staticmethod - def get_node_from_global_id(id, context, info, only_type=None): + def get_node_from_global_id(info, id, only_type=None): assert info.schema == schema if id in user_data: return user_data.get(id) diff --git a/graphene/tests/issues/test_313.py b/graphene/tests/issues/test_313.py index 3881590b..9df6c17b 100644 --- a/graphene/tests/issues/test_313.py +++ b/graphene/tests/issues/test_313.py @@ -29,7 +29,7 @@ class CreatePost(graphene.Mutation): result = graphene.Field(CreatePostResult) - def mutate(self, text): + def mutate(self, info, text): result = Success(yeah='yeah') return CreatePost(result=result) diff --git a/graphene/tests/issues/test_490.py b/graphene/tests/issues/test_490.py index f402e483..9bd00590 100644 --- a/graphene/tests/issues/test_490.py +++ b/graphene/tests/issues/test_490.py @@ -6,7 +6,7 @@ import graphene class Query(graphene.ObjectType): some_field = graphene.String(from_=graphene.String(name="from")) - def resolve_some_field(self, from_=None): + def resolve_some_field(self, info, from_=None): return from_ diff --git a/graphene/types/inputobjecttype.py b/graphene/types/inputobjecttype.py index cf66065f..3beb3ebf 100644 --- a/graphene/types/inputobjecttype.py +++ b/graphene/types/inputobjecttype.py @@ -11,7 +11,20 @@ class InputObjectTypeOptions(BaseOptions): create_container = None # type: Callable -class InputObjectType(dict, UnmountedType, BaseType): +class InputObjectTypeContainer(dict, BaseType): + class Meta: + abstract = True + + def __init__(self, *args, **kwargs): + dict.__init__(self, *args, **kwargs) + for key, value in self.items(): + setattr(self, key, value) + + def __init_subclass__(cls, *args, **kwargs): + pass + + +class InputObjectType(UnmountedType, BaseType): ''' Input Object Type Definition @@ -20,30 +33,9 @@ class InputObjectType(dict, UnmountedType, BaseType): Using `NonNull` will ensure that a value must be provided by the query ''' - def __init__(self, *args, **kwargs): - as_container = kwargs.pop('_as_container', False) - if as_container: - # Is inited as container for the input args - self.__init_container__(*args, **kwargs) - else: - # Is inited as UnmountedType, e.g. - # - # class MyObjectType(graphene.ObjectType): - # my_input = MyInputType(required=True) - # - UnmountedType.__init__(self, *args, **kwargs) - - def __init_container__(self, *args, **kwargs): - dict.__init__(self, *args, **kwargs) - for key, value in self.items(): - setattr(self, key, value) @classmethod - def create_container(cls, data): - return cls(data, _as_container=True) - - @classmethod - def __init_subclass_with_meta__(cls, create_container=None, **options): + def __init_subclass_with_meta__(cls, container=None, **options): _meta = InputObjectTypeOptions(cls) fields = OrderedDict() @@ -53,9 +45,9 @@ class InputObjectType(dict, UnmountedType, BaseType): ) _meta.fields = fields - if create_container is None: - create_container = cls.create_container - _meta.create_container = create_container + if container is None: + container = type(cls.__name__, (InputObjectTypeContainer, cls), {}) + _meta.container = container super(InputObjectType, cls).__init_subclass_with_meta__(_meta=_meta, **options) @classmethod diff --git a/graphene/types/interface.py b/graphene/types/interface.py index 054b7229..c98f0f1f 100644 --- a/graphene/types/interface.py +++ b/graphene/types/interface.py @@ -37,14 +37,10 @@ class Interface(BaseType): super(Interface, cls).__init_subclass_with_meta__(_meta=_meta, **options) @classmethod - def resolve_type(cls, instance, context, info): + def resolve_type(cls, instance, info): from .objecttype import ObjectType if isinstance(instance, ObjectType): return type(instance) def __init__(self, *args, **kwargs): raise Exception("An Interface cannot be intitialized") - - @classmethod - def implements(cls, objecttype): - pass diff --git a/graphene/types/resolver.py b/graphene/types/resolver.py index 1f395b50..e5652c2d 100644 --- a/graphene/types/resolver.py +++ b/graphene/types/resolver.py @@ -1,8 +1,8 @@ -def attr_resolver(attname, default_value, root, args, context, info): +def attr_resolver(attname, default_value, root, info, **args): return getattr(root, attname, default_value) -def dict_resolver(attname, default_value, root, args, context, info): +def dict_resolver(attname, default_value, root, info, **args): return root.get(attname, default_value) diff --git a/graphene/types/tests/test_datetime.py b/graphene/types/tests/test_datetime.py index 660ae091..9d23fee5 100644 --- a/graphene/types/tests/test_datetime.py +++ b/graphene/types/tests/test_datetime.py @@ -11,10 +11,10 @@ class Query(ObjectType): datetime = DateTime(_in=DateTime(name='in')) time = Time(_at=Time(name='at')) - def resolve_datetime(self, _in=None): + def resolve_datetime(self, info, _in=None): return _in - def resolve_time(self, _at=None): + def resolve_time(self, info, _at=None): return _at diff --git a/graphene/types/tests/test_generic.py b/graphene/types/tests/test_generic.py index 3a5eb88a..be832763 100644 --- a/graphene/types/tests/test_generic.py +++ b/graphene/types/tests/test_generic.py @@ -6,7 +6,7 @@ from ..schema import Schema class Query(ObjectType): generic = GenericScalar(input=GenericScalar()) - def resolve_generic(self, input=None): + def resolve_generic(self, info, input=None): return input diff --git a/graphene/types/tests/test_json.py b/graphene/types/tests/test_json.py index d671722d..cadc729f 100644 --- a/graphene/types/tests/test_json.py +++ b/graphene/types/tests/test_json.py @@ -7,7 +7,7 @@ from ..schema import Schema class Query(ObjectType): json = JSONString(input=JSONString()) - def resolve_json(self, input): + def resolve_json(self, info, input): return input schema = Schema(query=Query) diff --git a/graphene/types/tests/test_mutation.py b/graphene/types/tests/test_mutation.py index d195b6ac..b4d65dcc 100644 --- a/graphene/types/tests/test_mutation.py +++ b/graphene/types/tests/test_mutation.py @@ -12,13 +12,13 @@ def test_generate_mutation_no_args(): class MyMutation(Mutation): '''Documentation''' - def mutate(self, **args): + def mutate(self, info, **args): return args assert issubclass(MyMutation, ObjectType) assert MyMutation._meta.name == "MyMutation" assert MyMutation._meta.description == "Documentation" - resolved = MyMutation.Field().resolver(None, name='Peter') + resolved = MyMutation.Field().resolver(None, None, name='Peter') assert resolved == {'name': 'Peter'} @@ -29,12 +29,12 @@ def test_generate_mutation_with_meta(): name = 'MyOtherMutation' description = 'Documentation' - def mutate(self, **args): + def mutate(self, info, **args): return args assert MyMutation._meta.name == "MyOtherMutation" assert MyMutation._meta.description == "Documentation" - resolved = MyMutation.Field().resolver(None, name='Peter') + resolved = MyMutation.Field().resolver(None, None, name='Peter') assert resolved == {'name': 'Peter'} @@ -59,13 +59,13 @@ def test_mutation_custom_output_type(): Output = User - def mutate(self, name): + def mutate(self, info, name): return User(name=name) field = CreateUser.Field() assert field.type == User assert field.args == {'name': Argument(String)} - resolved = field.resolver(None, name='Peter') + resolved = field.resolver(None, None, name='Peter') assert isinstance(resolved, User) assert resolved.name == 'Peter' @@ -81,7 +81,7 @@ def test_mutation_execution(): name = String() dynamic = Dynamic(lambda: String()) - def mutate(self, name, dynamic): + def mutate(self, info, name, dynamic): return CreateUser(name=name, dynamic=dynamic) class Query(ObjectType): diff --git a/graphene/types/tests/test_query.py b/graphene/types/tests/test_query.py index f41f40bc..acaef0a1 100644 --- a/graphene/types/tests/test_query.py +++ b/graphene/types/tests/test_query.py @@ -14,7 +14,6 @@ from ..schema import Schema from ..structures import List from ..union import Union from ..context import Context -from ...utils.annotate import annotate def test_query(): @@ -39,14 +38,14 @@ def test_query_union(): one = String() @classmethod - def is_type_of(cls, root, context, info): + def is_type_of(cls, root, info): return isinstance(root, one_object) class Two(ObjectType): two = String() @classmethod - def is_type_of(cls, root, context, info): + def is_type_of(cls, root, info): return isinstance(root, two_object) class MyUnion(Union): @@ -57,7 +56,7 @@ def test_query_union(): class Query(ObjectType): unions = List(MyUnion) - def resolve_unions(self): + def resolve_unions(self, info): return [one_object(), two_object()] hello_schema = Schema(Query) @@ -91,7 +90,7 @@ def test_query_interface(): one = String() @classmethod - def is_type_of(cls, root, context, info): + def is_type_of(cls, root, info): return isinstance(root, one_object) class Two(ObjectType): @@ -102,13 +101,13 @@ def test_query_interface(): two = String() @classmethod - def is_type_of(cls, root, context, info): + def is_type_of(cls, root, info): return isinstance(root, two_object) class Query(ObjectType): interfaces = List(MyInterface) - def resolve_interfaces(self): + def resolve_interfaces(self, info): return [one_object(), two_object()] hello_schema = Schema(Query, types=[One, Two]) @@ -156,7 +155,7 @@ def test_query_wrong_default_value(): field = String() @classmethod - def is_type_of(cls, root, context, info): + def is_type_of(cls, root, info): return isinstance(root, MyType) class Query(ObjectType): @@ -188,7 +187,7 @@ def test_query_resolve_function(): class Query(ObjectType): hello = String() - def resolve_hello(self): + def resolve_hello(self, info): return 'World' hello_schema = Schema(Query) @@ -202,7 +201,7 @@ def test_query_arguments(): class Query(ObjectType): test = String(a_str=String(), a_int=Int()) - def resolve_test(self, **args): + def resolve_test(self, info, **args): return json.dumps([self, args], separators=(',', ':')) test_schema = Schema(Query) @@ -231,7 +230,7 @@ def test_query_input_field(): class Query(ObjectType): test = String(a_input=Input()) - def resolve_test(self, **args): + def resolve_test(self, info, **args): return json.dumps([self, args], separators=(',', ':')) test_schema = Schema(Query) @@ -254,10 +253,10 @@ def test_query_middlewares(): hello = String() other = String() - def resolve_hello(self): + def resolve_hello(self, info): return 'World' - def resolve_other(self): + def resolve_other(self, info): return 'other' def reversed_middleware(next, *args, **kwargs): @@ -280,14 +279,14 @@ def test_objecttype_on_instances(): class ShipType(ObjectType): name = String(description="Ship name", required=True) - def resolve_name(self): + def resolve_name(self, info): # Here self will be the Ship instance returned in resolve_ship return self.name class Query(ObjectType): ship = Field(ShipType) - def resolve_ship(self): + def resolve_ship(self, info): return Ship(name='xwing') schema = Schema(query=Query) @@ -302,7 +301,7 @@ def test_big_list_query_benchmark(benchmark): class Query(ObjectType): all_ints = List(Int) - def resolve_all_ints(self): + def resolve_all_ints(self, info): return big_list hello_schema = Schema(Query) @@ -319,7 +318,7 @@ def test_big_list_query_compiled_query_benchmark(benchmark): class Query(ObjectType): all_ints = List(Int) - def resolve_all_ints(self): + def resolve_all_ints(self, info): return big_list hello_schema = Schema(Query) @@ -341,7 +340,7 @@ def test_big_list_of_containers_query_benchmark(benchmark): class Query(ObjectType): all_containers = List(Container) - def resolve_all_containers(self): + def resolve_all_containers(self, info): return big_container_list hello_schema = Schema(Query) @@ -364,7 +363,7 @@ def test_big_list_of_containers_multiple_fields_query_benchmark(benchmark): class Query(ObjectType): all_containers = List(Container) - def resolve_all_containers(self): + def resolve_all_containers(self, info): return big_container_list hello_schema = Schema(Query) @@ -382,16 +381,16 @@ def test_big_list_of_containers_multiple_fields_custom_resolvers_query_benchmark z = Int() o = Int() - def resolve_x(self): + def resolve_x(self, info): return self.x - def resolve_y(self): + def resolve_y(self, info): return self.y - def resolve_z(self): + def resolve_z(self, info): return self.z - def resolve_o(self): + def resolve_o(self, info): return self.o big_container_list = [Container(x=x, y=x, z=x, o=x) for x in range(1000)] @@ -399,7 +398,7 @@ def test_big_list_of_containers_multiple_fields_custom_resolvers_query_benchmark class Query(ObjectType): all_containers = List(Container) - def resolve_all_containers(self): + def resolve_all_containers(self, info): return big_container_list hello_schema = Schema(Query) @@ -420,15 +419,13 @@ def test_query_annotated_resolvers(): context = String() info = String() - def resolve_annotated(self, id): + def resolve_annotated(self, info, id): return "{}-{}".format(self, id) - @annotate(context=Context, _trigger_warning=False) - def resolve_context(self, context): - assert isinstance(context, Context) - return "{}-{}".format(self, context.key) + def resolve_context(self, info): + assert isinstance(info.context, Context) + return "{}-{}".format(self, info.context.key) - @annotate(info=ResolveInfo, _trigger_warning=False) def resolve_info(self, info): assert isinstance(info, ResolveInfo) return "{}-{}".format(self, info.field_name) diff --git a/graphene/types/tests/test_resolver.py b/graphene/types/tests/test_resolver.py index 64fdf94e..2beb607e 100644 --- a/graphene/types/tests/test_resolver.py +++ b/graphene/types/tests/test_resolver.py @@ -16,22 +16,22 @@ class demo_obj(object): def test_attr_resolver(): - resolved = attr_resolver('attr', None, demo_obj, args, context, info) + resolved = attr_resolver('attr', None, demo_obj, info, **args) assert resolved == 'value' def test_attr_resolver_default_value(): - resolved = attr_resolver('attr2', 'default', demo_obj, args, context, info) + resolved = attr_resolver('attr2', 'default', demo_obj, info, **args) assert resolved == 'default' def test_dict_resolver(): - resolved = dict_resolver('attr', None, demo_dict, args, context, info) + resolved = dict_resolver('attr', None, demo_dict, info, **args) assert resolved == 'value' def test_dict_resolver_default_value(): - resolved = dict_resolver('attr2', 'default', demo_dict, args, context, info) + resolved = dict_resolver('attr2', 'default', demo_dict, info, **args) assert resolved == 'default' diff --git a/graphene/types/tests/test_typemap.py b/graphene/types/tests/test_typemap.py index 266173cf..082f25bd 100644 --- a/graphene/types/tests/test_typemap.py +++ b/graphene/types/tests/test_typemap.py @@ -204,5 +204,5 @@ def test_objecttype_with_possible_types(): typemap = TypeMap([MyObjectType]) graphql_type = typemap['MyObjectType'] assert graphql_type.is_type_of - assert graphql_type.is_type_of({}, None, None) is True - assert graphql_type.is_type_of(MyObjectType(), None, None) is False + assert graphql_type.is_type_of({}, None) is True + assert graphql_type.is_type_of(MyObjectType(), None) is False diff --git a/graphene/types/tests/test_uuid.py b/graphene/types/tests/test_uuid.py index 23eac6b4..c1419750 100644 --- a/graphene/types/tests/test_uuid.py +++ b/graphene/types/tests/test_uuid.py @@ -6,7 +6,7 @@ from ..schema import Schema class Query(ObjectType): uuid = UUID(input=UUID()) - def resolve_uuid(self, input): + def resolve_uuid(self, info, input): return input schema = Schema(query=Query) diff --git a/graphene/types/typemap.py b/graphene/types/typemap.py index 36d8db4b..3b389cd6 100644 --- a/graphene/types/typemap.py +++ b/graphene/types/typemap.py @@ -11,7 +11,6 @@ from graphql.type.typemap import GraphQLTypeMap from ..utils.get_unbound_function import get_unbound_function from ..utils.str_converters import to_camel_case -from ..utils.auto_resolver import auto_resolver, final_resolver from .definitions import (GrapheneEnumType, GrapheneGraphQLType, GrapheneInputObjectType, GrapheneInterfaceType, GrapheneObjectType, GrapheneScalarType, @@ -38,12 +37,12 @@ def is_graphene_type(_type): return True -def resolve_type(resolve_type_func, map, type_name, root, context, info): - _type = resolve_type_func(root, context, info) +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, context, info, return_type) + 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) @@ -55,7 +54,7 @@ def resolve_type(resolve_type_func, map, type_name, root, context, info): return _type -def is_type_of_from_possible_types(possible_types, root, context, info): +def is_type_of_from_possible_types(possible_types, root, info): return isinstance(root, possible_types) @@ -196,7 +195,7 @@ class TypeMap(GraphQLTypeMap): graphene_type=type, name=type._meta.name, description=type._meta.description, - container_type=type._meta.create_container, + container_type=type._meta.container, fields=partial( self.construct_fields_for_type, map, type, is_input_type=True), ) @@ -240,7 +239,7 @@ class TypeMap(GraphQLTypeMap): _field = GraphQLInputObjectField( field_type, default_value=field.default_value, - out_name=field.name or name, + out_name=name, description=field.description) else: args = OrderedDict() @@ -256,13 +255,13 @@ class TypeMap(GraphQLTypeMap): _field = GraphQLField( field_type, args=args, - resolver=auto_resolver(field.get_resolver( - auto_resolver(self.get_resolver_for_type( + 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) @@ -292,7 +291,7 @@ class TypeMap(GraphQLTypeMap): default_resolver = type._meta.default_resolver or get_default_resolver( ) - return final_resolver(partial(default_resolver, name, default_value)) + return partial(default_resolver, name, default_value) def get_field_type(self, map, type): if isinstance(type, List): diff --git a/graphene/types/union.py b/graphene/types/union.py index f5c12c04..f9797fc0 100644 --- a/graphene/types/union.py +++ b/graphene/types/union.py @@ -34,7 +34,7 @@ class Union(UnmountedType, BaseType): return cls @classmethod - def resolve_type(cls, instance, context, info): + def resolve_type(cls, instance, info): from .objecttype import ObjectType if isinstance(instance, ObjectType): return type(instance) diff --git a/graphene/utils/auto_resolver.py b/graphene/utils/auto_resolver.py deleted file mode 100644 index 72f33515..00000000 --- a/graphene/utils/auto_resolver.py +++ /dev/null @@ -1,21 +0,0 @@ -from .resolver_from_annotations import resolver_from_annotations - - -def final_resolver(func): - func._is_final_resolver = True - return func - - -def auto_resolver(func=None): - if not func: - return - - if not is_final_resolver(func): - # Is a Graphene 2.0 resolver function - return final_resolver(resolver_from_annotations(func)) - else: - return func - - -def is_final_resolver(func): - return getattr(func, '_is_final_resolver', False) diff --git a/graphene/utils/resolve_only_args.py b/graphene/utils/resolve_only_args.py index 0856ffcc..897e6223 100644 --- a/graphene/utils/resolve_only_args.py +++ b/graphene/utils/resolve_only_args.py @@ -1,18 +1,11 @@ -from six import PY2 -from .annotate import annotate +from functools import wraps from .deprecated import deprecated -if PY2: - deprecation_reason = ( - 'Please use @annotate instead.' - ) -else: - deprecation_reason = ( - 'Please use Python 3 type annotations instead. Read more: ' - 'https://docs.python.org/3/library/typing.html' - ) - -@deprecated(deprecation_reason) +@deprecated('This function is deprecated') def resolve_only_args(func): - return annotate(func) + @wraps(func) + def wrapped_func(root, info, **args): + return func(root, **args) + + return wrapped_func diff --git a/graphene/utils/tests/test_auto_resolver.py b/graphene/utils/tests/test_auto_resolver.py deleted file mode 100644 index 3b135d83..00000000 --- a/graphene/utils/tests/test_auto_resolver.py +++ /dev/null @@ -1,36 +0,0 @@ -import pytest -from ..annotate import annotate -from ..auto_resolver import auto_resolver, final_resolver - -from ...types import Context, ResolveInfo - - -@final_resolver -def resolver(root, args, context, info): - return root, args, context, info - - -def resolver_annotated(root, **args): - return root, args, None, None - - -@annotate(context=Context, info=ResolveInfo, _trigger_warning=False) -def resolver_with_context_and_info(root, context, info, **args): - return root, args, context, info - - -def test_auto_resolver_non_annotated(): - decorated_resolver = auto_resolver(resolver) - # We make sure the function is not wrapped - assert decorated_resolver == resolver - assert decorated_resolver(1, {}, 2, 3) == (1, {}, 2, 3) - - -def test_auto_resolver_annotated(): - decorated_resolver = auto_resolver(resolver_annotated) - assert decorated_resolver(1, {}, 2, 3) == (1, {}, None, None) - - -def test_auto_resolver_annotated_with_context_and_info(): - decorated_resolver = auto_resolver(resolver_with_context_and_info) - assert decorated_resolver(1, {}, 2, 3) == (1, {}, 2, 3) diff --git a/graphene/utils/tests/test_resolver_from_annotations.py b/graphene/utils/tests/test_resolver_from_annotations.py index 226b387c..e69de29b 100644 --- a/graphene/utils/tests/test_resolver_from_annotations.py +++ b/graphene/utils/tests/test_resolver_from_annotations.py @@ -1,44 +0,0 @@ -import pytest -from ..annotate import annotate -from ..resolver_from_annotations import resolver_from_annotations - -from ...types import Context, ResolveInfo - - -@annotate -def func(root, **args): - return root, args, None, None - - -@annotate(context=Context) -def func_with_context(root, context, **args): - return root, args, context, None - - -@annotate(info=ResolveInfo) -def func_with_info(root, info, **args): - return root, args, None, info - - -@annotate(context=Context, info=ResolveInfo) -def func_with_context_and_info(root, context, info, **args): - return root, args, context, info - -root = 1 -args = { - 'arg': 0 -} -context = 2 -info = 3 - - -@pytest.mark.parametrize("func,expected", [ - (func, (1, {'arg': 0}, None, None)), - (func_with_context, (1, {'arg': 0}, 2, None)), - (func_with_info, (1, {'arg': 0}, None, 3)), - (func_with_context_and_info, (1, {'arg': 0}, 2, 3)), -]) -def test_resolver_from_annotations(func, expected): - resolver_func = resolver_from_annotations(func) - resolved = resolver_func(root, args, context, info) - assert resolved == expected diff --git a/setup.py b/setup.py index 3bb6225f..3723915d 100644 --- a/setup.py +++ b/setup.py @@ -83,7 +83,7 @@ setup( install_requires=[ 'six>=1.10.0', - 'graphql-core>=1.2.dev', + 'graphql-core>=2.0.dev', 'graphql-relay>=0.4.5', 'promise>=2.1.dev', ],