Merge branch 'master' into master

This commit is contained in:
Syrus Akbary 2016-12-18 13:12:51 -08:00 committed by GitHub
commit dda294d911
57 changed files with 1187 additions and 183 deletions

View File

@ -6,19 +6,19 @@ python:
- 3.5
- pypy
before_install:
- |
if [ "$TRAVIS_PYTHON_VERSION" = "pypy" ]; then
export PYENV_ROOT="$HOME/.pyenv"
if [ -f "$PYENV_ROOT/bin/pyenv" ]; then
cd "$PYENV_ROOT" && git pull
else
rm -rf "$PYENV_ROOT" && git clone --depth 1 https://github.com/yyuu/pyenv.git "$PYENV_ROOT"
fi
export PYPY_VERSION="4.0.1"
"$PYENV_ROOT/bin/pyenv" install "pypy-$PYPY_VERSION"
virtualenv --python="$PYENV_ROOT/versions/pypy-$PYPY_VERSION/bin/python" "$HOME/virtualenvs/pypy-$PYPY_VERSION"
source "$HOME/virtualenvs/pypy-$PYPY_VERSION/bin/activate"
fi
- |
if [ "$TRAVIS_PYTHON_VERSION" = "pypy" ]; then
export PYENV_ROOT="$HOME/.pyenv"
if [ -f "$PYENV_ROOT/bin/pyenv" ]; then
cd "$PYENV_ROOT" && git pull
else
rm -rf "$PYENV_ROOT" && git clone --depth 1 https://github.com/yyuu/pyenv.git "$PYENV_ROOT"
fi
export PYPY_VERSION="4.0.1"
"$PYENV_ROOT/bin/pyenv" install "pypy-$PYPY_VERSION"
virtualenv --python="$PYENV_ROOT/versions/pypy-$PYPY_VERSION/bin/python" "$HOME/virtualenvs/pypy-$PYPY_VERSION"
source "$HOME/virtualenvs/pypy-$PYPY_VERSION/bin/activate"
fi
install:
- |
if [ "$TEST_TYPE" = build ]; then
@ -52,3 +52,10 @@ matrix:
include:
- python: '2.7'
env: TEST_TYPE=lint
deploy:
provider: pypi
user: syrusakbary
on:
tags: true
password:
secure: LHOp9DvYR+70vj4YVY8+JRNCKUOfYZREEUY3+4lMUpY7Zy5QwDfgEMXG64ybREH9dFldpUqVXRj53eeU3spfudSfh8NHkgqW7qihez2AhSnRc4dK6ooNfB+kLcSoJ4nUFGxdYImABc4V1hJvflGaUkTwDNYVxJF938bPaO797IvSbuI86llwqkvuK2Vegv9q/fy9sVGaF9VZIs4JgXwR5AyDR7FBArl+S84vWww4vTFD33hoE88VR4QvFY3/71BwRtQrnCMm7AOm31P9u29yi3bpzQpiOR2rHsgrsYdm597QzFKVxYwsmf9uAx2bpbSPy2WibunLePIvOFwm8xcfwnz4/J4ONBc5PSFmUytTWpzEnxb0bfUNLuYloIS24V6OZ8BfAhiYZ1AwySeJCQDM4Vk1V8IF6trTtyx5EW/uV9jsHCZ3LFsAD7UnFRTosIgN3SAK3ZWCEk5oF2IvjecsolEfkRXB3q9EjMkkuXRUeFDH2lWJLgNE27BzY6myvZVzPmfwZUsPBlPD/6w+WLSp97Rjgr9zS3T1d4ddqFM4ZYu04f2i7a/UUQqG+itzzuX5DWLPvzuNt37JB45mB9IsvxPyXZ6SkAcLl48NGyKok1f3vQnvphkfkl4lni29woKhaau8xlsuEDrcwOoeAsVcZXiItg+l+z2SlIwM0A06EvQ=

View File

