mirror of
https://github.com/graphql-python/graphene.git
synced 2025-02-23 07:00:56 +03:00
Manually merged in master.
This commit is contained in:
commit
3d6486a899
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
-----------------
|
||||
|
|
15
docs/conf.py
15
docs/conf.py
|
@ -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.
|
||||
#
|
||||
|
|
|
@ -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
2
docs/requirements.txt
Normal file
|
@ -0,0 +1,2 @@
|
|||
# Docs template
|
||||
https://github.com/graphql-python/graphene-python.org/archive/docs.zip
|
|
@ -32,7 +32,7 @@ plus the ones defined in ``UserFields``.
|
|||
pass
|
||||
|
||||
|
||||
.. code:: graphql
|
||||
.. code::
|
||||
|
||||
type User {
|
||||
name: String
|
||||
|
|
|
@ -10,4 +10,5 @@ Types Reference
|
|||
interfaces
|
||||
abstracttypes
|
||||
objecttypes
|
||||
schema
|
||||
mutations
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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") {
|
||||
|
|
|
@ -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
|
||||
~~~~~~~~~~~~~
|
||||
|
||||
|
|
|
@ -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
81
docs/types/schema.rst
Normal 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,
|
||||
)
|
|
@ -10,7 +10,7 @@ except NameError:
|
|||
__SETUP__ = False
|
||||
|
||||
|
||||
VERSION = (1, 0, 0, 'alpha', 0)
|
||||
VERSION = (1, 0, 2, 'final', 0)
|
||||
|
||||
__version__ = get_version(VERSION)
|
||||
|
||||
|
|
|
@ -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):
|
||||
|
|
|
@ -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),
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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'}}}}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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):
|
||||
|
|
|
@ -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")
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
)
|
||||
|
|
26
graphene/types/tests/test_argument.py
Normal file
26
graphene/types/tests/test_argument.py
Normal 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)
|
41
graphene/types/tests/test_datetime.py
Normal file
41
graphene/types/tests/test_datetime.py
Normal 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
|
||||
}
|
|
@ -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')
|
||||
|
|
39
graphene/types/tests/test_json.py
Normal file
39
graphene/types/tests/test_json.py
Normal 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
|
||||
}
|
|
@ -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"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
|
44
graphene/types/tests/test_structures.py
Normal file
44
graphene/types/tests/test_structures.py
Normal 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
|
|
@ -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):
|
||||
|
|
4
graphene/utils/get_unbound_function.py
Normal file
4
graphene/utils/get_unbound_function.py
Normal file
|
@ -0,0 +1,4 @@
|
|||
def get_unbound_function(func):
|
||||
if not getattr(func, '__self__', True):
|
||||
return func.__func__
|
||||
return func
|
Loading…
Reference in New Issue
Block a user