mirror of
https://github.com/graphql-python/graphene.git
synced 2025-02-24 15:47:23 +03:00
Merge branch 'master' into union_connections
This commit is contained in:
commit
0c3b913b63
|
@ -52,3 +52,10 @@ matrix:
|
||||||
include:
|
include:
|
||||||
- python: '2.7'
|
- python: '2.7'
|
||||||
env: TEST_TYPE=lint
|
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=
|
||||||
|
|
18
README.md
18
README.md
|
@ -83,3 +83,21 @@ After developing, the full test suite can be evaluated by running:
|
||||||
```sh
|
```sh
|
||||||
python setup.py test # Use --pytest-args="-v -s" for verbose mode
|
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
|
||||||
|
```
|
||||||
|
|
74
README.rst
74
README.rst
|
@ -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
|
`Graphene <http://graphene-python.org>`__ is a Python library for
|
||||||
and easily.
|
building GraphQL schemas/types fast and easily.
|
||||||
|
|
||||||
- **Easy to use:** Graphene helps you use GraphQL in Python without
|
- **Easy to use:** Graphene helps you use GraphQL in Python without
|
||||||
effort.
|
effort.
|
||||||
- **Relay:** Graphene has builtin support for Relay
|
- **Relay:** Graphene has builtin support for Relay
|
||||||
- **Data agnostic:** Graphene supports any kind of data source: SQL
|
- **Data agnostic:** Graphene supports any kind of data source: SQL
|
||||||
(Django, SQLAlchemy), NoSQL, custom Python objects, etc. We believe that
|
(Django, SQLAlchemy), NoSQL, custom Python objects, etc. We believe
|
||||||
by providing a complete API you could plug Graphene anywhere your
|
that by providing a complete API you could plug Graphene anywhere
|
||||||
data lives and make your data available through GraphQL.
|
your data lives and make your data available through GraphQL.
|
||||||
|
|
||||||
Integrations
|
Integrations
|
||||||
------------
|
------------
|
||||||
|
|
||||||
Graphene has multiple integrations with different frameworks:
|
Graphene has multiple integrations with different frameworks:
|
||||||
|
|
||||||
+---------------------+-------------------------------------+
|
+---------------------+----------------------------------------------------------------------------------------------+
|
||||||
| integration | Package |
|
| integration | Package |
|
||||||
+=====================+=====================================+
|
+=====================+==============================================================================================+
|
||||||
| Django | `graphene-django`_ |
|
| Django | `graphene-django <https://github.com/graphql-python/graphene-django/>`__ |
|
||||||
+---------------------+-------------------------------------+
|
+---------------------+----------------------------------------------------------------------------------------------+
|
||||||
| SQLAlchemy | `graphene-sqlalchemy`_ |
|
| SQLAlchemy | `graphene-sqlalchemy <https://github.com/graphql-python/graphene-sqlalchemy/>`__ |
|
||||||
+---------------------+-------------------------------------+
|
+---------------------+----------------------------------------------------------------------------------------------+
|
||||||
| Google App Engine | `graphene-gae`_ |
|
| Google App Engine | `graphene-gae <https://github.com/graphql-python/graphene-gae/>`__ |
|
||||||
+---------------------+-------------------------------------+
|
+---------------------+----------------------------------------------------------------------------------------------+
|
||||||
| Peewee | *In progress* (`Tracking Issue`_) |
|
| Peewee | *In progress* (`Tracking Issue <https://github.com/graphql-python/graphene/issues/289>`__) |
|
||||||
+---------------------+-------------------------------------+
|
+---------------------+----------------------------------------------------------------------------------------------+
|
||||||
|
|
||||||
Installation
|
Installation
|
||||||
------------
|
------------
|
||||||
|
@ -45,7 +46,8 @@ For instaling graphene, just run this command in your shell
|
||||||
1.0 Upgrade Guide
|
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
|
Examples
|
||||||
--------
|
--------
|
||||||
|
@ -74,10 +76,11 @@ Then Querying ``graphene.Schema`` is as simple as:
|
||||||
result = schema.execute(query)
|
result = schema.execute(query)
|
||||||
|
|
||||||
If you want to learn even more, you can also check the following
|
If you want to learn even more, you can also check the following
|
||||||
`examples`_:
|
`examples <examples/>`__:
|
||||||
|
|
||||||
- **Basic Schema**: `Starwars example`_
|
- **Basic Schema**: `Starwars example <examples/starwars>`__
|
||||||
- **Relay Schema**: `Starwars Relay example`_
|
- **Relay Schema**: `Starwars Relay
|
||||||
|
example <examples/starwars_relay>`__
|
||||||
|
|
||||||
Contributing
|
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
|
python setup.py test # Use --pytest-args="-v -s" for verbose mode
|
||||||
|
|
||||||
.. _UPGRADE-v1.0.md: /UPGRADE-v1.0.md
|
Documentation
|
||||||
.. _Graphene: http://graphene-python.org
|
~~~~~~~~~~~~~
|
||||||
.. _graphene-django: https://github.com/graphql-python/graphene-django/
|
|
||||||
.. _graphene-sqlalchemy: https://github.com/graphql-python/graphene-sqlalchemy/
|
The documentation is generated using the excellent
|
||||||
.. _graphene-gae: https://github.com/graphql-python/graphene-gae/
|
`Sphinx <http://www.sphinx-doc.org/>`__ and a custom theme.
|
||||||
.. _Tracking Issue: https://github.com/graphql-python/graphene/issues/289
|
|
||||||
.. _examples: examples/
|
The documentation dependencies are installed by running:
|
||||||
.. _Starwars example: examples/starwars
|
|
||||||
.. _Starwars Relay example: examples/starwars_relay
|
.. 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
|
.. |Graphene Logo| image:: http://graphene-python.org/favicon.png
|
||||||
.. |Build Status| image:: https://travis-ci.org/graphql-python/graphene.svg?branch=master
|
.. |Build Status| image:: https://travis-ci.org/graphql-python/graphene.svg?branch=master
|
||||||
|
|
|
@ -223,3 +223,7 @@ dummy:
|
||||||
$(SPHINXBUILD) -b dummy $(ALLSPHINXOPTS) $(BUILDDIR)/dummy
|
$(SPHINXBUILD) -b dummy $(ALLSPHINXOPTS) $(BUILDDIR)/dummy
|
||||||
@echo
|
@echo
|
||||||
@echo "Build finished. Dummy builder generates no files."
|
@echo "Build finished. Dummy builder generates no files."
|
||||||
|
|
||||||
|
.PHONY: livehtml
|
||||||
|
livehtml:
|
||||||
|
sphinx-autobuild -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html
|
||||||
|
|
|
@ -2,6 +2,38 @@
|
||||||
Execution
|
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::
|
.. toctree::
|
||||||
:maxdepth: 1
|
:maxdepth: 1
|
||||||
|
|
||||||
|
|
|
@ -76,3 +76,69 @@ We should receive:
|
||||||
"ok": true
|
"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)
|
||||||
|
|
||||||
|
|
||||||
|
|
39
examples/context_example.py
Normal file
39
examples/context_example.py
Normal 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'])
|
|
@ -10,7 +10,7 @@ except NameError:
|
||||||
__SETUP__ = False
|
__SETUP__ = False
|
||||||
|
|
||||||
|
|
||||||
VERSION = (1, 0, 2, 'final', 0)
|
VERSION = (1, 1, 0, 'final', 0)
|
||||||
|
|
||||||
__version__ = get_version(VERSION)
|
__version__ = get_version(VERSION)
|
||||||
|
|
||||||
|
|
|
@ -116,7 +116,7 @@ class IterableConnectionField(Field):
|
||||||
connection_type = type
|
connection_type = type
|
||||||
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(str(self), connection_type)
|
).format(self.__class__.__name__, connection_type)
|
||||||
return connection_type
|
return connection_type
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
|
@ -152,4 +152,5 @@ class IterableConnectionField(Field):
|
||||||
resolver = super(IterableConnectionField, self).get_resolver(parent_resolver)
|
resolver = super(IterableConnectionField, self).get_resolver(parent_resolver)
|
||||||
return partial(self.connection_resolver, resolver, self.type)
|
return partial(self.connection_resolver, resolver, self.type)
|
||||||
|
|
||||||
|
|
||||||
ConnectionField = IterableConnectionField
|
ConnectionField = IterableConnectionField
|
||||||
|
|
|
@ -12,9 +12,9 @@ def is_node(objecttype):
|
||||||
'''
|
'''
|
||||||
Check if the given objecttype has Node as an interface
|
Check if the given objecttype has Node as an interface
|
||||||
'''
|
'''
|
||||||
assert issubclass(objecttype, ObjectType), (
|
if not issubclass(objecttype, ObjectType):
|
||||||
'Only ObjectTypes can have a Node interface. Received %s'
|
return False
|
||||||
) % objecttype
|
|
||||||
for i in objecttype._meta.interfaces:
|
for i in objecttype._meta.interfaces:
|
||||||
if issubclass(i, Node):
|
if issubclass(i, Node):
|
||||||
return True
|
return True
|
||||||
|
@ -35,17 +35,19 @@ def get_default_connection(cls):
|
||||||
|
|
||||||
class GlobalID(Field):
|
class GlobalID(Field):
|
||||||
|
|
||||||
def __init__(self, node=None, required=True, *args, **kwargs):
|
def __init__(self, node=None, parent_type=None, required=True, *args, **kwargs):
|
||||||
super(GlobalID, self).__init__(ID, required=required, *args, **kwargs)
|
super(GlobalID, self).__init__(ID, required=required, *args, **kwargs)
|
||||||
self.node = node or Node
|
self.node = node or Node
|
||||||
|
self.parent_type_name = parent_type._meta.name if parent_type else None
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def id_resolver(parent_resolver, node, root, args, context, info):
|
def id_resolver(parent_resolver, node, root, args, context, info, parent_type_name=None):
|
||||||
id = parent_resolver(root, args, context, info)
|
type_id = parent_resolver(root, args, context, info)
|
||||||
return node.to_global_id(info.parent_type.name, id) # root._meta.name
|
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):
|
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):
|
class NodeMeta(InterfaceMeta):
|
||||||
|
|
|
@ -1,6 +1,8 @@
|
||||||
from ..node import Node, GlobalID
|
from graphql_relay import to_global_id
|
||||||
|
|
||||||
from ...types import NonNull, ID
|
from ..node import Node, GlobalID
|
||||||
|
from ...types import NonNull, ID, ObjectType, String
|
||||||
|
from ...types.definitions import GrapheneObjectType
|
||||||
|
|
||||||
|
|
||||||
class CustomNode(Node):
|
class CustomNode(Node):
|
||||||
|
@ -9,6 +11,26 @@ class CustomNode(Node):
|
||||||
name = 'Node'
|
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():
|
def test_global_id_defaults_to_required_and_node():
|
||||||
gid = GlobalID()
|
gid = GlobalID()
|
||||||
assert isinstance(gid.type, NonNull)
|
assert isinstance(gid.type, NonNull)
|
||||||
|
@ -20,3 +42,19 @@ def test_global_id_allows_overriding_of_node_and_required():
|
||||||
gid = GlobalID(node=CustomNode, required=False)
|
gid = GlobalID(node=CustomNode, required=False)
|
||||||
assert gid.type == ID
|
assert gid.type == ID
|
||||||
assert gid.node == CustomNode
|
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)
|
||||||
|
|
0
graphene/tests/__init__.py
Normal file
0
graphene/tests/__init__.py
Normal file
0
graphene/tests/issues/__init__.py
Normal file
0
graphene/tests/issues/__init__.py
Normal file
52
graphene/tests/issues/test_313.py
Normal file
52
graphene/tests/issues/test_313.py
Normal 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'
|
24
graphene/tests/issues/test_356.py
Normal file
24
graphene/tests/issues/test_356.py
Normal 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".'
|
|
@ -28,9 +28,14 @@ class Argument(OrderedType):
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def to_arguments(args, extra_args):
|
def to_arguments(args, extra_args=None):
|
||||||
from .unmountedtype import UnmountedType
|
from .unmountedtype import UnmountedType
|
||||||
|
from .field import Field
|
||||||
|
from .inputfield import InputField
|
||||||
|
if extra_args:
|
||||||
extra_args = sorted(extra_args.items(), key=lambda f: f[1])
|
extra_args = sorted(extra_args.items(), key=lambda f: f[1])
|
||||||
|
else:
|
||||||
|
extra_args = []
|
||||||
iter_arguments = chain(args.items(), extra_args)
|
iter_arguments = chain(args.items(), extra_args)
|
||||||
arguments = OrderedDict()
|
arguments = OrderedDict()
|
||||||
for default_name, arg in iter_arguments:
|
for default_name, arg in iter_arguments:
|
||||||
|
@ -44,6 +49,13 @@ def to_arguments(args, extra_args):
|
||||||
if isinstance(arg, UnmountedType):
|
if isinstance(arg, UnmountedType):
|
||||||
arg = arg.Argument()
|
arg = arg.Argument()
|
||||||
|
|
||||||
|
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):
|
if not isinstance(arg, Argument):
|
||||||
raise ValueError('Unknown argument "{}".'.format(default_name))
|
raise ValueError('Unknown argument "{}".'.format(default_name))
|
||||||
|
|
||||||
|
|
|
@ -49,6 +49,7 @@ class Scalar(six.with_metaclass(ScalarTypeMeta, UnmountedType)):
|
||||||
'''
|
'''
|
||||||
return cls
|
return cls
|
||||||
|
|
||||||
|
|
||||||
# As per the GraphQL Spec, Integers are only treated as valid when a valid
|
# As per the GraphQL Spec, Integers are only treated as valid when a valid
|
||||||
# 32-bit signed integer, providing the broadest support across platforms.
|
# 32-bit signed integer, providing the broadest support across platforms.
|
||||||
#
|
#
|
||||||
|
|
|
@ -9,6 +9,15 @@ class Structure(UnmountedType):
|
||||||
|
|
||||||
def __init__(self, of_type, *args, **kwargs):
|
def __init__(self, of_type, *args, **kwargs):
|
||||||
super(Structure, self).__init__(*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
|
self.of_type = of_type
|
||||||
|
|
||||||
def get_type(self):
|
def get_type(self):
|
||||||
|
@ -56,7 +65,7 @@ class NonNull(Structure):
|
||||||
super(NonNull, self).__init__(*args, **kwargs)
|
super(NonNull, self).__init__(*args, **kwargs)
|
||||||
assert not isinstance(self.of_type, NonNull), (
|
assert not isinstance(self.of_type, NonNull), (
|
||||||
'Can only create NonNull of a Nullable GraphQLType but got: {}.'
|
'Can only create NonNull of a Nullable GraphQLType but got: {}.'
|
||||||
).format(type)
|
).format(self.of_type)
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return '{}!'.format(self.of_type)
|
return '{}!'.format(self.of_type)
|
||||||
|
|
|
@ -1,6 +1,8 @@
|
||||||
import pytest
|
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 ..structures import NonNull
|
||||||
from ..scalars import String
|
from ..scalars import String
|
||||||
|
|
||||||
|
@ -24,3 +26,38 @@ def test_argument_comparasion():
|
||||||
def test_argument_required():
|
def test_argument_required():
|
||||||
arg = Argument(String, required=True)
|
arg = Argument(String, required=True)
|
||||||
assert arg.type == NonNull(String)
|
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).'
|
||||||
|
|
|
@ -10,12 +10,51 @@ def test_list():
|
||||||
assert str(_list) == '[String]'
|
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():
|
def test_nonnull():
|
||||||
nonnull = NonNull(String)
|
nonnull = NonNull(String)
|
||||||
assert nonnull.of_type == String
|
assert nonnull.of_type == String
|
||||||
assert str(nonnull) == '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():
|
def test_list_comparasion():
|
||||||
list1 = List(String)
|
list1 = List(String)
|
||||||
list2 = List(String)
|
list2 = List(String)
|
||||||
|
|
|
@ -53,7 +53,11 @@ class Union(six.with_metaclass(UnionMeta)):
|
||||||
to determine which type is actually used when the field is resolved.
|
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):
|
def __init__(self, *args, **kwargs):
|
||||||
raise Exception("A Union cannot be intitialized")
|
raise Exception("A Union cannot be intitialized")
|
||||||
|
|
Loading…
Reference in New Issue
Block a user