Manually merged in master.

This commit is contained in:
Markus Padourek 2016-10-13 13:29:29 +01:00
commit 3d6486a899
35 changed files with 551 additions and 77 deletions

View File

@ -22,7 +22,7 @@ before_install:
install:
- |
if [ "$TEST_TYPE" = build ]; then
pip install pytest pytest-cov pytest-benchmark coveralls six
pip install pytest pytest-cov pytest-benchmark coveralls six pytz iso8601
pip install -e .
python setup.py develop
elif [ "$TEST_TYPE" = lint ]; then

View File

@ -1,5 +1,4 @@
You are in the `next` unreleased version of Graphene (`1.0.dev`).
Please read [UPGRADE-v1.0.md](/UPGRADE-v1.0.md) to learn how to upgrade.
Please read [UPGRADE-v1.0.md](/UPGRADE-v1.0.md) to learn how to upgrade to Graphene `1.0`.
---
@ -32,7 +31,7 @@ Graphene has multiple integrations with different frameworks:
For instaling graphene, just run this command in your shell
```bash
pip install "graphene>=1.0.dev"
pip install "graphene>=1.0"
```
## 1.0 Upgrade Guide

View File

@ -1,5 +1,4 @@
You are in the ``next`` unreleased version of Graphene (``1.0.dev``).
Please read `UPGRADE-v1.0.md`_ to learn how to upgrade.
Please read `UPGRADE-v1.0.md`_ to learn how to upgrade to Graphene ``1.0``.
--------------
@ -41,7 +40,7 @@ For instaling graphene, just run this command in your shell
.. code:: bash
pip install "graphene>=1.0.dev"
pip install "graphene>=1.0"
1.0 Upgrade Guide
-----------------

View File

@ -73,7 +73,7 @@ author = u'Syrus Akbary'
# The short X.Y version.
version = u'1.0'
# The full version, including alpha/beta/rc tags.
release = u'1.0.dev'
release = u'1.0'
# The language for content autogenerated by Sphinx. Refer to documentation
# for a list of supported languages.
@ -133,9 +133,14 @@ todo_include_todos = True
# The theme to use for HTML and HTML Help pages. See the documentation for
# a list of builtin themes.
#
html_theme = 'alabaster'
if on_rtd:
html_theme = 'sphinx_rtd_theme'
# html_theme = 'alabaster'
# if on_rtd:
# html_theme = 'sphinx_rtd_theme'
import sphinx_graphene_theme
html_theme = "sphinx_graphene_theme"
html_theme_path = [sphinx_graphene_theme.get_html_theme_path()]
# Theme options are theme-specific and customize the look and feel of a theme
# further. For a list of options available for each theme, see the
@ -149,7 +154,7 @@ if on_rtd:
# The name for this set of Sphinx documents.
# "<project> v<release> documentation" by default.
#
# html_title = u'Graphene v1.0.dev'
# html_title = u'Graphene v1.0'
# A shorter title for the navigation bar. Default is the same as html_title.
#

View File

@ -14,7 +14,7 @@ Project setup
.. code:: bash
pip install graphene>=1.0
pip install "graphene>=1.0"
Creating a basic Schema
-----------------------

2
docs/requirements.txt Normal file
View File

@ -0,0 +1,2 @@
# Docs template
https://github.com/graphql-python/graphene-python.org/archive/docs.zip

View File