@ -83,3 +83,21 @@ After developing, the full test suite can be evaluated by running:
```sh
python setup.py test # Use --pytest-args="-v -s" for verbose mode
```
### Documentation
The documentation is generated using the excellent [Sphinx](http://www.sphinx-doc.org/) and a custom theme.
The documentation dependencies are installed by running:
```sh
cd docs
pip install -r requirements.txt
```
Then to produce a HTML version of the documentation:
```sh
make html
```

View File

@ -1,37 +1,38 @@
Please read `UPGRADE-v1.0.md`_ to learn how to upgrade to Graphene ``1.0``.
Please read `UPGRADE-v1.0.md </UPGRADE-v1.0.md>`__ to learn how to
upgrade to Graphene ``1.0``.
--------------
|Graphene Logo| `Graphene`_ |Build Status| |PyPI version| |Coverage Status|
===========================================================================
|Graphene Logo| `Graphene <http://graphene-python.org>`__ |Build Status| |PyPI version| |Coverage Status|
=========================================================================================================
`Graphene`_ is a Python library for building GraphQL schemas/types fast
and easily.
`Graphene <http://graphene-python.org>`__ is a Python library for
building GraphQL schemas/types fast and easily.
- **Easy to use:** Graphene helps you use GraphQL in Python without
effort.
- **Relay:** Graphene has builtin support for Relay
- **Data agnostic:** Graphene supports any kind of data source: SQL
(Django, SQLAlchemy), NoSQL, custom Python objects, etc. We believe that
by providing a complete API you could plug Graphene anywhere your
data lives and make your data available through GraphQL.
(Django, SQLAlchemy), NoSQL, custom Python objects, etc. We believe
that by providing a complete API you could plug Graphene anywhere
your data lives and make your data available through GraphQL.
Integrations
------------
Graphene has multiple integrations with different frameworks:
+---------------------+-------------------------------------+
| integration | Package |
+=====================+=====================================+
| Django | `graphene-django`_ |
+---------------------+-------------------------------------+
| SQLAlchemy | `graphene-sqlalchemy`_ |
+---------------------+-------------------------------------+
| Google App Engine | `graphene-gae`_ |
+---------------------+-------------------------------------+
| Peewee | *In progress* (`Tracking Issue`_) |
+---------------------+-------------------------------------+
+---------------------+----------------------------------------------------------------------------------------------+
| integration | Package |
+=====================+==============================================================================================+
| Django | `graphene-django <https://github.com/graphql-python/graphene-django/>`__ |
+---------------------+----------------------------------------------------------------------------------------------+
| SQLAlchemy | `graphene-sqlalchemy <https://github.com/graphql-python/graphene-sqlalchemy/>`__ |
+---------------------+----------------------------------------------------------------------------------------------+
| Google App Engine | `graphene-gae <https://github.com/graphql-python/graphene-gae/>`__ |
+---------------------+----------------------------------------------------------------------------------------------+
| Peewee | *In progress* (`Tracking Issue <https://github.com/graphql-python/graphene/issues/289>`__) |
+---------------------+----------------------------------------------------------------------------------------------+
Installation
------------
@ -45,7 +46,8 @@ For instaling graphene, just run this command in your shell
1.0 Upgrade Guide
-----------------
Please read `UPGRADE-v1.0.md`_ to learn how to upgrade.
Please read `UPGRADE-v1.0.md </UPGRADE-v1.0.md>`__ to learn how to
upgrade.
Examples
--------
@ -74,10 +76,11 @@ Then Querying ``graphene.Schema`` is as simple as:
result = schema.execute(query)
If you want to learn even more, you can also check the following
`examples`_:
`examples <examples/>`__:
- **Basic Schema**: `Starwars example`_
- **Relay Schema**: `Starwars Relay example`_
- **Basic Schema**: `Starwars example <examples/starwars>`__
- **Relay Schema**: `Starwars Relay
example <examples/starwars_relay>`__
Contributing
------------
@ -94,15 +97,24 @@ After developing, the full test suite can be evaluated by running:
python setup.py test # Use --pytest-args="-v -s" for verbose mode
.. _UPGRADE-v1.0.md: /UPGRADE-v1.0.md
.. _Graphene: http://graphene-python.org
.. _graphene-django: https://github.com/graphql-python/graphene-django/
.. _graphene-sqlalchemy: https://github.com/graphql-python/graphene-sqlalchemy/
.. _graphene-gae: https://github.com/graphql-python/graphene-gae/
.. _Tracking Issue: https://github.com/graphql-python/graphene/issues/289
.. _examples: examples/
.. _Starwars example: examples/starwars
.. _Starwars Relay example: examples/starwars_relay
Documentation
~~~~~~~~~~~~~
The documentation is generated using the excellent
`Sphinx <http://www.sphinx-doc.org/>`__ and a custom theme.
The documentation dependencies are installed by running:
.. code:: sh
cd docs
pip install -r requirements.txt
Then to produce a HTML version of the documentation:
.. code:: sh
make html
.. |Graphene Logo| image:: http://graphene-python.org/favicon.png
.. |Build Status| image:: https://travis-ci.org/graphql-python/graphene.svg?branch=master

View File

@ -223,3 +223,7 @@ dummy:
$(SPHINXBUILD) -b dummy $(ALLSPHINXOPTS) $(BUILDDIR)/dummy
@echo
@echo "Build finished. Dummy builder generates no files."
.PHONY: livehtml
livehtml:
sphinx-autobuild -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html

40
docs/execution/index.rst Normal file
View File

@ -0,0 +1,40 @@
=========
Execution
=========
For executing a query a schema, you can directly call the ``execute`` method on it.
.. code:: python
schema = graphene.Schema(...)
result = schema.execute('{ name }')
``result`` represents he result of execution. ``result.data`` is the result of executing the query, ``result.errors`` is ``None`` if no errors occurred, and is a non-empty list if an error occurred.
Context
_______
You can pass context to a query via ``context_value``.
.. code:: python
class Query(graphene.ObjectType):
name = graphene.String()
def resolve_name(self, args, context, info):
return context.get('name')
schema = graphene.Schema(Query)
result = schema.execute('{ name }', context_value={'name': 'Syrus'})
Middleware
__________
.. toctree::
:maxdepth: 1
middleware

View File

@ -0,0 +1,44 @@
Middleware
==========
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)``.
Inside that method, it should either:
- Send ``resolve`` to the next middleware to continue the evaluation; or
- Return a value to end the evaluation early.
Resolve arguments
-----------------
Middlewares ``resolve`` is invoked with several arguments:
- ``next`` represents the execution chain. Call ``next`` to continue evalution.
- ``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.
Example
-------
This middleware only continues evaluation if the ``field_name`` is not ``'user'``
.. code:: python
class AuthorizationMiddleware(object):
def resolve(self, next, root, args, context, info):
if info.field_name == 'user':
return None
return next(root, args, context, info)
And then execute it with:
.. code:: python
result = schema.execute('THE QUERY', middleware=[AuthorizationMiddleware()])

View File

@ -8,6 +8,7 @@ Contents:
quickstart
types/index
execution/index
relay/index
Integrations

View File

@ -1,6 +1,12 @@
Getting started
===============
What is GraphQL?
----------------
For an introduction to GraphQL and an overview of its concepts, please refer
to `the official introduction <http://graphql.org/learn/>`.
Lets build a basic GraphQL schema from scratch.
Requirements

View File

@ -23,14 +23,14 @@ and ``Droid`` are two implementations of that interface.
name = graphene.String()
# Human is a Character implementation
class Human(ObjectType):
class Human(graphene.ObjectType):
class Meta:
interfaces = (Character, )
born_in = graphene.String()
# Droid is a Character implementation
class Droid(Character):
class Droid(graphene.ObjectType):
class Meta:
interfaces = (Character, )

View File

@ -76,3 +76,69 @@ We should receive:
"ok": true
}
}
InputFields and InputObjectTypes
----------------------
InputFields are used in mutations to allow nested input data for mutations
To use an InputField you define an InputObjectType that specifies the structure of your input data
.. code:: python
import graphene
class PersonInput(graphene.InputObjectType):
name = graphene.String()
age = graphene.Int()
class CreatePerson(graphene.Mutation):
class Input:
person_data = graphene.InputField(PersonInput)
person = graphene.Field(lambda: Person)
def mutate(self, args, context, info):
p_data = args.get('person_data')
name = p_data.get('name')
age = p_data.get('age')
person = Person(name=name, age=age)
return CreatePerson(person=person)
Note that **name** and **age** are part of **person_data** now
Using the above mutation your new query would look like this:
.. code:: graphql
mutation myFirstMutation {
createPerson(personData: {name:"Peter", age: 24}) {
person {
name,
age
}
}
}
InputObjectTypes can also be fields of InputObjectTypes allowing you to have
as complex of input data as you need
.. code:: python
import graphene
class LatLngInput(graphene.InputObjectType):
lat = graphene.Float()
lng = graphene.Float()
#A location has a latlng associated to it
class LocationInput(graphene.InputObjectType):
name = graphene.String()
latlng = graphene.InputField(LatLngInputType)

View File

@ -9,9 +9,10 @@ Graphene defines the following base Scalar Types:
- ``graphene.Boolean``
- ``graphene.ID``
Graphene also provides custom scalars for Dates and JSON:
Graphene also provides custom scalars for Dates, Times, and JSON:
- ``graphene.types.datetime.DateTime``
- ``graphene.types.datetime.Time``
- ``graphene.types.json.JSONString``
@ -24,8 +25,8 @@ The following is an example for creating a DateTime scalar:
.. code:: python
import datetime
from graphene.core.classtypes import Scalar
from graphql.core.language import ast
from graphene.types import Scalar
from graphql.language import ast
class DateTime(Scalar):
'''DateTime Scalar Description'''
@ -57,14 +58,18 @@ Scalars mounted in a ``ObjectType``, ``Interface`` or ``Mutation`` act as
# Is equivalent to:
class Person(graphene.ObjectType):
name = graphene.Field(graphene.String())
name = graphene.Field(graphene.String)
**Note:** when using the ``Field`` constructor directly, pass the type and
not an instance.
Types mounted in a ``Field`` act as ``Argument``\ s.
.. code:: python
graphene.Field(graphene.String(), to=graphene.String())
graphene.Field(graphene.String, to=graphene.String())
# Is equivalent to:
graphene.Field(graphene.String(), to=graphene.Argument(graphene.String()))
graphene.Field(graphene.String, to=graphene.Argument(graphene.String()))

