mirror of
https://github.com/graphql-python/graphene.git
synced 2024-11-22 17:46:57 +03:00
Merge branch 'master' into master
This commit is contained in:
commit
dda294d911
|
@ -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
|
||||||
|
|
40
docs/execution/index.rst
Normal file
40
docs/execution/index.rst
Normal 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
|
44
docs/execution/middleware.rst
Normal file
44
docs/execution/middleware.rst
Normal 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()])
|
|
@ -8,6 +8,7 @@ Contents:
|
||||||
|
|
||||||
quickstart
|
quickstart
|
||||||
types/index
|
types/index
|
||||||
|
execution/index
|
||||||
relay/index
|
relay/index
|
||||||
|
|
||||||
Integrations
|
Integrations
|
||||||
|
|
|
@ -1,6 +1,12 @@
|
||||||
Getting started
|
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/>`.
|
||||||
|
|
||||||
Let’s build a basic GraphQL schema from scratch.
|
Let’s build a basic GraphQL schema from scratch.
|
||||||
|
|
||||||
Requirements
|
Requirements
|
||||||
|
|
|
@ -23,14 +23,14 @@ and ``Droid`` are two implementations of that interface.
|
||||||
name = graphene.String()
|
name = graphene.String()
|
||||||
|
|
||||||
# Human is a Character implementation
|
# Human is a Character implementation
|
||||||
class Human(ObjectType):
|
class Human(graphene.ObjectType):
|
||||||
class Meta:
|
class Meta:
|
||||||
interfaces = (Character, )
|
interfaces = (Character, )
|
||||||
|
|
||||||
born_in = graphene.String()
|
born_in = graphene.String()
|
||||||
|
|
||||||
# Droid is a Character implementation
|
# Droid is a Character implementation
|
||||||
class Droid(Character):
|
class Droid(graphene.ObjectType):
|
||||||
class Meta:
|
class Meta:
|
||||||
interfaces = (Character, )
|
interfaces = (Character, )
|
||||||
|
|
||||||
|
|
|
@ -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)
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -9,9 +9,10 @@ Graphene defines the following base Scalar Types:
|
||||||
- ``graphene.Boolean``
|
- ``graphene.Boolean``
|
||||||
- ``graphene.ID``
|
- ``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.DateTime``
|
||||||
|
- ``graphene.types.datetime.Time``
|
||||||
- ``graphene.types.json.JSONString``
|
- ``graphene.types.json.JSONString``
|
||||||
|
|
||||||
|
|
||||||
|
@ -24,8 +25,8 @@ The following is an example for creating a DateTime scalar:
|
||||||
.. code:: python
|
.. code:: python
|
||||||
|
|
||||||
import datetime
|
import datetime
|
||||||
from graphene.core.classtypes import Scalar
|
from graphene.types import Scalar
|
||||||
from graphql.core.language import ast
|
from graphql.language import ast
|
||||||
|
|
||||||
class DateTime(Scalar):
|
class DateTime(Scalar):
|
||||||
'''DateTime Scalar Description'''
|
'''DateTime Scalar Description'''
|
||||||
|
@ -57,14 +58,18 @@ Scalars mounted in a ``ObjectType``, ``Interface`` or ``Mutation`` act as
|
||||||
|
|
||||||
# Is equivalent to:
|
# Is equivalent to:
|
||||||
class Person(graphene.ObjectType):
|
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.
|
Types mounted in a ``Field`` act as ``Argument``\ s.
|
||||||
|
|
||||||
|
|
||||||
.. code:: python
|
.. code:: python
|
||||||
|
|
||||||
graphene.Field(graphene.String(), to=graphene.String())
|
graphene.Field(graphene.String, to=graphene.String())
|
||||||
|
|
||||||
# Is equivalent to:
|
# Is equivalent to:
|
||||||
graphene.Field(graphene.String(), to=graphene.Argument(graphene.String()))
|
graphene.Field(graphene.String, to=graphene.Argument(graphene.String()))
|
||||||
|
|
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'])
|
|
@ -56,7 +56,7 @@ type Ship implements Node {
|
||||||
|
|
||||||
type ShipConnection {
|
type ShipConnection {
|
||||||
pageInfo: PageInfo!
|
pageInfo: PageInfo!
|
||||||
edges: [ShipEdge]
|
edges: [ShipEdge]!
|
||||||
}
|
}
|
||||||
|
|
||||||
type ShipEdge {
|
type ShipEdge {
|
||||||
|
|
|
@ -10,7 +10,7 @@ except NameError:
|
||||||
__SETUP__ = False
|
__SETUP__ = False
|
||||||
|
|
||||||
|
|
||||||
VERSION = (1, 0, 2, 'final', 0)
|
VERSION = (1, 1, 3, 'final', 0)
|
||||||
|
|
||||||
__version__ = get_version(VERSION)
|
__version__ = get_version(VERSION)
|
||||||
|
|
||||||
|
|
|
@ -71,18 +71,18 @@ def _is_descriptor(obj):
|
||||||
|
|
||||||
def _is_dunder(name):
|
def _is_dunder(name):
|
||||||
"""Returns True if a __dunder__ name, False otherwise."""
|
"""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[2:3] != '_' and
|
||||||
name[-3:-2] != '_' and
|
name[-3:-2] != '_')
|
||||||
len(name) > 4)
|
|
||||||
|
|
||||||
|
|
||||||
def _is_sunder(name):
|
def _is_sunder(name):
|
||||||
"""Returns True if a _sunder_ name, False otherwise."""
|
"""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[1:2] != '_' and
|
||||||
name[-2:-1] != '_' and
|
name[-2:-1] != '_')
|
||||||
len(name) > 2)
|
|
||||||
|
|
||||||
|
|
||||||
def _make_class_unpicklable(cls):
|
def _make_class_unpicklable(cls):
|
||||||
|
|
0
graphene/pyutils/tests/__init__.py
Normal file
0
graphene/pyutils/tests/__init__.py
Normal file
41
graphene/pyutils/tests/test_enum.py
Normal file
41
graphene/pyutils/tests/test_enum.py
Normal 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
|
|
@ -5,6 +5,7 @@ from functools import partial
|
||||||
import six
|
import six
|
||||||
|
|
||||||
from graphql_relay import connection_from_list
|
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,
|
from ..types import (AbstractType, Boolean, Enum, Int, Interface, List, NonNull, Scalar, String,
|
||||||
Union)
|
Union)
|
||||||
|
@ -81,7 +82,7 @@ class ConnectionMeta(ObjectTypeMeta):
|
||||||
|
|
||||||
class ConnectionBase(AbstractType):
|
class ConnectionBase(AbstractType):
|
||||||
page_info = Field(PageInfo, name='pageInfo', required=True)
|
page_info = Field(PageInfo, name='pageInfo', required=True)
|
||||||
edges = List(edge)
|
edges = NonNull(List(edge))
|
||||||
|
|
||||||
bases = (ConnectionBase, ) + bases
|
bases = (ConnectionBase, ) + bases
|
||||||
attrs = dict(attrs, _meta=options, Edge=edge)
|
attrs = dict(attrs, _meta=options, Edge=edge)
|
||||||
|
@ -114,32 +115,41 @@ 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
|
||||||
def connection_resolver(cls, resolver, connection, root, args, context, info):
|
def resolve_connection(cls, connection_type, args, resolved):
|
||||||
resolved = resolver(root, args, context, info)
|
if isinstance(resolved, connection_type):
|
||||||
|
|
||||||
if isinstance(resolved, connection):
|
|
||||||
return resolved
|
return resolved
|
||||||
|
|
||||||
assert isinstance(resolved, Iterable), (
|
assert isinstance(resolved, Iterable), (
|
||||||
'Resolved value from the connection field have to be iterable or instance of {}. '
|
'Resolved value from the connection field have to be iterable or instance of {}. '
|
||||||
'Received "{}"'
|
'Received "{}"'
|
||||||
).format(connection, resolved)
|
).format(connection_type, resolved)
|
||||||
connection = connection_from_list(
|
connection = connection_from_list(
|
||||||
resolved,
|
resolved,
|
||||||
args,
|
args,
|
||||||
connection_type=connection,
|
connection_type=connection_type,
|
||||||
edge_type=connection.Edge,
|
edge_type=connection_type.Edge,
|
||||||
pageinfo_type=PageInfo
|
pageinfo_type=PageInfo
|
||||||
)
|
)
|
||||||
connection.iterable = resolved
|
connection.iterable = resolved
|
||||||
return connection
|
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):
|
def get_resolver(self, parent_resolver):
|
||||||
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.'
|
return False
|
||||||
)
|
|
||||||
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,24 +35,26 @@ def get_default_connection(cls):
|
||||||
|
|
||||||
class GlobalID(Field):
|
class GlobalID(Field):
|
||||||
|
|
||||||
def __init__(self, node, *args, **kwargs):
|
def __init__(self, node=None, parent_type=None, required=True, *args, **kwargs):
|
||||||
super(GlobalID, self).__init__(ID, *args, **kwargs)
|
super(GlobalID, self).__init__(ID, required=required, *args, **kwargs)
|
||||||
self.node = 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):
|
||||||
|
|
||||||
def __new__(cls, name, bases, attrs):
|
def __new__(cls, name, bases, attrs):
|
||||||
cls = InterfaceMeta.__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
|
return cls
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -28,8 +28,9 @@ def test_connection():
|
||||||
pageinfo_field = fields['page_info']
|
pageinfo_field = fields['page_info']
|
||||||
|
|
||||||
assert isinstance(edge_field, Field)
|
assert isinstance(edge_field, Field)
|
||||||
assert isinstance(edge_field.type, List)
|
assert isinstance(edge_field.type, NonNull)
|
||||||
assert edge_field.type.of_type == MyObjectConnection.Edge
|
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, Field)
|
||||||
assert isinstance(pageinfo_field.type, NonNull)
|
assert isinstance(pageinfo_field.type, NonNull)
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
from collections import OrderedDict
|
from collections import OrderedDict
|
||||||
|
|
||||||
from graphql_relay.utils import base64
|
from graphql_relay.utils import base64
|
||||||
|
from promise import Promise
|
||||||
|
|
||||||
from ...types import ObjectType, Schema, String
|
from ...types import ObjectType, Schema, String
|
||||||
from ..connection import ConnectionField, PageInfo
|
from ..connection import ConnectionField, PageInfo
|
||||||
|
@ -20,12 +21,16 @@ class Letter(ObjectType):
|
||||||
class Query(ObjectType):
|
class Query(ObjectType):
|
||||||
letters = ConnectionField(Letter)
|
letters = ConnectionField(Letter)
|
||||||
connection_letters = ConnectionField(Letter)
|
connection_letters = ConnectionField(Letter)
|
||||||
|
promise_letters = ConnectionField(Letter)
|
||||||
|
|
||||||
node = Node.Field()
|
node = Node.Field()
|
||||||
|
|
||||||
def resolve_letters(self, args, context, info):
|
def resolve_letters(self, args, context, info):
|
||||||
return list(letters.values())
|
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):
|
def resolve_connection_letters(self, args, context, info):
|
||||||
return Letter.Connection(
|
return Letter.Connection(
|
||||||
page_info=PageInfo(
|
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,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
60
graphene/relay/tests/test_global_id.py
Normal file
60
graphene/relay/tests/test_global_id.py
Normal 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)
|
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".'
|
|
@ -1,11 +1,12 @@
|
||||||
from collections import OrderedDict
|
from collections import OrderedDict
|
||||||
from itertools import chain
|
from itertools import chain
|
||||||
|
|
||||||
from ..utils.orderedtype import OrderedType
|
from .mountedtype import MountedType
|
||||||
from .structures import NonNull
|
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):
|
def __init__(self, type, default_value=None, description=None, name=None, required=False, _creation_counter=None):
|
||||||
super(Argument, self).__init__(_creation_counter=_creation_counter)
|
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
|
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:
|
||||||
|
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):
|
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):
|
if not isinstance(arg, Argument):
|
||||||
raise ValueError('Unknown argument "{}".'.format(default_name))
|
raise ValueError('Unknown argument "{}".'.format(default_name))
|
||||||
|
|
|
@ -29,11 +29,37 @@ class DateTime(Scalar):
|
||||||
)
|
)
|
||||||
return dt.isoformat()
|
return dt.isoformat()
|
||||||
|
|
||||||
@staticmethod
|
@classmethod
|
||||||
def parse_literal(node):
|
def parse_literal(cls, node):
|
||||||
if isinstance(node, ast.StringValue):
|
if isinstance(node, ast.StringValue):
|
||||||
return iso8601.parse_date(node.value)
|
return cls.parse_value(node.value)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def parse_value(value):
|
def parse_value(value):
|
||||||
return iso8601.parse_date(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)
|
||||||
|
|
|
@ -1,9 +1,9 @@
|
||||||
import inspect
|
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
|
A Dynamic Type let us get the type in runtime when we generate
|
||||||
the schema. So we can have lazy fields.
|
the schema. So we can have lazy fields.
|
||||||
|
|
|
@ -58,5 +58,10 @@ class Enum(six.with_metaclass(EnumTypeMeta, UnmountedType)):
|
||||||
kind of type, often integers.
|
kind of type, often integers.
|
||||||
'''
|
'''
|
||||||
|
|
||||||
def get_type(self):
|
@classmethod
|
||||||
return type(self)
|
def get_type(cls):
|
||||||
|
'''
|
||||||
|
This function is called when the unmounted type (Enum instance)
|
||||||
|
is mounted (as a Field, InputField or Argument)
|
||||||
|
'''
|
||||||
|
return cls
|
||||||
|
|
|
@ -2,8 +2,8 @@ import inspect
|
||||||
from collections import Mapping, OrderedDict
|
from collections import Mapping, OrderedDict
|
||||||
from functools import partial
|
from functools import partial
|
||||||
|
|
||||||
from ..utils.orderedtype import OrderedType
|
|
||||||
from .argument import Argument, to_arguments
|
from .argument import Argument, to_arguments
|
||||||
|
from .mountedtype import MountedType
|
||||||
from .structures import NonNull
|
from .structures import NonNull
|
||||||
from .unmountedtype import UnmountedType
|
from .unmountedtype import UnmountedType
|
||||||
|
|
||||||
|
@ -18,7 +18,7 @@ def source_resolver(source, root, args, context, info):
|
||||||
return resolved
|
return resolved
|
||||||
|
|
||||||
|
|
||||||
class Field(OrderedType):
|
class Field(MountedType):
|
||||||
|
|
||||||
def __init__(self, type, args=None, resolver=None, source=None,
|
def __init__(self, type, args=None, resolver=None, source=None,
|
||||||
deprecation_reason=None, name=None, description=None,
|
deprecation_reason=None, name=None, description=None,
|
||||||
|
@ -60,7 +60,7 @@ class Field(OrderedType):
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def type(self):
|
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()
|
||||||
return self._type
|
return self._type
|
||||||
|
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
from ..utils.orderedtype import OrderedType
|
from .mountedtype import MountedType
|
||||||
from .structures import NonNull
|
from .structures import NonNull
|
||||||
|
|
||||||
|
|
||||||
class InputField(OrderedType):
|
class InputField(MountedType):
|
||||||
|
|
||||||
def __init__(self, type, name=None, default_value=None,
|
def __init__(self, type, name=None, default_value=None,
|
||||||
deprecation_reason=None, description=None,
|
deprecation_reason=None, description=None,
|
||||||
|
|
|
@ -50,4 +50,8 @@ class InputObjectType(six.with_metaclass(InputObjectTypeMeta, UnmountedType)):
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def get_type(cls):
|
def get_type(cls):
|
||||||
|
'''
|
||||||
|
This function is called when the unmounted type (InputObjectType instance)
|
||||||
|
is mounted (as a Field, InputField or Argument)
|
||||||
|
'''
|
||||||
return cls
|
return cls
|
||||||
|
|
21
graphene/types/mountedtype.py
Normal file
21
graphene/types/mountedtype.py
Normal 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
|
||||||
|
)
|
|
@ -19,18 +19,18 @@ class Options(object):
|
||||||
for attr_name, value in defaults.items():
|
for attr_name, value in defaults.items():
|
||||||
if attr_name in meta_attrs:
|
if attr_name in meta_attrs:
|
||||||
value = meta_attrs.pop(attr_name)
|
value = meta_attrs.pop(attr_name)
|
||||||
elif hasattr(meta, attr_name):
|
|
||||||
value = getattr(meta, attr_name)
|
|
||||||
setattr(self, attr_name, value)
|
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
|
# it received invalid attributes
|
||||||
if meta_attrs:
|
if meta_attrs:
|
||||||
raise TypeError(
|
raise TypeError(
|
||||||
"Invalid attributes: {}".format(
|
"Invalid attributes: {}".format(
|
||||||
','.join(meta_attrs.keys())
|
', '.join(sorted(meta_attrs.keys()))
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
def __repr__(self):
|
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)
|
||||||
|
|
|
@ -43,8 +43,13 @@ class Scalar(six.with_metaclass(ScalarTypeMeta, UnmountedType)):
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def get_type(cls):
|
def get_type(cls):
|
||||||
|
'''
|
||||||
|
This function is called when the unmounted type (Scalar instance)
|
||||||
|
is mounted (as a Field, InputField or Argument)
|
||||||
|
'''
|
||||||
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.
|
||||||
#
|
#
|
||||||
|
|
|
@ -6,6 +6,7 @@ from graphql.type.introspection import IntrospectionSchema
|
||||||
from graphql.utils.introspection_query import introspection_query
|
from graphql.utils.introspection_query import introspection_query
|
||||||
from graphql.utils.schema_printer import print_schema
|
from graphql.utils.schema_printer import print_schema
|
||||||
|
|
||||||
|
from .definitions import GrapheneGraphQLType
|
||||||
from .typemap import TypeMap, is_graphene_type
|
from .typemap import TypeMap, is_graphene_type
|
||||||
|
|
||||||
|
|
||||||
|
@ -46,6 +47,20 @@ class Schema(GraphQLSchema):
|
||||||
def get_subscription_type(self):
|
def get_subscription_type(self):
|
||||||
return self.get_graphql_type(self._subscription)
|
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):
|
def get_graphql_type(self, _type):
|
||||||
if not _type:
|
if not _type:
|
||||||
return _type
|
return _type
|
||||||
|
@ -61,9 +76,6 @@ class Schema(GraphQLSchema):
|
||||||
def execute(self, *args, **kwargs):
|
def execute(self, *args, **kwargs):
|
||||||
return graphql(self, *args, **kwargs)
|
return graphql(self, *args, **kwargs)
|
||||||
|
|
||||||
def register(self, object_type):
|
|
||||||
self.types.append(object_type)
|
|
||||||
|
|
||||||
def introspect(self):
|
def introspect(self):
|
||||||
return self.execute(introspection_query).data
|
return self.execute(introspection_query).data
|
||||||
|
|
||||||
|
|
|
@ -9,9 +9,22 @@ 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):
|
||||||
|
'''
|
||||||
|
This function is called when the unmounted type (List or NonNull instance)
|
||||||
|
is mounted (as a Field, InputField or Argument)
|
||||||
|
'''
|
||||||
return self
|
return self
|
||||||
|
|
||||||
|
|
||||||
|
@ -52,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).'
|
||||||
|
|
|
@ -1,18 +1,22 @@
|
||||||
import datetime
|
import datetime
|
||||||
import pytz
|
import pytz
|
||||||
|
|
||||||
from ..datetime import DateTime
|
from ..datetime import DateTime, Time
|
||||||
from ..objecttype import ObjectType
|
from ..objecttype import ObjectType
|
||||||
from ..schema import Schema
|
from ..schema import Schema
|
||||||
|
|
||||||
|
|
||||||
class Query(ObjectType):
|
class Query(ObjectType):
|
||||||
datetime = DateTime(_in=DateTime(name='in'))
|
datetime = DateTime(_in=DateTime(name='in'))
|
||||||
|
time = Time(_at=Time(name='at'))
|
||||||
|
|
||||||
def resolve_datetime(self, args, context, info):
|
def resolve_datetime(self, args, context, info):
|
||||||
_in = args.get('in')
|
_in = args.get('in')
|
||||||
return _in
|
return _in
|
||||||
|
|
||||||
|
def resolve_time(self, args, context, info):
|
||||||
|
return args.get('at')
|
||||||
|
|
||||||
schema = Schema(query=Query)
|
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():
|
def test_datetime_query_variable():
|
||||||
now = datetime.datetime.now().replace(tzinfo=pytz.utc)
|
now = datetime.datetime.now().replace(tzinfo=pytz.utc)
|
||||||
isoformat = now.isoformat()
|
isoformat = now.isoformat()
|
||||||
|
@ -39,3 +54,18 @@ def test_datetime_query_variable():
|
||||||
assert result.data == {
|
assert result.data == {
|
||||||
'datetime': isoformat
|
'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
|
||||||
|
}
|
||||||
|
|
27
graphene/types/tests/test_dynamic.py
Normal file
27
graphene/types/tests/test_dynamic.py
Normal 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!]'
|
|
@ -1,4 +1,7 @@
|
||||||
from ..enum import Enum, PyEnum
|
from ..enum import Enum, PyEnum
|
||||||
|
from ..field import Field
|
||||||
|
from ..inputfield import InputField
|
||||||
|
from ..argument import Argument
|
||||||
|
|
||||||
|
|
||||||
def test_enum_construction():
|
def test_enum_construction():
|
||||||
|
@ -72,3 +75,39 @@ def test_enum_value_from_class():
|
||||||
assert RGB.RED.value == 1
|
assert RGB.RED.value == 1
|
||||||
assert RGB.GREEN.value == 2
|
assert RGB.GREEN.value == 2
|
||||||
assert RGB.BLUE.value == 3
|
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
|
||||||
|
|
|
@ -3,6 +3,7 @@ import pytest
|
||||||
from ..argument import Argument
|
from ..argument import Argument
|
||||||
from ..field import Field
|
from ..field import Field
|
||||||
from ..structures import NonNull
|
from ..structures import NonNull
|
||||||
|
from ..scalars import String
|
||||||
|
|
||||||
|
|
||||||
class MyInstance(object):
|
class MyInstance(object):
|
||||||
|
@ -75,6 +76,20 @@ def test_field_source_func():
|
||||||
assert field.resolver(MyInstance(), {}, None, None) == MyInstance.value_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():
|
def test_field_source_argument_as_kw():
|
||||||
MyType = object()
|
MyType = object()
|
||||||
field = Field(MyType, b=NonNull(True), c=Argument(None), a=NonNull(False))
|
field = Field(MyType, b=NonNull(True), c=Argument(None), a=NonNull(False))
|
||||||
|
|
|
@ -1,7 +1,9 @@
|
||||||
|
|
||||||
from ..abstracttype import AbstractType
|
from ..abstracttype import AbstractType
|
||||||
from ..field import Field
|
from ..field import Field
|
||||||
|
from ..argument import Argument
|
||||||
from ..inputfield import InputField
|
from ..inputfield import InputField
|
||||||
|
from ..objecttype import ObjectType
|
||||||
from ..inputobjecttype import InputObjectType
|
from ..inputobjecttype import InputObjectType
|
||||||
from ..unmountedtype import UnmountedType
|
from ..unmountedtype import UnmountedType
|
||||||
|
|
||||||
|
@ -61,6 +63,22 @@ def test_generate_inputobjecttype_unmountedtype():
|
||||||
assert isinstance(MyInputObjectType._meta.fields['field'], InputField)
|
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():
|
def test_generate_inputobjecttype_inherit_abstracttype():
|
||||||
class MyAbstractType(AbstractType):
|
class MyAbstractType(AbstractType):
|
||||||
field1 = MyScalar(MyType)
|
field1 = MyScalar(MyType)
|
||||||
|
|
26
graphene/types/tests/test_mountedtype.py
Normal file
26
graphene/types/tests/test_mountedtype.py
Normal 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!'}
|
|
@ -4,6 +4,7 @@ from ..mutation import Mutation
|
||||||
from ..objecttype import ObjectType
|
from ..objecttype import ObjectType
|
||||||
from ..schema import Schema
|
from ..schema import Schema
|
||||||
from ..scalars import String
|
from ..scalars import String
|
||||||
|
from ..dynamic import Dynamic
|
||||||
|
|
||||||
|
|
||||||
def test_generate_mutation_no_args():
|
def test_generate_mutation_no_args():
|
||||||
|
@ -47,12 +48,16 @@ def test_mutation_execution():
|
||||||
class CreateUser(Mutation):
|
class CreateUser(Mutation):
|
||||||
class Input:
|
class Input:
|
||||||
name = String()
|
name = String()
|
||||||
|
dynamic = Dynamic(lambda: String())
|
||||||
|
dynamic_none = Dynamic(lambda: None)
|
||||||
|
|
||||||
name = String()
|
name = String()
|
||||||
|
dynamic = Dynamic(lambda: String())
|
||||||
|
|
||||||
def mutate(self, args, context, info):
|
def mutate(self, args, context, info):
|
||||||
name = args.get('name')
|
name = args.get('name')
|
||||||
return CreateUser(name=name)
|
dynamic = args.get('dynamic')
|
||||||
|
return CreateUser(name=name, dynamic=dynamic)
|
||||||
|
|
||||||
class Query(ObjectType):
|
class Query(ObjectType):
|
||||||
a = String()
|
a = String()
|
||||||
|
@ -62,14 +67,16 @@ def test_mutation_execution():
|
||||||
|
|
||||||
schema = Schema(query=Query, mutation=MyMutation)
|
schema = Schema(query=Query, mutation=MyMutation)
|
||||||
result = schema.execute(''' mutation mymutation {
|
result = schema.execute(''' mutation mymutation {
|
||||||
createUser(name:"Peter") {
|
createUser(name:"Peter", dynamic: "dynamic") {
|
||||||
name
|
name
|
||||||
|
dynamic
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
''')
|
''')
|
||||||
assert not result.errors
|
assert not result.errors
|
||||||
assert result.data == {
|
assert result.data == {
|
||||||
'createUser': {
|
'createUser': {
|
||||||
'name': "Peter"
|
'name': 'Peter',
|
||||||
|
'dynamic': 'dynamic',
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
30
graphene/types/tests/test_options.py
Normal file
30
graphene/types/tests/test_options.py
Normal 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>'
|
|
@ -4,12 +4,15 @@ from functools import partial
|
||||||
from graphql import Source, execute, parse, GraphQLError
|
from graphql import Source, execute, parse, GraphQLError
|
||||||
|
|
||||||
from ..field import Field
|
from ..field import Field
|
||||||
|
from ..interface import Interface
|
||||||
from ..inputfield import InputField
|
from ..inputfield import InputField
|
||||||
from ..inputobjecttype import InputObjectType
|
from ..inputobjecttype import InputObjectType
|
||||||
from ..objecttype import ObjectType
|
from ..objecttype import ObjectType
|
||||||
from ..scalars import Int, String
|
from ..scalars import Int, String
|
||||||
from ..schema import Schema
|
from ..schema import Schema
|
||||||
from ..structures import List
|
from ..structures import List
|
||||||
|
from ..union import Union
|
||||||
|
from ..dynamic import Dynamic
|
||||||
|
|
||||||
|
|
||||||
def test_query():
|
def test_query():
|
||||||
|
@ -23,6 +26,112 @@ def test_query():
|
||||||
assert executed.data == {'hello': 'World'}
|
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():
|
def test_query_default_value():
|
||||||
class MyType(ObjectType):
|
class MyType(ObjectType):
|
||||||
field = String()
|
field = String()
|
||||||
|
|
54
graphene/types/tests/test_schema.py
Normal file
54
graphene/types/tests/test_schema.py
Normal 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()
|
|
@ -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)
|
||||||
|
|
|
@ -5,20 +5,26 @@ from functools import partial
|
||||||
from graphql import (GraphQLArgument, GraphQLBoolean, GraphQLField,
|
from graphql import (GraphQLArgument, GraphQLBoolean, GraphQLField,
|
||||||
GraphQLFloat, GraphQLID, GraphQLInputObjectField,
|
GraphQLFloat, GraphQLID, GraphQLInputObjectField,
|
||||||
GraphQLInt, GraphQLList, GraphQLNonNull, GraphQLString)
|
GraphQLInt, GraphQLList, GraphQLNonNull, GraphQLString)
|
||||||
from graphql.type import GraphQLEnumValue
|
|
||||||
from graphql.execution.executor import get_default_resolve_type_fn
|
from graphql.execution.executor import get_default_resolve_type_fn
|
||||||
|
from graphql.type import GraphQLEnumValue
|
||||||
from graphql.type.typemap import GraphQLTypeMap
|
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.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 .dynamic import Dynamic
|
||||||
from .enum import Enum
|
from .enum import Enum
|
||||||
|
from .field import Field
|
||||||
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 ID, Boolean, Float, Int, Scalar, String
|
from .scalars import ID, Boolean, Float, Int, Scalar, String
|
||||||
from .structures import List, NonNull
|
from .structures import List, NonNull
|
||||||
from .union import Union
|
from .union import Union
|
||||||
|
from .utils import get_field_as
|
||||||
|
|
||||||
|
|
||||||
def is_graphene_type(_type):
|
def is_graphene_type(_type):
|
||||||
|
@ -28,18 +34,18 @@ def is_graphene_type(_type):
|
||||||
return True
|
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)
|
_type = resolve_type_func(root, context, info)
|
||||||
# assert inspect.isclass(_type) and issubclass(_type, ObjectType), (
|
|
||||||
# 'Received incompatible type "{}".'.format(_type)
|
|
||||||
# )
|
|
||||||
if not _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):
|
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 and graphql_type.graphene_type == _type
|
||||||
return graphql_type
|
return graphql_type
|
||||||
|
|
||||||
return _type
|
return _type
|
||||||
|
|
||||||
|
|
||||||
|
@ -63,7 +69,7 @@ class TypeMap(GraphQLTypeMap):
|
||||||
return self.reducer(map, type.of_type)
|
return self.reducer(map, type.of_type)
|
||||||
if type._meta.name in map:
|
if type._meta.name in map:
|
||||||
_type = map[type._meta.name]
|
_type = map[type._meta.name]
|
||||||
if is_graphene_type(_type):
|
if isinstance(_type, GrapheneGraphQLType):
|
||||||
assert _type.graphene_type == type
|
assert _type.graphene_type == type
|
||||||
return map
|
return map
|
||||||
if issubclass(type, ObjectType):
|
if issubclass(type, ObjectType):
|
||||||
|
@ -81,7 +87,6 @@ class TypeMap(GraphQLTypeMap):
|
||||||
return map
|
return map
|
||||||
|
|
||||||
def construct_scalar(self, map, type):
|
def construct_scalar(self, map, type):
|
||||||
from .definitions import GrapheneScalarType
|
|
||||||
_scalars = {
|
_scalars = {
|
||||||
String: GraphQLString,
|
String: GraphQLString,
|
||||||
Int: GraphQLInt,
|
Int: GraphQLInt,
|
||||||
|
@ -104,7 +109,6 @@ class TypeMap(GraphQLTypeMap):
|
||||||
return map
|
return map
|
||||||
|
|
||||||
def construct_enum(self, map, type):
|
def construct_enum(self, map, type):
|
||||||
from .definitions import GrapheneEnumType
|
|
||||||
values = OrderedDict()
|
values = OrderedDict()
|
||||||
for name, value in type._meta.enum.__members__.items():
|
for name, value in type._meta.enum.__members__.items():
|
||||||
values[name] = GraphQLEnumValue(
|
values[name] = GraphQLEnumValue(
|
||||||
|
@ -122,10 +126,9 @@ class TypeMap(GraphQLTypeMap):
|
||||||
return map
|
return map
|
||||||
|
|
||||||
def construct_objecttype(self, map, type):
|
def construct_objecttype(self, map, type):
|
||||||
from .definitions import GrapheneObjectType
|
|
||||||
if type._meta.name in map:
|
if type._meta.name in map:
|
||||||
_type = map[type._meta.name]
|
_type = map[type._meta.name]
|
||||||
if is_graphene_type(_type):
|
if isinstance(_type, GrapheneGraphQLType):
|
||||||
assert _type.graphene_type == type
|
assert _type.graphene_type == type
|
||||||
return map
|
return map
|
||||||
map[type._meta.name] = GrapheneObjectType(
|
map[type._meta.name] = GrapheneObjectType(
|
||||||
|
@ -146,10 +149,9 @@ class TypeMap(GraphQLTypeMap):
|
||||||
return map
|
return map
|
||||||
|
|
||||||
def construct_interface(self, map, type):
|
def construct_interface(self, map, type):
|
||||||
from .definitions import GrapheneInterfaceType
|
|
||||||
_resolve_type = None
|
_resolve_type = None
|
||||||
if type.resolve_type:
|
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(
|
map[type._meta.name] = GrapheneInterfaceType(
|
||||||
graphene_type=type,
|
graphene_type=type,
|
||||||
name=type._meta.name,
|
name=type._meta.name,
|
||||||
|
@ -162,7 +164,6 @@ class TypeMap(GraphQLTypeMap):
|
||||||
return map
|
return map
|
||||||
|
|
||||||
def construct_inputobjecttype(self, map, type):
|
def construct_inputobjecttype(self, map, type):
|
||||||
from .definitions import GrapheneInputObjectType
|
|
||||||
map[type._meta.name] = GrapheneInputObjectType(
|
map[type._meta.name] = GrapheneInputObjectType(
|
||||||
graphene_type=type,
|
graphene_type=type,
|
||||||
name=type._meta.name,
|
name=type._meta.name,
|
||||||
|
@ -173,10 +174,9 @@ class TypeMap(GraphQLTypeMap):
|
||||||
return map
|
return map
|
||||||
|
|
||||||
def construct_union(self, map, type):
|
def construct_union(self, map, type):
|
||||||
from .definitions import GrapheneUnionType
|
|
||||||
_resolve_type = None
|
_resolve_type = None
|
||||||
if type.resolve_type:
|
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 = []
|
types = []
|
||||||
for i in type._meta.types:
|
for i in type._meta.types:
|
||||||
map = self.construct_objecttype(map, i)
|
map = self.construct_objecttype(map, i)
|
||||||
|
@ -202,7 +202,7 @@ class TypeMap(GraphQLTypeMap):
|
||||||
fields = OrderedDict()
|
fields = OrderedDict()
|
||||||
for name, field in type._meta.fields.items():
|
for name, field in type._meta.fields.items():
|
||||||
if isinstance(field, Dynamic):
|
if isinstance(field, Dynamic):
|
||||||
field = field.get_type()
|
field = get_field_as(field.get_type(), _as=Field)
|
||||||
if not field:
|
if not field:
|
||||||
continue
|
continue
|
||||||
map = self.reducer(map, field.type)
|
map = self.reducer(map, field.type)
|
||||||
|
|
|
@ -39,7 +39,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("An Union cannot be intitialized")
|
raise Exception("A Union cannot be intitialized")
|
||||||
|
|
|
@ -8,7 +8,7 @@ class UnmountedType(OrderedType):
|
||||||
|
|
||||||
Instead of writing
|
Instead of writing
|
||||||
>>> class MyObjectType(ObjectType):
|
>>> class MyObjectType(ObjectType):
|
||||||
>>> my_field = Field(String(), description='Description here')
|
>>> my_field = Field(String, description='Description here')
|
||||||
|
|
||||||
It let you write
|
It let you write
|
||||||
>>> class MyObjectType(ObjectType):
|
>>> class MyObjectType(ObjectType):
|
||||||
|
@ -21,43 +21,35 @@ class UnmountedType(OrderedType):
|
||||||
self.kwargs = kwargs
|
self.kwargs = kwargs
|
||||||
|
|
||||||
def get_type(self):
|
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))
|
raise NotImplementedError("get_type not implemented in {}".format(self))
|
||||||
|
|
||||||
|
def mount_as(self, _as):
|
||||||
|
return _as.mount(self)
|
||||||
|
|
||||||
def Field(self): # noqa: N802
|
def Field(self): # noqa: N802
|
||||||
'''
|
'''
|
||||||
Mount the UnmountedType as Field
|
Mount the UnmountedType as Field
|
||||||
'''
|
'''
|
||||||
from .field import Field
|
from .field import Field
|
||||||
return Field(
|
return self.mount_as(Field)
|
||||||
self.get_type(),
|
|
||||||
*self.args,
|
|
||||||
_creation_counter=self.creation_counter,
|
|
||||||
**self.kwargs
|
|
||||||
)
|
|
||||||
|
|
||||||
def InputField(self): # noqa: N802
|
def InputField(self): # noqa: N802
|
||||||
'''
|
'''
|
||||||
Mount the UnmountedType as InputField
|
Mount the UnmountedType as InputField
|
||||||
'''
|
'''
|
||||||
from .inputfield import InputField
|
from .inputfield import InputField
|
||||||
return InputField(
|
return self.mount_as(InputField)
|
||||||
self.get_type(),
|
|
||||||
*self.args,
|
|
||||||
_creation_counter=self.creation_counter,
|
|
||||||
**self.kwargs
|
|
||||||
)
|
|
||||||
|
|
||||||
def Argument(self): # noqa: N802
|
def Argument(self): # noqa: N802
|
||||||
'''
|
'''
|
||||||
Mount the UnmountedType as Argument
|
Mount the UnmountedType as Argument
|
||||||
'''
|
'''
|
||||||
from .argument import Argument
|
from .argument import Argument
|
||||||
return Argument(
|
return self.mount_as(Argument)
|
||||||
self.get_type(),
|
|
||||||
*self.args,
|
|
||||||
_creation_counter=self.creation_counter,
|
|
||||||
**self.kwargs
|
|
||||||
)
|
|
||||||
|
|
||||||
def __eq__(self, other):
|
def __eq__(self, other):
|
||||||
return (
|
return (
|
||||||
|
|
|
@ -1,8 +1,6 @@
|
||||||
from collections import OrderedDict
|
from collections import OrderedDict
|
||||||
|
|
||||||
from .dynamic import Dynamic
|
from .mountedtype import MountedType
|
||||||
from .field import Field
|
|
||||||
from .inputfield import InputField
|
|
||||||
from .unmountedtype import UnmountedType
|
from .unmountedtype import UnmountedType
|
||||||
|
|
||||||
|
|
||||||
|
@ -35,34 +33,16 @@ def get_base_fields(bases, _as=None):
|
||||||
return fields
|
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):
|
def get_field_as(value, _as=None):
|
||||||
'''
|
'''
|
||||||
Get type mounted
|
Get type mounted
|
||||||
'''
|
'''
|
||||||
if isinstance(value, (Field, InputField, Dynamic)):
|
if isinstance(value, MountedType):
|
||||||
return value
|
return value
|
||||||
elif isinstance(value, UnmountedType):
|
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):
|
def yank_fields_from_attrs(attrs, _as=None, delete=True, sort=True):
|
||||||
|
|
|
@ -23,3 +23,19 @@ def test_orderedtype_hash():
|
||||||
|
|
||||||
assert hash(one) == hash(one)
|
assert hash(one) == hash(one)
|
||||||
assert hash(one) != hash(two)
|
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
|
||||||
|
|
6
setup.py
6
setup.py
|
@ -70,9 +70,9 @@ setup(
|
||||||
|
|
||||||
install_requires=[
|
install_requires=[
|
||||||
'six>=1.10.0',
|
'six>=1.10.0',
|
||||||
'graphql-core>=1.0',
|
'graphql-core>=1.0.1',
|
||||||
'graphql-relay>=0.4.4',
|
'graphql-relay>=0.4.5',
|
||||||
'promise',
|
'promise>=1.0.1',
|
||||||
],
|
],
|
||||||
tests_require=[
|
tests_require=[
|
||||||
'pytest>=2.7.2',
|
'pytest>=2.7.2',
|
||||||
|
|
7
tox.ini
7
tox.ini
|
@ -5,12 +5,15 @@ skipsdist = true
|
||||||
[testenv]
|
[testenv]
|
||||||
deps=
|
deps=
|
||||||
pytest>=2.7.2
|
pytest>=2.7.2
|
||||||
graphql-core>=0.5.1
|
graphql-core>=1.0.1
|
||||||
graphql-relay>=0.4.3
|
graphql-relay>=0.4.5
|
||||||
six
|
six
|
||||||
blinker
|
blinker
|
||||||
singledispatch
|
singledispatch
|
||||||
mock
|
mock
|
||||||
|
pytz
|
||||||
|
iso8601
|
||||||
|
pytest-benchmark
|
||||||
setenv =
|
setenv =
|
||||||
PYTHONPATH = .:{envdir}
|
PYTHONPATH = .:{envdir}
|
||||||
commands=
|
commands=
|
||||||
|
|
Loading…
Reference in New Issue
Block a user