@ -32,7 +32,7 @@ plus the ones defined in ``UserFields``.
pass
.. code:: graphql
.. code::
type User {
name: String

View File

@ -10,4 +10,5 @@ Types Reference
interfaces
abstracttypes
objecttypes
schema
mutations

View File

@ -5,6 +5,7 @@ An Interface contains the essential fields that will be implemented among
multiple ObjectTypes.
The basics:
- Each Interface is a Python class that inherits from ``graphene.Interface``.
- Each attribute of the Interface represents a GraphQL field.
@ -43,7 +44,7 @@ time.
The above types would have the following representation in a schema:
.. code:: graphql
.. code::
interface Character {
name: String

View File

@ -53,7 +53,7 @@ Executing the Mutation
Then, if we query (``schema.execute(query_str)``) the following:
.. code:: graphql
.. code::
mutation myFirstMutation {
createPerson(name:"Peter") {

View File

@ -8,7 +8,7 @@ querying.
The basics:
- Each ObjectType is a Python class that inherits
``graphene.ObjectType`` or inherits an implemented `Interface`_.
``graphene.ObjectType``.
- Each attribute of the ObjectType represents a ``Field``.
Quick example
@ -36,7 +36,7 @@ Field.
The above ``Person`` ObjectType would have the following representation
in a schema:
.. code:: graphql
.. code::
type Person {
firstName: String
@ -55,6 +55,11 @@ otherwise, the ``resolve_{field_name}`` within the ``ObjectType``.
By default a resolver will take the ``args``, ``context`` and ``info``
arguments.
NOTE: The class resolvers in a ``ObjectType`` are treated as ``staticmethod``s
always, so the first argument in the resolver: ``self`` (or ``root``) doesn't
need to be an actual instance of the ``ObjectType``.
Quick example
~~~~~~~~~~~~~

View File

@ -2,6 +2,7 @@ Scalars
=======
Graphene define the following base Scalar Types:
- ``graphene.String``
- ``graphene.Int``
- ``graphene.Float``
@ -9,6 +10,7 @@ Graphene define the following base Scalar Types:
- ``graphene.ID``
Graphene also provides custom scalars for Dates and JSON:
- ``graphene.types.datetime.DateTime``
- ``graphene.types.json.JSONString``

81
docs/types/schema.rst Normal file
View File

@ -0,0 +1,81 @@
Schema
======
A Schema is created by supplying the root types of each type of operation, query and mutation (optional).
A schema definition is then supplied to the validator and executor.
.. code:: python
my_schema = Schema(
query=MyRootQuery,
mutation=MyRootMutation,
)
Types
-----
There are some cases where the schema could not access all the types that we plan to have.
For example, when a field returns an ``Interface``, the schema doesn't know any of the
implementations.
In this case, we would need to use the ``types`` argument when creating the Schema.
.. code:: python
my_schema = Schema(
query=MyRootQuery,
types=[SomeExtraObjectType, ]
)
Querying
--------
If you need to query a schema, you can directly call the ``execute`` method on it.
.. code:: python
my_schema.execute('{ lastName }')
Auto CamelCase field names
--------------------------
By default all field and argument names (that are not
explicitly set with the ``name`` arg) will be converted from
`snake_case` to `camelCase` (`as the API is usually being consumed by a js/mobile client`)
So, for example if we have the following ObjectType
.. code:: python
class Person(graphene.ObjectType):
last_name = graphene.String()
other_name = graphene.String(name='_other_Name')
Then the ``last_name`` field name is converted to ``lastName``.
In the case we don't want to apply any transformation, we can specify
the field name with the ``name`` argument. So ``other_name`` field name
would be converted to ``_other_Name`` (without any other transformation).
So, you would need to query with:
.. code::
{
lastName
_other_Name
}
If you want to disable this behavior, you set use the ``auto_camelcase`` argument
to ``False`` when you create the Schema.
.. code:: python
my_schema = Schema(
query=MyRootQuery,
auto_camelcase=False,
)

View File

@ -10,7 +10,7 @@ except NameError:
__SETUP__ = False
VERSION = (1, 0, 0, 'alpha', 0)
VERSION = (1, 0, 2, 'final', 0)
__version__ = get_version(VERSION)

View File

@ -95,13 +95,13 @@ class Connection(six.with_metaclass(ConnectionMeta, ObjectType)):
class IterableConnectionField(Field):
def __init__(self, type, *args, **kwargs):
kwargs.setdefault('before', String())
kwargs.setdefault('after', String())
kwargs.setdefault('first', Int())
kwargs.setdefault('last', Int())
super(IterableConnectionField, self).__init__(
type,
*args,
before=String(),
after=String(),
first=Int(),
last=Int(),
**kwargs
)
@ -117,21 +117,25 @@ class IterableConnectionField(Field):
).format(str(self), connection_type)
return connection_type
@staticmethod
def connection_resolver(resolver, connection, root, args, context, info):
iterable = resolver(root, args, context, info)
assert isinstance(iterable, Iterable), (
'Resolved value from the connection field have to be iterable. '
@classmethod
def connection_resolver(cls, resolver, connection, root, args, context, info):
resolved = resolver(root, args, context, info)
if isinstance(resolved, connection):
return resolved
assert isinstance(resolved, Iterable), (
'Resolved value from the connection field have to be iterable or instance of {}. '
'Received "{}"'
).format(iterable)
).format(connection, resolved)
connection = connection_from_list(
iterable,
resolved,
args,
connection_type=connection,
edge_type=connection.Edge,
pageinfo_type=PageInfo
)
connection.iterable = iterable
connection.iterable = resolved
return connection
def get_resolver(self, parent_resolver):

View File

@ -1,6 +1,6 @@
from ...types import AbstractType, Field, List, NonNull, ObjectType, String
from ..connection import Connection, PageInfo
from ...types import AbstractType, Field, List, NonNull, ObjectType, String, Argument, Int
from ..connection import Connection, PageInfo, ConnectionField
from ..node import Node
@ -109,3 +109,32 @@ def test_pageinfo():
assert PageInfo._meta.name == 'PageInfo'
fields = PageInfo._meta.fields
assert list(fields.keys()) == ['has_next_page', 'has_previous_page', 'start_cursor', 'end_cursor']
def test_connectionfield():
class MyObjectConnection(Connection):
class Meta:
node = MyObject
field = ConnectionField(MyObjectConnection)
assert field.args == {
'before': Argument(String),
'after': Argument(String),
'first': Argument(Int),
'last': Argument(Int),
}
def test_connectionfield_custom_args():
class MyObjectConnection(Connection):
class Meta:
node = MyObject
field = ConnectionField(MyObjectConnection, before=String(required=True), extra=String())
assert field.args == {
'before': Argument(NonNull(String)),
'after': Argument(String),
'first': Argument(Int),
'last': Argument(Int),
'extra': Argument(String),
}

View File

@ -3,7 +3,7 @@ from collections import OrderedDict
from graphql_relay.utils import base64
from ...types import ObjectType, Schema, String
from ..connection import ConnectionField
from ..connection import ConnectionField, PageInfo
from ..node import Node
letter_chars = ['A', 'B', 'C', 'D', 'E']
@ -19,11 +19,26 @@ class Letter(ObjectType):
class Query(ObjectType):
letters = ConnectionField(Letter)
connection_letters = ConnectionField(Letter)
node = Node.Field()
def resolve_letters(self, args, context, info):
return list(letters.values())
node = Node.Field()
def resolve_connection_letters(self, args, context, info):
return Letter.Connection(
page_info=PageInfo(
has_next_page=True,
has_previous_page=False
),
edges=[
Letter.Connection.Edge(
node=Letter(id=0, letter='A'),
cursor='a-cursor'
),
]
)
schema = Schema(Query)
@ -176,3 +191,40 @@ def test_returns_all_elements_if_cursors_are_on_the_outside():
def test_returns_no_elements_if_cursors_cross():
check('before: "{}" after: "{}"'.format(base64('arrayconnection:%s' % 2), base64('arrayconnection:%s' % 4)), '')
def test_connection_type_nodes():
result = schema.execute('''
{
connectionLetters {
edges {
node {
id
letter
}
cursor
}
pageInfo {
hasPreviousPage
hasNextPage
}
}
}
''')
assert not result.errors
assert result.data == {
'connectionLetters': {
'edges': [{
'node': {
'id': 'TGV0dGVyOjA=',
'letter': 'A',
},
'cursor': 'a-cursor',
}],
'pageInfo': {
'hasPreviousPage': False,
'hasNextPage': True,
}
}
}

View File

@ -84,6 +84,9 @@ def test_mutation():
assert isinstance(field.args['input'], Argument)
assert isinstance(field.args['input'].type, NonNull)
assert field.args['input'].type.of_type == SaySomething.Input
assert isinstance(fields['client_mutation_id'], Field)
assert fields['client_mutation_id'].name == 'clientMutationId'
assert fields['client_mutation_id'].type == String
def test_mutation_input():
@ -132,7 +135,7 @@ def test_node_query():
def test_edge_query():
executed = schema.execute(
'mutation a { other(input: {clientMutationId:"1"}) { myNodeEdge { cursor node { name }} } }'
'mutation a { other(input: {clientMutationId:"1"}) { clientMutationId, myNodeEdge { cursor node { name }} } }'
)
assert not executed.errors
assert dict(executed.data) == {'other': {'myNodeEdge': {'cursor': '1', 'node': {'name': 'name'}}}}
assert dict(executed.data) == {'other': {'clientMutationId': '1', 'myNodeEdge': {'cursor': '1', 'node': {'name': 'name'}}}}

View File

@ -18,6 +18,14 @@ class Argument(OrderedType):
self.default_value = default_value
self.description = description
def __eq__(self, other):
return isinstance(other, Argument) and (
self.name == other.name,
self.type == other.type,
self.default_value == other.default_value,
self.description == other.description
)
def to_arguments(args, extra_args):
from .unmountedtype import UnmountedType

View File

@ -36,4 +36,4 @@ class DateTime(Scalar):
@staticmethod
def parse_value(value):
return datetime.datetime.strptime(value, "%Y-%m-%dT%H:%M:%S.%f")
return iso8601.parse_date(value)

View File

@ -8,6 +8,9 @@ from .structures import NonNull
from .unmountedtype import UnmountedType
base_type = type
def source_resolver(source, root, args, context, info):
resolved = getattr(root, source, None)
if inspect.isfunction(resolved):
@ -19,7 +22,8 @@ class Field(OrderedType):
def __init__(self, type, args=None, resolver=None, source=None,
deprecation_reason=None, name=None, description=None,
required=False, _creation_counter=None, **extra_args):
required=False, _creation_counter=None, default_value=None,
**extra_args):
super(Field, self).__init__(_creation_counter=_creation_counter)
assert not args or isinstance(args, Mapping), (
'Arguments in a field have to be a mapping, received "{}".'
@ -27,6 +31,9 @@ class Field(OrderedType):
assert not (source and resolver), (
'A Field cannot have a source and a resolver in at the same time.'
)
assert not callable(default_value), (
'The default value can not be a function but received "{}".'
).format(base_type(default_value))
if required:
type = NonNull(type)
@ -49,6 +56,7 @@ class Field(OrderedType):
self.resolver = resolver
self.deprecation_reason = deprecation_reason
self.description = description
self.default_value = default_value
@property
def type(self):

View File

@ -48,7 +48,11 @@ class Interface(six.with_metaclass(InterfaceMeta)):
when the field is resolved.
'''
resolve_type = None
@classmethod
def resolve_type(cls, instance, context, info):
from .objecttype import ObjectType
if isinstance(instance, ObjectType):
return type(instance)
def __init__(self, *args, **kwargs):
raise Exception("An Interface cannot be intitialized")

View File

@ -3,6 +3,7 @@ from functools import partial
import six
from ..utils.is_base_type import is_base_type
from ..utils.get_unbound_function import get_unbound_function
from ..utils.props import props
from .field import Field
from .objecttype import ObjectType, ObjectTypeMeta
@ -22,6 +23,7 @@ class MutationMeta(ObjectTypeMeta):
field_args = props(input_class) if input_class else {}
resolver = getattr(cls, 'mutate', None)
assert resolver, 'All mutations must define a mutate method in it'
resolver = get_unbound_function(resolver)
cls.Field = partial(Field, cls, args=field_args, resolver=resolver)
return cls

View File

@ -63,10 +63,7 @@ class ObjectType(six.with_metaclass(ObjectTypeMeta)):
have a name, but most importantly describe their fields.
'''
@classmethod
def is_type_of(cls, root, context, info):
if isinstance(root, cls):
return True
is_type_of = None
def __init__(self, *args, **kwargs):
# ObjectType acting as container

View File

@ -27,6 +27,13 @@ class List(Structure):
def __str__(self):
return '[{}]'.format(self.of_type)
def __eq__(self, other):
return isinstance(other, List) and (
self.of_type == other.of_type and
self.args == other.args and
self.kwargs == other.kwargs
)
class NonNull(Structure):
'''
@ -49,3 +56,10 @@ class NonNull(Structure):
def __str__(self):
return '{}!'.format(self.of_type)
def __eq__(self, other):
return isinstance(other, NonNull) and (
self.of_type == other.of_type and
self.args == other.args and
self.kwargs == other.kwargs
)

View File

@ -0,0 +1,26 @@
import pytest
from ..argument import Argument
from ..structures import NonNull
from ..scalars import String
def test_argument():
arg = Argument(String, default_value='a', description='desc', name='b')
assert arg.type == String
assert arg.default_value == 'a'
assert arg.description == 'desc'
assert arg.name == 'b'
def test_argument_comparasion():
arg1 = Argument(String, name='Hey', description='Desc', default_value='default')
arg2 = Argument(String, name='Hey', description='Desc', default_value='default')
assert arg1 == arg2
assert arg1 != String()
def test_argument_required():
arg = Argument(String, required=True)
assert arg.type == NonNull(String)

View File

@ -0,0 +1,41 @@
import datetime
import pytz
from ..datetime import DateTime
from ..objecttype import ObjectType
from ..schema import Schema
class Query(ObjectType):
datetime = DateTime(_in=DateTime(name='in'))
def resolve_datetime(self, args, context, info):
_in = args.get('in')
return _in
schema = Schema(query=Query)
def test_datetime_query():
now = datetime.datetime.now().replace(tzinfo=pytz.utc)
isoformat = now.isoformat()
result = schema.execute('''{ datetime(in: "%s") }'''%isoformat)
assert not result.errors
assert result.data == {
'datetime': isoformat
}
def test_datetime_query_variable():
now = datetime.datetime.now().replace(tzinfo=pytz.utc)
isoformat = now.isoformat()
result = schema.execute(
'''query Test($date: DateTime){ datetime(in: $date) }''',
variable_values={'date': isoformat}
)
assert not result.errors
assert result.data == {
'datetime': isoformat
}

View File

@ -16,19 +16,22 @@ def test_field_basic():
resolver = lambda: None
deprecation_reason = 'Deprecated now'
description = 'My Field'
my_default='something'
field = Field(
MyType,
name='name',
args=args,
resolver=resolver,
description=description,
deprecation_reason=deprecation_reason
deprecation_reason=deprecation_reason,
default_value=my_default,
)
assert field.name == 'name'
assert field.args == args
assert field.resolver == resolver
assert field.deprecation_reason == deprecation_reason
assert field.description == description
assert field.default_value == my_default
def test_field_required():
@ -38,6 +41,15 @@ def test_field_required():
assert field.type.of_type == MyType
def test_field_default_value_not_callable():
MyType = object()
try:
Field(MyType, default_value=lambda: True)
except AssertionError as e:
# substring comparison for py 2/3 compatibility
assert 'The default value can not be a function but received' in str(e)
def test_field_source():
MyType = object()
field = Field(MyType, source='value')

View File

@ -0,0 +1,39 @@
import json
from ..json import JSONString
from ..objecttype import ObjectType
from ..schema import Schema
class Query(ObjectType):
json = JSONString(input=JSONString())
def resolve_json(self, args, context, info):
input = args.get('input')
return input
schema = Schema(query=Query)
def test_jsonstring_query():
json_value = '{"key": "value"}'
json_value_quoted = json_value.replace('"', '\\"')
result = schema.execute('''{ json(input: "%s") }'''%json_value_quoted)
assert not result.errors
assert result.data == {
'json': json_value
}
def test_jsonstring_query_variable():
json_value = '{"key": "value"}'
result = schema.execute(
'''query Test($json: JSONString){ json(input: $json) }''',
variable_values={'json': json_value}
)
assert not result.errors
assert result.data == {
'json': json_value
}

View File

@ -2,6 +2,8 @@ import pytest
from ..mutation import Mutation
from ..objecttype import ObjectType
from ..schema import Schema
from ..scalars import String
def test_generate_mutation_no_args():
@ -17,26 +19,6 @@ def test_generate_mutation_no_args():
assert MyMutation.Field().resolver == MyMutation.mutate
# def test_generate_mutation_with_args():
# class MyMutation(Mutation):
# '''Documentation'''
# class Input:
# s = String()
# @classmethod
# def mutate(cls, *args, **kwargs):
# pass
# graphql_type = MyMutation._meta.graphql_type
# field = MyMutation.Field()
# assert graphql_type.name == "MyMutation"
# assert graphql_type.description == "Documentation"
# assert isinstance(field, Field)
# assert field.type == MyMutation._meta.graphql_type
# assert 's' in field.args
# assert field.args['s'].type == String
def test_generate_mutation_with_meta():
class MyMutation(Mutation):
@ -59,3 +41,35 @@ def test_mutation_raises_exception_if_no_mutate():
pass
assert "All mutations must define a mutate method in it" == str(excinfo.value)
def test_mutation_execution():
class CreateUser(Mutation):
class Input:
name = String()
name = String()
def mutate(self, args, context, info):
name = args.get('name')
return CreateUser(name=name)
class Query(ObjectType):
a = String()
class MyMutation(ObjectType):
create_user = CreateUser.Field()
schema = Schema(query=Query, mutation=MyMutation)
result = schema.execute(''' mutation mymutation {
createUser(name:"Peter") {
name
}
}
''')
assert not result.errors
assert result.data == {
'createUser': {
'name': "Peter"
}
}

View File

@ -1,8 +1,9 @@
import json
from functools import partial
from graphql import Source, execute, parse
from graphql import Source, execute, parse, GraphQLError
from ..field import Field
from ..inputfield import InputField
from ..inputobjecttype import InputObjectType
from ..objecttype import ObjectType
@ -22,6 +23,53 @@ def test_query():
assert executed.data == {'hello': 'World'}
def test_query_default_value():
class MyType(ObjectType):
field = String()
class Query(ObjectType):
hello = Field(MyType, default_value=MyType(field='something else!'))
hello_schema = Schema(Query)
executed = hello_schema.execute('{ hello { field } }')
assert not executed.errors
assert executed.data == {'hello': {'field': 'something else!'}}
def test_query_wrong_default_value():
class MyType(ObjectType):
field = String()
@classmethod
def is_type_of(cls, root, context, info):
return isinstance(root, MyType)
class Query(ObjectType):
hello = Field(MyType, default_value='hello')
hello_schema = Schema(Query)
executed = hello_schema.execute('{ hello { field } }')
assert len(executed.errors) == 1
assert executed.errors[0].message == GraphQLError('Expected value of type "MyType" but got: str.').message
assert executed.data == {'hello': None}
def test_query_default_value_ignored_by_resolver():
class MyType(ObjectType):
field = String()
class Query(ObjectType):
hello = Field(MyType, default_value='hello', resolver=lambda *_: MyType(field='no default.'))
hello_schema = Schema(Query)
executed = hello_schema.execute('{ hello { field } }')
assert not executed.errors
assert executed.data == {'hello': {'field': 'no default.'}}
def test_query_resolve_function():
class Query(ObjectType):
hello = String()
@ -109,6 +157,30 @@ def test_query_middlewares():
assert executed.data == {'hello': 'dlroW', 'other': 'rehto'}
def test_objecttype_on_instances():
class Ship:
def __init__(self, name):
self.name = name
class ShipType(ObjectType):
name = String(description="Ship name", required=True)
def resolve_name(self, context, args, 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, context, args, info):
return Ship(name='xwing')
schema = Schema(query=Query)
executed = schema.execute('{ ship { name } }')
assert not executed.errors
assert executed.data == {'ship': {'name': 'xwing'}}
def test_big_list_query_benchmark(benchmark):
big_list = range(10000)

View File

@ -0,0 +1,44 @@
import pytest
from ..structures import List, NonNull
from ..scalars import String
def test_list():
_list = List(String)
assert _list.of_type == String
assert str(_list) == '[String]'
def test_nonnull():
nonnull = NonNull(String)
assert nonnull.of_type == String
assert str(nonnull) == 'String!'
def test_list_comparasion():
list1 = List(String)
list2 = List(String)
list3 = List(None)
list1_argskwargs = List(String, None, b=True)
list2_argskwargs = List(String, None, b=True)
assert list1 == list2
assert list1 != list3
assert list1_argskwargs == list2_argskwargs
assert list1 != list1_argskwargs
def test_nonnull_comparasion():
nonnull1 = NonNull(String)
nonnull2 = NonNull(String)
nonnull3 = NonNull(None)
nonnull1_argskwargs = NonNull(String, None, b=True)
nonnull2_argskwargs = NonNull(String, None, b=True)
assert nonnull1 == nonnull2
assert nonnull1 != nonnull3
assert nonnull1_argskwargs == nonnull2_argskwargs
assert nonnull1 != nonnull1_argskwargs

View File

@ -6,9 +6,11 @@ from graphql import (GraphQLArgument, GraphQLBoolean, GraphQLField,
GraphQLFloat, GraphQLID, GraphQLInputObjectField,
GraphQLInt, GraphQLList, GraphQLNonNull, GraphQLString)
from graphql.type import GraphQLEnumValue
from graphql.execution.executor import get_default_resolve_type_fn
from graphql.type.typemap import GraphQLTypeMap
from ..utils.str_converters import to_camel_case
from ..utils.get_unbound_function import get_unbound_function
from .dynamic import Dynamic
from .enum import Enum
from .inputobjecttype import InputObjectType
@ -26,11 +28,14 @@ def is_graphene_type(_type):
return True
def resolve_type(resolve_type_func, map, root, args, info):
_type = resolve_type_func(root, args, info)
def resolve_type(resolve_type_func, map, root, context, info):
_type = resolve_type_func(root, context, info)
# assert inspect.isclass(_type) and issubclass(_type, ObjectType), (
# 'Received incompatible type "{}".'.format(_type)
# )
if not _type:
return get_default_resolve_type_fn(root, context, info, info.return_type)
if inspect.isclass(_type) and issubclass(_type, ObjectType):
graphql_type = map.get(_type._meta.name)
assert graphql_type and graphql_type.graphene_type == _type
@ -190,8 +195,8 @@ class TypeMap(GraphQLTypeMap):
return to_camel_case(name)
return name
def default_resolver(self, attname, root, *_):
return getattr(root, attname, None)
def default_resolver(self, attname, default_value, root, *_):
return getattr(root, attname, default_value)
def construct_fields_for_type(self, map, type, is_input_type=False):
fields = OrderedDict()
@ -224,7 +229,7 @@ class TypeMap(GraphQLTypeMap):
_field = GraphQLField(
field_type,
args=args,
resolver=field.get_resolver(self.get_resolver_for_type(type, name)),
resolver=field.get_resolver(self.get_resolver_for_type(type, name, field.default_value)),
deprecation_reason=field.deprecation_reason,
description=field.description
)
@ -232,7 +237,7 @@ class TypeMap(GraphQLTypeMap):
fields[field_name] = _field
return fields
def get_resolver_for_type(self, type, name):
def get_resolver_for_type(self, type, name, default_value):
if not issubclass(type, ObjectType):
return
resolver = getattr(type, 'resolve_{}'.format(name), None)
@ -247,13 +252,12 @@ class TypeMap(GraphQLTypeMap):
if interface_resolver:
break
resolver = interface_resolver
# Only if is not decorated with classmethod
if resolver:
if not getattr(resolver, '__self__', True):
return resolver.__func__
return resolver
return get_unbound_function(resolver)
return partial(self.default_resolver, name)
return partial(self.default_resolver, name, default_value)
def get_field_type(self, map, type):
if isinstance(type, List):

View File

@ -0,0 +1,4 @@
def get_unbound_function(func):
if not getattr(func, '__self__', True):
return func.__func__
return func

View File

@ -70,7 +70,7 @@ setup(
install_requires=[
'six>=1.10.0',
'graphql-core>=1.0.dev',
'graphql-core>=1.0',
'graphql-relay>=0.4.4',
'promise',
],
@ -78,6 +78,8 @@ setup(
'pytest>=2.7.2',
'pytest-benchmark',
'mock',
'pytz',
'iso8601',
],
extras_require={
'django': [