View File

@ -0,0 +1,39 @@
import graphene
class User(graphene.ObjectType):
id = graphene.ID()
name = graphene.String()
class Query(graphene.ObjectType):
me = graphene.Field(User)
def resolve_me(self, args, context, info):
return context['user']
schema = graphene.Schema(query=Query)
query = '''
query something{
me {
id
name
}
}
'''
def test_query():
result = schema.execute(query, context_value={'user': User(id='1', name='Syrus')})
assert not result.errors
assert result.data == {
'me': {
'id': '1',
'name': 'Syrus',
}
}
if __name__ == '__main__':
result = schema.execute(query, context_value={'user': User(id='X', name='Console')})
print(result.data['me'])

View File

@ -56,7 +56,7 @@ type Ship implements Node {
type ShipConnection {
pageInfo: PageInfo!
edges: [ShipEdge]
edges: [ShipEdge]!
}
type ShipEdge {

View File

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

View File

@ -71,18 +71,18 @@ def _is_descriptor(obj):
def _is_dunder(name):
"""Returns True if a __dunder__ name, False otherwise."""
return (name[:2] == name[-2:] == '__' and
return (len(name) > 4 and
name[:2] == name[-2:] == '__' and
name[2:3] != '_' and
name[-3:-2] != '_' and
len(name) > 4)
name[-3:-2] != '_')
def _is_sunder(name):
"""Returns True if a _sunder_ name, False otherwise."""
return (name[0] == name[-1] == '_' and
return (len(name) > 2 and
name[0] == name[-1] == '_' and
name[1:2] != '_' and
name[-2:-1] != '_' and
len(name) > 2)
name[-2:-1] != '_')
def _make_class_unpicklable(cls):

View File

View File

@ -0,0 +1,41 @@
from ..enum import _is_dunder, _is_sunder
def test__is_dunder():
dunder_names = [
'__i__',
'__test__',
]
non_dunder_names = [
'test',
'__test',
'_test',
'_test_',
'test__',
'',
]
for name in dunder_names:
assert _is_dunder(name) is True
for name in non_dunder_names:
assert _is_dunder(name) is False
def test__is_sunder():
sunder_names = [
'_i_',
'_test_',
]
non_sunder_names = [
'__i__',
'_i__',
'__i_',
'',
]
for name in sunder_names:
assert _is_sunder(name) is True
for name in non_sunder_names:
assert _is_sunder(name) is False

View File

@ -5,6 +5,7 @@ from functools import partial
import six
from graphql_relay import connection_from_list
from promise import is_thenable, promisify
from ..types import (AbstractType, Boolean, Enum, Int, Interface, List, NonNull, Scalar, String,
Union)
@ -81,7 +82,7 @@ class ConnectionMeta(ObjectTypeMeta):
class ConnectionBase(AbstractType):
page_info = Field(PageInfo, name='pageInfo', required=True)
edges = List(edge)
edges = NonNull(List(edge))
bases = (ConnectionBase, ) + bases
attrs = dict(attrs, _meta=options, Edge=edge)
@ -114,32 +115,41 @@ class IterableConnectionField(Field):
connection_type = type
assert issubclass(connection_type, Connection), (
'{} type have to be a subclass of Connection. Received "{}".'
).format(str(self), connection_type)
).format(self.__class__.__name__, connection_type)
return connection_type
@classmethod
def connection_resolver(cls, resolver, connection, root, args, context, info):
resolved = resolver(root, args, context, info)
if isinstance(resolved, connection):
def resolve_connection(cls, connection_type, args, resolved):
if isinstance(resolved, connection_type):
return resolved
assert isinstance(resolved, Iterable), (
'Resolved value from the connection field have to be iterable or instance of {}. '
'Received "{}"'
).format(connection, resolved)
).format(connection_type, resolved)
connection = connection_from_list(
resolved,
args,
connection_type=connection,
edge_type=connection.Edge,
connection_type=connection_type,
edge_type=connection_type.Edge,
pageinfo_type=PageInfo
)
connection.iterable = resolved
return connection
@classmethod
def connection_resolver(cls, resolver, connection_type, root, args, context, info):
resolved = resolver(root, args, context, info)
on_resolve = partial(cls.resolve_connection, connection_type, args)
if is_thenable(resolved):
return promisify(resolved).then(on_resolve)
return on_resolve(resolved)
def get_resolver(self, parent_resolver):
resolver = super(IterableConnectionField, self).get_resolver(parent_resolver)
return partial(self.connection_resolver, resolver, self.type)
ConnectionField = IterableConnectionField

View File

@ -12,9 +12,9 @@ def is_node(objecttype):
'''
Check if the given objecttype has Node as an interface
'''
assert issubclass(objecttype, ObjectType), (
'Only ObjectTypes can have a Node interface.'
)
if not issubclass(objecttype, ObjectType):
return False
for i in objecttype._meta.interfaces:
if issubclass(i, Node):
return True
@ -35,24 +35,26 @@ def get_default_connection(cls):
class GlobalID(Field):
def __init__(self, node, *args, **kwargs):
super(GlobalID, self).__init__(ID, *args, **kwargs)
self.node = node
def __init__(self, node=None, parent_type=None, required=True, *args, **kwargs):
super(GlobalID, self).__init__(ID, required=required, *args, **kwargs)
self.node = node or Node
self.parent_type_name = parent_type._meta.name if parent_type else None
@staticmethod
def id_resolver(parent_resolver, node, root, args, context, info):
id = parent_resolver(root, args, context, info)
return node.to_global_id(info.parent_type.name, id) # root._meta.name
def id_resolver(parent_resolver, node, root, args, context, info, parent_type_name=None):
type_id = parent_resolver(root, args, context, info)
parent_type_name = parent_type_name or info.parent_type.name
return node.to_global_id(parent_type_name, type_id) # root._meta.name
def get_resolver(self, parent_resolver):
return partial(self.id_resolver, parent_resolver, self.node)
return partial(self.id_resolver, parent_resolver, self.node, parent_type_name=self.parent_type_name)
class NodeMeta(InterfaceMeta):
def __new__(cls, name, bases, attrs):
cls = InterfaceMeta.__new__(cls, name, bases, attrs)
cls._meta.fields['id'] = GlobalID(cls, required=True, description='The ID of the object.')
cls._meta.fields['id'] = GlobalID(cls, description='The ID of the object.')
return cls

View File

@ -28,8 +28,9 @@ def test_connection():
pageinfo_field = fields['page_info']
assert isinstance(edge_field, Field)
assert isinstance(edge_field.type, List)
assert edge_field.type.of_type == MyObjectConnection.Edge
assert isinstance(edge_field.type, NonNull)
assert isinstance(edge_field.type.of_type, List)
assert edge_field.type.of_type.of_type == MyObjectConnection.Edge
assert isinstance(pageinfo_field, Field)
assert isinstance(pageinfo_field.type, NonNull)

