mirror of
https://github.com/graphql-python/graphene.git
synced 2024-11-22 09:36:44 +03:00
Merge branch 'master' into feature/date-improvements
# Conflicts: # graphene/types/datetime.py
This commit is contained in:
commit
85e354c139
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -42,6 +42,7 @@ htmlcov/
|
||||||
.coverage
|
.coverage
|
||||||
.coverage.*
|
.coverage.*
|
||||||
.cache
|
.cache
|
||||||
|
.pytest_cache
|
||||||
nosetests.xml
|
nosetests.xml
|
||||||
coverage.xml
|
coverage.xml
|
||||||
*,cover
|
*,cover
|
||||||
|
|
|
@ -21,7 +21,7 @@ developer has to write to use them.
|
||||||
|
|
||||||
|
|
||||||
> The type metaclasses are now deleted as they are no longer necessary. If your code was depending
|
> The type metaclasses are now deleted as they are no longer necessary. If your code was depending
|
||||||
> on this strategy for creating custom attrs, see an [example on how to do it in 2.0](https://github.com/graphql-python/graphene/blob/2.0/graphene/tests/issues/test_425.py).
|
> on this strategy for creating custom attrs, see an [example on how to do it in 2.0](https://github.com/graphql-python/graphene/blob/v2.0.0/graphene/tests/issues/test_425.py).
|
||||||
|
|
||||||
## Deprecations
|
## Deprecations
|
||||||
|
|
||||||
|
|
|
@ -3,7 +3,7 @@ Middleware
|
||||||
|
|
||||||
You can use ``middleware`` to affect the evaluation of fields in your schema.
|
You can use ``middleware`` to affect the evaluation of fields in your schema.
|
||||||
|
|
||||||
A middleware is any object that responds to ``resolve(*args, next_middleware)``.
|
A middleware is any object or function that responds to ``resolve(next_middleware, *args)``.
|
||||||
|
|
||||||
Inside that method, it should either:
|
Inside that method, it should either:
|
||||||
|
|
||||||
|
@ -18,10 +18,8 @@ Middlewares ``resolve`` is invoked with several arguments:
|
||||||
|
|
||||||
- ``next`` represents the execution chain. Call ``next`` to continue evalution.
|
- ``next`` represents the execution chain. Call ``next`` to continue evalution.
|
||||||
- ``root`` is the root value object passed throughout the query.
|
- ``root`` is the root value object passed throughout the query.
|
||||||
- ``args`` is the hash of arguments passed to the field.
|
|
||||||
- ``context`` is the context object passed throughout the query.
|
|
||||||
- ``info`` is the resolver info.
|
- ``info`` is the resolver info.
|
||||||
|
- ``args`` is the dict of arguments passed to the field.
|
||||||
|
|
||||||
Example
|
Example
|
||||||
-------
|
-------
|
||||||
|
@ -42,3 +40,32 @@ And then execute it with:
|
||||||
.. code:: python
|
.. code:: python
|
||||||
|
|
||||||
result = schema.execute('THE QUERY', middleware=[AuthorizationMiddleware()])
|
result = schema.execute('THE QUERY', middleware=[AuthorizationMiddleware()])
|
||||||
|
|
||||||
|
|
||||||
|
Functional example
|
||||||
|
------------------
|
||||||
|
|
||||||
|
Middleware can also be defined as a function. Here we define a middleware that
|
||||||
|
logs the time it takes to resolve each field
|
||||||
|
|
||||||
|
.. code:: python
|
||||||
|
|
||||||
|
from time import time as timer
|
||||||
|
|
||||||
|
def timing_middleware(next, root, info, **args):
|
||||||
|
start = timer()
|
||||||
|
return_value = next(root, info, **args)
|
||||||
|
duration = timer() - start
|
||||||
|
logger.debug("{parent_type}.{field_name}: {duration} ms".format(
|
||||||
|
parent_type=root._meta.name if root and hasattr(root, '_meta') else '',
|
||||||
|
field_name=info.field_name,
|
||||||
|
duration=round(duration * 1000, 2)
|
||||||
|
))
|
||||||
|
return return_value
|
||||||
|
|
||||||
|
|
||||||
|
And then execute it with:
|
||||||
|
|
||||||
|
.. code:: python
|
||||||
|
|
||||||
|
result = schema.execute('THE QUERY', middleware=[timing_middleware])
|
||||||
|
|
|
@ -21,9 +21,9 @@ Useful links
|
||||||
- `Relay Cursor Connection Specification`_
|
- `Relay Cursor Connection Specification`_
|
||||||
- `Relay input Object Mutation`_
|
- `Relay input Object Mutation`_
|
||||||
|
|
||||||
.. _Relay: https://facebook.github.io/relay/docs/graphql-relay-specification.html
|
.. _Relay: https://facebook.github.io/relay/docs/en/graphql-server-specification.html
|
||||||
.. _Relay specification: https://facebook.github.io/relay/graphql/objectidentification.htm#sec-Node-root-field
|
.. _Relay specification: https://facebook.github.io/relay/graphql/objectidentification.htm#sec-Node-root-field
|
||||||
.. _Getting started with Relay: https://facebook.github.io/relay/docs/graphql-relay-specification.html
|
.. _Getting started with Relay: https://facebook.github.io/relay/docs/en/quick-start-guide.html
|
||||||
.. _Relay Global Identification Specification: https://facebook.github.io/relay/graphql/objectidentification.htm
|
.. _Relay Global Identification Specification: https://facebook.github.io/relay/graphql/objectidentification.htm
|
||||||
.. _Relay Cursor Connection Specification: https://facebook.github.io/relay/graphql/connections.htm
|
.. _Relay Cursor Connection Specification: https://facebook.github.io/relay/graphql/connections.htm
|
||||||
.. _Relay input Object Mutation: https://facebook.github.io/relay/graphql/mutations.htm
|
.. _Relay input Object Mutation: https://facebook.github.io/relay/graphql/mutations.htm
|
||||||
|
|
|
@ -55,12 +55,12 @@ Example of a custom node:
|
||||||
return '{}:{}'.format(type, id)
|
return '{}:{}'.format(type, id)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def get_node_from_global_id(info global_id, only_type=None):
|
def get_node_from_global_id(info, global_id, only_type=None):
|
||||||
type, id = global_id.split(':')
|
type, id = global_id.split(':')
|
||||||
if only_node:
|
if only_type:
|
||||||
# We assure that the node type that we want to retrieve
|
# We assure that the node type that we want to retrieve
|
||||||
# is the same that was indicated in the field type
|
# is the same that was indicated in the field type
|
||||||
assert type == only_node._meta.name, 'Received not compatible node.'
|
assert type == only_type._meta.name, 'Received not compatible node.'
|
||||||
|
|
||||||
if type == 'User':
|
if type == 'User':
|
||||||
return get_user(id)
|
return get_user(id)
|
||||||
|
@ -75,10 +75,10 @@ Accessing node types
|
||||||
--------------------
|
--------------------
|
||||||
|
|
||||||
If we want to retrieve node instances from a ``global_id`` (scalar that identifies an instance by it's type name and id),
|
If we want to retrieve node instances from a ``global_id`` (scalar that identifies an instance by it's type name and id),
|
||||||
we can simply do ``Node.get_node_from_global_id(global_id, context, info)``.
|
we can simply do ``Node.get_node_from_global_id(info, global_id)``.
|
||||||
|
|
||||||
In the case we want to restrict the instance retrieval to a specific type, we can do:
|
In the case we want to restrict the instance retrieval to a specific type, we can do:
|
||||||
``Node.get_node_from_global_id(global_id, context, info, only_type=Ship)``. This will raise an error
|
``Node.get_node_from_global_id(info, global_id, only_type=Ship)``. This will raise an error
|
||||||
if the ``global_id`` doesn't correspond to a Ship type.
|
if the ``global_id`` doesn't correspond to a Ship type.
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
# Required library
|
# Required library
|
||||||
Sphinx==1.5.3
|
Sphinx==1.5.3
|
||||||
# Docs template
|
# Docs template
|
||||||
https://github.com/graphql-python/graphene-python.org/archive/docs.zip
|
http://graphene-python.org/sphinx_graphene_theme.zip
|
||||||
|
|
|
@ -54,6 +54,13 @@ the ``Enum.from_enum`` function.
|
||||||
|
|
||||||
graphene.Enum.from_enum(AlreadyExistingPyEnum)
|
graphene.Enum.from_enum(AlreadyExistingPyEnum)
|
||||||
|
|
||||||
|
``Enum.from_enum`` supports a ``description`` and ``deprecation_reason`` lambdas as input so
|
||||||
|
you can add description etc. to your enum without changing the original:
|
||||||
|
|
||||||
|
.. code:: python
|
||||||
|
|
||||||
|
graphene.Enum.from_enum(AlreadyExistingPyEnum, description=lambda value: return 'foo' if value == AlreadyExistingPyEnum.Foo else 'bar')
|
||||||
|
|
||||||
|
|
||||||
Notes
|
Notes
|
||||||
-----
|
-----
|
||||||
|
@ -65,7 +72,7 @@ member getters.
|
||||||
In the Python ``Enum`` implementation you can access a member by initing the Enum.
|
In the Python ``Enum`` implementation you can access a member by initing the Enum.
|
||||||
|
|
||||||
.. code:: python
|
.. code:: python
|
||||||
|
|
||||||
from enum import Enum
|
from enum import Enum
|
||||||
class Color(Enum):
|
class Color(Enum):
|
||||||
RED = 1
|
RED = 1
|
||||||
|
@ -78,7 +85,7 @@ In the Python ``Enum`` implementation you can access a member by initing the Enu
|
||||||
However, in Graphene ``Enum`` you need to call get to have the same effect:
|
However, in Graphene ``Enum`` you need to call get to have the same effect:
|
||||||
|
|
||||||
.. code:: python
|
.. code:: python
|
||||||
|
|
||||||
from graphene import Enum
|
from graphene import Enum
|
||||||
class Color(Enum):
|
class Color(Enum):
|
||||||
RED = 1
|
RED = 1
|
||||||
|
|
|
@ -48,3 +48,24 @@ Lists work in a similar way: We can use a type modifier to mark a type as a
|
||||||
``List``, which indicates that this field will return a list of that type.
|
``List``, which indicates that this field will return a list of that type.
|
||||||
It works the same for arguments, where the validation step will expect a list
|
It works the same for arguments, where the validation step will expect a list
|
||||||
for that value.
|
for that value.
|
||||||
|
|
||||||
|
NonNull Lists
|
||||||
|
-------------
|
||||||
|
|
||||||
|
By default items in a list will be considered nullable. To define a list without
|
||||||
|
any nullable items the type needs to be marked as ``NonNull``. For example:
|
||||||
|
|
||||||
|
.. code:: python
|
||||||
|
|
||||||
|
import graphene
|
||||||
|
|
||||||
|
class Character(graphene.ObjectType):
|
||||||
|
appears_in = graphene.List(graphene.NonNull(graphene.String))
|
||||||
|
|
||||||
|
The above results in the type definition:
|
||||||
|
|
||||||
|
.. code::
|
||||||
|
|
||||||
|
type Character {
|
||||||
|
appearsIn: [String!]
|
||||||
|
}
|
||||||
|
|
|
@ -1,19 +1,83 @@
|
||||||
Scalars
|
Scalars
|
||||||
=======
|
=======
|
||||||
|
|
||||||
|
All Scalar types accept the following arguments. All are optional:
|
||||||
|
|
||||||
|
``name``: *string*
|
||||||
|
|
||||||
|
Override the name of the Field.
|
||||||
|
|
||||||
|
``description``: *string*
|
||||||
|
|
||||||
|
A description of the type to show in the GraphiQL browser.
|
||||||
|
|
||||||
|
``required``: *boolean*
|
||||||
|
|
||||||
|
If ``True``, the server will enforce a value for this field. See `NonNull <./list-and-nonnull.html#nonnull>`_. Default is ``False``.
|
||||||
|
|
||||||
|
``deprecation_reason``: *string*
|
||||||
|
|
||||||
|
Provide a deprecation reason for the Field.
|
||||||
|
|
||||||
|
``default_value``: *any*
|
||||||
|
|
||||||
|
Provide a default value for the Field.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
Base scalars
|
||||||
|
------------
|
||||||
|
|
||||||
Graphene defines the following base Scalar Types:
|
Graphene defines the following base Scalar Types:
|
||||||
|
|
||||||
- ``graphene.String``
|
``graphene.String``
|
||||||
- ``graphene.Int``
|
|
||||||
- ``graphene.Float``
|
Represents textual data, represented as UTF-8
|
||||||
- ``graphene.Boolean``
|
character sequences. The String type is most often used by GraphQL to
|
||||||
- ``graphene.ID``
|
represent free-form human-readable text.
|
||||||
|
|
||||||
|
``graphene.Int``
|
||||||
|
|
||||||
|
Represents non-fractional signed whole numeric
|
||||||
|
values. Int can represent values between `-(2^53 - 1)` and `2^53 - 1` since
|
||||||
|
represented in JSON as double-precision floating point numbers specified
|
||||||
|
by `IEEE 754 <http://en.wikipedia.org/wiki/IEEE_floating_point>`_.
|
||||||
|
|
||||||
|
``graphene.Float``
|
||||||
|
|
||||||
|
Represents signed double-precision fractional
|
||||||
|
values as specified by
|
||||||
|
`IEEE 754 <http://en.wikipedia.org/wiki/IEEE_floating_point>`_.
|
||||||
|
|
||||||
|
``graphene.Boolean``
|
||||||
|
|
||||||
|
Represents `true` or `false`.
|
||||||
|
|
||||||
|
``graphene.ID``
|
||||||
|
|
||||||
|
Represents a unique identifier, often used to
|
||||||
|
refetch an object or as key for a cache. The ID type appears in a JSON
|
||||||
|
response as a String; however, it is not intended to be human-readable.
|
||||||
|
When expected as an input type, any string (such as `"4"`) or integer
|
||||||
|
(such as `4`) input value will be accepted as an ID.
|
||||||
|
|
||||||
Graphene also provides custom scalars for Dates, Times, and JSON:
|
Graphene also provides custom scalars for Dates, Times, and JSON:
|
||||||
|
|
||||||
- ``graphene.types.datetime.DateTime``
|
``graphene.types.datetime.Date``
|
||||||
- ``graphene.types.datetime.Time``
|
|
||||||
- ``graphene.types.json.JSONString``
|
Represents a Date value as specified by `iso8601 <https://en.wikipedia.org/wiki/ISO_8601>`_.
|
||||||
|
|
||||||
|
``graphene.types.datetime.DateTime``
|
||||||
|
|
||||||
|
Represents a DateTime value as specified by `iso8601 <https://en.wikipedia.org/wiki/ISO_8601>`_.
|
||||||
|
|
||||||
|
``graphene.types.datetime.Time``
|
||||||
|
|
||||||
|
Represents a Time value as specified by `iso8601 <https://en.wikipedia.org/wiki/ISO_8601>`_.
|
||||||
|
|
||||||
|
``graphene.types.json.JSONString``
|
||||||
|
|
||||||
|
Represents a JSON string.
|
||||||
|
|
||||||
|
|
||||||
Custom scalars
|
Custom scalars
|
||||||
|
|
|
@ -12,8 +12,8 @@ The basics:
|
||||||
Quick example
|
Quick example
|
||||||
-------------
|
-------------
|
||||||
|
|
||||||
This example model defines a ``Character`` interface with a name. ``Human``
|
This example model defines several ObjectTypes with their own fields.
|
||||||
and ``Droid`` are two implementations of that interface.
|
``SearchResult`` is the implementation of ``Union`` of this object types.
|
||||||
|
|
||||||
.. code:: python
|
.. code:: python
|
||||||
|
|
||||||
|
|
|
@ -34,7 +34,7 @@ from .utils.resolve_only_args import resolve_only_args
|
||||||
from .utils.module_loading import lazy_import
|
from .utils.module_loading import lazy_import
|
||||||
|
|
||||||
|
|
||||||
VERSION = (2, 0, 0, 'final', 0)
|
VERSION = (2, 0, 1, 'final', 0)
|
||||||
|
|
||||||
__version__ = get_version(VERSION)
|
__version__ = get_version(VERSION)
|
||||||
|
|
||||||
|
|
|
@ -73,7 +73,7 @@ class Connection(ObjectType):
|
||||||
edge = type(edge_name, edge_bases, {})
|
edge = type(edge_name, edge_bases, {})
|
||||||
cls.Edge = edge
|
cls.Edge = edge
|
||||||
|
|
||||||
_meta.name = name
|
options['name'] = name
|
||||||
_meta.node = node
|
_meta.node = node
|
||||||
_meta.fields = OrderedDict([
|
_meta.fields = OrderedDict([
|
||||||
('page_info', Field(PageInfo, name='pageInfo', required=True)),
|
('page_info', Field(PageInfo, name='pageInfo', required=True)),
|
||||||
|
@ -99,16 +99,19 @@ class IterableConnectionField(Field):
|
||||||
def type(self):
|
def type(self):
|
||||||
type = super(IterableConnectionField, self).type
|
type = super(IterableConnectionField, self).type
|
||||||
connection_type = type
|
connection_type = type
|
||||||
if is_node(type):
|
if isinstance(type, NonNull):
|
||||||
|
connection_type = type.of_type
|
||||||
|
|
||||||
|
if is_node(connection_type):
|
||||||
raise Exception(
|
raise Exception(
|
||||||
"ConnectionField's now need a explicit ConnectionType for Nodes.\n"
|
"ConnectionField's now need a explicit ConnectionType for Nodes.\n"
|
||||||
"Read more: https://github.com/graphql-python/graphene/blob/2.0/UPGRADE-v2.0.md#node-connections"
|
"Read more: https://github.com/graphql-python/graphene/blob/v2.0.0/UPGRADE-v2.0.md#node-connections"
|
||||||
)
|
)
|
||||||
|
|
||||||
assert issubclass(connection_type, Connection), (
|
assert issubclass(connection_type, Connection), (
|
||||||
'{} type have to be a subclass of Connection. Received "{}".'
|
'{} type have to be a subclass of Connection. Received "{}".'
|
||||||
).format(self.__class__.__name__, connection_type)
|
).format(self.__class__.__name__, connection_type)
|
||||||
return connection_type
|
return type
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def resolve_connection(cls, connection_type, args, resolved):
|
def resolve_connection(cls, connection_type, args, resolved):
|
||||||
|
@ -133,6 +136,9 @@ class IterableConnectionField(Field):
|
||||||
def connection_resolver(cls, resolver, connection_type, root, info, **args):
|
def connection_resolver(cls, resolver, connection_type, root, info, **args):
|
||||||
resolved = resolver(root, info, **args)
|
resolved = resolver(root, info, **args)
|
||||||
|
|
||||||
|
if isinstance(connection_type, NonNull):
|
||||||
|
connection_type = connection_type.of_type
|
||||||
|
|
||||||
on_resolve = partial(cls.resolve_connection, connection_type, args)
|
on_resolve = partial(cls.resolve_connection, connection_type, args)
|
||||||
if is_thenable(resolved):
|
if is_thenable(resolved):
|
||||||
return Promise.resolve(resolved).then(on_resolve)
|
return Promise.resolve(resolved).then(on_resolve)
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from ...types import Argument, Field, Int, List, NonNull, ObjectType, String
|
from ...types import Argument, Field, Int, List, NonNull, ObjectType, String, Schema
|
||||||
from ..connection import Connection, ConnectionField, PageInfo
|
from ..connection import Connection, ConnectionField, PageInfo
|
||||||
from ..node import Node
|
from ..node import Node
|
||||||
|
|
||||||
|
@ -52,6 +52,21 @@ def test_connection_inherit_abstracttype():
|
||||||
assert list(fields.keys()) == ['page_info', 'edges', 'extra']
|
assert list(fields.keys()) == ['page_info', 'edges', 'extra']
|
||||||
|
|
||||||
|
|
||||||
|
def test_connection_name():
|
||||||
|
custom_name = "MyObjectCustomNameConnection"
|
||||||
|
|
||||||
|
class BaseConnection(object):
|
||||||
|
extra = String()
|
||||||
|
|
||||||
|
class MyObjectConnection(BaseConnection, Connection):
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
node = MyObject
|
||||||
|
name = custom_name
|
||||||
|
|
||||||
|
assert MyObjectConnection._meta.name == custom_name
|
||||||
|
|
||||||
|
|
||||||
def test_edge():
|
def test_edge():
|
||||||
class MyObjectConnection(Connection):
|
class MyObjectConnection(Connection):
|
||||||
|
|
||||||
|
@ -122,9 +137,10 @@ def test_connectionfield_node_deprecated():
|
||||||
field = ConnectionField(MyObject)
|
field = ConnectionField(MyObject)
|
||||||
with pytest.raises(Exception) as exc_info:
|
with pytest.raises(Exception) as exc_info:
|
||||||
field.type
|
field.type
|
||||||
|
|
||||||
assert "ConnectionField's now need a explicit ConnectionType for Nodes." in str(exc_info.value)
|
assert "ConnectionField's now need a explicit ConnectionType for Nodes." in str(exc_info.value)
|
||||||
|
|
||||||
|
|
||||||
def test_connectionfield_custom_args():
|
def test_connectionfield_custom_args():
|
||||||
class MyObjectConnection(Connection):
|
class MyObjectConnection(Connection):
|
||||||
|
|
||||||
|
@ -139,3 +155,23 @@ def test_connectionfield_custom_args():
|
||||||
'last': Argument(Int),
|
'last': Argument(Int),
|
||||||
'extra': Argument(String),
|
'extra': Argument(String),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def test_connectionfield_required():
|
||||||
|
class MyObjectConnection(Connection):
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
node = MyObject
|
||||||
|
|
||||||
|
class Query(ObjectType):
|
||||||
|
test_connection = ConnectionField(MyObjectConnection, required=True)
|
||||||
|
|
||||||
|
def resolve_test_connection(root, info, **args):
|
||||||
|
return []
|
||||||
|
|
||||||
|
schema = Schema(query=Query)
|
||||||
|
executed = schema.execute(
|
||||||
|
'{ testConnection { edges { cursor } } }'
|
||||||
|
)
|
||||||
|
assert not executed.errors
|
||||||
|
assert executed.data == {'testConnection': {'edges': []}}
|
||||||
|
|
|
@ -2,8 +2,11 @@
|
||||||
# Adapted for Graphene 2.0
|
# Adapted for Graphene 2.0
|
||||||
|
|
||||||
from graphene.types.objecttype import ObjectType, ObjectTypeOptions
|
from graphene.types.objecttype import ObjectType, ObjectTypeOptions
|
||||||
|
from graphene.types.inputobjecttype import InputObjectType, InputObjectTypeOptions
|
||||||
|
from graphene.types.enum import Enum, EnumOptions
|
||||||
|
|
||||||
|
|
||||||
|
# ObjectType
|
||||||
class SpecialOptions(ObjectTypeOptions):
|
class SpecialOptions(ObjectTypeOptions):
|
||||||
other_attr = None
|
other_attr = None
|
||||||
|
|
||||||
|
@ -40,3 +43,77 @@ def test_special_objecttype_inherit_meta_options():
|
||||||
assert MyType._meta.name == 'MyType'
|
assert MyType._meta.name == 'MyType'
|
||||||
assert MyType._meta.default_resolver is None
|
assert MyType._meta.default_resolver is None
|
||||||
assert MyType._meta.interfaces == ()
|
assert MyType._meta.interfaces == ()
|
||||||
|
|
||||||
|
|
||||||
|
# InputObjectType
|
||||||
|
class SpecialInputObjectTypeOptions(ObjectTypeOptions):
|
||||||
|
other_attr = None
|
||||||
|
|
||||||
|
|
||||||
|
class SpecialInputObjectType(InputObjectType):
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def __init_subclass_with_meta__(cls, other_attr='default', **options):
|
||||||
|
_meta = SpecialInputObjectTypeOptions(cls)
|
||||||
|
_meta.other_attr = other_attr
|
||||||
|
super(SpecialInputObjectType, cls).__init_subclass_with_meta__(_meta=_meta, **options)
|
||||||
|
|
||||||
|
|
||||||
|
def test_special_inputobjecttype_could_be_subclassed():
|
||||||
|
class MyInputObjectType(SpecialInputObjectType):
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
other_attr = 'yeah!'
|
||||||
|
|
||||||
|
assert MyInputObjectType._meta.other_attr == 'yeah!'
|
||||||
|
|
||||||
|
|
||||||
|
def test_special_inputobjecttype_could_be_subclassed_default():
|
||||||
|
class MyInputObjectType(SpecialInputObjectType):
|
||||||
|
pass
|
||||||
|
|
||||||
|
assert MyInputObjectType._meta.other_attr == 'default'
|
||||||
|
|
||||||
|
|
||||||
|
def test_special_inputobjecttype_inherit_meta_options():
|
||||||
|
class MyInputObjectType(SpecialInputObjectType):
|
||||||
|
pass
|
||||||
|
|
||||||
|
assert MyInputObjectType._meta.name == 'MyInputObjectType'
|
||||||
|
|
||||||
|
|
||||||
|
# Enum
|
||||||
|
class SpecialEnumOptions(EnumOptions):
|
||||||
|
other_attr = None
|
||||||
|
|
||||||
|
|
||||||
|
class SpecialEnum(Enum):
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def __init_subclass_with_meta__(cls, other_attr='default', **options):
|
||||||
|
_meta = SpecialEnumOptions(cls)
|
||||||
|
_meta.other_attr = other_attr
|
||||||
|
super(SpecialEnum, cls).__init_subclass_with_meta__(_meta=_meta, **options)
|
||||||
|
|
||||||
|
|
||||||
|
def test_special_enum_could_be_subclassed():
|
||||||
|
class MyEnum(SpecialEnum):
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
other_attr = 'yeah!'
|
||||||
|
|
||||||
|
assert MyEnum._meta.other_attr == 'yeah!'
|
||||||
|
|
||||||
|
|
||||||
|
def test_special_enum_could_be_subclassed_default():
|
||||||
|
class MyEnum(SpecialEnum):
|
||||||
|
pass
|
||||||
|
|
||||||
|
assert MyEnum._meta.other_attr == 'default'
|
||||||
|
|
||||||
|
|
||||||
|
def test_special_enum_inherit_meta_options():
|
||||||
|
class MyEnum(SpecialEnum):
|
||||||
|
pass
|
||||||
|
|
||||||
|
assert MyEnum._meta.name == 'MyEnum'
|
||||||
|
|
|
@ -7,6 +7,6 @@ class AbstractType(SubclassWithMeta):
|
||||||
def __init_subclass__(cls, *args, **kwargs):
|
def __init_subclass__(cls, *args, **kwargs):
|
||||||
warn_deprecation(
|
warn_deprecation(
|
||||||
"Abstract type is deprecated, please use normal object inheritance instead.\n"
|
"Abstract type is deprecated, please use normal object inheritance instead.\n"
|
||||||
"See more: https://github.com/graphql-python/graphene/blob/2.0/UPGRADE-v2.0.md#deprecations"
|
"See more: https://github.com/graphql-python/graphene/blob/master/UPGRADE-v2.0.md#deprecations"
|
||||||
)
|
)
|
||||||
super(AbstractType, cls).__init_subclass__(*args, **kwargs)
|
super(AbstractType, cls).__init_subclass__(*args, **kwargs)
|
||||||
|
|
|
@ -21,7 +21,7 @@ class BaseOptions(object):
|
||||||
raise Exception("Can't modify frozen Options {0}".format(self))
|
raise Exception("Can't modify frozen Options {0}".format(self))
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
return "<{} type={}>".format(self.__class__.__name__, self.class_type.__name__)
|
return "<{} name={}>".format(self.__class__.__name__, repr(self.name))
|
||||||
|
|
||||||
|
|
||||||
class BaseType(SubclassWithMeta):
|
class BaseType(SubclassWithMeta):
|
||||||
|
|
|
@ -31,7 +31,10 @@ class Date(Scalar):
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def parse_value(value):
|
def parse_value(value):
|
||||||
return parse_date(value)
|
try:
|
||||||
|
return parse_date(value)
|
||||||
|
except ValueError:
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
class DateTime(Scalar):
|
class DateTime(Scalar):
|
||||||
|
@ -55,7 +58,10 @@ class DateTime(Scalar):
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def parse_value(value):
|
def parse_value(value):
|
||||||
return parse_datetime(value)
|
try:
|
||||||
|
return parse_datetime(value)
|
||||||
|
except ValueError:
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
class Time(Scalar):
|
class Time(Scalar):
|
||||||
|
@ -79,4 +85,7 @@ class Time(Scalar):
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def parse_value(cls, value):
|
def parse_value(cls, value):
|
||||||
return parse_time(value)
|
try:
|
||||||
|
return parse_time(value)
|
||||||
|
except ValueError:
|
||||||
|
return None
|
||||||
|
|
|
@ -27,7 +27,11 @@ class EnumOptions(BaseOptions):
|
||||||
class EnumMeta(SubclassWithMeta_Meta):
|
class EnumMeta(SubclassWithMeta_Meta):
|
||||||
|
|
||||||
def __new__(cls, name, bases, classdict, **options):
|
def __new__(cls, name, bases, classdict, **options):
|
||||||
enum = PyEnum(cls.__name__, OrderedDict(classdict, __eq__=eq_enum))
|
enum_members = OrderedDict(classdict, __eq__=eq_enum)
|
||||||
|
# We remove the Meta attribute from the class to not collide
|
||||||
|
# with the enum values.
|
||||||
|
enum_members.pop('Meta', None)
|
||||||
|
enum = PyEnum(cls.__name__, enum_members)
|
||||||
return SubclassWithMeta_Meta.__new__(cls, name, bases, OrderedDict(classdict, __enum__=enum), **options)
|
return SubclassWithMeta_Meta.__new__(cls, name, bases, OrderedDict(classdict, __enum__=enum), **options)
|
||||||
|
|
||||||
def get(cls, value):
|
def get(cls, value):
|
||||||
|
@ -60,8 +64,9 @@ class EnumMeta(SubclassWithMeta_Meta):
|
||||||
class Enum(six.with_metaclass(EnumMeta, UnmountedType, BaseType)):
|
class Enum(six.with_metaclass(EnumMeta, UnmountedType, BaseType)):
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def __init_subclass_with_meta__(cls, enum=None, **options):
|
def __init_subclass_with_meta__(cls, enum=None, _meta=None, **options):
|
||||||
_meta = EnumOptions(cls)
|
if not _meta:
|
||||||
|
_meta = EnumOptions(cls)
|
||||||
_meta.enum = enum or cls.__enum__
|
_meta.enum = enum or cls.__enum__
|
||||||
_meta.deprecation_reason = options.pop('deprecation_reason', None)
|
_meta.deprecation_reason = options.pop('deprecation_reason', None)
|
||||||
for key, value in _meta.enum.__members__.items():
|
for key, value in _meta.enum.__members__.items():
|
||||||
|
|
|
@ -5,7 +5,6 @@ from .inputfield import InputField
|
||||||
from .unmountedtype import UnmountedType
|
from .unmountedtype import UnmountedType
|
||||||
from .utils import yank_fields_from_attrs
|
from .utils import yank_fields_from_attrs
|
||||||
|
|
||||||
|
|
||||||
# For static type checking with Mypy
|
# For static type checking with Mypy
|
||||||
MYPY = False
|
MYPY = False
|
||||||
if MYPY:
|
if MYPY:
|
||||||
|
@ -14,7 +13,7 @@ if MYPY:
|
||||||
|
|
||||||
class InputObjectTypeOptions(BaseOptions):
|
class InputObjectTypeOptions(BaseOptions):
|
||||||
fields = None # type: Dict[str, InputField]
|
fields = None # type: Dict[str, InputField]
|
||||||
create_container = None # type: Callable
|
container = None # type: InputObjectTypeContainer
|
||||||
|
|
||||||
|
|
||||||
class InputObjectTypeContainer(dict, BaseType):
|
class InputObjectTypeContainer(dict, BaseType):
|
||||||
|
@ -23,8 +22,8 @@ class InputObjectTypeContainer(dict, BaseType):
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
dict.__init__(self, *args, **kwargs)
|
dict.__init__(self, *args, **kwargs)
|
||||||
for key, value in self.items():
|
for key in self._meta.fields.keys():
|
||||||
setattr(self, key, value)
|
setattr(self, key, self.get(key, None))
|
||||||
|
|
||||||
def __init_subclass__(cls, *args, **kwargs):
|
def __init_subclass__(cls, *args, **kwargs):
|
||||||
pass
|
pass
|
||||||
|
@ -41,8 +40,9 @@ class InputObjectType(UnmountedType, BaseType):
|
||||||
'''
|
'''
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def __init_subclass_with_meta__(cls, container=None, **options):
|
def __init_subclass_with_meta__(cls, container=None, _meta=None, **options):
|
||||||
_meta = InputObjectTypeOptions(cls)
|
if not _meta:
|
||||||
|
_meta = InputObjectTypeOptions(cls)
|
||||||
|
|
||||||
fields = OrderedDict()
|
fields = OrderedDict()
|
||||||
for base in reversed(cls.__mro__):
|
for base in reversed(cls.__mro__):
|
||||||
|
@ -54,7 +54,8 @@ class InputObjectType(UnmountedType, BaseType):
|
||||||
if container is None:
|
if container is None:
|
||||||
container = type(cls.__name__, (InputObjectTypeContainer, cls), {})
|
container = type(cls.__name__, (InputObjectTypeContainer, cls), {})
|
||||||
_meta.container = container
|
_meta.container = container
|
||||||
super(InputObjectType, cls).__init_subclass_with_meta__(_meta=_meta, **options)
|
super(InputObjectType, cls).__init_subclass_with_meta__(
|
||||||
|
_meta=_meta, **options)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def get_type(cls):
|
def get_type(cls):
|
||||||
|
|
|
@ -50,7 +50,8 @@ class Mutation(ObjectType):
|
||||||
warn_deprecation((
|
warn_deprecation((
|
||||||
"Please use {name}.Arguments instead of {name}.Input."
|
"Please use {name}.Arguments instead of {name}.Input."
|
||||||
"Input is now only used in ClientMutationID.\n"
|
"Input is now only used in ClientMutationID.\n"
|
||||||
"Read more: https://github.com/graphql-python/graphene/blob/2.0/UPGRADE-v2.0.md#mutation-input"
|
"Read more:"
|
||||||
|
" https://github.com/graphql-python/graphene/blob/v2.0.0/UPGRADE-v2.0.md#mutation-input"
|
||||||
).format(name=cls.__name__))
|
).format(name=cls.__name__))
|
||||||
|
|
||||||
if input_class:
|
if input_class:
|
||||||
|
@ -72,10 +73,17 @@ class Mutation(ObjectType):
|
||||||
_meta.resolver = resolver
|
_meta.resolver = resolver
|
||||||
_meta.arguments = arguments
|
_meta.arguments = arguments
|
||||||
|
|
||||||
super(Mutation, cls).__init_subclass_with_meta__(_meta=_meta, **options)
|
super(Mutation, cls).__init_subclass_with_meta__(
|
||||||
|
_meta=_meta, **options)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def Field(cls, *args, **kwargs):
|
def Field(cls, name=None, description=None, deprecation_reason=None, required=False):
|
||||||
return Field(
|
return Field(
|
||||||
cls._meta.output, args=cls._meta.arguments, resolver=cls._meta.resolver
|
cls._meta.output,
|
||||||
|
args=cls._meta.arguments,
|
||||||
|
resolver=cls._meta.resolver,
|
||||||
|
name=name,
|
||||||
|
description=description,
|
||||||
|
deprecation_reason=deprecation_reason,
|
||||||
|
required=required,
|
||||||
)
|
)
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import inspect
|
import inspect
|
||||||
|
|
||||||
from graphql import GraphQLSchema, graphql, is_type
|
from graphql import GraphQLSchema, graphql, is_type, GraphQLObjectType
|
||||||
from graphql.type.directives import (GraphQLDirective, GraphQLIncludeDirective,
|
from graphql.type.directives import (GraphQLDirective, GraphQLIncludeDirective,
|
||||||
GraphQLSkipDirective)
|
GraphQLSkipDirective)
|
||||||
from graphql.type.introspection import IntrospectionSchema
|
from graphql.type.introspection import IntrospectionSchema
|
||||||
|
@ -12,6 +12,17 @@ from .objecttype import ObjectType
|
||||||
from .typemap import TypeMap, is_graphene_type
|
from .typemap import TypeMap, is_graphene_type
|
||||||
|
|
||||||
|
|
||||||
|
def assert_valid_root_type(_type):
|
||||||
|
if _type is None:
|
||||||
|
return
|
||||||
|
is_graphene_objecttype = inspect.isclass(
|
||||||
|
_type) and issubclass(_type, ObjectType)
|
||||||
|
is_graphql_objecttype = isinstance(_type, GraphQLObjectType)
|
||||||
|
assert is_graphene_objecttype or is_graphql_objecttype, (
|
||||||
|
"Type {} is not a valid ObjectType."
|
||||||
|
).format(_type)
|
||||||
|
|
||||||
|
|
||||||
class Schema(GraphQLSchema):
|
class Schema(GraphQLSchema):
|
||||||
'''
|
'''
|
||||||
Schema Definition
|
Schema Definition
|
||||||
|
@ -20,21 +31,23 @@ class Schema(GraphQLSchema):
|
||||||
query and mutation (optional).
|
query and mutation (optional).
|
||||||
'''
|
'''
|
||||||
|
|
||||||
def __init__(self, query=None, mutation=None, subscription=None,
|
def __init__(self,
|
||||||
directives=None, types=None, auto_camelcase=True):
|
query=None,
|
||||||
assert inspect.isclass(query) and issubclass(query, ObjectType), (
|
mutation=None,
|
||||||
'Schema query must be Object Type but got: {}.'
|
subscription=None,
|
||||||
).format(query)
|
directives=None,
|
||||||
|
types=None,
|
||||||
|
auto_camelcase=True):
|
||||||
|
assert_valid_root_type(query)
|
||||||
|
assert_valid_root_type(mutation)
|
||||||
|
assert_valid_root_type(subscription)
|
||||||
self._query = query
|
self._query = query
|
||||||
self._mutation = mutation
|
self._mutation = mutation
|
||||||
self._subscription = subscription
|
self._subscription = subscription
|
||||||
self.types = types
|
self.types = types
|
||||||
self.auto_camelcase = auto_camelcase
|
self.auto_camelcase = auto_camelcase
|
||||||
if directives is None:
|
if directives is None:
|
||||||
directives = [
|
directives = [GraphQLIncludeDirective, GraphQLSkipDirective]
|
||||||
GraphQLIncludeDirective,
|
|
||||||
GraphQLSkipDirective
|
|
||||||
]
|
|
||||||
|
|
||||||
assert all(isinstance(d, GraphQLDirective) for d in directives), \
|
assert all(isinstance(d, GraphQLDirective) for d in directives), \
|
||||||
'Schema directives must be List[GraphQLDirective] if provided but got: {}.'.format(
|
'Schema directives must be List[GraphQLDirective] if provided but got: {}.'.format(
|
||||||
|
@ -61,7 +74,8 @@ class Schema(GraphQLSchema):
|
||||||
'''
|
'''
|
||||||
_type = super(Schema, self).get_type(type_name)
|
_type = super(Schema, self).get_type(type_name)
|
||||||
if _type is None:
|
if _type is None:
|
||||||
raise AttributeError('Type "{}" not found in the Schema'.format(type_name))
|
raise AttributeError(
|
||||||
|
'Type "{}" not found in the Schema'.format(type_name))
|
||||||
if isinstance(_type, GrapheneGraphQLType):
|
if isinstance(_type, GrapheneGraphQLType):
|
||||||
return _type.graphene_type
|
return _type.graphene_type
|
||||||
return _type
|
return _type
|
||||||
|
@ -73,7 +87,8 @@ class Schema(GraphQLSchema):
|
||||||
return _type
|
return _type
|
||||||
if is_graphene_type(_type):
|
if is_graphene_type(_type):
|
||||||
graphql_type = self.get_type(_type._meta.name)
|
graphql_type = self.get_type(_type._meta.name)
|
||||||
assert graphql_type, "Type {} not found in this schema.".format(_type._meta.name)
|
assert graphql_type, "Type {} not found in this schema.".format(
|
||||||
|
_type._meta.name)
|
||||||
assert graphql_type.graphene_type == _type
|
assert graphql_type.graphene_type == _type
|
||||||
return graphql_type
|
return graphql_type
|
||||||
raise Exception("{} is not a valid GraphQL type.".format(_type))
|
raise Exception("{} is not a valid GraphQL type.".format(_type))
|
||||||
|
@ -102,4 +117,8 @@ class Schema(GraphQLSchema):
|
||||||
]
|
]
|
||||||
if self.types:
|
if self.types:
|
||||||
initial_types += self.types
|
initial_types += self.types
|
||||||
self._type_map = TypeMap(initial_types, auto_camelcase=self.auto_camelcase, schema=self)
|
self._type_map = TypeMap(
|
||||||
|
initial_types,
|
||||||
|
auto_camelcase=self.auto_camelcase,
|
||||||
|
schema=self
|
||||||
|
)
|
||||||
|
|
|
@ -2,6 +2,8 @@ import datetime
|
||||||
|
|
||||||
import pytz
|
import pytz
|
||||||
|
|
||||||
|
from graphql import GraphQLError
|
||||||
|
|
||||||
from ..datetime import DateTime, Date, Time
|
from ..datetime import DateTime, Date, Time
|
||||||
from ..objecttype import ObjectType
|
from ..objecttype import ObjectType
|
||||||
from ..schema import Schema
|
from ..schema import Schema
|
||||||
|
@ -34,7 +36,7 @@ def test_datetime_query():
|
||||||
assert result.data == {'datetime': isoformat}
|
assert result.data == {'datetime': isoformat}
|
||||||
|
|
||||||
|
|
||||||
def test_datetime_query():
|
def test_date_query():
|
||||||
now = datetime.datetime.now().replace(tzinfo=pytz.utc).date()
|
now = datetime.datetime.now().replace(tzinfo=pytz.utc).date()
|
||||||
isoformat = now.isoformat()
|
isoformat = now.isoformat()
|
||||||
|
|
||||||
|
@ -53,6 +55,32 @@ def test_time_query():
|
||||||
assert not result.errors
|
assert not result.errors
|
||||||
assert result.data == {'time': isoformat}
|
assert result.data == {'time': isoformat}
|
||||||
|
|
||||||
|
def test_bad_datetime_query():
|
||||||
|
not_a_date = "Some string that's not a date"
|
||||||
|
|
||||||
|
result = schema.execute('''{ datetime(in: "%s") }''' % not_a_date)
|
||||||
|
|
||||||
|
assert len(result.errors) == 1
|
||||||
|
assert isinstance(result.errors[0], GraphQLError)
|
||||||
|
assert result.data == None
|
||||||
|
|
||||||
|
def test_bad_date_query():
|
||||||
|
not_a_date = "Some string that's not a date"
|
||||||
|
|
||||||
|
result = schema.execute('''{ date(in: "%s") }''' % not_a_date)
|
||||||
|
|
||||||
|
assert len(result.errors) == 1
|
||||||
|
assert isinstance(result.errors[0], GraphQLError)
|
||||||
|
assert result.data == None
|
||||||
|
|
||||||
|
def test_bad_time_query():
|
||||||
|
not_a_date = "Some string that's not a date"
|
||||||
|
|
||||||
|
result = schema.execute('''{ time(at: "%s") }''' % not_a_date)
|
||||||
|
|
||||||
|
assert len(result.errors) == 1
|
||||||
|
assert isinstance(result.errors[0], GraphQLError)
|
||||||
|
assert result.data == None
|
||||||
|
|
||||||
def test_datetime_query_variable():
|
def test_datetime_query_variable():
|
||||||
now = datetime.datetime.now().replace(tzinfo=pytz.utc)
|
now = datetime.datetime.now().replace(tzinfo=pytz.utc)
|
||||||
|
|
|
@ -80,7 +80,8 @@ def test_enum_from_builtin_enum_accepts_lambda_description():
|
||||||
return 'meh' if value == Episode.NEWHOPE else None
|
return 'meh' if value == Episode.NEWHOPE else None
|
||||||
|
|
||||||
PyEpisode = PyEnum('PyEpisode', 'NEWHOPE,EMPIRE,JEDI')
|
PyEpisode = PyEnum('PyEpisode', 'NEWHOPE,EMPIRE,JEDI')
|
||||||
Episode = Enum.from_enum(PyEpisode, description=custom_description, deprecation_reason=custom_deprecation_reason)
|
Episode = Enum.from_enum(PyEpisode, description=custom_description,
|
||||||
|
deprecation_reason=custom_deprecation_reason)
|
||||||
|
|
||||||
class Query(ObjectType):
|
class Query(ObjectType):
|
||||||
foo = Episode()
|
foo = Episode()
|
||||||
|
@ -214,3 +215,19 @@ def test_enum_to_enum_comparison_should_differ():
|
||||||
assert RGB1.RED != RGB2.RED
|
assert RGB1.RED != RGB2.RED
|
||||||
assert RGB1.GREEN != RGB2.GREEN
|
assert RGB1.GREEN != RGB2.GREEN
|
||||||
assert RGB1.BLUE != RGB2.BLUE
|
assert RGB1.BLUE != RGB2.BLUE
|
||||||
|
|
||||||
|
|
||||||
|
def test_enum_skip_meta_from_members():
|
||||||
|
class RGB1(Enum):
|
||||||
|
class Meta:
|
||||||
|
name = 'RGB'
|
||||||
|
|
||||||
|
RED = 1
|
||||||
|
GREEN = 2
|
||||||
|
BLUE = 3
|
||||||
|
|
||||||
|
assert dict(RGB1._meta.enum.__members__) == {
|
||||||
|
'RED': RGB1.RED,
|
||||||
|
'GREEN': RGB1.GREEN,
|
||||||
|
'BLUE': RGB1.BLUE,
|
||||||
|
}
|
||||||
|
|
|
@ -5,6 +5,8 @@ from ..inputfield import InputField
|
||||||
from ..inputobjecttype import InputObjectType
|
from ..inputobjecttype import InputObjectType
|
||||||
from ..objecttype import ObjectType
|
from ..objecttype import ObjectType
|
||||||
from ..unmountedtype import UnmountedType
|
from ..unmountedtype import UnmountedType
|
||||||
|
from ..scalars import String, Boolean
|
||||||
|
from ..schema import Schema
|
||||||
|
|
||||||
|
|
||||||
class MyType(object):
|
class MyType(object):
|
||||||
|
@ -51,7 +53,8 @@ def test_ordered_fields_in_inputobjecttype():
|
||||||
field = MyScalar()
|
field = MyScalar()
|
||||||
asa = InputField(MyType)
|
asa = InputField(MyType)
|
||||||
|
|
||||||
assert list(MyInputObjectType._meta.fields.keys()) == ['b', 'a', 'field', 'asa']
|
assert list(MyInputObjectType._meta.fields.keys()) == [
|
||||||
|
'b', 'a', 'field', 'asa']
|
||||||
|
|
||||||
|
|
||||||
def test_generate_inputobjecttype_unmountedtype():
|
def test_generate_inputobjecttype_unmountedtype():
|
||||||
|
@ -86,7 +89,8 @@ def test_generate_inputobjecttype_inherit_abstracttype():
|
||||||
field2 = MyScalar(MyType)
|
field2 = MyScalar(MyType)
|
||||||
|
|
||||||
assert list(MyInputObjectType._meta.fields.keys()) == ['field1', 'field2']
|
assert list(MyInputObjectType._meta.fields.keys()) == ['field1', 'field2']
|
||||||
assert [type(x) for x in MyInputObjectType._meta.fields.values()] == [InputField, InputField]
|
assert [type(x) for x in MyInputObjectType._meta.fields.values()] == [
|
||||||
|
InputField, InputField]
|
||||||
|
|
||||||
|
|
||||||
def test_generate_inputobjecttype_inherit_abstracttype_reversed():
|
def test_generate_inputobjecttype_inherit_abstracttype_reversed():
|
||||||
|
@ -97,4 +101,34 @@ def test_generate_inputobjecttype_inherit_abstracttype_reversed():
|
||||||
field2 = MyScalar(MyType)
|
field2 = MyScalar(MyType)
|
||||||
|
|
||||||
assert list(MyInputObjectType._meta.fields.keys()) == ['field1', 'field2']
|
assert list(MyInputObjectType._meta.fields.keys()) == ['field1', 'field2']
|
||||||
assert [type(x) for x in MyInputObjectType._meta.fields.values()] == [InputField, InputField]
|
assert [type(x) for x in MyInputObjectType._meta.fields.values()] == [
|
||||||
|
InputField, InputField]
|
||||||
|
|
||||||
|
|
||||||
|
def test_inputobjecttype_of_input():
|
||||||
|
class Child(InputObjectType):
|
||||||
|
first_name = String()
|
||||||
|
last_name = String()
|
||||||
|
|
||||||
|
@property
|
||||||
|
def full_name(self):
|
||||||
|
return "{} {}".format(self.first_name, self.last_name)
|
||||||
|
|
||||||
|
class Parent(InputObjectType):
|
||||||
|
child = InputField(Child)
|
||||||
|
|
||||||
|
class Query(ObjectType):
|
||||||
|
is_child = Boolean(parent=Parent())
|
||||||
|
|
||||||
|
def resolve_is_child(self, info, parent):
|
||||||
|
return isinstance(parent.child, Child) and parent.child.full_name == "Peter Griffin"
|
||||||
|
|
||||||
|
schema = Schema(query=Query)
|
||||||
|
result = schema.execute('''query basequery {
|
||||||
|
isChild(parent: {child: {firstName: "Peter", lastName: "Griffin"}})
|
||||||
|
}
|
||||||
|
''')
|
||||||
|
assert not result.errors
|
||||||
|
assert result.data == {
|
||||||
|
'isChild': True
|
||||||
|
}
|
||||||
|
|
|
@ -6,6 +6,7 @@ from ..mutation import Mutation
|
||||||
from ..objecttype import ObjectType
|
from ..objecttype import ObjectType
|
||||||
from ..scalars import String
|
from ..scalars import String
|
||||||
from ..schema import Schema
|
from ..schema import Schema
|
||||||
|
from ..structures import NonNull
|
||||||
|
|
||||||
|
|
||||||
def test_generate_mutation_no_args():
|
def test_generate_mutation_no_args():
|
||||||
|
@ -133,3 +134,27 @@ def test_mutation_no_fields_output():
|
||||||
'name': None,
|
'name': None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def test_mutation_allow_to_have_custom_args():
|
||||||
|
class CreateUser(Mutation):
|
||||||
|
|
||||||
|
class Arguments:
|
||||||
|
name = String()
|
||||||
|
|
||||||
|
name = String()
|
||||||
|
|
||||||
|
def mutate(self, info, name):
|
||||||
|
return CreateUser(name=name)
|
||||||
|
|
||||||
|
class MyMutation(ObjectType):
|
||||||
|
create_user = CreateUser.Field(
|
||||||
|
description='Create a user',
|
||||||
|
deprecation_reason='Is deprecated',
|
||||||
|
required=True
|
||||||
|
)
|
||||||
|
|
||||||
|
field = MyMutation._meta.fields['create_user']
|
||||||
|
assert field.description == 'Create a user'
|
||||||
|
assert field.deprecation_reason == 'Is deprecated'
|
||||||
|
assert field.type == NonNull(CreateUser)
|
||||||
|
|
|
@ -44,6 +44,8 @@ def test_generate_objecttype():
|
||||||
assert MyObjectType._meta.description == "Documentation"
|
assert MyObjectType._meta.description == "Documentation"
|
||||||
assert MyObjectType._meta.interfaces == tuple()
|
assert MyObjectType._meta.interfaces == tuple()
|
||||||
assert MyObjectType._meta.fields == {}
|
assert MyObjectType._meta.fields == {}
|
||||||
|
assert repr(
|
||||||
|
MyObjectType) == "<MyObjectType meta=<ObjectTypeOptions name='MyObjectType'>>"
|
||||||
|
|
||||||
|
|
||||||
def test_generate_objecttype_with_meta():
|
def test_generate_objecttype_with_meta():
|
||||||
|
@ -65,7 +67,6 @@ def test_generate_lazy_objecttype():
|
||||||
|
|
||||||
class InnerObjectType(ObjectType):
|
class InnerObjectType(ObjectType):
|
||||||
field = Field(MyType)
|
field = Field(MyType)
|
||||||
|
|
||||||
|
|
||||||
assert MyObjectType._meta.name == "MyObjectType"
|
assert MyObjectType._meta.name == "MyObjectType"
|
||||||
example_field = MyObjectType._meta.fields['example']
|
example_field = MyObjectType._meta.fields['example']
|
||||||
|
@ -115,7 +116,8 @@ def test_generate_objecttype_inherit_abstracttype():
|
||||||
assert MyObjectType._meta.interfaces == ()
|
assert MyObjectType._meta.interfaces == ()
|
||||||
assert MyObjectType._meta.name == "MyObjectType"
|
assert MyObjectType._meta.name == "MyObjectType"
|
||||||
assert list(MyObjectType._meta.fields.keys()) == ['field1', 'field2']
|
assert list(MyObjectType._meta.fields.keys()) == ['field1', 'field2']
|
||||||
assert list(map(type, MyObjectType._meta.fields.values())) == [Field, Field]
|
assert list(map(type, MyObjectType._meta.fields.values())) == [
|
||||||
|
Field, Field]
|
||||||
|
|
||||||
|
|
||||||
def test_generate_objecttype_inherit_abstracttype_reversed():
|
def test_generate_objecttype_inherit_abstracttype_reversed():
|
||||||
|
@ -129,7 +131,8 @@ def test_generate_objecttype_inherit_abstracttype_reversed():
|
||||||
assert MyObjectType._meta.interfaces == ()
|
assert MyObjectType._meta.interfaces == ()
|
||||||
assert MyObjectType._meta.name == "MyObjectType"
|
assert MyObjectType._meta.name == "MyObjectType"
|
||||||
assert list(MyObjectType._meta.fields.keys()) == ['field1', 'field2']
|
assert list(MyObjectType._meta.fields.keys()) == ['field1', 'field2']
|
||||||
assert list(map(type, MyObjectType._meta.fields.values())) == [Field, Field]
|
assert list(map(type, MyObjectType._meta.fields.values())) == [
|
||||||
|
Field, Field]
|
||||||
|
|
||||||
|
|
||||||
def test_generate_objecttype_unmountedtype():
|
def test_generate_objecttype_unmountedtype():
|
||||||
|
@ -145,7 +148,8 @@ def test_parent_container_get_fields():
|
||||||
|
|
||||||
|
|
||||||
def test_parent_container_interface_get_fields():
|
def test_parent_container_interface_get_fields():
|
||||||
assert list(ContainerWithInterface._meta.fields.keys()) == ['ifield', 'field1', 'field2']
|
assert list(ContainerWithInterface._meta.fields.keys()) == [
|
||||||
|
'ifield', 'field1', 'field2']
|
||||||
|
|
||||||
|
|
||||||
def test_objecttype_as_container_only_args():
|
def test_objecttype_as_container_only_args():
|
||||||
|
@ -182,7 +186,8 @@ def test_objecttype_as_container_invalid_kwargs():
|
||||||
with pytest.raises(TypeError) as excinfo:
|
with pytest.raises(TypeError) as excinfo:
|
||||||
Container(unexisting_field="3")
|
Container(unexisting_field="3")
|
||||||
|
|
||||||
assert "'unexisting_field' is an invalid keyword argument for Container" == str(excinfo.value)
|
assert "'unexisting_field' is an invalid keyword argument for Container" == str(
|
||||||
|
excinfo.value)
|
||||||
|
|
||||||
|
|
||||||
def test_objecttype_container_benchmark(benchmark):
|
def test_objecttype_container_benchmark(benchmark):
|
||||||
|
@ -238,7 +243,6 @@ def test_objecttype_no_fields_output():
|
||||||
def resolve_user(self, info):
|
def resolve_user(self, info):
|
||||||
return User()
|
return User()
|
||||||
|
|
||||||
|
|
||||||
schema = Schema(query=Query)
|
schema = Schema(query=Query)
|
||||||
result = schema.execute(''' query basequery {
|
result = schema.execute(''' query basequery {
|
||||||
user {
|
user {
|
||||||
|
@ -252,3 +256,12 @@ def test_objecttype_no_fields_output():
|
||||||
'name': None,
|
'name': None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def test_abstract_objecttype_can_str():
|
||||||
|
class MyObjectType(ObjectType):
|
||||||
|
class Meta:
|
||||||
|
abstract = True
|
||||||
|
field = MyScalar()
|
||||||
|
|
||||||
|
assert str(MyObjectType) == "MyObjectType"
|
||||||
|
|
|
@ -1,9 +1,11 @@
|
||||||
|
import pytest
|
||||||
|
|
||||||
from graphql.type import (GraphQLArgument, GraphQLEnumType, GraphQLEnumValue,
|
from graphql.type import (GraphQLArgument, GraphQLEnumType, GraphQLEnumValue,
|
||||||
GraphQLField, GraphQLInputObjectField,
|
GraphQLField, GraphQLInputObjectField,
|
||||||
GraphQLInputObjectType, GraphQLInterfaceType,
|
GraphQLInputObjectType, GraphQLInterfaceType,
|
||||||
GraphQLObjectType, GraphQLString)
|
GraphQLObjectType, GraphQLString)
|
||||||
|
|
||||||
|
from ..structures import List, NonNull
|
||||||
from ..dynamic import Dynamic
|
from ..dynamic import Dynamic
|
||||||
from ..enum import Enum
|
from ..enum import Enum
|
||||||
from ..field import Field
|
from ..field import Field
|
||||||
|
@ -11,8 +13,8 @@ from ..inputfield import InputField
|
||||||
from ..inputobjecttype import InputObjectType
|
from ..inputobjecttype import InputObjectType
|
||||||
from ..interface import Interface
|
from ..interface import Interface
|
||||||
from ..objecttype import ObjectType
|
from ..objecttype import ObjectType
|
||||||
from ..scalars import String
|
from ..scalars import String, Int
|
||||||
from ..typemap import TypeMap
|
from ..typemap import TypeMap, resolve_type
|
||||||
|
|
||||||
|
|
||||||
def test_enum():
|
def test_enum():
|
||||||
|
@ -38,7 +40,8 @@ def test_enum():
|
||||||
assert graphql_enum.description == 'Description'
|
assert graphql_enum.description == 'Description'
|
||||||
values = graphql_enum.values
|
values = graphql_enum.values
|
||||||
assert values == [
|
assert values == [
|
||||||
GraphQLEnumValue(name='foo', value=1, description='Description foo=1', deprecation_reason='Is deprecated'),
|
GraphQLEnumValue(name='foo', value=1, description='Description foo=1',
|
||||||
|
deprecation_reason='Is deprecated'),
|
||||||
GraphQLEnumValue(name='bar', value=2, description='Description bar=2'),
|
GraphQLEnumValue(name='bar', value=2, description='Description bar=2'),
|
||||||
]
|
]
|
||||||
|
|
||||||
|
@ -46,7 +49,8 @@ def test_enum():
|
||||||
def test_objecttype():
|
def test_objecttype():
|
||||||
class MyObjectType(ObjectType):
|
class MyObjectType(ObjectType):
|
||||||
'''Description'''
|
'''Description'''
|
||||||
foo = String(bar=String(description='Argument description', default_value='x'), description='Field description')
|
foo = String(bar=String(description='Argument description',
|
||||||
|
default_value='x'), description='Field description')
|
||||||
bar = String(name='gizmo')
|
bar = String(name='gizmo')
|
||||||
|
|
||||||
def resolve_foo(self, bar):
|
def resolve_foo(self, bar):
|
||||||
|
@ -91,8 +95,10 @@ def test_dynamic_objecttype():
|
||||||
def test_interface():
|
def test_interface():
|
||||||
class MyInterface(Interface):
|
class MyInterface(Interface):
|
||||||
'''Description'''
|
'''Description'''
|
||||||
foo = String(bar=String(description='Argument description', default_value='x'), description='Field description')
|
foo = String(bar=String(description='Argument description',
|
||||||
bar = String(name='gizmo', first_arg=String(), other_arg=String(name='oth_arg'))
|
default_value='x'), description='Field description')
|
||||||
|
bar = String(name='gizmo', first_arg=String(),
|
||||||
|
other_arg=String(name='oth_arg'))
|
||||||
own = Field(lambda: MyInterface)
|
own = Field(lambda: MyInterface)
|
||||||
|
|
||||||
def resolve_foo(self, args, info):
|
def resolve_foo(self, args, info):
|
||||||
|
@ -119,10 +125,18 @@ def test_interface():
|
||||||
|
|
||||||
|
|
||||||
def test_inputobject():
|
def test_inputobject():
|
||||||
|
class OtherObjectType(InputObjectType):
|
||||||
|
thingy = NonNull(Int)
|
||||||
|
|
||||||
|
class MyInnerObjectType(InputObjectType):
|
||||||
|
some_field = String()
|
||||||
|
some_other_field = List(OtherObjectType)
|
||||||
|
|
||||||
class MyInputObjectType(InputObjectType):
|
class MyInputObjectType(InputObjectType):
|
||||||
'''Description'''
|
'''Description'''
|
||||||
foo_bar = String(description='Field description')
|
foo_bar = String(description='Field description')
|
||||||
bar = String(name='gizmo')
|
bar = String(name='gizmo')
|
||||||
|
baz = NonNull(MyInnerObjectType)
|
||||||
own = InputField(lambda: MyInputObjectType)
|
own = InputField(lambda: MyInputObjectType)
|
||||||
|
|
||||||
def resolve_foo_bar(self, args, info):
|
def resolve_foo_bar(self, args, info):
|
||||||
|
@ -135,15 +149,28 @@ def test_inputobject():
|
||||||
assert graphql_type.name == 'MyInputObjectType'
|
assert graphql_type.name == 'MyInputObjectType'
|
||||||
assert graphql_type.description == 'Description'
|
assert graphql_type.description == 'Description'
|
||||||
|
|
||||||
# Container
|
other_graphql_type = typemap['OtherObjectType']
|
||||||
container = graphql_type.create_container({'bar': 'oh!'})
|
inner_graphql_type = typemap['MyInnerObjectType']
|
||||||
|
container = graphql_type.create_container({
|
||||||
|
'bar': 'oh!',
|
||||||
|
'baz': inner_graphql_type.create_container({
|
||||||
|
'some_other_field': [
|
||||||
|
other_graphql_type.create_container({'thingy': 1}),
|
||||||
|
other_graphql_type.create_container({'thingy': 2})
|
||||||
|
]
|
||||||
|
})
|
||||||
|
})
|
||||||
assert isinstance(container, MyInputObjectType)
|
assert isinstance(container, MyInputObjectType)
|
||||||
assert 'bar' in container
|
assert 'bar' in container
|
||||||
assert container.bar == 'oh!'
|
assert container.bar == 'oh!'
|
||||||
assert 'foo_bar' not in container
|
assert 'foo_bar' not in container
|
||||||
|
assert container.foo_bar is None
|
||||||
|
assert container.baz.some_field is None
|
||||||
|
assert container.baz.some_other_field[0].thingy == 1
|
||||||
|
assert container.baz.some_other_field[1].thingy == 2
|
||||||
|
|
||||||
fields = graphql_type.fields
|
fields = graphql_type.fields
|
||||||
assert list(fields.keys()) == ['fooBar', 'gizmo', 'own']
|
assert list(fields.keys()) == ['fooBar', 'gizmo', 'baz', 'own']
|
||||||
own_field = fields['own']
|
own_field = fields['own']
|
||||||
assert own_field.type == graphql_type
|
assert own_field.type == graphql_type
|
||||||
foo_field = fields['fooBar']
|
foo_field = fields['fooBar']
|
||||||
|
@ -206,3 +233,22 @@ def test_objecttype_with_possible_types():
|
||||||
assert graphql_type.is_type_of
|
assert graphql_type.is_type_of
|
||||||
assert graphql_type.is_type_of({}, None) is True
|
assert graphql_type.is_type_of({}, None) is True
|
||||||
assert graphql_type.is_type_of(MyObjectType(), None) is False
|
assert graphql_type.is_type_of(MyObjectType(), None) is False
|
||||||
|
|
||||||
|
|
||||||
|
def test_resolve_type_with_missing_type():
|
||||||
|
class MyObjectType(ObjectType):
|
||||||
|
foo_bar = String()
|
||||||
|
|
||||||
|
class MyOtherObjectType(ObjectType):
|
||||||
|
fizz_buzz = String()
|
||||||
|
|
||||||
|
def resolve_type_func(root, info):
|
||||||
|
return MyOtherObjectType
|
||||||
|
|
||||||
|
typemap = TypeMap([MyObjectType])
|
||||||
|
with pytest.raises(AssertionError) as excinfo:
|
||||||
|
resolve_type(
|
||||||
|
resolve_type_func, typemap, 'MyOtherObjectType', {}, {}
|
||||||
|
)
|
||||||
|
|
||||||
|
assert 'MyOtherObjectTyp' in str(excinfo.value)
|
||||||
|
|
|
@ -46,7 +46,10 @@ def resolve_type(resolve_type_func, map, type_name, root, info):
|
||||||
|
|
||||||
if inspect.isclass(_type) and issubclass(_type, ObjectType):
|
if inspect.isclass(_type) and issubclass(_type, ObjectType):
|
||||||
graphql_type = map.get(_type._meta.name)
|
graphql_type = map.get(_type._meta.name)
|
||||||
assert graphql_type and graphql_type.graphene_type == _type, (
|
assert graphql_type, "Can't find type {} in schema".format(
|
||||||
|
_type._meta.name
|
||||||
|
)
|
||||||
|
assert graphql_type.graphene_type == _type, (
|
||||||
'The type {} does not match with the associated graphene type {}.'
|
'The type {} does not match with the associated graphene type {}.'
|
||||||
).format(_type, graphql_type.graphene_type)
|
).format(_type, graphql_type.graphene_type)
|
||||||
return graphql_type
|
return graphql_type
|
||||||
|
|
|
@ -1,13 +1,13 @@
|
||||||
import re
|
import re
|
||||||
|
|
||||||
|
|
||||||
# From this response in Stackoverflow
|
# Adapted from this response in Stackoverflow
|
||||||
# http://stackoverflow.com/a/19053800/1072990
|
# http://stackoverflow.com/a/19053800/1072990
|
||||||
def to_camel_case(snake_str):
|
def to_camel_case(snake_str):
|
||||||
components = snake_str.split('_')
|
components = snake_str.split('_')
|
||||||
# We capitalize the first letter of each component except the first one
|
# We capitalize the first letter of each component except the first one
|
||||||
# with the 'title' method and join them together.
|
# with the 'capitalize' method and join them together.
|
||||||
return components[0] + "".join(x.title() if x else '_' for x in components[1:])
|
return components[0] + ''.join(x.capitalize() if x else '_' for x in components[1:])
|
||||||
|
|
||||||
|
|
||||||
# From this response in Stackoverflow
|
# From this response in Stackoverflow
|
||||||
|
|
|
@ -6,9 +6,15 @@ from .props import props
|
||||||
|
|
||||||
|
|
||||||
class SubclassWithMeta_Meta(InitSubclassMeta):
|
class SubclassWithMeta_Meta(InitSubclassMeta):
|
||||||
|
_meta = None
|
||||||
|
|
||||||
|
def __str__(cls):
|
||||||
|
if cls._meta:
|
||||||
|
return cls._meta.name
|
||||||
|
return cls.__name__
|
||||||
|
|
||||||
def __repr__(cls):
|
def __repr__(cls):
|
||||||
return cls._meta.name
|
return "<{} meta={}>".format(cls.__name__, repr(cls._meta))
|
||||||
|
|
||||||
|
|
||||||
class SubclassWithMeta(six.with_metaclass(SubclassWithMeta_Meta)):
|
class SubclassWithMeta(six.with_metaclass(SubclassWithMeta_Meta)):
|
||||||
|
@ -24,7 +30,8 @@ class SubclassWithMeta(six.with_metaclass(SubclassWithMeta_Meta)):
|
||||||
elif isclass(_Meta):
|
elif isclass(_Meta):
|
||||||
_meta_props = props(_Meta)
|
_meta_props = props(_Meta)
|
||||||
else:
|
else:
|
||||||
raise Exception("Meta have to be either a class or a dict. Received {}".format(_Meta))
|
raise Exception(
|
||||||
|
"Meta have to be either a class or a dict. Received {}".format(_Meta))
|
||||||
delattr(cls, "Meta")
|
delattr(cls, "Meta")
|
||||||
options = dict(meta_options, **_meta_props)
|
options = dict(meta_options, **_meta_props)
|
||||||
|
|
||||||
|
|
|
@ -16,6 +16,7 @@ def test_camel_case():
|
||||||
assert to_camel_case('snakes_on_a_plane') == 'snakesOnAPlane'
|
assert to_camel_case('snakes_on_a_plane') == 'snakesOnAPlane'
|
||||||
assert to_camel_case('snakes_on_a__plane') == 'snakesOnA_Plane'
|
assert to_camel_case('snakes_on_a__plane') == 'snakesOnA_Plane'
|
||||||
assert to_camel_case('i_phone_hysteria') == 'iPhoneHysteria'
|
assert to_camel_case('i_phone_hysteria') == 'iPhoneHysteria'
|
||||||
|
assert to_camel_case('field_i18n') == 'fieldI18n'
|
||||||
|
|
||||||
|
|
||||||
def test_to_const():
|
def test_to_const():
|
||||||
|
|
2
setup.py
2
setup.py
|
@ -83,7 +83,7 @@ setup(
|
||||||
|
|
||||||
keywords='api graphql protocol rest relay graphene',
|
keywords='api graphql protocol rest relay graphene',
|
||||||
|
|
||||||
packages=find_packages(exclude=['tests', 'tests.*']),
|
packages=find_packages(exclude=['tests', 'tests.*', 'examples']),
|
||||||
|
|
||||||
install_requires=[
|
install_requires=[
|
||||||
'six>=1.10.0,<2',
|
'six>=1.10.0,<2',
|
||||||
|
|
Loading…
Reference in New Issue
Block a user