View File

@ -1,6 +1,7 @@
from collections import OrderedDict
from graphql_relay.utils import base64
from promise import Promise
from ...types import ObjectType, Schema, String
from ..connection import ConnectionField, PageInfo
@ -20,12 +21,16 @@ class Letter(ObjectType):
class Query(ObjectType):
letters = ConnectionField(Letter)
connection_letters = ConnectionField(Letter)
promise_letters = ConnectionField(Letter)
node = Node.Field()
def resolve_letters(self, args, context, info):
return list(letters.values())
def resolve_promise_letters(self, args, context, info):
return Promise.resolve(list(letters.values()))
def resolve_connection_letters(self, args, context, info):
return Letter.Connection(
page_info=PageInfo(
@ -228,3 +233,38 @@ def test_connection_type_nodes():
}
}
}
def test_connection_promise():
result = schema.execute('''
{
promiseLetters(first:1) {
edges {
node {
id
letter
}
}
pageInfo {
hasPreviousPage
hasNextPage
}
}
}
''')
assert not result.errors
assert result.data == {
'promiseLetters': {
'edges': [{
'node': {
'id': 'TGV0dGVyOjA=',
'letter': 'A',
},
}],
'pageInfo': {
'hasPreviousPage': False,
'hasNextPage': True,
}
}
}

View File

@ -0,0 +1,60 @@
from graphql_relay import to_global_id
from ..node import Node, GlobalID
from ...types import NonNull, ID, ObjectType, String
from ...types.definitions import GrapheneObjectType
class CustomNode(Node):
class Meta:
name = 'Node'
class User(ObjectType):
class Meta:
interfaces = [CustomNode]
name = String()
class Info(object):
def __init__(self, parent_type):
self.parent_type = GrapheneObjectType(
graphene_type=parent_type,
name=parent_type._meta.name,
description=parent_type._meta.description,
fields=None,
is_type_of=parent_type.is_type_of,
interfaces=None
)
def test_global_id_defaults_to_required_and_node():
gid = GlobalID()
assert isinstance(gid.type, NonNull)
assert gid.type.of_type == ID
assert gid.node == Node
def test_global_id_allows_overriding_of_node_and_required():
gid = GlobalID(node=CustomNode, required=False)
assert gid.type == ID
assert gid.node == CustomNode
def test_global_id_defaults_to_info_parent_type():
my_id = '1'
gid = GlobalID()
id_resolver = gid.get_resolver(lambda *_: my_id)
my_global_id = id_resolver(None, None, None, Info(User))
assert my_global_id == to_global_id(User._meta.name, my_id)
def test_global_id_allows_setting_customer_parent_type():
my_id = '1'
gid = GlobalID(parent_type=User)
id_resolver = gid.get_resolver(lambda *_: my_id)
my_global_id = id_resolver(None, None, None, None)
assert my_global_id == to_global_id(User._meta.name, my_id)

View File

View File

View File

@ -0,0 +1,52 @@
# https://github.com/graphql-python/graphene/issues/313
import graphene
from graphene import resolve_only_args
class Success(graphene.ObjectType):
yeah = graphene.String()
class Error(graphene.ObjectType):
message = graphene.String()
class CreatePostResult(graphene.Union):
class Meta:
types = [Success, Error]
class CreatePost(graphene.Mutation):
class Input:
text = graphene.String(required=True)
result = graphene.Field(CreatePostResult)
@resolve_only_args
def mutate(self, text):
result = Success(yeah='yeah')
return CreatePost(result=result)
class Mutations(graphene.ObjectType):
create_post = CreatePost.Field()
# tests.py
def test_create_post():
query_string = '''
mutation {
createPost(text: "Try this out") {
result {
__typename
}
}
}
'''
schema = graphene.Schema(mutation=Mutations)
result = schema.execute(query_string)
assert not result.errors
assert result.data['createPost']['result']['__typename'] == 'Success'

View File

@ -0,0 +1,24 @@
# https://github.com/graphql-python/graphene/issues/356
import pytest
import graphene
from graphene import relay
class SomeTypeOne(graphene.ObjectType):
pass
class SomeTypeTwo(graphene.ObjectType):
pass
class MyUnion(graphene.Union):
class Meta:
types = (SomeTypeOne, SomeTypeTwo)
def test_issue():
with pytest.raises(Exception) as exc_info:
class Query(graphene.ObjectType):
things = relay.ConnectionField(MyUnion)
schema = graphene.Schema(query=Query)
assert str(exc_info.value) == 'IterableConnectionField type have to be a subclass of Connection. Received "MyUnion".'

View File

@ -1,11 +1,12 @@
from collections import OrderedDict
from itertools import chain
from ..utils.orderedtype import OrderedType
from .mountedtype import MountedType
from .structures import NonNull
from .dynamic import Dynamic
class Argument(OrderedType):
class Argument(MountedType):
def __init__(self, type, default_value=None, description=None, name=None, required=False, _creation_counter=None):
super(Argument, self).__init__(_creation_counter=_creation_counter)
@ -27,14 +28,33 @@ class Argument(OrderedType):
)
def to_arguments(args, extra_args):
def to_arguments(args, extra_args=None):
from .unmountedtype import UnmountedType
extra_args = sorted(extra_args.items(), key=lambda f: f[1])
from .field import Field
from .inputfield import InputField
if extra_args:
extra_args = sorted(extra_args.items(), key=lambda f: f[1])
else:
extra_args = []
iter_arguments = chain(args.items(), extra_args)
arguments = OrderedDict()
for default_name, arg in iter_arguments:
if isinstance(arg, Dynamic):
arg = arg.get_type()
if arg is None:
# If the Dynamic type returned None
# then we skip the Argument
continue
if isinstance(arg, UnmountedType):
arg = arg.Argument()
arg = Argument.mount(arg)
if isinstance(arg, (InputField, Field)):
raise ValueError('Expected {} to be Argument, but received {}. Try using Argument({}).'.format(
default_name,
type(arg).__name__,
arg.type
))
if not isinstance(arg, Argument):
raise ValueError('Unknown argument "{}".'.format(default_name))

View File

@ -29,11 +29,37 @@ class DateTime(Scalar):
)
return dt.isoformat()
@staticmethod
def parse_literal(node):
@classmethod
def parse_literal(cls, node):
if isinstance(node, ast.StringValue):
return iso8601.parse_date(node.value)
return cls.parse_value(node.value)
@staticmethod
def parse_value(value):
return iso8601.parse_date(value)
class Time(Scalar):
'''
The `Time` scalar type represents a Time value as
specified by
[iso8601](https://en.wikipedia.org/wiki/ISO_8601).
'''
epoch_date = '1970-01-01'
@staticmethod
def serialize(time):
assert isinstance(time, datetime.time), (
'Received not compatible time "{}"'.format(repr(time))
)
return time.isoformat()
@classmethod
def parse_literal(cls, node):
if isinstance(node, ast.StringValue):
return cls.parse_value(node.value)
@classmethod
def parse_value(cls, value):
dt = iso8601.parse_date('{}T{}'.format(cls.epoch_date, value))
return datetime.time(dt.hour, dt.minute, dt.second, dt.microsecond, dt.tzinfo)

View File

@ -1,9 +1,9 @@
import inspect
from ..utils.orderedtype import OrderedType
from .mountedtype import MountedType
class Dynamic(OrderedType):
class Dynamic(MountedType):
'''
A Dynamic Type let us get the type in runtime when we generate
the schema. So we can have lazy fields.

View File

@ -58,5 +58,10 @@ class Enum(six.with_metaclass(EnumTypeMeta, UnmountedType)):
kind of type, often integers.
'''
def get_type(self):
return type(self)
@classmethod
def get_type(cls):
'''
This function is called when the unmounted type (Enum instance)
is mounted (as a Field, InputField or Argument)
'''
return cls

View File

@ -2,8 +2,8 @@ import inspect
from collections import Mapping, OrderedDict
from functools import partial
from ..utils.orderedtype import OrderedType
from .argument import Argument, to_arguments
from .mountedtype import MountedType
from .structures import NonNull
from .unmountedtype import UnmountedType
@ -18,7 +18,7 @@ def source_resolver(source, root, args, context, info):
return resolved
class Field(OrderedType):
class Field(MountedType):
def __init__(self, type, args=None, resolver=None, source=None,
deprecation_reason=None, name=None, description=None,
@ -60,7 +60,7 @@ class Field(OrderedType):
@property
def type(self):
if inspect.isfunction(self._type):
if inspect.isfunction(self._type) or type(self._type) is partial:
return self._type()
return self._type

View File

@ -1,8 +1,8 @@
from ..utils.orderedtype import OrderedType
from .mountedtype import MountedType
from .structures import NonNull
class InputField(OrderedType):
class InputField(MountedType):
def __init__(self, type, name=None, default_value=None,
deprecation_reason=None, description=None,

View File

@ -50,4 +50,8 @@ class InputObjectType(six.with_metaclass(InputObjectTypeMeta, UnmountedType)):
@classmethod
def get_type(cls):
'''
This function is called when the unmounted type (InputObjectType instance)
is mounted (as a Field, InputField or Argument)
'''
return cls

View File

@ -0,0 +1,21 @@
from ..utils.orderedtype import OrderedType
from .unmountedtype import UnmountedType
class MountedType(OrderedType):
@classmethod
def mount(cls, unmounted): # noqa: N802
'''
Mount the UnmountedType instance
'''
assert isinstance(unmounted, UnmountedType), (
'{} can\'t mount {}'
).format(cls.__name__, repr(unmounted))
return cls(
unmounted.get_type(),
*unmounted.args,
_creation_counter=unmounted.creation_counter,
**unmounted.kwargs
)

View File

@ -19,18 +19,18 @@ class Options(object):
for attr_name, value in defaults.items():
if attr_name in meta_attrs:
value = meta_attrs.pop(attr_name)
elif hasattr(meta, attr_name):
value = getattr(meta, attr_name)
setattr(self, attr_name, value)
# If meta_attrs is not empty, it implicit means
# If meta_attrs is not empty, it implicitly means
# it received invalid attributes
if meta_attrs:
raise TypeError(
"Invalid attributes: {}".format(
','.join(meta_attrs.keys())
', '.join(sorted(meta_attrs.keys()))
)
)
def __repr__(self):
return '<Meta \n{} >'.format(props(self))
options_props = props(self)
props_as_attrs = ' '.join(['{}={}'.format(key, value) for key, value in options_props.items()])
return '<Options {}>'.format(props_as_attrs)

View File

@ -43,8 +43,13 @@ class Scalar(six.with_metaclass(ScalarTypeMeta, UnmountedType)):
@classmethod
def get_type(cls):
'''
This function is called when the unmounted type (Scalar instance)
is mounted (as a Field, InputField or Argument)
'''
return cls
# As per the GraphQL Spec, Integers are only treated as valid when a valid
# 32-bit signed integer, providing the broadest support across platforms.
#

View File

@ -6,6 +6,7 @@ from graphql.type.introspection import IntrospectionSchema
from graphql.utils.introspection_query import introspection_query
from graphql.utils.schema_printer import print_schema
from .definitions import GrapheneGraphQLType
from .typemap import TypeMap, is_graphene_type
@ -46,6 +47,20 @@ class Schema(GraphQLSchema):
def get_subscription_type(self):
return self.get_graphql_type(self._subscription)
def __getattr__(self, type_name):
'''
This function let the developer select a type in a given schema
by accessing its attrs.
Example: using schema.Query for accessing the "Query" type in the Schema
'''
_type = super(Schema, self).get_type(type_name)
if _type is None:
raise AttributeError('Type "{}" not found in the Schema'.format(type_name))
if isinstance(_type, GrapheneGraphQLType):
return _type.graphene_type
return _type
def get_graphql_type(self, _type):
if not _type:
return _type
@ -61,9 +76,6 @@ class Schema(GraphQLSchema):
def execute(self, *args, **kwargs):
return graphql(self, *args, **kwargs)
def register(self, object_type):
self.types.append(object_type)
def introspect(self):
return self.execute(introspection_query).data

View File

@ -9,9 +9,22 @@ class Structure(UnmountedType):
def __init__(self, of_type, *args, **kwargs):
super(Structure, self).__init__(*args, **kwargs)
if not isinstance(of_type, Structure) and isinstance(of_type, UnmountedType):
cls_name = type(self).__name__
of_type_name = type(of_type).__name__
raise Exception("{} could not have a mounted {}() as inner type. Try with {}({}).".format(
cls_name,
of_type_name,
cls_name,
of_type_name,
))
self.of_type = of_type
def get_type(self):
'''
This function is called when the unmounted type (List or NonNull instance)
is mounted (as a Field, InputField or Argument)
'''
return self
@ -52,7 +65,7 @@ class NonNull(Structure):
super(NonNull, self).__init__(*args, **kwargs)
assert not isinstance(self.of_type, NonNull), (
'Can only create NonNull of a Nullable GraphQLType but got: {}.'
).format(type)
).format(self.of_type)
def __str__(self):
return '{}!'.format(self.of_type)

View File

@ -1,6 +1,8 @@
import pytest
from ..argument import Argument
from ..argument import Argument, to_arguments
from ..field import Field
from ..inputfield import InputField
from ..structures import NonNull
from ..scalars import String
@ -24,3 +26,38 @@ def test_argument_comparasion():
def test_argument_required():
arg = Argument(String, required=True)
assert arg.type == NonNull(String)
def test_to_arguments():
args = {
'arg_string': Argument(String),
'unmounted_arg': String(required=True)
}
my_args = to_arguments(args)
assert my_args == {
'arg_string': Argument(String),
'unmounted_arg': Argument(String, required=True)
}
def test_to_arguments_raises_if_field():
args = {
'arg_string': Field(String),
}
with pytest.raises(ValueError) as exc_info:
to_arguments(args)
assert str(exc_info.value) == 'Expected arg_string to be Argument, but received Field. Try using Argument(String).'
def test_to_arguments_raises_if_inputfield():
args = {
'arg_string': InputField(String),
}
with pytest.raises(ValueError) as exc_info:
to_arguments(args)
assert str(exc_info.value) == 'Expected arg_string to be Argument, but received InputField. Try using Argument(String).'

View File

@ -1,18 +1,22 @@
import datetime
import pytz
from ..datetime import DateTime
from ..datetime import DateTime, Time
from ..objecttype import ObjectType
from ..schema import Schema
class Query(ObjectType):
datetime = DateTime(_in=DateTime(name='in'))
time = Time(_at=Time(name='at'))
def resolve_datetime(self, args, context, info):
_in = args.get('in')
return _in
def resolve_time(self, args, context, info):
return args.get('at')
schema = Schema(query=Query)
@ -27,6 +31,17 @@ def test_datetime_query():
}
def test_time_query():
now = datetime.datetime.now().replace(tzinfo=pytz.utc)
time = datetime.time(now.hour, now.minute, now.second, now.microsecond, now.tzinfo)
isoformat = time.isoformat()
result = schema.execute('''{ time(at: "%s") }'''%isoformat)
assert not result.errors
assert result.data == {
'time': isoformat
}
def test_datetime_query_variable():
now = datetime.datetime.now().replace(tzinfo=pytz.utc)
isoformat = now.isoformat()
@ -39,3 +54,18 @@ def test_datetime_query_variable():
assert result.data == {
'datetime': isoformat
}
def test_time_query_variable():
now = datetime.datetime.now().replace(tzinfo=pytz.utc)
time = datetime.time(now.hour, now.minute, now.second, now.microsecond, now.tzinfo)
isoformat = time.isoformat()
result = schema.execute(
'''query Test($time: Time){ time(at: $time) }''',
variable_values={'time': isoformat}
)
assert not result.errors
assert result.data == {
'time': isoformat
}

View File

@ -0,0 +1,27 @@
from ..structures import List, NonNull
from ..scalars import String
from ..dynamic import Dynamic
def test_dynamic():
dynamic = Dynamic(lambda: String)
assert dynamic.get_type() == String
assert str(dynamic.get_type()) == 'String'
def test_nonnull():
dynamic = Dynamic(lambda: NonNull(String))
assert dynamic.get_type().of_type == String
assert str(dynamic.get_type()) == 'String!'
def test_list():
dynamic = Dynamic(lambda: List(String))
assert dynamic.get_type().of_type == String
assert str(dynamic.get_type()) == '[String]'
def test_list_non_null():
dynamic = Dynamic(lambda: List(NonNull(String)))
assert dynamic.get_type().of_type.of_type == String
assert str(dynamic.get_type()) == '[String!]'

View File

@ -1,4 +1,7 @@
from ..enum import Enum, PyEnum
from ..field import Field
from ..inputfield import InputField
from ..argument import Argument
def test_enum_construction():
@ -72,3 +75,39 @@ def test_enum_value_from_class():
assert RGB.RED.value == 1
assert RGB.GREEN.value == 2
assert RGB.BLUE.value == 3
def test_enum_value_as_unmounted_field():
class RGB(Enum):
RED = 1
GREEN = 2
BLUE = 3
unmounted = RGB()
unmounted_field = unmounted.Field()
assert isinstance(unmounted_field, Field)
assert unmounted_field.type == RGB
def test_enum_value_as_unmounted_inputfield():
class RGB(Enum):
RED = 1
GREEN = 2
BLUE = 3
unmounted = RGB()
unmounted_field = unmounted.InputField()
assert isinstance(unmounted_field, InputField)
assert unmounted_field.type == RGB
def test_enum_value_as_unmounted_argument():
class RGB(Enum):
RED = 1
GREEN = 2
BLUE = 3
unmounted = RGB()
unmounted_field = unmounted.Argument()
assert isinstance(unmounted_field, Argument)
assert unmounted_field.type == RGB

View File

@ -3,6 +3,7 @@ import pytest
from ..argument import Argument
from ..field import Field
from ..structures import NonNull
from ..scalars import String
class MyInstance(object):
@ -75,6 +76,20 @@ def test_field_source_func():
assert field.resolver(MyInstance(), {}, None, None) == MyInstance.value_func()
def test_field_source_as_argument():
MyType = object()
field = Field(MyType, source=String())
assert 'source' in field.args
assert field.args['source'].type == String
def test_field_name_as_argument():
MyType = object()
field = Field(MyType, name=String())
assert 'name' in field.args
assert field.args['name'].type == String
def test_field_source_argument_as_kw():
MyType = object()
field = Field(MyType, b=NonNull(True), c=Argument(None), a=NonNull(False))

View File

@ -1,7 +1,9 @@
from ..abstracttype import AbstractType
from ..field import Field
from ..argument import Argument
from ..inputfield import InputField
from ..objecttype import ObjectType
from ..inputobjecttype import InputObjectType
from ..unmountedtype import UnmountedType
@ -61,6 +63,22 @@ def test_generate_inputobjecttype_unmountedtype():
assert isinstance(MyInputObjectType._meta.fields['field'], InputField)
def test_generate_inputobjecttype_as_argument():
class MyInputObjectType(InputObjectType):
field = MyScalar()
class MyObjectType(ObjectType):
field = Field(MyType, input=MyInputObjectType())
assert 'field' in MyObjectType._meta.fields
field = MyObjectType._meta.fields['field']
assert isinstance(field, Field)
assert field.type == MyType
assert 'input' in field.args
assert isinstance(field.args['input'], Argument)
assert field.args['input'].type == MyInputObjectType
def test_generate_inputobjecttype_inherit_abstracttype():
class MyAbstractType(AbstractType):
field1 = MyScalar(MyType)

View File

@ -0,0 +1,26 @@
import pytest
from ..mountedtype import MountedType
from ..field import Field
from ..scalars import String
class CustomField(Field):
def __init__(self, *args, **kwargs):
self.metadata = kwargs.pop('metadata', None)
super(CustomField, self).__init__(*args, **kwargs)
def test_mounted_type():
unmounted = String()
mounted = Field.mount(unmounted)
assert isinstance(mounted, Field)
assert mounted.type == String
def test_mounted_type_custom():
unmounted = String(metadata={'hey': 'yo!'})
mounted = CustomField.mount(unmounted)
assert isinstance(mounted, CustomField)
assert mounted.type == String
assert mounted.metadata == {'hey': 'yo!'}

View File

@ -4,6 +4,7 @@ from ..mutation import Mutation
from ..objecttype import ObjectType
from ..schema import Schema
from ..scalars import String
from ..dynamic import Dynamic
def test_generate_mutation_no_args():
@ -47,12 +48,16 @@ def test_mutation_execution():
class CreateUser(Mutation):
class Input:
name = String()
dynamic = Dynamic(lambda: String())
dynamic_none = Dynamic(lambda: None)
name = String()
dynamic = Dynamic(lambda: String())
def mutate(self, args, context, info):
name = args.get('name')
return CreateUser(name=name)
dynamic = args.get('dynamic')
return CreateUser(name=name, dynamic=dynamic)
class Query(ObjectType):
a = String()
@ -62,14 +67,16 @@ def test_mutation_execution():
schema = Schema(query=Query, mutation=MyMutation)
result = schema.execute(''' mutation mymutation {
createUser(name:"Peter") {
createUser(name:"Peter", dynamic: "dynamic") {
name
dynamic
}
}
''')
assert not result.errors
assert result.data == {
'createUser': {
'name': "Peter"
'name': 'Peter',
'dynamic': 'dynamic',
}
}

View File

@ -0,0 +1,30 @@
import pytest
from ..options import Options
def test_options():
class BaseOptions:
option_1 = False
name = True
meta = Options(BaseOptions, name=False, option_1=False)
assert meta.name == True
assert meta.option_1 == False
def test_options_extra_attrs():
class BaseOptions:
name = True
type = True
with pytest.raises(Exception) as exc_info:
meta = Options(BaseOptions)
assert str(exc_info.value) == 'Invalid attributes: name, type'
def test_options_repr():
class BaseOptions:
name = True
meta = Options(BaseOptions, name=False)
assert repr(meta) == '<Options name=True>'

View File

@ -4,12 +4,15 @@ from functools import partial
from graphql import Source, execute, parse, GraphQLError
from ..field import Field
from ..interface import Interface
from ..inputfield import InputField
from ..inputobjecttype import InputObjectType
from ..objecttype import ObjectType
from ..scalars import Int, String
from ..schema import Schema
from ..structures import List
from ..union import Union
from ..dynamic import Dynamic
def test_query():
@ -23,6 +26,112 @@ def test_query():
assert executed.data == {'hello': 'World'}
def test_query_union():
class one_object(object):
pass
class two_object(object):
pass
class One(ObjectType):
one = String()
@classmethod
def is_type_of(cls, root, context, info):
return isinstance(root, one_object)
class Two(ObjectType):
two = String()
@classmethod
def is_type_of(cls, root, context, info):
return isinstance(root, two_object)
class MyUnion(Union):
class Meta:
types = (One, Two)
class Query(ObjectType):
unions = List(MyUnion)
def resolve_unions(self, args, context, info):
return [one_object(), two_object()]
hello_schema = Schema(Query)
executed = hello_schema.execute('{ unions { __typename } }')
assert not executed.errors
assert executed.data == {
'unions': [{
'__typename': 'One'
}, {
'__typename': 'Two'
}]
}
def test_query_interface():
class one_object(object):
pass
class two_object(object):
pass
class MyInterface(Interface):
base = String()
class One(ObjectType):
class Meta:
interfaces = (MyInterface, )
one = String()
@classmethod
def is_type_of(cls, root, context, info):
return isinstance(root, one_object)
class Two(ObjectType):
class Meta:
interfaces = (MyInterface, )
two = String()
@classmethod
def is_type_of(cls, root, context, info):
return isinstance(root, two_object)
class Query(ObjectType):
interfaces = List(MyInterface)
def resolve_interfaces(self, args, context, info):
return [one_object(), two_object()]
hello_schema = Schema(Query, types=[One, Two])
executed = hello_schema.execute('{ interfaces { __typename } }')
assert not executed.errors
assert executed.data == {
'interfaces': [{
'__typename': 'One'
}, {
'__typename': 'Two'
}]
}
def test_query_dynamic():
class Query(ObjectType):
hello = Dynamic(lambda: String(resolver=lambda *_: 'World'))
hellos = Dynamic(lambda: List(String, resolver=lambda *_: ['Worlds']))
hello_field = Dynamic(lambda: Field(String, resolver=lambda *_: 'Field World'))
hello_schema = Schema(Query)
executed = hello_schema.execute('{ hello hellos helloField }')
assert not executed.errors
assert executed.data == {'hello': 'World', 'hellos': ['Worlds'], 'helloField': 'Field World'}
def test_query_default_value():
class MyType(ObjectType):
field = String()

View File

@ -0,0 +1,54 @@
import pytest
from ..schema import Schema
from ..objecttype import ObjectType
from ..scalars import String
from ..field import Field
class MyOtherType(ObjectType):
field = String()
class Query(ObjectType):
inner = Field(MyOtherType)
def test_schema():
schema = Schema(Query)
assert schema.get_query_type() == schema.get_graphql_type(Query)
def test_schema_get_type():
schema = Schema(Query)
assert schema.Query == Query
assert schema.MyOtherType == MyOtherType
def test_schema_get_type_error():
schema = Schema(Query)
with pytest.raises(AttributeError) as exc_info:
schema.X
assert str(exc_info.value) == 'Type "X" not found in the Schema'
def test_schema_str():
schema = Schema(Query)
assert str(schema) == """schema {
query: Query
}
type MyOtherType {
field: String
}
type Query {
inner: MyOtherType
}
"""
def test_schema_introspect():
schema = Schema(Query)
assert '__schema' in schema.introspect()

View File

@ -10,12 +10,51 @@ def test_list():
assert str(_list) == '[String]'
def test_list_with_unmounted_type():
with pytest.raises(Exception) as exc_info:
List(String())
assert str(exc_info.value) == 'List could not have a mounted String() as inner type. Try with List(String).'
def test_list_inherited_works_list():
_list = List(List(String))
assert isinstance(_list.of_type, List)
assert _list.of_type.of_type == String
def test_list_inherited_works_nonnull():
_list = List(NonNull(String))
assert isinstance(_list.of_type, NonNull)
assert _list.of_type.of_type == String
def test_nonnull():
nonnull = NonNull(String)
assert nonnull.of_type == String
assert str(nonnull) == 'String!'
def test_nonnull_inherited_works_list():
_list = NonNull(List(String))
assert isinstance(_list.of_type, List)
assert _list.of_type.of_type == String
def test_nonnull_inherited_dont_work_nonnull():
with pytest.raises(Exception) as exc_info:
NonNull(NonNull(String))
assert str(exc_info.value) == 'Can only create NonNull of a Nullable GraphQLType but got: String!.'
def test_nonnull_with_unmounted_type():
with pytest.raises(Exception) as exc_info:
NonNull(String())
assert str(exc_info.value) == 'NonNull could not have a mounted String() as inner type. Try with NonNull(String).'
def test_list_comparasion():
list1 = List(String)
list2 = List(String)

View File

@ -5,20 +5,26 @@ from functools import partial
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 import GraphQLEnumValue
from graphql.type.typemap import GraphQLTypeMap
from ..utils.str_converters import to_camel_case
from ..utils.get_unbound_function import get_unbound_function
from ..utils.str_converters import to_camel_case
from .definitions import (GrapheneEnumType, GrapheneInputObjectType,
GrapheneInterfaceType, GrapheneObjectType,
GrapheneScalarType, GrapheneUnionType,
GrapheneGraphQLType)
from .dynamic import Dynamic
from .enum import Enum
from .field import Field
from .inputobjecttype import InputObjectType
from .interface import Interface
from .objecttype import ObjectType
from .scalars import ID, Boolean, Float, Int, Scalar, String
from .structures import List, NonNull
from .union import Union
from .utils import get_field_as
def is_graphene_type(_type):
@ -28,18 +34,18 @@ def is_graphene_type(_type):
return True
def resolve_type(resolve_type_func, map, root, context, info):
def resolve_type(resolve_type_func, map, type_name, 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)
return_type = map[type_name]
return get_default_resolve_type_fn(root, context, 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
return graphql_type
return _type
@ -63,7 +69,7 @@ class TypeMap(GraphQLTypeMap):
return self.reducer(map, type.of_type)
if type._meta.name in map:
_type = map[type._meta.name]
if is_graphene_type(_type):
if isinstance(_type, GrapheneGraphQLType):
assert _type.graphene_type == type
return map
if issubclass(type, ObjectType):
@ -81,7 +87,6 @@ class TypeMap(GraphQLTypeMap):
return map
def construct_scalar(self, map, type):
from .definitions import GrapheneScalarType
_scalars = {
String: GraphQLString,
Int: GraphQLInt,
@ -104,7 +109,6 @@ class TypeMap(GraphQLTypeMap):
return map
def construct_enum(self, map, type):
from .definitions import GrapheneEnumType
values = OrderedDict()
for name, value in type._meta.enum.__members__.items():
values[name] = GraphQLEnumValue(
@ -122,10 +126,9 @@ class TypeMap(GraphQLTypeMap):
return map
def construct_objecttype(self, map, type):
from .definitions import GrapheneObjectType
if type._meta.name in map:
_type = map[type._meta.name]
if is_graphene_type(_type):
if isinstance(_type, GrapheneGraphQLType):
assert _type.graphene_type == type
return map
map[type._meta.name] = GrapheneObjectType(
@ -146,10 +149,9 @@ class TypeMap(GraphQLTypeMap):
return map
def construct_interface(self, map, type):
from .definitions import GrapheneInterfaceType
_resolve_type = None
if type.resolve_type:
_resolve_type = partial(resolve_type, type.resolve_type, map)
_resolve_type = partial(resolve_type, type.resolve_type, map, type._meta.name)
map[type._meta.name] = GrapheneInterfaceType(
graphene_type=type,
name=type._meta.name,
@ -162,7 +164,6 @@ class TypeMap(GraphQLTypeMap):
return map
def construct_inputobjecttype(self, map, type):
from .definitions import GrapheneInputObjectType
map[type._meta.name] = GrapheneInputObjectType(
graphene_type=type,
name=type._meta.name,
@ -173,10 +174,9 @@ class TypeMap(GraphQLTypeMap):
return map
def construct_union(self, map, type):
from .definitions import GrapheneUnionType
_resolve_type = None
if type.resolve_type:
_resolve_type = partial(resolve_type, type.resolve_type, map)
_resolve_type = partial(resolve_type, type.resolve_type, map, type._meta.name)
types = []
for i in type._meta.types:
map = self.construct_objecttype(map, i)
@ -202,7 +202,7 @@ class TypeMap(GraphQLTypeMap):
fields = OrderedDict()
for name, field in type._meta.fields.items():
if isinstance(field, Dynamic):
field = field.get_type()
field = get_field_as(field.get_type(), _as=Field)
if not field:
continue
map = self.reducer(map, field.type)

View File

@ -39,7 +39,11 @@ class Union(six.with_metaclass(UnionMeta)):
to determine which type is actually used 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 Union cannot be intitialized")
raise Exception("A Union cannot be intitialized")

View File

@ -8,7 +8,7 @@ class UnmountedType(OrderedType):
Instead of writing
>>> class MyObjectType(ObjectType):
>>> my_field = Field(String(), description='Description here')
>>> my_field = Field(String, description='Description here')
It let you write
>>> class MyObjectType(ObjectType):
@ -21,43 +21,35 @@ class UnmountedType(OrderedType):
self.kwargs = kwargs
def get_type(self):
'''
This function is called when the UnmountedType instance
is mounted (as a Field, InputField or Argument)
'''
raise NotImplementedError("get_type not implemented in {}".format(self))
def mount_as(self, _as):
return _as.mount(self)
def Field(self): # noqa: N802
'''
Mount the UnmountedType as Field
'''
from .field import Field
return Field(
self.get_type(),
*self.args,
_creation_counter=self.creation_counter,
**self.kwargs
)
return self.mount_as(Field)
def InputField(self): # noqa: N802
'''
Mount the UnmountedType as InputField
'''
from .inputfield import InputField
return InputField(
self.get_type(),
*self.args,
_creation_counter=self.creation_counter,
**self.kwargs
)
return self.mount_as(InputField)
def Argument(self): # noqa: N802
'''
Mount the UnmountedType as Argument
'''
from .argument import Argument
return Argument(
self.get_type(),
*self.args,
_creation_counter=self.creation_counter,
**self.kwargs
)
return self.mount_as(Argument)
def __eq__(self, other):
return (

View File

@ -1,8 +1,6 @@
from collections import OrderedDict
from .dynamic import Dynamic
from .field import Field
from .inputfield import InputField
from .mountedtype import MountedType
from .unmountedtype import UnmountedType
@ -35,34 +33,16 @@ def get_base_fields(bases, _as=None):
return fields
def mount_as(unmounted_field, _as):
'''
Mount the UnmountedType dinamically as Field or InputField
'''
if _as is None:
return unmounted_field
elif _as is Field:
return unmounted_field.Field()
elif _as is InputField:
return unmounted_field.InputField()
raise Exception(
'Unmounted field "{}" cannot be mounted in {}.'.format(
unmounted_field, _as
)
)
def get_field_as(value, _as=None):
'''
Get type mounted
'''
if isinstance(value, (Field, InputField, Dynamic)):
if isinstance(value, MountedType):
return value
elif isinstance(value, UnmountedType):
return mount_as(value, _as)
if _as is None:
return value
return _as.mount(value)
def yank_fields_from_attrs(attrs, _as=None, delete=True, sort=True):

View File

@ -23,3 +23,19 @@ def test_orderedtype_hash():
assert hash(one) == hash(one)
assert hash(one) != hash(two)
def test_orderedtype_resetcounter():
one = OrderedType()
two = OrderedType()
one.reset_counter()
assert one > two
def test_orderedtype_non_orderabletypes():
one = OrderedType()
assert one.__lt__(1) == NotImplemented
assert one.__gt__(1) == NotImplemented
assert not one == 1

View File

@ -70,9 +70,9 @@ setup(
install_requires=[
'six>=1.10.0',
'graphql-core>=1.0',
'graphql-relay>=0.4.4',
'promise',
'graphql-core>=1.0.1',
'graphql-relay>=0.4.5',
'promise>=1.0.1',
],
tests_require=[
'pytest>=2.7.2',

View File

@ -5,12 +5,15 @@ skipsdist = true
[testenv]
deps=
pytest>=2.7.2
graphql-core>=0.5.1
graphql-relay>=0.4.3
graphql-core>=1.0.1
graphql-relay>=0.4.5
six
blinker
singledispatch
mock
pytz
iso8601
pytest-benchmark
setenv =
PYTHONPATH = .:{envdir}
commands=