mirror of
https://github.com/graphql-python/graphene.git
synced 2025-05-31 03:33:09 +03:00
commit
3e8e65f033
2
.gitignore
vendored
2
.gitignore
vendored
|
@ -59,3 +59,5 @@ docs/_build/
|
||||||
# PyBuilder
|
# PyBuilder
|
||||||
target/
|
target/
|
||||||
|
|
||||||
|
|
||||||
|
/tests/django.sqlite
|
||||||
|
|
|
@ -6,9 +6,10 @@ python:
|
||||||
- 3.4
|
- 3.4
|
||||||
- 3.5
|
- 3.5
|
||||||
- pypy
|
- pypy
|
||||||
|
cache: pip
|
||||||
install:
|
install:
|
||||||
- pip install pytest pytest-cov coveralls flake8 six blinker pytest-django
|
- pip install --cache-dir $HOME/.cache/pip pytest pytest-cov coveralls flake8 six blinker pytest-django
|
||||||
- pip install -e .[django]
|
- pip install --cache-dir $HOME/.cache/pip -e .[django]
|
||||||
- python setup.py develop
|
- python setup.py develop
|
||||||
script:
|
script:
|
||||||
- py.test --cov=graphene
|
- py.test --cov=graphene
|
||||||
|
|
80
README.md
80
README.md
|
@ -1,10 +1,13 @@
|
||||||
#  [Graphene](http://graphene-python.org) [](https://travis-ci.org/graphql-python/graphene) [](https://coveralls.io/github/graphql-python/graphene?branch=master)
|
#  [Graphene](http://graphene-python.org) [](https://travis-ci.org/graphql-python/graphene) [](https://badge.fury.io/py/graphene) [](https://coveralls.io/github/graphql-python/graphene?branch=master)
|
||||||
|
|
||||||
|
|
||||||
Graphene is a Python library for building GraphQL schemas/types fast and easily.
|
Graphene is a Python library for building GraphQL schemas/types fast and easily.
|
||||||
* **Easy to use:** It maps the models/fields to internal GraphQL objects without effort.
|
* **Easy to use:** Graphene helps you use GraphQL in Python without effort.
|
||||||
* **Relay:** Graphene has builtin support for Relay
|
* **Relay:** Graphene has builtin support for Relay
|
||||||
* **Django:** Automatic [Django models](#djangorelay-schema) conversion. *See an [example Django](http://github.com/graphql-python/swapi-graphene) implementation*
|
* **Django:** Automatic *Django model* mapping to Graphene Types. *See an [example Django](http://github.com/graphql-python/swapi-graphene) implementation*
|
||||||
|
|
||||||
|
|
||||||
|
*But, what is supported in this Python version?* **Everything**: Interfaces, ObjectTypes, Mutations and Relay (Nodes, Connections and Mutations).
|
||||||
|
|
||||||
|
|
||||||
## Installation
|
## Installation
|
||||||
|
@ -13,29 +16,26 @@ For instaling graphene, just run this command in your shell
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
pip install graphene
|
pip install graphene
|
||||||
|
# Or in case of need Django model support
|
||||||
|
pip install graphene[django]
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
||||||
## Usage
|
## Examples
|
||||||
|
|
||||||
Example code of a GraphQL schema using Graphene:
|
Here is one example for get you started:
|
||||||
|
|
||||||
### Schema definition
|
|
||||||
|
|
||||||
```python
|
```python
|
||||||
class Character(graphene.Interface):
|
|
||||||
id = graphene.IDField()
|
|
||||||
name = graphene.StringField()
|
|
||||||
friends = graphene.ListField('self')
|
|
||||||
|
|
||||||
def resolve_friends(self, args, *_):
|
|
||||||
return [Human(f) for f in self.instance.friends]
|
|
||||||
|
|
||||||
class Human(Character):
|
|
||||||
homePlanet = graphene.StringField()
|
|
||||||
|
|
||||||
class Query(graphene.ObjectType):
|
class Query(graphene.ObjectType):
|
||||||
human = graphene.Field(Human)
|
hello = graphene.StringField(description='A typical hello world')
|
||||||
|
ping = graphene.StringField(description='Ping someone',
|
||||||
|
to=graphene.Argument(graphene.String))
|
||||||
|
|
||||||
|
def resolve_hello(self, args, info):
|
||||||
|
return 'World'
|
||||||
|
|
||||||
|
def resolve_ping(self, args, info):
|
||||||
|
return 'Pinging {}'.format(args.get('to'))
|
||||||
|
|
||||||
schema = graphene.Schema(query=Query)
|
schema = graphene.Schema(query=Query)
|
||||||
```
|
```
|
||||||
|
@ -44,48 +44,20 @@ Then Querying `graphene.Schema` is as simple as:
|
||||||
|
|
||||||
```python
|
```python
|
||||||
query = '''
|
query = '''
|
||||||
query HeroNameQuery {
|
query SayHello {
|
||||||
hero {
|
hello
|
||||||
name
|
ping(to:'peter')
|
||||||
}
|
|
||||||
}
|
}
|
||||||
'''
|
'''
|
||||||
result = schema.execute(query)
|
result = schema.execute(query)
|
||||||
```
|
```
|
||||||
|
|
||||||
### Relay Schema
|
If you want to learn even more, you can also check the following [examples](examples/):
|
||||||
|
|
||||||
Graphene also supports Relay, check the [Starwars Relay example](tests/starwars_relay)!
|
* **Basic Schema**: [Starwars example](examples/starwars)
|
||||||
|
* **Relay Schema**: [Starwars Relay example](examples/starwars_relay)
|
||||||
|
* **Django model mapping**: [Starwars Django example](examples/starwars_django)
|
||||||
|
|
||||||
```python
|
|
||||||
class Ship(relay.Node):
|
|
||||||
name = graphene.StringField()
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def get_node(cls, id):
|
|
||||||
return Ship(your_ship_instance)
|
|
||||||
|
|
||||||
|
|
||||||
class Query(graphene.ObjectType):
|
|
||||||
ships = relay.ConnectionField(Ship)
|
|
||||||
node = relay.NodeField()
|
|
||||||
|
|
||||||
```
|
|
||||||
|
|
||||||
### Django+Relay Schema
|
|
||||||
|
|
||||||
If you want to use graphene with your Django Models check the [Starwars Django example](tests/starwars_django)!
|
|
||||||
|
|
||||||
```python
|
|
||||||
class Ship(DjangoNode):
|
|
||||||
class Meta:
|
|
||||||
model = YourDjangoModelHere
|
|
||||||
# only_fields = ('id', 'name') # Only map this fields from the model
|
|
||||||
# exclude_fields ('field_to_exclude', ) # Exclude mapping this fields from the model
|
|
||||||
|
|
||||||
class Query(graphene.ObjectType):
|
|
||||||
node = relay.NodeField()
|
|
||||||
```
|
|
||||||
|
|
||||||
## Contributing
|
## Contributing
|
||||||
|
|
||||||
|
|
114
README.rst
114
README.rst
|
@ -1,9 +1,16 @@
|
||||||
Graphene |Build Status| |Coverage Status|
|
|Graphene Logo| `Graphene <http://graphene-python.org>`__ |Build Status| |PyPI version| |Coverage Status|
|
||||||
=========================================
|
=========================================================================================================
|
||||||
|
|
||||||
Graphene is a Python library for creating GraphQL schemas/types easly.
|
Graphene is a Python library for building GraphQL schemas/types fast and
|
||||||
It maps the models/fields to internal GraphQL objects without effort.
|
easily. \* **Easy to use:** Graphene helps you use GraphQL in Python
|
||||||
Including automatic `Django models`_ conversion.
|
without effort. \* **Relay:** Graphene has builtin support for Relay \*
|
||||||
|
**Django:** Automatic *Django model* mapping to Graphene Types. *See an
|
||||||
|
`example Django <http://github.com/graphql-python/swapi-graphene>`__
|
||||||
|
implementation*
|
||||||
|
|
||||||
|
*But, what is supported in this Python version?* **Everything**:
|
||||||
|
Interfaces, ObjectTypes, Mutations and Relay (Nodes, Connections and
|
||||||
|
Mutations).
|
||||||
|
|
||||||
Installation
|
Installation
|
||||||
------------
|
------------
|
||||||
|
@ -13,89 +20,49 @@ For instaling graphene, just run this command in your shell
|
||||||
.. code:: bash
|
.. code:: bash
|
||||||
|
|
||||||
pip install graphene
|
pip install graphene
|
||||||
|
# Or in case of need Django model support
|
||||||
|
pip install graphene[django]
|
||||||
|
|
||||||
Usage
|
Examples
|
||||||
-----
|
--------
|
||||||
|
|
||||||
Example code of a GraphQL schema using Graphene:
|
Here is one example for get you started:
|
||||||
|
|
||||||
Schema definition
|
|
||||||
~~~~~~~~~~~~~~~~~
|
|
||||||
|
|
||||||
.. code:: python
|
.. code:: python
|
||||||
|
|
||||||
class Character(graphene.Interface):
|
|
||||||
id = graphene.IDField()
|
|
||||||
name = graphene.StringField()
|
|
||||||
friends = graphene.ListField('self')
|
|
||||||
|
|
||||||
def resolve_friends(self, args, *_):
|
|
||||||
return [Human(f) for f in self.instance.friends]
|
|
||||||
|
|
||||||
class Human(Character):
|
|
||||||
homePlanet = graphene.StringField()
|
|
||||||
|
|
||||||
class Query(graphene.ObjectType):
|
class Query(graphene.ObjectType):
|
||||||
human = graphene.Field(Human)
|
hello = graphene.StringField(description='A typical hello world')
|
||||||
|
ping = graphene.StringField(description='Ping someone',
|
||||||
|
to=graphene.Argument(graphene.String))
|
||||||
|
|
||||||
|
def resolve_hello(self, args, info):
|
||||||
|
return 'World'
|
||||||
|
|
||||||
|
def resolve_ping(self, args, info):
|
||||||
|
return 'Pinging {}'.format(args.get('to'))
|
||||||
|
|
||||||
schema = graphene.Schema(query=Query)
|
schema = graphene.Schema(query=Query)
|
||||||
|
|
||||||
Querying
|
Then Querying ``graphene.Schema`` is as simple as:
|
||||||
~~~~~~~~
|
|
||||||
|
|
||||||
Querying ``graphene.Schema`` is as simple as:
|
|
||||||
|
|
||||||
.. code:: python
|
.. code:: python
|
||||||
|
|
||||||
query = '''
|
query = '''
|
||||||
query HeroNameQuery {
|
query SayHello {
|
||||||
hero {
|
hello
|
||||||
name
|
ping(to:'peter')
|
||||||
}
|
|
||||||
}
|
}
|
||||||
'''
|
'''
|
||||||
result = schema.execute(query)
|
result = schema.execute(query)
|
||||||
|
|
||||||
Relay Schema
|
If you want to learn even more, you can also check the following
|
||||||
~~~~~~~~~~~~
|
`examples <examples/>`__:
|
||||||
|
|
||||||
Graphene also supports Relay, check the `Starwars Relay example`_!
|
- **Basic Schema**: `Starwars example <examples/starwars>`__
|
||||||
|
- **Relay Schema**: `Starwars Relay
|
||||||
.. code:: python
|
example <examples/starwars_relay>`__
|
||||||
|
- **Django model mapping**: `Starwars Django
|
||||||
class Ship(relay.Node):
|
example <examples/starwars_django>`__
|
||||||
'''A ship in the Star Wars saga'''
|
|
||||||
name = graphene.StringField(description='The name of the ship.')
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def get_node(cls, id):
|
|
||||||
return Ship(getShip(id))
|
|
||||||
|
|
||||||
|
|
||||||
class Query(graphene.ObjectType):
|
|
||||||
ships = relay.ConnectionField(Ship, description='The ships used by the faction.')
|
|
||||||
node = relay.NodeField()
|
|
||||||
|
|
||||||
@resolve_only_args
|
|
||||||
def resolve_ships(self):
|
|
||||||
return [Ship(s) for s in getShips()]
|
|
||||||
|
|
||||||
Django+Relay Schema
|
|
||||||
~~~~~~~~~~~~~~~~~~~
|
|
||||||
|
|
||||||
If you want to use graphene with your Django Models check the `Starwars
|
|
||||||
Django example`_!
|
|
||||||
|
|
||||||
.. code:: python
|
|
||||||
|
|
||||||
class Ship(DjangoNode):
|
|
||||||
class Meta:
|
|
||||||
model = YourDjangoModelHere
|
|
||||||
# only_fields = ('id', 'name') # Only map this fields from the model
|
|
||||||
# excluxe_fields ('field_to_excluxe', ) # Exclude mapping this fields from the model
|
|
||||||
|
|
||||||
class Query(graphene.ObjectType):
|
|
||||||
node = relay.NodeField()
|
|
||||||
|
|
||||||
Contributing
|
Contributing
|
||||||
------------
|
------------
|
||||||
|
@ -112,11 +79,10 @@ 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
|
||||||
|
|
||||||
.. _Django models: #djangorelay-schema
|
.. |Graphene Logo| image:: http://graphene-python.org/favicon.png
|
||||||
.. _Starwars Relay example: tests/starwars_relay
|
|
||||||
.. _Starwars Django example: tests/starwars_django
|
|
||||||
|
|
||||||
.. |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
|
||||||
:target: https://travis-ci.org/graphql-python/graphene
|
:target: https://travis-ci.org/graphql-python/graphene
|
||||||
|
.. |PyPI version| image:: https://badge.fury.io/py/graphene.svg
|
||||||
|
:target: https://badge.fury.io/py/graphene
|
||||||
.. |Coverage Status| image:: https://coveralls.io/repos/graphql-python/graphene/badge.svg?branch=master&service=github
|
.. |Coverage Status| image:: https://coveralls.io/repos/graphql-python/graphene/badge.svg?branch=master&service=github
|
||||||
:target: https://coveralls.io/github/graphql-python/graphene?branch=master
|
:target: https://coveralls.io/github/graphql-python/graphene?branch=master
|
||||||
|
|
3
bin/convert_documentation
Executable file
3
bin/convert_documentation
Executable file
|
@ -0,0 +1,3 @@
|
||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
pandoc README.md --from markdown --to rst -s -o README.rst
|
97
examples/starwars/data.py
Normal file
97
examples/starwars/data.py
Normal file
|
@ -0,0 +1,97 @@
|
||||||
|
humanData = {}
|
||||||
|
droidData = {}
|
||||||
|
|
||||||
|
|
||||||
|
def setup():
|
||||||
|
from .schema import Human, Droid
|
||||||
|
global humanData, droidData
|
||||||
|
luke = Human(
|
||||||
|
id='1000',
|
||||||
|
name='Luke Skywalker',
|
||||||
|
friends=['1002', '1003', '2000', '2001'],
|
||||||
|
appears_in=[4, 5, 6],
|
||||||
|
home_planet='Tatooine',
|
||||||
|
)
|
||||||
|
|
||||||
|
vader = Human(
|
||||||
|
id='1001',
|
||||||
|
name='Darth Vader',
|
||||||
|
friends=['1004'],
|
||||||
|
appears_in=[4, 5, 6],
|
||||||
|
home_planet='Tatooine',
|
||||||
|
)
|
||||||
|
|
||||||
|
han = Human(
|
||||||
|
id='1002',
|
||||||
|
name='Han Solo',
|
||||||
|
friends=['1000', '1003', '2001'],
|
||||||
|
appears_in=[4, 5, 6],
|
||||||
|
home_planet=None,
|
||||||
|
)
|
||||||
|
|
||||||
|
leia = Human(
|
||||||
|
id='1003',
|
||||||
|
name='Leia Organa',
|
||||||
|
friends=['1000', '1002', '2000', '2001'],
|
||||||
|
appears_in=[4, 5, 6],
|
||||||
|
home_planet='Alderaan',
|
||||||
|
)
|
||||||
|
|
||||||
|
tarkin = Human(
|
||||||
|
id='1004',
|
||||||
|
name='Wilhuff Tarkin',
|
||||||
|
friends=['1001'],
|
||||||
|
appears_in=[4],
|
||||||
|
home_planet=None,
|
||||||
|
)
|
||||||
|
|
||||||
|
humanData = {
|
||||||
|
'1000': luke,
|
||||||
|
'1001': vader,
|
||||||
|
'1002': han,
|
||||||
|
'1003': leia,
|
||||||
|
'1004': tarkin,
|
||||||
|
}
|
||||||
|
|
||||||
|
threepio = Droid(
|
||||||
|
id='2000',
|
||||||
|
name='C-3PO',
|
||||||
|
friends=['1000', '1002', '1003', '2001'],
|
||||||
|
appears_in=[4, 5, 6],
|
||||||
|
primary_function='Protocol',
|
||||||
|
)
|
||||||
|
|
||||||
|
artoo = Droid(
|
||||||
|
id='2001',
|
||||||
|
name='R2-D2',
|
||||||
|
friends=['1000', '1002', '1003'],
|
||||||
|
appears_in=[4, 5, 6],
|
||||||
|
primary_function='Astromech',
|
||||||
|
)
|
||||||
|
|
||||||
|
droidData = {
|
||||||
|
'2000': threepio,
|
||||||
|
'2001': artoo,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def getCharacter(id):
|
||||||
|
return humanData.get(id) or droidData.get(id)
|
||||||
|
|
||||||
|
|
||||||
|
def getFriends(character):
|
||||||
|
return map(getCharacter, character.friends)
|
||||||
|
|
||||||
|
|
||||||
|
def getHero(episode):
|
||||||
|
if episode == 5:
|
||||||
|
return humanData['1000']
|
||||||
|
return droidData['2001']
|
||||||
|
|
||||||
|
|
||||||
|
def getHuman(id):
|
||||||
|
return humanData.get(id)
|
||||||
|
|
||||||
|
|
||||||
|
def getDroid(id):
|
||||||
|
return droidData.get(id)
|
|
@ -2,7 +2,7 @@ from graphql.core.type import GraphQLEnumValue
|
||||||
import graphene
|
import graphene
|
||||||
from graphene import resolve_only_args
|
from graphene import resolve_only_args
|
||||||
|
|
||||||
from .data import getHero, getHuman, getCharacter, getDroid, Human as _Human, Droid as _Droid
|
from .data import getHero, getHuman, getCharacter, getDroid
|
||||||
|
|
||||||
Episode = graphene.Enum('Episode', dict(
|
Episode = graphene.Enum('Episode', dict(
|
||||||
NEWHOPE=GraphQLEnumValue(4),
|
NEWHOPE=GraphQLEnumValue(4),
|
||||||
|
@ -11,29 +11,23 @@ Episode = graphene.Enum('Episode', dict(
|
||||||
))
|
))
|
||||||
|
|
||||||
|
|
||||||
def wrap_character(character):
|
|
||||||
if isinstance(character, _Human):
|
|
||||||
return Human(character)
|
|
||||||
elif isinstance(character, _Droid):
|
|
||||||
return Droid(character)
|
|
||||||
|
|
||||||
|
|
||||||
class Character(graphene.Interface):
|
class Character(graphene.Interface):
|
||||||
id = graphene.IDField()
|
id = graphene.IDField()
|
||||||
name = graphene.StringField()
|
name = graphene.StringField()
|
||||||
friends = graphene.ListField('self')
|
friends = graphene.ListField('self')
|
||||||
appearsIn = graphene.ListField(Episode)
|
appears_in = graphene.ListField(Episode)
|
||||||
|
|
||||||
def resolve_friends(self, args, *_):
|
def resolve_friends(self, args, *_):
|
||||||
return [wrap_character(getCharacter(f)) for f in self.instance.friends]
|
# The character friends is a list of strings
|
||||||
|
return [getCharacter(f) for f in self.friends]
|
||||||
|
|
||||||
|
|
||||||
class Human(Character):
|
class Human(Character):
|
||||||
homePlanet = graphene.StringField()
|
home_planet = graphene.StringField()
|
||||||
|
|
||||||
|
|
||||||
class Droid(Character):
|
class Droid(Character):
|
||||||
primaryFunction = graphene.StringField()
|
primary_function = graphene.StringField()
|
||||||
|
|
||||||
|
|
||||||
class Query(graphene.ObjectType):
|
class Query(graphene.ObjectType):
|
||||||
|
@ -47,20 +41,17 @@ class Query(graphene.ObjectType):
|
||||||
id=graphene.Argument(graphene.String)
|
id=graphene.Argument(graphene.String)
|
||||||
)
|
)
|
||||||
|
|
||||||
class Meta:
|
|
||||||
type_name = 'core_Query'
|
|
||||||
|
|
||||||
@resolve_only_args
|
@resolve_only_args
|
||||||
def resolve_hero(self, episode=None):
|
def resolve_hero(self, episode=None):
|
||||||
return wrap_character(getHero(episode))
|
return getHero(episode)
|
||||||
|
|
||||||
@resolve_only_args
|
@resolve_only_args
|
||||||
def resolve_human(self, id):
|
def resolve_human(self, id):
|
||||||
return wrap_character(getHuman(id))
|
return getHuman(id)
|
||||||
|
|
||||||
@resolve_only_args
|
@resolve_only_args
|
||||||
def resolve_droid(self, id):
|
def resolve_droid(self, id):
|
||||||
return wrap_character(getDroid(id))
|
return getDroid(id)
|
||||||
|
|
||||||
|
|
||||||
Schema = graphene.Schema(query=Query)
|
Schema = graphene.Schema(query=Query)
|
|
@ -1,6 +1,8 @@
|
||||||
from .schema import Schema, Query
|
from ..schema import Schema, Query
|
||||||
from graphql.core import graphql
|
from graphql.core import graphql
|
||||||
|
from ..data import setup
|
||||||
|
|
||||||
|
setup()
|
||||||
|
|
||||||
def test_hero_name_query():
|
def test_hero_name_query():
|
||||||
query = '''
|
query = '''
|
0
examples/starwars_django/__init__.py
Normal file
0
examples/starwars_django/__init__.py
Normal file
|
@ -85,31 +85,30 @@ def initialize():
|
||||||
executor.save()
|
executor.save()
|
||||||
|
|
||||||
|
|
||||||
def createShip(shipName, factionId):
|
def create_ship(ship_name, faction_id):
|
||||||
nextShip = len(data['Ship'].keys())+1
|
new_ship = Ship(
|
||||||
newShip = Ship(
|
name=ship_name,
|
||||||
id=str(nextShip),
|
faction_id=faction_id
|
||||||
name=shipName
|
|
||||||
)
|
)
|
||||||
newShip.save()
|
new_ship.save()
|
||||||
return newShip
|
return new_ship
|
||||||
|
|
||||||
|
|
||||||
def getShip(_id):
|
def get_ship(_id):
|
||||||
return Ship.objects.get(id=_id)
|
return Ship.objects.get(id=_id)
|
||||||
|
|
||||||
|
|
||||||
def getShips():
|
def get_ships():
|
||||||
return Ship.objects.all()
|
return Ship.objects.all()
|
||||||
|
|
||||||
|
|
||||||
def getFaction(_id):
|
def get_faction(_id):
|
||||||
return Faction.objects.get(id=_id)
|
return Faction.objects.get(id=_id)
|
||||||
|
|
||||||
|
|
||||||
def getRebels():
|
def get_rebels():
|
||||||
return getFaction(1)
|
return get_faction(1)
|
||||||
|
|
||||||
|
|
||||||
def getEmpire():
|
def get_empire():
|
||||||
return getFaction(2)
|
return get_faction(2)
|
|
@ -7,11 +7,12 @@ from graphene.contrib.django import (
|
||||||
from .models import (
|
from .models import (
|
||||||
Ship as ShipModel, Faction as FactionModel, Character as CharacterModel)
|
Ship as ShipModel, Faction as FactionModel, Character as CharacterModel)
|
||||||
from .data import (
|
from .data import (
|
||||||
getFaction,
|
get_faction,
|
||||||
getShip,
|
get_ship,
|
||||||
getShips,
|
get_ships,
|
||||||
getRebels,
|
get_rebels,
|
||||||
getEmpire,
|
get_empire,
|
||||||
|
create_ship
|
||||||
)
|
)
|
||||||
|
|
||||||
schema = graphene.Schema(name='Starwars Django Relay Schema')
|
schema = graphene.Schema(name='Starwars Django Relay Schema')
|
||||||
|
@ -23,11 +24,11 @@ class Ship(DjangoNode):
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def get_node(cls, id):
|
def get_node(cls, id):
|
||||||
return Ship(getShip(id))
|
return Ship(get_ship(id))
|
||||||
|
|
||||||
|
|
||||||
@schema.register
|
@schema.register
|
||||||
class CharacterModel(DjangoObjectType):
|
class Character(DjangoObjectType):
|
||||||
class Meta:
|
class Meta:
|
||||||
model = CharacterModel
|
model = CharacterModel
|
||||||
|
|
||||||
|
@ -38,7 +39,24 @@ class Faction(DjangoNode):
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def get_node(cls, id):
|
def get_node(cls, id):
|
||||||
return Faction(getFaction(id))
|
return Faction(get_faction(id))
|
||||||
|
|
||||||
|
|
||||||
|
class IntroduceShip(relay.ClientIDMutation):
|
||||||
|
class Input:
|
||||||
|
ship_name = graphene.StringField(required=True)
|
||||||
|
faction_id = graphene.StringField(required=True)
|
||||||
|
|
||||||
|
ship = graphene.Field(Ship)
|
||||||
|
faction = graphene.Field(Faction)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def mutate_and_get_payload(cls, input, info):
|
||||||
|
ship_name = input.get('ship_name')
|
||||||
|
faction_id = input.get('faction_id')
|
||||||
|
ship = create_ship(ship_name, faction_id)
|
||||||
|
faction = get_faction(faction_id)
|
||||||
|
return IntroduceShip(ship=ship, faction=faction)
|
||||||
|
|
||||||
|
|
||||||
class Query(graphene.ObjectType):
|
class Query(graphene.ObjectType):
|
||||||
|
@ -49,15 +67,20 @@ class Query(graphene.ObjectType):
|
||||||
|
|
||||||
@resolve_only_args
|
@resolve_only_args
|
||||||
def resolve_ships(self):
|
def resolve_ships(self):
|
||||||
return [Ship(s) for s in getShips()]
|
return [Ship(s) for s in get_ships()]
|
||||||
|
|
||||||
@resolve_only_args
|
@resolve_only_args
|
||||||
def resolve_rebels(self):
|
def resolve_rebels(self):
|
||||||
return Faction(getRebels())
|
return Faction(get_rebels())
|
||||||
|
|
||||||
@resolve_only_args
|
@resolve_only_args
|
||||||
def resolve_empire(self):
|
def resolve_empire(self):
|
||||||
return Faction(getEmpire())
|
return Faction(get_empire())
|
||||||
|
|
||||||
|
|
||||||
|
class Mutation(graphene.ObjectType):
|
||||||
|
introduce_ship = graphene.Field(IntroduceShip)
|
||||||
|
|
||||||
|
|
||||||
schema.query = Query
|
schema.query = Query
|
||||||
|
schema.mutation = Mutation
|
0
examples/starwars_django/tests/__init__.py
Normal file
0
examples/starwars_django/tests/__init__.py
Normal file
|
@ -1,9 +1,9 @@
|
||||||
import pytest
|
import pytest
|
||||||
from graphql.core import graphql
|
from graphql.core import graphql
|
||||||
|
|
||||||
from .models import *
|
from ..models import *
|
||||||
from .schema import schema
|
from ..schema import schema
|
||||||
from .data import initialize
|
from ..data import initialize
|
||||||
|
|
||||||
pytestmark = pytest.mark.django_db
|
pytestmark = pytest.mark.django_db
|
||||||
|
|
78
examples/starwars_django/tests/test_mutation.py
Normal file
78
examples/starwars_django/tests/test_mutation.py
Normal file
|
@ -0,0 +1,78 @@
|
||||||
|
import pytest
|
||||||
|
from ..schema import schema
|
||||||
|
from ..data import initialize
|
||||||
|
|
||||||
|
pytestmark = pytest.mark.django_db
|
||||||
|
|
||||||
|
|
||||||
|
def test_mutations():
|
||||||
|
initialize()
|
||||||
|
|
||||||
|
query = '''
|
||||||
|
mutation MyMutation {
|
||||||
|
introduceShip(input:{clientMutationId:"abc", shipName: "Peter", factionId: "1"}) {
|
||||||
|
ship {
|
||||||
|
id
|
||||||
|
name
|
||||||
|
}
|
||||||
|
faction {
|
||||||
|
name
|
||||||
|
ships {
|
||||||
|
edges {
|
||||||
|
node {
|
||||||
|
id
|
||||||
|
name
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
'''
|
||||||
|
expected = {
|
||||||
|
'introduceShip': {
|
||||||
|
'ship': {
|
||||||
|
'id': 'U2hpcDo5',
|
||||||
|
'name': 'Peter'
|
||||||
|
},
|
||||||
|
'faction': {
|
||||||
|
'name': 'Alliance to Restore the Republic',
|
||||||
|
'ships': {
|
||||||
|
'edges': [{
|
||||||
|
'node': {
|
||||||
|
'id': 'U2hpcDox',
|
||||||
|
'name': 'X-Wing'
|
||||||
|
}
|
||||||
|
}, {
|
||||||
|
'node': {
|
||||||
|
'id': 'U2hpcDoy',
|
||||||
|
'name': 'Y-Wing'
|
||||||
|
}
|
||||||
|
}, {
|
||||||
|
'node': {
|
||||||
|
'id': 'U2hpcDoz',
|
||||||
|
'name': 'A-Wing'
|
||||||
|
}
|
||||||
|
}, {
|
||||||
|
'node': {
|
||||||
|
'id': 'U2hpcDo0',
|
||||||
|
'name': 'Millenium Falcon'
|
||||||
|
}
|
||||||
|
}, {
|
||||||
|
'node': {
|
||||||
|
'id': 'U2hpcDo1',
|
||||||
|
'name': 'Home One'
|
||||||
|
}
|
||||||
|
}, {
|
||||||
|
'node': {
|
||||||
|
'id': 'U2hpcDo5',
|
||||||
|
'name': 'Peter'
|
||||||
|
}
|
||||||
|
}]
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
result = schema.execute(query)
|
||||||
|
assert not result.errors
|
||||||
|
assert result.data == expected
|
|
@ -1,9 +1,9 @@
|
||||||
import pytest
|
import pytest
|
||||||
from pytest import raises
|
from pytest import raises
|
||||||
from graphql.core import graphql
|
from graphql.core import graphql
|
||||||
from .data import initialize
|
from ..data import initialize
|
||||||
|
|
||||||
from .schema import schema
|
from ..schema import schema
|
||||||
|
|
||||||
pytestmark = pytest.mark.django_db
|
pytestmark = pytest.mark.django_db
|
||||||
|
|
0
examples/starwars_relay/__init__.py
Normal file
0
examples/starwars_relay/__init__.py
Normal file
105
examples/starwars_relay/data.py
Normal file
105
examples/starwars_relay/data.py
Normal file
|
@ -0,0 +1,105 @@
|
||||||
|
data = {}
|
||||||
|
|
||||||
|
|
||||||
|
def setup():
|
||||||
|
global data
|
||||||
|
|
||||||
|
from .schema import Ship, Faction
|
||||||
|
xwing = Ship(
|
||||||
|
id='1',
|
||||||
|
name='X-Wing',
|
||||||
|
)
|
||||||
|
|
||||||
|
ywing = Ship(
|
||||||
|
id='2',
|
||||||
|
name='Y-Wing',
|
||||||
|
)
|
||||||
|
|
||||||
|
awing = Ship(
|
||||||
|
id='3',
|
||||||
|
name='A-Wing',
|
||||||
|
)
|
||||||
|
|
||||||
|
# Yeah, technically it's Corellian. But it flew in the service of the rebels,
|
||||||
|
# so for the purposes of this demo it's a rebel ship.
|
||||||
|
falcon = Ship(
|
||||||
|
id='4',
|
||||||
|
name='Millenium Falcon',
|
||||||
|
)
|
||||||
|
|
||||||
|
homeOne = Ship(
|
||||||
|
id='5',
|
||||||
|
name='Home One',
|
||||||
|
)
|
||||||
|
|
||||||
|
tieFighter = Ship(
|
||||||
|
id='6',
|
||||||
|
name='TIE Fighter',
|
||||||
|
)
|
||||||
|
|
||||||
|
tieInterceptor = Ship(
|
||||||
|
id='7',
|
||||||
|
name='TIE Interceptor',
|
||||||
|
)
|
||||||
|
|
||||||
|
executor = Ship(
|
||||||
|
id='8',
|
||||||
|
name='Executor',
|
||||||
|
)
|
||||||
|
|
||||||
|
rebels = Faction(
|
||||||
|
id='1',
|
||||||
|
name='Alliance to Restore the Republic',
|
||||||
|
ships=['1', '2', '3', '4', '5']
|
||||||
|
)
|
||||||
|
|
||||||
|
empire = Faction(
|
||||||
|
id='2',
|
||||||
|
name='Galactic Empire',
|
||||||
|
ships=['6', '7', '8']
|
||||||
|
)
|
||||||
|
|
||||||
|
data = {
|
||||||
|
'Faction': {
|
||||||
|
'1': rebels,
|
||||||
|
'2': empire
|
||||||
|
},
|
||||||
|
'Ship': {
|
||||||
|
'1': xwing,
|
||||||
|
'2': ywing,
|
||||||
|
'3': awing,
|
||||||
|
'4': falcon,
|
||||||
|
'5': homeOne,
|
||||||
|
'6': tieFighter,
|
||||||
|
'7': tieInterceptor,
|
||||||
|
'8': executor
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def create_ship(ship_name, faction_id):
|
||||||
|
from .schema import Ship
|
||||||
|
next_ship = len(data['Ship'].keys()) + 1
|
||||||
|
new_ship = Ship(
|
||||||
|
id=str(next_ship),
|
||||||
|
name=ship_name
|
||||||
|
)
|
||||||
|
data['Ship'][new_ship.id] = new_ship
|
||||||
|
data['Faction'][faction_id].ships.append(new_ship.id)
|
||||||
|
return new_ship
|
||||||
|
|
||||||
|
|
||||||
|
def get_ship(_id):
|
||||||
|
return data['Ship'][_id]
|
||||||
|
|
||||||
|
|
||||||
|
def get_faction(_id):
|
||||||
|
return data['Faction'][_id]
|
||||||
|
|
||||||
|
|
||||||
|
def get_rebels():
|
||||||
|
return get_faction('1')
|
||||||
|
|
||||||
|
|
||||||
|
def get_empire():
|
||||||
|
return get_faction('2')
|
76
examples/starwars_relay/schema.py
Normal file
76
examples/starwars_relay/schema.py
Normal file
|
@ -0,0 +1,76 @@
|
||||||
|
import graphene
|
||||||
|
from graphene import resolve_only_args, relay
|
||||||
|
|
||||||
|
from .data import (
|
||||||
|
get_faction,
|
||||||
|
get_ship,
|
||||||
|
get_rebels,
|
||||||
|
get_empire,
|
||||||
|
create_ship,
|
||||||
|
)
|
||||||
|
|
||||||
|
schema = graphene.Schema(name='Starwars Relay Schema')
|
||||||
|
|
||||||
|
|
||||||
|
class Ship(relay.Node):
|
||||||
|
'''A ship in the Star Wars saga'''
|
||||||
|
name = graphene.StringField(description='The name of the ship.')
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def get_node(cls, id):
|
||||||
|
return get_ship(id)
|
||||||
|
|
||||||
|
|
||||||
|
class Faction(relay.Node):
|
||||||
|
'''A faction in the Star Wars saga'''
|
||||||
|
name = graphene.StringField(description='The name of the faction.')
|
||||||
|
ships = relay.ConnectionField(
|
||||||
|
Ship, description='The ships used by the faction.')
|
||||||
|
|
||||||
|
@resolve_only_args
|
||||||
|
def resolve_ships(self, **args):
|
||||||
|
# Transform the instance ship_ids into real instances
|
||||||
|
return [get_ship(ship_id) for ship_id in self.ships]
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def get_node(cls, id):
|
||||||
|
return get_faction(id)
|
||||||
|
|
||||||
|
|
||||||
|
class IntroduceShip(relay.ClientIDMutation):
|
||||||
|
class Input:
|
||||||
|
ship_name = graphene.StringField(required=True)
|
||||||
|
faction_id = graphene.StringField(required=True)
|
||||||
|
|
||||||
|
ship = graphene.Field(Ship)
|
||||||
|
faction = graphene.Field(Faction)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def mutate_and_get_payload(cls, input, info):
|
||||||
|
ship_name = input.get('ship_name')
|
||||||
|
faction_id = input.get('faction_id')
|
||||||
|
ship = create_ship(ship_name, faction_id)
|
||||||
|
faction = get_faction(faction_id)
|
||||||
|
return IntroduceShip(ship=ship, faction=faction)
|
||||||
|
|
||||||
|
|
||||||
|
class Query(graphene.ObjectType):
|
||||||
|
rebels = graphene.Field(Faction)
|
||||||
|
empire = graphene.Field(Faction)
|
||||||
|
node = relay.NodeField()
|
||||||
|
|
||||||
|
@resolve_only_args
|
||||||
|
def resolve_rebels(self):
|
||||||
|
return get_rebels()
|
||||||
|
|
||||||
|
@resolve_only_args
|
||||||
|
def resolve_empire(self):
|
||||||
|
return get_empire()
|
||||||
|
|
||||||
|
|
||||||
|
class Mutation(graphene.ObjectType):
|
||||||
|
introduce_ship = graphene.Field(IntroduceShip)
|
||||||
|
|
||||||
|
|
||||||
|
schema.query = Query
|
||||||
|
schema.mutation = Mutation
|
0
examples/starwars_relay/tests/__init__.py
Normal file
0
examples/starwars_relay/tests/__init__.py
Normal file
|
@ -1,7 +1,7 @@
|
||||||
from pytest import raises
|
from ..schema import schema
|
||||||
from graphql.core import graphql
|
from ..data import setup
|
||||||
|
|
||||||
from .schema import schema
|
setup()
|
||||||
|
|
||||||
|
|
||||||
def test_correct_fetch_first_ship_rebels():
|
def test_correct_fetch_first_ship_rebels():
|
75
examples/starwars_relay/tests/test_mutation.py
Normal file
75
examples/starwars_relay/tests/test_mutation.py
Normal file
|
@ -0,0 +1,75 @@
|
||||||
|
from ..schema import schema
|
||||||
|
from ..data import setup
|
||||||
|
|
||||||
|
setup()
|
||||||
|
|
||||||
|
|
||||||
|
def test_mutations():
|
||||||
|
query = '''
|
||||||
|
mutation MyMutation {
|
||||||
|
introduceShip(input:{clientMutationId:"abc", shipName: "Peter", factionId: "1"}) {
|
||||||
|
ship {
|
||||||
|
id
|
||||||
|
name
|
||||||
|
}
|
||||||
|
faction {
|
||||||
|
name
|
||||||
|
ships {
|
||||||
|
edges {
|
||||||
|
node {
|
||||||
|
id
|
||||||
|
name
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
'''
|
||||||
|
expected = {
|
||||||
|
'introduceShip': {
|
||||||
|
'ship': {
|
||||||
|
'id': 'U2hpcDo5',
|
||||||
|
'name': 'Peter'
|
||||||
|
},
|
||||||
|
'faction': {
|
||||||
|
'name': 'Alliance to Restore the Republic',
|
||||||
|
'ships': {
|
||||||
|
'edges': [{
|
||||||
|
'node': {
|
||||||
|
'id': 'U2hpcDox',
|
||||||
|
'name': 'X-Wing'
|
||||||
|
}
|
||||||
|
}, {
|
||||||
|
'node': {
|
||||||
|
'id': 'U2hpcDoy',
|
||||||
|
'name': 'Y-Wing'
|
||||||
|
}
|
||||||
|
}, {
|
||||||
|
'node': {
|
||||||
|
'id': 'U2hpcDoz',
|
||||||
|
'name': 'A-Wing'
|
||||||
|
}
|
||||||
|
}, {
|
||||||
|
'node': {
|
||||||
|
'id': 'U2hpcDo0',
|
||||||
|
'name': 'Millenium Falcon'
|
||||||
|
}
|
||||||
|
}, {
|
||||||
|
'node': {
|
||||||
|
'id': 'U2hpcDo1',
|
||||||
|
'name': 'Home One'
|
||||||
|
}
|
||||||
|
}, {
|
||||||
|
'node': {
|
||||||
|
'id': 'U2hpcDo5',
|
||||||
|
'name': 'Peter'
|
||||||
|
}
|
||||||
|
}]
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
result = schema.execute(query)
|
||||||
|
assert not result.errors
|
||||||
|
assert result.data == expected
|
|
@ -1,7 +1,7 @@
|
||||||
from pytest import raises
|
from ..schema import schema
|
||||||
from graphql.core import graphql
|
from ..data import setup
|
||||||
|
|
||||||
from .schema import schema
|
setup()
|
||||||
|
|
||||||
|
|
||||||
def test_correctly_fetches_id_name_rebels():
|
def test_correctly_fetches_id_name_rebels():
|
|
@ -14,7 +14,8 @@ from graphene.core.schema import (
|
||||||
|
|
||||||
from graphene.core.types import (
|
from graphene.core.types import (
|
||||||
ObjectType,
|
ObjectType,
|
||||||
Interface
|
Interface,
|
||||||
|
Mutation,
|
||||||
)
|
)
|
||||||
|
|
||||||
from graphene.core.fields import (
|
from graphene.core.fields import (
|
||||||
|
@ -31,3 +32,8 @@ from graphene.core.fields import (
|
||||||
from graphene.decorators import (
|
from graphene.decorators import (
|
||||||
resolve_only_args
|
resolve_only_args
|
||||||
)
|
)
|
||||||
|
|
||||||
|
__all__ = ['Enum', 'Argument', 'String', 'Int', 'ID', 'signals', 'Schema',
|
||||||
|
'ObjectType', 'Interface', 'Mutation', 'Field', 'StringField',
|
||||||
|
'IntField', 'BooleanField', 'IDField', 'ListField', 'NonNullField',
|
||||||
|
'FloatField', 'resolve_only_args']
|
||||||
|
|
|
@ -4,9 +4,7 @@ from graphene.core.fields import (
|
||||||
from graphene import relay
|
from graphene import relay
|
||||||
|
|
||||||
from graphene.core.fields import Field, LazyField
|
from graphene.core.fields import Field, LazyField
|
||||||
from graphene.utils import cached_property, memoize, LazyMap
|
|
||||||
|
|
||||||
from graphene.relay.types import BaseNode
|
|
||||||
from graphene.relay.utils import is_node
|
from graphene.relay.utils import is_node
|
||||||
from graphene.contrib.django.utils import get_type_for_model, lazy_map
|
from graphene.contrib.django.utils import get_type_for_model, lazy_map
|
||||||
|
|
||||||
|
@ -25,7 +23,6 @@ class LazyListField(ListField):
|
||||||
|
|
||||||
|
|
||||||
class ConnectionOrListField(LazyField):
|
class ConnectionOrListField(LazyField):
|
||||||
@memoize
|
|
||||||
def get_field(self, schema):
|
def get_field(self, schema):
|
||||||
model_field = self.field_type
|
model_field = self.field_type
|
||||||
field_object_type = model_field.get_object_type(schema)
|
field_object_type = model_field.get_object_type(schema)
|
||||||
|
@ -52,7 +49,6 @@ class DjangoModelField(Field):
|
||||||
))
|
))
|
||||||
return _type(resolved)
|
return _type(resolved)
|
||||||
|
|
||||||
@memoize
|
|
||||||
def internal_type(self, schema):
|
def internal_type(self, schema):
|
||||||
_type = self.get_object_type(schema)
|
_type = self.get_object_type(schema)
|
||||||
if not _type and self.object_type._meta.only_fields:
|
if not _type and self.object_type._meta.only_fields:
|
||||||
|
@ -66,7 +62,7 @@ class DjangoModelField(Field):
|
||||||
self.object_type
|
self.object_type
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
return _type and _type.internal_type(schema) or Field.SKIP
|
return schema.T(_type) or Field.SKIP
|
||||||
|
|
||||||
def get_object_type(self, schema):
|
def get_object_type(self, schema):
|
||||||
return get_type_for_model(schema, self.model)
|
return get_type_for_model(schema, self.model)
|
||||||
|
|
|
@ -37,11 +37,20 @@ class DjangoObjectTypeMeta(ObjectTypeMeta):
|
||||||
cls.add_to_class(field.name, converted_field)
|
cls.add_to_class(field.name, converted_field)
|
||||||
|
|
||||||
|
|
||||||
class DjangoObjectType(six.with_metaclass(DjangoObjectTypeMeta, BaseObjectType)):
|
class InstanceObjectType(BaseObjectType):
|
||||||
|
def __init__(self, instance=None):
|
||||||
|
self.instance = instance
|
||||||
|
super(InstanceObjectType, self).__init__()
|
||||||
|
|
||||||
|
def __getattr__(self, attr):
|
||||||
|
return getattr(self.instance, attr)
|
||||||
|
|
||||||
|
|
||||||
|
class DjangoObjectType(six.with_metaclass(DjangoObjectTypeMeta, InstanceObjectType)):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
class DjangoInterface(six.with_metaclass(DjangoObjectTypeMeta, BaseObjectType)):
|
class DjangoInterface(six.with_metaclass(DjangoObjectTypeMeta, InstanceObjectType)):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -2,10 +2,9 @@ from django.db import models
|
||||||
from django.db.models.query import QuerySet
|
from django.db.models.query import QuerySet
|
||||||
from django.db.models.manager import Manager
|
from django.db.models.manager import Manager
|
||||||
|
|
||||||
from graphene.utils import memoize, LazyMap
|
from graphene.utils import LazyMap
|
||||||
|
|
||||||
|
|
||||||
# @memoize
|
|
||||||
def get_type_for_model(schema, model):
|
def get_type_for_model(schema, model):
|
||||||
schema = schema
|
schema = schema
|
||||||
types = schema.types.values()
|
types = schema.types.values()
|
||||||
|
|
|
@ -1,5 +1,10 @@
|
||||||
import inspect
|
import inspect
|
||||||
import six
|
import six
|
||||||
|
try:
|
||||||
|
from enum import Enum
|
||||||
|
except ImportError:
|
||||||
|
class Enum(object):
|
||||||
|
pass
|
||||||
from functools import total_ordering, wraps
|
from functools import total_ordering, wraps
|
||||||
from graphql.core.type import (
|
from graphql.core.type import (
|
||||||
GraphQLField,
|
GraphQLField,
|
||||||
|
@ -11,9 +16,10 @@ from graphql.core.type import (
|
||||||
GraphQLID,
|
GraphQLID,
|
||||||
GraphQLArgument,
|
GraphQLArgument,
|
||||||
GraphQLFloat,
|
GraphQLFloat,
|
||||||
|
GraphQLInputObjectField,
|
||||||
)
|
)
|
||||||
from graphene.utils import memoize, to_camel_case
|
from graphene.utils import to_camel_case, ProxySnakeDict, enum_to_graphql_enum
|
||||||
from graphene.core.types import BaseObjectType
|
from graphene.core.types import BaseObjectType, InputObjectType
|
||||||
from graphene.core.scalars import GraphQLSkipField
|
from graphene.core.scalars import GraphQLSkipField
|
||||||
|
|
||||||
|
|
||||||
|
@ -27,7 +33,7 @@ class Field(object):
|
||||||
creation_counter = 0
|
creation_counter = 0
|
||||||
required = False
|
required = False
|
||||||
|
|
||||||
def __init__(self, field_type, name=None, resolve=None, required=False, args=None, description='', **extra_args):
|
def __init__(self, field_type, name=None, resolve=None, required=False, args=None, description='', default=None, **extra_args):
|
||||||
self.field_type = field_type
|
self.field_type = field_type
|
||||||
self.resolve_fn = resolve
|
self.resolve_fn = resolve
|
||||||
self.required = self.required or required
|
self.required = self.required or required
|
||||||
|
@ -37,9 +43,13 @@ class Field(object):
|
||||||
self.name = name
|
self.name = name
|
||||||
self.description = description or self.__doc__
|
self.description = description or self.__doc__
|
||||||
self.object_type = None
|
self.object_type = None
|
||||||
|
self.default = default
|
||||||
self.creation_counter = Field.creation_counter
|
self.creation_counter = Field.creation_counter
|
||||||
Field.creation_counter += 1
|
Field.creation_counter += 1
|
||||||
|
|
||||||
|
def get_default(self):
|
||||||
|
return self.default
|
||||||
|
|
||||||
def contribute_to_class(self, cls, name, add=True):
|
def contribute_to_class(self, cls, name, add=True):
|
||||||
if not self.name:
|
if not self.name:
|
||||||
self.name = to_camel_case(name)
|
self.name = to_camel_case(name)
|
||||||
|
@ -51,20 +61,18 @@ class Field(object):
|
||||||
cls._meta.add_field(self)
|
cls._meta.add_field(self)
|
||||||
|
|
||||||
def resolve(self, instance, args, info):
|
def resolve(self, instance, args, info):
|
||||||
resolve_fn = self.get_resolve_fn()
|
schema = info and getattr(info.schema, 'graphene_schema', None)
|
||||||
|
resolve_fn = self.get_resolve_fn(schema)
|
||||||
if resolve_fn:
|
if resolve_fn:
|
||||||
return resolve_fn(instance, args, info)
|
return resolve_fn(instance, ProxySnakeDict(args), info)
|
||||||
else:
|
else:
|
||||||
if isinstance(instance, BaseObjectType):
|
return getattr(instance, self.field_name, self.get_default())
|
||||||
return instance.get_field(self.field_name)
|
|
||||||
if hasattr(instance, self.field_name):
|
|
||||||
return getattr(instance, self.field_name)
|
|
||||||
elif hasattr(instance, self.name):
|
|
||||||
return getattr(instance, self.name)
|
|
||||||
|
|
||||||
@memoize
|
def get_resolve_fn(self, schema):
|
||||||
def get_resolve_fn(self):
|
object_type = self.get_object_type(schema)
|
||||||
if self.resolve_fn:
|
if object_type and object_type._meta.is_mutation:
|
||||||
|
return object_type.mutate
|
||||||
|
elif self.resolve_fn:
|
||||||
return self.resolve_fn
|
return self.resolve_fn
|
||||||
else:
|
else:
|
||||||
custom_resolve_fn_name = 'resolve_%s' % self.field_name
|
custom_resolve_fn_name = 'resolve_%s' % self.field_name
|
||||||
|
@ -96,24 +104,25 @@ class Field(object):
|
||||||
field_type = GraphQLNonNull(field_type)
|
field_type = GraphQLNonNull(field_type)
|
||||||
return field_type
|
return field_type
|
||||||
|
|
||||||
@memoize
|
|
||||||
def internal_type(self, schema):
|
def internal_type(self, schema):
|
||||||
field_type = self.field_type
|
field_type = self.field_type
|
||||||
|
_is_class = inspect.isclass(field_type)
|
||||||
if isinstance(field_type, Field):
|
if isinstance(field_type, Field):
|
||||||
field_type = self.field_type.internal_type(schema)
|
field_type = self.field_type.internal_type(schema)
|
||||||
|
elif _is_class and issubclass(field_type, Enum):
|
||||||
|
field_type = enum_to_graphql_enum(field_type)
|
||||||
else:
|
else:
|
||||||
object_type = self.get_object_type(schema)
|
object_type = self.get_object_type(schema)
|
||||||
if object_type:
|
if object_type:
|
||||||
field_type = object_type.internal_type(schema)
|
field_type = schema.T(object_type)
|
||||||
|
|
||||||
field_type = self.type_wrapper(field_type)
|
field_type = self.type_wrapper(field_type)
|
||||||
return field_type
|
return field_type
|
||||||
|
|
||||||
@memoize
|
|
||||||
def internal_field(self, schema):
|
def internal_field(self, schema):
|
||||||
if not self.object_type:
|
if not self.object_type:
|
||||||
raise Exception(
|
raise Exception(
|
||||||
'Field could not be constructed in a non graphene.Type or graphene.Interface')
|
'Field could not be constructed in a non graphene.ObjectType or graphene.Interface')
|
||||||
|
|
||||||
extra_args = self.extra_args.copy()
|
extra_args = self.extra_args.copy()
|
||||||
for arg_name, arg_value in self.extra_args.items():
|
for arg_name, arg_value in self.extra_args.items():
|
||||||
|
@ -128,27 +137,44 @@ class Field(object):
|
||||||
','.join(extra_args.keys())
|
','.join(extra_args.keys())
|
||||||
))
|
))
|
||||||
|
|
||||||
|
args = self.args
|
||||||
|
|
||||||
|
object_type = self.get_object_type(schema)
|
||||||
|
if object_type and object_type._meta.is_mutation:
|
||||||
|
assert not self.args, 'Arguments provided for mutations are defined in Input class in Mutation'
|
||||||
|
args = object_type.get_input_type().fields_as_arguments(schema)
|
||||||
|
|
||||||
internal_type = self.internal_type(schema)
|
internal_type = self.internal_type(schema)
|
||||||
if not internal_type:
|
if not internal_type:
|
||||||
raise Exception("Internal type for field %s is None" % self)
|
raise Exception("Internal type for field %s is None" % self)
|
||||||
|
|
||||||
resolve_fn = self.get_resolve_fn()
|
description = self.description
|
||||||
|
resolve_fn = self.get_resolve_fn(schema)
|
||||||
if resolve_fn:
|
if resolve_fn:
|
||||||
|
description = resolve_fn.__doc__ or description
|
||||||
|
|
||||||
@wraps(resolve_fn)
|
@wraps(resolve_fn)
|
||||||
def resolver(*args):
|
def resolver(*args):
|
||||||
return self.resolve(*args)
|
return self.resolve(*args)
|
||||||
else:
|
else:
|
||||||
resolver = self.resolve
|
resolver = self.resolve
|
||||||
|
|
||||||
|
if issubclass(self.object_type, InputObjectType):
|
||||||
|
return GraphQLInputObjectField(
|
||||||
|
internal_type,
|
||||||
|
description=description,
|
||||||
|
)
|
||||||
|
|
||||||
return GraphQLField(
|
return GraphQLField(
|
||||||
internal_type,
|
internal_type,
|
||||||
description=self.description,
|
description=description,
|
||||||
args=self.args,
|
args=args,
|
||||||
resolver=resolver,
|
resolver=resolver,
|
||||||
)
|
)
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
""" Return "object_type.name". """
|
""" Return "object_type.name". """
|
||||||
return '%s.%s' % (self.object_type, self.field_name)
|
return '%s.%s' % (self.object_type.__name__, self.field_name)
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
"""
|
"""
|
||||||
|
@ -189,7 +215,6 @@ class Field(object):
|
||||||
|
|
||||||
class LazyField(Field):
|
class LazyField(Field):
|
||||||
|
|
||||||
@memoize
|
|
||||||
def inner_field(self, schema):
|
def inner_field(self, schema):
|
||||||
return self.get_field(schema)
|
return self.get_field(schema)
|
||||||
|
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
from graphene.utils import cached_property
|
from graphene.utils import cached_property
|
||||||
from collections import OrderedDict, namedtuple
|
from collections import OrderedDict, namedtuple
|
||||||
|
|
||||||
DEFAULT_NAMES = ('description', 'name', 'interface',
|
DEFAULT_NAMES = ('description', 'name', 'is_interface', 'is_mutation',
|
||||||
'type_name', 'interfaces', 'proxy')
|
'type_name', 'interfaces', 'proxy')
|
||||||
|
|
||||||
|
|
||||||
|
@ -10,7 +10,8 @@ class Options(object):
|
||||||
def __init__(self, meta=None):
|
def __init__(self, meta=None):
|
||||||
self.meta = meta
|
self.meta = meta
|
||||||
self.local_fields = []
|
self.local_fields = []
|
||||||
self.interface = False
|
self.is_interface = False
|
||||||
|
self.is_mutation = False
|
||||||
self.proxy = False
|
self.proxy = False
|
||||||
self.interfaces = []
|
self.interfaces = []
|
||||||
self.parents = []
|
self.parents = []
|
||||||
|
@ -54,15 +55,8 @@ class Options(object):
|
||||||
else:
|
else:
|
||||||
self.proxy = False
|
self.proxy = False
|
||||||
|
|
||||||
if self.interfaces != [] and self.interface:
|
|
||||||
raise Exception("A interface cannot inherit from interfaces")
|
|
||||||
|
|
||||||
del self.meta
|
del self.meta
|
||||||
|
|
||||||
@cached_property
|
|
||||||
def object(self):
|
|
||||||
return namedtuple(self.type_name, self.fields_map.keys())
|
|
||||||
|
|
||||||
def add_field(self, field):
|
def add_field(self, field):
|
||||||
self.local_fields.append(field)
|
self.local_fields.append(field)
|
||||||
|
|
||||||
|
|
|
@ -25,6 +25,7 @@ class Schema(object):
|
||||||
|
|
||||||
def __init__(self, query=None, mutation=None, name='Schema', executor=None):
|
def __init__(self, query=None, mutation=None, name='Schema', executor=None):
|
||||||
self._internal_types = {}
|
self._internal_types = {}
|
||||||
|
self._types = {}
|
||||||
self.mutation = mutation
|
self.mutation = mutation
|
||||||
self.query = query
|
self.query = query
|
||||||
self.name = name
|
self.name = name
|
||||||
|
@ -34,6 +35,13 @@ class Schema(object):
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
return '<Schema: %s (%s)>' % (str(self.name), hash(self))
|
return '<Schema: %s (%s)>' % (str(self.name), hash(self))
|
||||||
|
|
||||||
|
def T(self, object_type):
|
||||||
|
if not object_type:
|
||||||
|
return
|
||||||
|
if object_type not in self._types:
|
||||||
|
self._types[object_type] = object_type.internal_type(self)
|
||||||
|
return self._types[object_type]
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def query(self):
|
def query(self):
|
||||||
return self._query
|
return self._query
|
||||||
|
@ -41,7 +49,14 @@ class Schema(object):
|
||||||
@query.setter
|
@query.setter
|
||||||
def query(self, query):
|
def query(self, query):
|
||||||
self._query = query
|
self._query = query
|
||||||
self._query_type = query and query.internal_type(self)
|
|
||||||
|
@property
|
||||||
|
def mutation(self):
|
||||||
|
return self._mutation
|
||||||
|
|
||||||
|
@mutation.setter
|
||||||
|
def mutation(self, mutation):
|
||||||
|
self._mutation = mutation
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def executor(self):
|
def executor(self):
|
||||||
|
@ -53,11 +68,11 @@ class Schema(object):
|
||||||
def executor(self, value):
|
def executor(self, value):
|
||||||
self._executor = value
|
self._executor = value
|
||||||
|
|
||||||
@cached_property
|
@property
|
||||||
def schema(self):
|
def schema(self):
|
||||||
if not self._query_type:
|
if not self._query:
|
||||||
raise Exception('You have to define a base query type')
|
raise Exception('You have to define a base query type')
|
||||||
return GraphQLSchema(self, query=self._query_type, mutation=self.mutation)
|
return GraphQLSchema(self, query=self.T(self._query), mutation=self.T(self._mutation))
|
||||||
|
|
||||||
def associate_internal_type(self, internal_type, object_type):
|
def associate_internal_type(self, internal_type, object_type):
|
||||||
self._internal_types[internal_type.name] = object_type
|
self._internal_types[internal_type.name] = object_type
|
||||||
|
@ -67,6 +82,7 @@ class Schema(object):
|
||||||
return object_type
|
return object_type
|
||||||
|
|
||||||
def get_type(self, type_name):
|
def get_type(self, type_name):
|
||||||
|
self.schema._build_type_map()
|
||||||
if type_name not in self._internal_types:
|
if type_name not in self._internal_types:
|
||||||
raise Exception('Type %s not found in %r' % (type_name, self))
|
raise Exception('Type %s not found in %r' % (type_name, self))
|
||||||
return self._internal_types[type_name]
|
return self._internal_types[type_name]
|
||||||
|
|
|
@ -5,7 +5,9 @@ from collections import OrderedDict
|
||||||
|
|
||||||
from graphql.core.type import (
|
from graphql.core.type import (
|
||||||
GraphQLObjectType,
|
GraphQLObjectType,
|
||||||
GraphQLInterfaceType
|
GraphQLInputObjectType,
|
||||||
|
GraphQLInterfaceType,
|
||||||
|
GraphQLArgument
|
||||||
)
|
)
|
||||||
|
|
||||||
from graphene import signals
|
from graphene import signals
|
||||||
|
@ -21,7 +23,7 @@ class ObjectTypeMeta(type):
|
||||||
return Interface in parents
|
return Interface in parents
|
||||||
|
|
||||||
def is_mutation(cls, parents):
|
def is_mutation(cls, parents):
|
||||||
return Mutation in parents
|
return issubclass(cls, Mutation)
|
||||||
|
|
||||||
def __new__(cls, name, bases, attrs):
|
def __new__(cls, name, bases, attrs):
|
||||||
super_new = super(ObjectTypeMeta, cls).__new__
|
super_new = super(ObjectTypeMeta, cls).__new__
|
||||||
|
@ -47,14 +49,28 @@ class ObjectTypeMeta(type):
|
||||||
|
|
||||||
new_class.add_to_class('_meta', new_class.options_cls(meta))
|
new_class.add_to_class('_meta', new_class.options_cls(meta))
|
||||||
|
|
||||||
new_class._meta.interface = new_class.is_interface(parents)
|
new_class._meta.is_interface = new_class.is_interface(parents)
|
||||||
new_class._meta.mutation = new_class.is_mutation(parents)
|
new_class._meta.is_mutation = new_class.is_mutation(parents)
|
||||||
|
|
||||||
assert not (new_class._meta.interface and new_class._meta.mutation)
|
assert not (new_class._meta.is_interface and new_class._meta.is_mutation)
|
||||||
|
|
||||||
|
input_class = None
|
||||||
|
if new_class._meta.is_mutation:
|
||||||
|
input_class = attrs.pop('Input', None)
|
||||||
|
|
||||||
# Add all attributes to the class.
|
# Add all attributes to the class.
|
||||||
for obj_name, obj in attrs.items():
|
for obj_name, obj in attrs.items():
|
||||||
new_class.add_to_class(obj_name, obj)
|
new_class.add_to_class(obj_name, obj)
|
||||||
|
|
||||||
|
if new_class._meta.is_mutation:
|
||||||
|
assert hasattr(new_class, 'mutate'), "All mutations must implement mutate method"
|
||||||
|
|
||||||
|
if input_class:
|
||||||
|
items = dict(input_class.__dict__)
|
||||||
|
items.pop('__dict__', None)
|
||||||
|
input_type = type('{}Input'.format(new_class._meta.type_name), (ObjectType, ), items)
|
||||||
|
new_class.add_to_class('input_type', input_type)
|
||||||
|
|
||||||
new_class.add_extra_fields()
|
new_class.add_extra_fields()
|
||||||
|
|
||||||
new_fields = new_class._meta.local_fields
|
new_fields = new_class._meta.local_fields
|
||||||
|
@ -88,7 +104,7 @@ class ObjectTypeMeta(type):
|
||||||
new_class.add_to_class(field.field_name, new_field)
|
new_class.add_to_class(field.field_name, new_field)
|
||||||
|
|
||||||
new_class._meta.parents.append(base)
|
new_class._meta.parents.append(base)
|
||||||
if base._meta.interface:
|
if base._meta.is_interface:
|
||||||
new_class._meta.interfaces.append(base)
|
new_class._meta.interfaces.append(base)
|
||||||
# new_class._meta.parents.extend(base._meta.parents)
|
# new_class._meta.parents.extend(base._meta.parents)
|
||||||
|
|
||||||
|
@ -113,50 +129,67 @@ class ObjectTypeMeta(type):
|
||||||
|
|
||||||
class BaseObjectType(object):
|
class BaseObjectType(object):
|
||||||
|
|
||||||
def __new__(cls, instance=None, **kwargs):
|
def __new__(cls, *args, **kwargs):
|
||||||
if cls._meta.interface:
|
if cls._meta.is_interface:
|
||||||
raise Exception("An interface cannot be initialized")
|
raise Exception("An interface cannot be initialized")
|
||||||
if instance is None:
|
|
||||||
if not kwargs:
|
|
||||||
return None
|
|
||||||
elif type(instance) is cls:
|
|
||||||
instance = instance.instance
|
|
||||||
|
|
||||||
return super(BaseObjectType, cls).__new__(cls)
|
return super(BaseObjectType, cls).__new__(cls)
|
||||||
|
|
||||||
def __init__(self, instance=None, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
signals.pre_init.send(self.__class__, instance=instance)
|
signals.pre_init.send(self.__class__, args=args, kwargs=kwargs)
|
||||||
assert instance or kwargs
|
args_len = len(args)
|
||||||
if not instance:
|
fields = self._meta.fields
|
||||||
init_kwargs = dict({k: None for k in self._meta.fields_map.keys()}, **kwargs)
|
if args_len > len(fields):
|
||||||
instance = self._meta.object(**init_kwargs)
|
# Daft, but matches old exception sans the err msg.
|
||||||
self.instance = instance
|
raise IndexError("Number of args exceeds number of fields")
|
||||||
|
fields_iter = iter(fields)
|
||||||
|
|
||||||
|
if not kwargs:
|
||||||
|
for val, field in zip(args, fields_iter):
|
||||||
|
setattr(self, field.field_name, val)
|
||||||
|
else:
|
||||||
|
for val, field in zip(args, fields_iter):
|
||||||
|
setattr(self, field.field_name, val)
|
||||||
|
kwargs.pop(field.field_name, None)
|
||||||
|
|
||||||
|
for field in fields_iter:
|
||||||
|
try:
|
||||||
|
val = kwargs.pop(field.field_name)
|
||||||
|
setattr(self, field.field_name, val)
|
||||||
|
except KeyError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
if kwargs:
|
||||||
|
for prop in list(kwargs):
|
||||||
|
try:
|
||||||
|
if isinstance(getattr(self.__class__, prop), property):
|
||||||
|
setattr(self, prop, kwargs.pop(prop))
|
||||||
|
except AttributeError:
|
||||||
|
pass
|
||||||
|
if kwargs:
|
||||||
|
raise TypeError("'%s' is an invalid keyword argument for this function" % list(kwargs)[0])
|
||||||
|
|
||||||
signals.post_init.send(self.__class__, instance=self)
|
signals.post_init.send(self.__class__, instance=self)
|
||||||
|
|
||||||
def __getattr__(self, name):
|
@classmethod
|
||||||
if self.instance:
|
def fields_as_arguments(cls, schema):
|
||||||
return getattr(self.instance, name)
|
return OrderedDict([(f.field_name, GraphQLArgument(f.internal_type(schema)))
|
||||||
|
for f in cls._meta.fields])
|
||||||
def get_field(self, field):
|
|
||||||
return getattr(self.instance, field, None)
|
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def resolve_objecttype(cls, schema, instance, *_):
|
def resolve_objecttype(cls, schema, instance, *_):
|
||||||
return instance
|
return instance.__class__
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def resolve_type(cls, schema, instance, *_):
|
def resolve_type(cls, schema, instance, *_):
|
||||||
objecttype = cls.resolve_objecttype(schema, instance, *_)
|
objecttype = cls.resolve_objecttype(schema, instance, *_)
|
||||||
return objecttype.internal_type(schema)
|
return schema.T(objecttype)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
@memoize
|
|
||||||
@register_internal_type
|
@register_internal_type
|
||||||
def internal_type(cls, schema):
|
def internal_type(cls, schema):
|
||||||
fields_list = cls._meta.fields
|
|
||||||
fields = lambda: OrderedDict([(f.name, f.internal_field(schema))
|
fields = lambda: OrderedDict([(f.name, f.internal_field(schema))
|
||||||
for f in fields_list])
|
for f in cls._meta.fields])
|
||||||
if cls._meta.interface:
|
if cls._meta.is_interface:
|
||||||
return GraphQLInterfaceType(
|
return GraphQLInterfaceType(
|
||||||
cls._meta.type_name,
|
cls._meta.type_name,
|
||||||
description=cls._meta.description,
|
description=cls._meta.description,
|
||||||
|
@ -167,19 +200,34 @@ class BaseObjectType(object):
|
||||||
return GraphQLObjectType(
|
return GraphQLObjectType(
|
||||||
cls._meta.type_name,
|
cls._meta.type_name,
|
||||||
description=cls._meta.description,
|
description=cls._meta.description,
|
||||||
interfaces=[i.internal_type(schema) for i in cls._meta.interfaces],
|
interfaces=[schema.T(i) for i in cls._meta.interfaces],
|
||||||
fields=fields,
|
fields=fields,
|
||||||
is_type_of=getattr(cls, 'is_type_of', None)
|
is_type_of=getattr(cls, 'is_type_of', None)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class Interface(six.with_metaclass(ObjectTypeMeta, BaseObjectType)):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
class ObjectType(six.with_metaclass(ObjectTypeMeta, BaseObjectType)):
|
class ObjectType(six.with_metaclass(ObjectTypeMeta, BaseObjectType)):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
class Mutation(six.with_metaclass(ObjectTypeMeta, BaseObjectType)):
|
class Mutation(six.with_metaclass(ObjectTypeMeta, BaseObjectType)):
|
||||||
pass
|
@classmethod
|
||||||
|
def get_input_type(cls):
|
||||||
|
return getattr(cls, 'input_type', None)
|
||||||
|
|
||||||
|
|
||||||
class Interface(six.with_metaclass(ObjectTypeMeta, BaseObjectType)):
|
class InputObjectType(ObjectType):
|
||||||
pass
|
@classmethod
|
||||||
|
@register_internal_type
|
||||||
|
def internal_type(cls, schema):
|
||||||
|
fields = lambda: OrderedDict([(f.name, f.internal_field(schema))
|
||||||
|
for f in cls._meta.fields])
|
||||||
|
return GraphQLInputObjectType(
|
||||||
|
cls._meta.type_name,
|
||||||
|
description=cls._meta.description,
|
||||||
|
fields=fields,
|
||||||
|
)
|
||||||
|
|
|
@ -8,7 +8,8 @@ from graphene.relay.types import (
|
||||||
Node,
|
Node,
|
||||||
PageInfo,
|
PageInfo,
|
||||||
Edge,
|
Edge,
|
||||||
Connection
|
Connection,
|
||||||
|
ClientIDMutation
|
||||||
)
|
)
|
||||||
|
|
||||||
from graphene.relay.utils import is_node
|
from graphene.relay.utils import is_node
|
||||||
|
|
|
@ -16,7 +16,6 @@ from graphql.core.type import (
|
||||||
)
|
)
|
||||||
|
|
||||||
from graphene.core.fields import Field, IDField
|
from graphene.core.fields import Field, IDField
|
||||||
from graphene.utils import memoize
|
|
||||||
|
|
||||||
|
|
||||||
class ConnectionField(Field):
|
class ConnectionField(Field):
|
||||||
|
@ -51,24 +50,21 @@ class ConnectionField(Field):
|
||||||
connection.set_connection_data(resolved)
|
connection.set_connection_data(resolved)
|
||||||
return connection
|
return connection
|
||||||
|
|
||||||
@memoize
|
|
||||||
def get_connection_type(self, node):
|
def get_connection_type(self, node):
|
||||||
connection_type = self.connection_type or node.get_connection_type()
|
connection_type = self.connection_type or node.get_connection_type()
|
||||||
edge_type = self.get_edge_type(node)
|
edge_type = self.get_edge_type(node)
|
||||||
return connection_type.for_node(node, edge_type=edge_type)
|
return connection_type.for_node(node, edge_type=edge_type)
|
||||||
|
|
||||||
@memoize
|
|
||||||
def get_edge_type(self, node):
|
def get_edge_type(self, node):
|
||||||
return self.edge_type or node.get_edge_type()
|
return self.edge_type or node.get_edge_type()
|
||||||
|
|
||||||
@memoize
|
|
||||||
def internal_type(self, schema):
|
def internal_type(self, schema):
|
||||||
from graphene.relay.utils import is_node
|
from graphene.relay.utils import is_node
|
||||||
node = self.get_object_type(schema)
|
node = self.get_object_type(schema)
|
||||||
assert is_node(node), 'Only nodes have connections.'
|
assert is_node(node), 'Only nodes have connections.'
|
||||||
schema.register(node)
|
schema.register(node)
|
||||||
|
connection_type = self.get_connection_type(node)
|
||||||
return self.get_connection_type(node).internal_type(schema)
|
return schema.T(connection_type)
|
||||||
|
|
||||||
|
|
||||||
class NodeField(Field):
|
class NodeField(Field):
|
||||||
|
|
|
@ -2,7 +2,7 @@ from graphql_relay.node.node import (
|
||||||
to_global_id
|
to_global_id
|
||||||
)
|
)
|
||||||
|
|
||||||
from graphene.core.types import Interface, ObjectType
|
from graphene.core.types import Interface, ObjectType, Mutation, InputObjectType
|
||||||
from graphene.core.fields import BooleanField, StringField, ListField, Field
|
from graphene.core.fields import BooleanField, StringField, ListField, Field
|
||||||
from graphene.relay.fields import GlobalIDField
|
from graphene.relay.fields import GlobalIDField
|
||||||
from graphene.utils import memoize
|
from graphene.utils import memoize
|
||||||
|
@ -84,3 +84,32 @@ class BaseNode(object):
|
||||||
class Node(BaseNode, Interface):
|
class Node(BaseNode, Interface):
|
||||||
'''An object with an ID'''
|
'''An object with an ID'''
|
||||||
id = GlobalIDField()
|
id = GlobalIDField()
|
||||||
|
|
||||||
|
|
||||||
|
class MutationInputType(InputObjectType):
|
||||||
|
client_mutation_id = StringField(required=True)
|
||||||
|
|
||||||
|
|
||||||
|
class ClientIDMutation(Mutation):
|
||||||
|
client_mutation_id = StringField(required=True)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def _prepare_class(cls):
|
||||||
|
input_type = getattr(cls, 'input_type', None)
|
||||||
|
if input_type:
|
||||||
|
assert hasattr(cls, 'mutate_and_get_payload'), 'You have to implement mutate_and_get_payload'
|
||||||
|
new_input_inner_type = type('{}InnerInput'.format(cls._meta.type_name), (MutationInputType, input_type, ), {})
|
||||||
|
items = {
|
||||||
|
'input': Field(new_input_inner_type)
|
||||||
|
}
|
||||||
|
assert issubclass(new_input_inner_type, InputObjectType)
|
||||||
|
input_type = type('{}Input'.format(cls._meta.type_name), (ObjectType, ), items)
|
||||||
|
setattr(cls, 'input_type', input_type)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def mutate(cls, instance, args, info):
|
||||||
|
input = args.get('input')
|
||||||
|
payload = cls.mutate_and_get_payload(input, info)
|
||||||
|
client_mutation_id = input.get('client_mutation_id')
|
||||||
|
setattr(payload, 'client_mutation_id', client_mutation_id)
|
||||||
|
return payload
|
||||||
|
|
|
@ -1,86 +0,0 @@
|
||||||
from functools import wraps
|
|
||||||
|
|
||||||
|
|
||||||
class cached_property(object):
|
|
||||||
"""
|
|
||||||
A property that is only computed once per instance and then replaces itself
|
|
||||||
with an ordinary attribute. Deleting the attribute resets the property.
|
|
||||||
Source: https://github.com/bottlepy/bottle/commit/fa7733e075da0d790d809aa3d2f53071897e6f76
|
|
||||||
""" # noqa
|
|
||||||
|
|
||||||
def __init__(self, func):
|
|
||||||
self.__doc__ = getattr(func, '__doc__')
|
|
||||||
self.func = func
|
|
||||||
|
|
||||||
def __get__(self, obj, cls):
|
|
||||||
if obj is None:
|
|
||||||
return self
|
|
||||||
value = obj.__dict__[self.func.__name__] = self.func(obj)
|
|
||||||
return value
|
|
||||||
|
|
||||||
|
|
||||||
def memoize(fun):
|
|
||||||
"""A simple memoize decorator for functions supporting positional args."""
|
|
||||||
@wraps(fun)
|
|
||||||
def wrapper(*args, **kwargs):
|
|
||||||
key = (args, frozenset(sorted(kwargs.items())))
|
|
||||||
try:
|
|
||||||
return cache[key]
|
|
||||||
except KeyError:
|
|
||||||
ret = cache[key] = fun(*args, **kwargs)
|
|
||||||
return ret
|
|
||||||
cache = {}
|
|
||||||
return wrapper
|
|
||||||
|
|
||||||
|
|
||||||
# From this response in Stackoverflow
|
|
||||||
# http://stackoverflow.com/a/19053800/1072990
|
|
||||||
def to_camel_case(snake_str):
|
|
||||||
components = snake_str.split('_')
|
|
||||||
# We capitalize the first letter of each component except the first one
|
|
||||||
# with the 'title' method and join them together.
|
|
||||||
return components[0] + "".join(x.title() for x in components[1:])
|
|
||||||
|
|
||||||
|
|
||||||
class LazyMap(object):
|
|
||||||
def __init__(self, origin, _map, state=None):
|
|
||||||
self._origin = origin
|
|
||||||
self._origin_iter = origin.__iter__()
|
|
||||||
self._state = state or []
|
|
||||||
self._finished = False
|
|
||||||
self._map = _map
|
|
||||||
|
|
||||||
def __iter__(self):
|
|
||||||
return self if not self._finished else iter(self._state)
|
|
||||||
|
|
||||||
def iter(self):
|
|
||||||
return self.__iter__()
|
|
||||||
|
|
||||||
def __len__(self):
|
|
||||||
return self._origin.__len__()
|
|
||||||
|
|
||||||
def __next__(self):
|
|
||||||
try:
|
|
||||||
n = next(self._origin_iter)
|
|
||||||
n = self._map(n)
|
|
||||||
except StopIteration as e:
|
|
||||||
self._finished = True
|
|
||||||
raise e
|
|
||||||
else:
|
|
||||||
self._state.append(n)
|
|
||||||
return n
|
|
||||||
|
|
||||||
def next(self):
|
|
||||||
return self.__next__()
|
|
||||||
|
|
||||||
def __getitem__(self, key):
|
|
||||||
item = self._origin.__getitem__(key)
|
|
||||||
if isinstance(key, slice):
|
|
||||||
return LazyMap(item, self._map)
|
|
||||||
return self._map(item)
|
|
||||||
|
|
||||||
def __getattr__(self, name):
|
|
||||||
return getattr(self._origin, name)
|
|
||||||
|
|
||||||
def __repr__(self):
|
|
||||||
return "<LazyMap %s>" % repr(self._origin)
|
|
8
graphene/utils/__init__.py
Normal file
8
graphene/utils/__init__.py
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
from .str_converters import to_camel_case, to_snake_case
|
||||||
|
from .proxy_snake_dict import ProxySnakeDict
|
||||||
|
from .caching import cached_property, memoize
|
||||||
|
from .lazymap import LazyMap
|
||||||
|
from .misc import enum_to_graphql_enum
|
||||||
|
|
||||||
|
__all__ = ['to_camel_case', 'to_snake_case', 'ProxySnakeDict',
|
||||||
|
'cached_property', 'memoize', 'LazyMap', 'enum_to_graphql_enum']
|
35
graphene/utils/caching.py
Normal file
35
graphene/utils/caching.py
Normal file
|
@ -0,0 +1,35 @@
|
||||||
|
from functools import wraps
|
||||||
|
|
||||||
|
|
||||||
|
class CachedPropery(object):
|
||||||
|
"""
|
||||||
|
A property that is only computed once per instance and then replaces itself
|
||||||
|
with an ordinary attribute. Deleting the attribute resets the property.
|
||||||
|
Source: https://github.com/bottlepy/bottle/commit/fa7733e075da0d790d809aa3d2f53071897e6f76
|
||||||
|
""" # noqa
|
||||||
|
|
||||||
|
def __init__(self, func):
|
||||||
|
self.__doc__ = getattr(func, '__doc__')
|
||||||
|
self.func = func
|
||||||
|
|
||||||
|
def __get__(self, obj, cls):
|
||||||
|
if obj is None:
|
||||||
|
return self
|
||||||
|
value = obj.__dict__[self.func.__name__] = self.func(obj)
|
||||||
|
return value
|
||||||
|
|
||||||
|
cached_property = CachedPropery
|
||||||
|
|
||||||
|
|
||||||
|
def memoize(fun):
|
||||||
|
"""A simple memoize decorator for functions supporting positional args."""
|
||||||
|
@wraps(fun)
|
||||||
|
def wrapper(*args, **kwargs):
|
||||||
|
key = (args, frozenset(sorted(kwargs.items())))
|
||||||
|
try:
|
||||||
|
return cache[key]
|
||||||
|
except KeyError:
|
||||||
|
ret = cache[key] = fun(*args, **kwargs)
|
||||||
|
return ret
|
||||||
|
cache = {}
|
||||||
|
return wrapper
|
42
graphene/utils/lazymap.py
Normal file
42
graphene/utils/lazymap.py
Normal file
|
@ -0,0 +1,42 @@
|
||||||
|
class LazyMap(object):
|
||||||
|
def __init__(self, origin, _map, state=None):
|
||||||
|
self._origin = origin
|
||||||
|
self._origin_iter = origin.__iter__()
|
||||||
|
self._state = state or []
|
||||||
|
self._finished = False
|
||||||
|
self._map = _map
|
||||||
|
|
||||||
|
def __iter__(self):
|
||||||
|
return self if not self._finished else iter(self._state)
|
||||||
|
|
||||||
|
def iter(self):
|
||||||
|
return self.__iter__()
|
||||||
|
|
||||||
|
def __len__(self):
|
||||||
|
return self._origin.__len__()
|
||||||
|
|
||||||
|
def __next__(self):
|
||||||
|
try:
|
||||||
|
n = next(self._origin_iter)
|
||||||
|
n = self._map(n)
|
||||||
|
except StopIteration as e:
|
||||||
|
self._finished = True
|
||||||
|
raise e
|
||||||
|
else:
|
||||||
|
self._state.append(n)
|
||||||
|
return n
|
||||||
|
|
||||||
|
def next(self):
|
||||||
|
return self.__next__()
|
||||||
|
|
||||||
|
def __getitem__(self, key):
|
||||||
|
item = self._origin.__getitem__(key)
|
||||||
|
if isinstance(key, slice):
|
||||||
|
return LazyMap(item, self._map)
|
||||||
|
return self._map(item)
|
||||||
|
|
||||||
|
def __getattr__(self, name):
|
||||||
|
return getattr(self._origin, name)
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return "<LazyMap %s>" % repr(self._origin)
|
10
graphene/utils/misc.py
Normal file
10
graphene/utils/misc.py
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
from collections import OrderedDict
|
||||||
|
from graphql.core.type import GraphQLEnumType, GraphQLEnumValue
|
||||||
|
|
||||||
|
|
||||||
|
def enum_to_graphql_enum(enumeration):
|
||||||
|
return GraphQLEnumType(
|
||||||
|
name=enumeration.__name__,
|
||||||
|
values=OrderedDict([(it.name, GraphQLEnumValue(it.value)) for it in enumeration]),
|
||||||
|
description=enumeration.__doc__
|
||||||
|
)
|
70
graphene/utils/proxy_snake_dict.py
Normal file
70
graphene/utils/proxy_snake_dict.py
Normal file
|
@ -0,0 +1,70 @@
|
||||||
|
import collections
|
||||||
|
|
||||||
|
from .str_converters import to_camel_case, to_snake_case
|
||||||
|
|
||||||
|
|
||||||
|
class ProxySnakeDict(collections.MutableMapping):
|
||||||
|
__slots__ = ('data')
|
||||||
|
|
||||||
|
def __init__(self, data):
|
||||||
|
self.data = data
|
||||||
|
|
||||||
|
def __contains__(self, key):
|
||||||
|
return key in self.data or to_camel_case(key) in self.data
|
||||||
|
|
||||||
|
def get(self, key, default=None):
|
||||||
|
try:
|
||||||
|
return self.__getitem__(key)
|
||||||
|
except KeyError:
|
||||||
|
return default
|
||||||
|
|
||||||
|
def __iter__(self):
|
||||||
|
return self.iterkeys()
|
||||||
|
|
||||||
|
def __len__(self):
|
||||||
|
return len(self.data)
|
||||||
|
|
||||||
|
def __delitem__(self, item):
|
||||||
|
raise TypeError('ProxySnakeDict does not support item deletion')
|
||||||
|
|
||||||
|
def __setitem__(self, item, value):
|
||||||
|
raise TypeError('ProxySnakeDict does not support item assignment')
|
||||||
|
|
||||||
|
def __getitem__(self, key):
|
||||||
|
if key in self.data:
|
||||||
|
item = self.data[key]
|
||||||
|
else:
|
||||||
|
camel_key = to_camel_case(key)
|
||||||
|
if camel_key in self.data:
|
||||||
|
item = self.data[camel_key]
|
||||||
|
else:
|
||||||
|
raise KeyError(key, camel_key)
|
||||||
|
|
||||||
|
if isinstance(item, dict):
|
||||||
|
return ProxySnakeDict(item)
|
||||||
|
return item
|
||||||
|
|
||||||
|
def keys(self):
|
||||||
|
return list(self.iterkeys())
|
||||||
|
|
||||||
|
def items(self):
|
||||||
|
return list(self.iteritems())
|
||||||
|
|
||||||
|
def iterkeys(self):
|
||||||
|
for k in self.data.keys():
|
||||||
|
yield to_snake_case(k)
|
||||||
|
return
|
||||||
|
|
||||||
|
def iteritems(self):
|
||||||
|
for k in self.iterkeys():
|
||||||
|
yield k, self[k]
|
||||||
|
|
||||||
|
def to_data_dict(self):
|
||||||
|
return self.data.__class__(self.iteritems())
|
||||||
|
|
||||||
|
def __eq__(self, other):
|
||||||
|
return self.to_data_dict() == other.to_data_dict()
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
data_repr = self.to_data_dict().__repr__()
|
||||||
|
return '<ProxySnakeDict {}>'.format(data_repr)
|
17
graphene/utils/str_converters.py
Normal file
17
graphene/utils/str_converters.py
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
import re
|
||||||
|
|
||||||
|
|
||||||
|
# From this response in Stackoverflow
|
||||||
|
# http://stackoverflow.com/a/19053800/1072990
|
||||||
|
def to_camel_case(snake_str):
|
||||||
|
components = snake_str.split('_')
|
||||||
|
# We capitalize the first letter of each component except the first one
|
||||||
|
# with the 'title' method and join them together.
|
||||||
|
return components[0] + "".join(x.title() for x in components[1:])
|
||||||
|
|
||||||
|
|
||||||
|
# From this response in Stackoverflow
|
||||||
|
# http://stackoverflow.com/a/1176023/1072990
|
||||||
|
def to_snake_case(name):
|
||||||
|
s1 = re.sub('(.)([A-Z][a-z]+)', r'\1_\2', name)
|
||||||
|
return re.sub('([a-z0-9])([A-Z])', r'\1_\2', s1).lower()
|
2
setup.py
2
setup.py
|
@ -24,7 +24,7 @@ class PyTest(TestCommand):
|
||||||
|
|
||||||
setup(
|
setup(
|
||||||
name='graphene',
|
name='graphene',
|
||||||
version='0.1.6.1',
|
version='0.3.0',
|
||||||
|
|
||||||
description='Graphene: Python DSL for GraphQL',
|
description='Graphene: Python DSL for GraphQL',
|
||||||
long_description=open('README.rst').read(),
|
long_description=open('README.rst').read(),
|
||||||
|
|
|
@ -46,12 +46,12 @@ schema = Schema()
|
||||||
|
|
||||||
|
|
||||||
def test_django_interface():
|
def test_django_interface():
|
||||||
assert DjangoNode._meta.interface is True
|
assert DjangoNode._meta.is_interface is True
|
||||||
|
|
||||||
|
|
||||||
def test_pseudo_interface():
|
def test_pseudo_interface():
|
||||||
object_type = Character.internal_type(schema)
|
object_type = schema.T(Character)
|
||||||
assert Character._meta.interface is True
|
assert Character._meta.is_interface is True
|
||||||
assert isinstance(object_type, GraphQLInterfaceType)
|
assert isinstance(object_type, GraphQLInterfaceType)
|
||||||
assert Character._meta.model == Reporter
|
assert Character._meta.model == Reporter
|
||||||
assert_equal_lists(
|
assert_equal_lists(
|
||||||
|
@ -81,9 +81,9 @@ def test_interface_resolve_type():
|
||||||
|
|
||||||
|
|
||||||
def test_object_type():
|
def test_object_type():
|
||||||
object_type = Human.internal_type(schema)
|
object_type = schema.T(Human)
|
||||||
fields_map = Human._meta.fields_map
|
fields_map = Human._meta.fields_map
|
||||||
assert Human._meta.interface is False
|
assert Human._meta.is_interface is False
|
||||||
assert isinstance(object_type, GraphQLObjectType)
|
assert isinstance(object_type, GraphQLObjectType)
|
||||||
assert_equal_lists(
|
assert_equal_lists(
|
||||||
object_type.get_fields().keys(),
|
object_type.get_fields().keys(),
|
||||||
|
@ -95,9 +95,9 @@ def test_object_type():
|
||||||
# 'reporter': fields_map['reporter'].internal_field(schema),
|
# 'reporter': fields_map['reporter'].internal_field(schema),
|
||||||
# 'pubDate': fields_map['pub_date'].internal_field(schema),
|
# 'pubDate': fields_map['pub_date'].internal_field(schema),
|
||||||
# }
|
# }
|
||||||
assert DjangoNode.internal_type(schema) in object_type.get_interfaces()
|
assert schema.T(DjangoNode) in object_type.get_interfaces()
|
||||||
|
|
||||||
|
|
||||||
def test_node_notinterface():
|
def test_node_notinterface():
|
||||||
assert Human._meta.interface is False
|
assert Human._meta.is_interface is False
|
||||||
assert DjangoNode in Human._meta.interfaces
|
assert DjangoNode in Human._meta.interfaces
|
||||||
|
|
|
@ -24,19 +24,14 @@ from graphene.core.types import ObjectType
|
||||||
class ObjectType(object):
|
class ObjectType(object):
|
||||||
_meta = Options()
|
_meta = Options()
|
||||||
|
|
||||||
def resolve(self, *args, **kwargs):
|
def resolve_customdoc(self, *args, **kwargs):
|
||||||
|
'''Resolver documentation'''
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def can_resolve(self, *args):
|
|
||||||
return True
|
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return "ObjectType"
|
return "ObjectType"
|
||||||
|
|
||||||
ot = ObjectType()
|
ot = ObjectType
|
||||||
|
|
||||||
ObjectType._meta.contribute_to_class(ObjectType, '_meta')
|
|
||||||
|
|
||||||
|
|
||||||
schema = Schema()
|
schema = Schema()
|
||||||
|
|
||||||
|
@ -89,7 +84,7 @@ def test_field_resolve():
|
||||||
f = StringField(required=True, resolve=lambda *args: 'RESOLVED')
|
f = StringField(required=True, resolve=lambda *args: 'RESOLVED')
|
||||||
f.contribute_to_class(ot, 'field_name')
|
f.contribute_to_class(ot, 'field_name')
|
||||||
field_type = f.internal_field(schema)
|
field_type = f.internal_field(schema)
|
||||||
assert 'RESOLVED' == field_type.resolver(ot, 2, 3)
|
assert 'RESOLVED' == field_type.resolver(ot, None, None)
|
||||||
|
|
||||||
|
|
||||||
def test_field_resolve_type_custom():
|
def test_field_resolve_type_custom():
|
||||||
|
@ -172,3 +167,10 @@ def test_field_repr_contributed():
|
||||||
f = StringField()
|
f = StringField()
|
||||||
f.contribute_to_class(ot, 'field_name')
|
f.contribute_to_class(ot, 'field_name')
|
||||||
assert repr(f) == "<graphene.core.fields.StringField: field_name>"
|
assert repr(f) == "<graphene.core.fields.StringField: field_name>"
|
||||||
|
|
||||||
|
|
||||||
|
def test_field_resolve_objecttype_cos():
|
||||||
|
f = StringField()
|
||||||
|
f.contribute_to_class(ot, 'customdoc')
|
||||||
|
field = f.internal_field(schema)
|
||||||
|
assert field.description == 'Resolver documentation'
|
||||||
|
|
66
tests/core/test_mutations.py
Normal file
66
tests/core/test_mutations.py
Normal file
|
@ -0,0 +1,66 @@
|
||||||
|
import graphene
|
||||||
|
from py.test import raises
|
||||||
|
from graphene.core.schema import Schema
|
||||||
|
|
||||||
|
my_id = 0
|
||||||
|
|
||||||
|
|
||||||
|
class Query(graphene.ObjectType):
|
||||||
|
base = graphene.StringField()
|
||||||
|
|
||||||
|
|
||||||
|
class ChangeNumber(graphene.Mutation):
|
||||||
|
'''Result mutation'''
|
||||||
|
class Input:
|
||||||
|
to = graphene.IntField()
|
||||||
|
|
||||||
|
result = graphene.StringField()
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def mutate(cls, instance, args, info):
|
||||||
|
global my_id
|
||||||
|
my_id = args.get('to', my_id + 1)
|
||||||
|
return ChangeNumber(result=my_id)
|
||||||
|
|
||||||
|
|
||||||
|
class MyResultMutation(graphene.ObjectType):
|
||||||
|
change_number = graphene.Field(ChangeNumber)
|
||||||
|
|
||||||
|
|
||||||
|
schema = Schema(query=Query, mutation=MyResultMutation)
|
||||||
|
|
||||||
|
|
||||||
|
def test_mutation_input():
|
||||||
|
assert ChangeNumber.input_type
|
||||||
|
assert ChangeNumber.input_type._meta.type_name == 'ChangeNumberInput'
|
||||||
|
assert list(ChangeNumber.input_type._meta.fields_map.keys()) == ['to']
|
||||||
|
|
||||||
|
|
||||||
|
def test_execute_mutations():
|
||||||
|
query = '''
|
||||||
|
mutation M{
|
||||||
|
first: changeNumber {
|
||||||
|
result
|
||||||
|
},
|
||||||
|
second: changeNumber {
|
||||||
|
result
|
||||||
|
}
|
||||||
|
third: changeNumber(to: 5) {
|
||||||
|
result
|
||||||
|
}
|
||||||
|
}
|
||||||
|
'''
|
||||||
|
expected = {
|
||||||
|
'first': {
|
||||||
|
'result': '1',
|
||||||
|
},
|
||||||
|
'second': {
|
||||||
|
'result': '2',
|
||||||
|
},
|
||||||
|
'third': {
|
||||||
|
'result': '5',
|
||||||
|
}
|
||||||
|
}
|
||||||
|
result = schema.execute(query, root=object())
|
||||||
|
assert not result.errors
|
||||||
|
assert result.data == expected
|
|
@ -10,7 +10,7 @@ from graphene.core.options import Options
|
||||||
|
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
interface = True
|
is_interface = True
|
||||||
type_name = 'Character'
|
type_name = 'Character'
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -46,11 +46,11 @@ class Human(Character):
|
||||||
|
|
||||||
schema = Schema()
|
schema = Schema()
|
||||||
|
|
||||||
Human_type = Human.internal_type(schema)
|
Human_type = schema.T(Human)
|
||||||
|
|
||||||
|
|
||||||
def test_type():
|
def test_type():
|
||||||
assert Human._meta.fields_map['name'].resolve(Human(object()), 1, 2) == 'Peter'
|
assert Human._meta.fields_map['name'].resolve(Human(object()), None, None) == 'Peter'
|
||||||
|
|
||||||
|
|
||||||
def test_query():
|
def test_query():
|
||||||
|
|
|
@ -125,9 +125,23 @@ def test_schema_register():
|
||||||
class MyType(ObjectType):
|
class MyType(ObjectType):
|
||||||
type = StringField(resolve=lambda *_: 'Dog')
|
type = StringField(resolve=lambda *_: 'Dog')
|
||||||
|
|
||||||
|
schema.query = MyType
|
||||||
|
|
||||||
assert schema.get_type('MyType') == MyType
|
assert schema.get_type('MyType') == MyType
|
||||||
|
|
||||||
|
|
||||||
|
def test_schema_register():
|
||||||
|
schema = Schema(name='My own schema')
|
||||||
|
|
||||||
|
@schema.register
|
||||||
|
class MyType(ObjectType):
|
||||||
|
type = StringField(resolve=lambda *_: 'Dog')
|
||||||
|
|
||||||
|
with raises(Exception) as excinfo:
|
||||||
|
schema.get_type('MyType')
|
||||||
|
assert 'base query type' in str(excinfo.value)
|
||||||
|
|
||||||
|
|
||||||
def test_schema_introspect():
|
def test_schema_introspect():
|
||||||
schema = Schema(name='My own schema')
|
schema = Schema(name='My own schema')
|
||||||
|
|
||||||
|
@ -138,4 +152,3 @@ def test_schema_introspect():
|
||||||
|
|
||||||
introspection = schema.introspect()
|
introspection = schema.introspect()
|
||||||
assert '__schema' in introspection
|
assert '__schema' in introspection
|
||||||
|
|
||||||
|
|
|
@ -34,18 +34,34 @@ class Human(Character):
|
||||||
class Meta:
|
class Meta:
|
||||||
type_name = 'core_Human'
|
type_name = 'core_Human'
|
||||||
|
|
||||||
|
@property
|
||||||
|
def readonly_prop(self):
|
||||||
|
return 'readonly'
|
||||||
|
|
||||||
|
@property
|
||||||
|
def write_prop(self):
|
||||||
|
return self._write_prop
|
||||||
|
|
||||||
|
@write_prop.setter
|
||||||
|
def write_prop(self, value):
|
||||||
|
self._write_prop = value
|
||||||
|
|
||||||
schema = Schema()
|
schema = Schema()
|
||||||
|
|
||||||
|
|
||||||
def test_interface():
|
def test_interface():
|
||||||
object_type = Character.internal_type(schema)
|
object_type = schema.T(Character)
|
||||||
assert Character._meta.interface is True
|
assert Character._meta.is_interface is True
|
||||||
assert isinstance(object_type, GraphQLInterfaceType)
|
assert isinstance(object_type, GraphQLInterfaceType)
|
||||||
assert Character._meta.type_name == 'core_Character'
|
assert Character._meta.type_name == 'core_Character'
|
||||||
assert object_type.description == 'Character description'
|
assert object_type.description == 'Character description'
|
||||||
assert list(object_type.get_fields().keys()) == ['name']
|
assert list(object_type.get_fields().keys()) == ['name']
|
||||||
# assert object_type.get_fields() == {
|
|
||||||
# 'name': Character._meta.fields_map['name'].internal_field(schema)}
|
|
||||||
|
def test_interface_cannot_initialize():
|
||||||
|
with raises(Exception) as excinfo:
|
||||||
|
c = Character()
|
||||||
|
assert 'An interface cannot be initialized' == str(excinfo.value)
|
||||||
|
|
||||||
|
|
||||||
def test_interface_resolve_type():
|
def test_interface_resolve_type():
|
||||||
|
@ -54,18 +70,39 @@ def test_interface_resolve_type():
|
||||||
|
|
||||||
|
|
||||||
def test_object_type():
|
def test_object_type():
|
||||||
object_type = Human.internal_type(schema)
|
object_type = schema.T(Human)
|
||||||
assert Human._meta.interface is False
|
assert Human._meta.is_interface is False
|
||||||
assert Human._meta.type_name == 'core_Human'
|
assert Human._meta.type_name == 'core_Human'
|
||||||
assert isinstance(object_type, GraphQLObjectType)
|
assert isinstance(object_type, GraphQLObjectType)
|
||||||
assert object_type.description == 'Human description'
|
assert object_type.description == 'Human description'
|
||||||
assert list(object_type.get_fields().keys()) == ['name', 'friends']
|
assert list(object_type.get_fields().keys()) == ['name', 'friends']
|
||||||
# assert object_type.get_fields() == {'name': Human._meta.fields_map['name'].internal_field(
|
# assert object_type.get_fields() == {'name': Human._meta.fields_map['name'].internal_field(
|
||||||
# schema), 'friends': Human._meta.fields_map['friends'].internal_field(schema)}
|
# schema), 'friends': Human._meta.fields_map['friends'].internal_field(schema)}
|
||||||
assert object_type.get_interfaces() == [Character.internal_type(schema)]
|
assert object_type.get_interfaces() == [schema.T(Character)]
|
||||||
assert Human._meta.fields_map['name'].object_type == Human
|
assert Human._meta.fields_map['name'].object_type == Human
|
||||||
|
|
||||||
|
|
||||||
|
def test_object_type_container():
|
||||||
|
h = Human(name='My name')
|
||||||
|
assert h.name == 'My name'
|
||||||
|
|
||||||
|
|
||||||
|
def test_object_type_set_properties():
|
||||||
|
h = Human(readonly_prop='custom', write_prop='custom')
|
||||||
|
assert h.readonly_prop == 'readonly'
|
||||||
|
assert h.write_prop == 'custom'
|
||||||
|
|
||||||
|
|
||||||
|
def test_object_type_container_invalid_kwarg():
|
||||||
|
with raises(TypeError):
|
||||||
|
Human(invalid='My name')
|
||||||
|
|
||||||
|
|
||||||
|
def test_object_type_container_too_many_args():
|
||||||
|
with raises(IndexError):
|
||||||
|
Human('Peter', 'No friends :(', None)
|
||||||
|
|
||||||
|
|
||||||
def test_field_clashes():
|
def test_field_clashes():
|
||||||
with raises(Exception) as excinfo:
|
with raises(Exception) as excinfo:
|
||||||
class Droid(Character):
|
class Droid(Character):
|
||||||
|
|
|
@ -1,8 +1,7 @@
|
||||||
SECRET_KEY = 1
|
SECRET_KEY = 1
|
||||||
|
|
||||||
INSTALLED_APPS = [
|
INSTALLED_APPS = [
|
||||||
'graphene.contrib.django',
|
'examples.starwars_django',
|
||||||
'tests.starwars_django',
|
|
||||||
'tests.contrib_django',
|
'tests.contrib_django',
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
|
@ -32,21 +32,3 @@ def test_node_should_have_same_connection_always():
|
||||||
|
|
||||||
def test_node_should_have_id_field():
|
def test_node_should_have_id_field():
|
||||||
assert 'id' in OtherNode._meta.fields_map
|
assert 'id' in OtherNode._meta.fields_map
|
||||||
|
|
||||||
|
|
||||||
# def test_field_no_contributed_raises_error():
|
|
||||||
# with raises(Exception) as excinfo:
|
|
||||||
# class Ship(graphene.ObjectType):
|
|
||||||
# name = graphene.StringField()
|
|
||||||
# class Meta:
|
|
||||||
# schema = schema
|
|
||||||
|
|
||||||
# class Faction(relay.Node):
|
|
||||||
# name = graphene.StringField()
|
|
||||||
# ships = relay.ConnectionField(Ship)
|
|
||||||
# @classmethod
|
|
||||||
# def get_node(cls):
|
|
||||||
# pass
|
|
||||||
# class Meta:
|
|
||||||
# schema = schema
|
|
||||||
# assert 'same type_name' in str(excinfo.value)
|
|
||||||
|
|
84
tests/relay/test_relay_mutations.py
Normal file
84
tests/relay/test_relay_mutations.py
Normal file
|
@ -0,0 +1,84 @@
|
||||||
|
from graphql.core.type import (
|
||||||
|
GraphQLInputObjectField
|
||||||
|
)
|
||||||
|
|
||||||
|
import graphene
|
||||||
|
from graphene import relay
|
||||||
|
from graphene.core.types import InputObjectType
|
||||||
|
from graphene.core.schema import Schema
|
||||||
|
|
||||||
|
my_id = 0
|
||||||
|
|
||||||
|
|
||||||
|
class Query(graphene.ObjectType):
|
||||||
|
base = graphene.StringField()
|
||||||
|
|
||||||
|
|
||||||
|
class ChangeNumber(relay.ClientIDMutation):
|
||||||
|
'''Result mutation'''
|
||||||
|
class Input:
|
||||||
|
to = graphene.IntField()
|
||||||
|
|
||||||
|
result = graphene.StringField()
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def mutate_and_get_payload(cls, input, info):
|
||||||
|
global my_id
|
||||||
|
my_id = input.get('to', my_id + 1)
|
||||||
|
return ChangeNumber(result=my_id)
|
||||||
|
|
||||||
|
|
||||||
|
class MyResultMutation(graphene.ObjectType):
|
||||||
|
change_number = graphene.Field(ChangeNumber)
|
||||||
|
|
||||||
|
|
||||||
|
schema = Schema(query=Query, mutation=MyResultMutation)
|
||||||
|
|
||||||
|
|
||||||
|
def test_mutation_input():
|
||||||
|
assert ChangeNumber.input_type
|
||||||
|
assert ChangeNumber.input_type._meta.type_name == 'ChangeNumberInput'
|
||||||
|
assert list(ChangeNumber.input_type._meta.fields_map.keys()) == ['input']
|
||||||
|
_input = ChangeNumber.input_type._meta.fields_map['input']
|
||||||
|
inner_type = _input.get_object_type(schema)
|
||||||
|
client_mutation_id_field = inner_type._meta.fields_map['client_mutation_id']
|
||||||
|
assert issubclass(inner_type, InputObjectType)
|
||||||
|
assert isinstance(client_mutation_id_field, graphene.StringField)
|
||||||
|
assert client_mutation_id_field.object_type == inner_type
|
||||||
|
assert isinstance(client_mutation_id_field.internal_field(schema), GraphQLInputObjectField)
|
||||||
|
|
||||||
|
|
||||||
|
def test_execute_mutations():
|
||||||
|
query = '''
|
||||||
|
mutation M{
|
||||||
|
first: changeNumber(input: {clientMutationId: "mutation1"}) {
|
||||||
|
clientMutationId
|
||||||
|
result
|
||||||
|
},
|
||||||
|
second: changeNumber(input: {clientMutationId: "mutation2"}) {
|
||||||
|
clientMutationId
|
||||||
|
result
|
||||||
|
}
|
||||||
|
third: changeNumber(input: {clientMutationId: "mutation3", to: 5}) {
|
||||||
|
result
|
||||||
|
clientMutationId
|
||||||
|
}
|
||||||
|
}
|
||||||
|
'''
|
||||||
|
expected = {
|
||||||
|
'first': {
|
||||||
|
'clientMutationId': 'mutation1',
|
||||||
|
'result': '1',
|
||||||
|
},
|
||||||
|
'second': {
|
||||||
|
'clientMutationId': 'mutation2',
|
||||||
|
'result': '2',
|
||||||
|
},
|
||||||
|
'third': {
|
||||||
|
'clientMutationId': 'mutation3',
|
||||||
|
'result': '5',
|
||||||
|
}
|
||||||
|
}
|
||||||
|
result = schema.execute(query, root=object())
|
||||||
|
assert not result.errors
|
||||||
|
assert result.data == expected
|
|
@ -1,4 +1,3 @@
|
||||||
from pytest import raises
|
|
||||||
from graphql.core.type import (
|
from graphql.core.type import (
|
||||||
GraphQLNonNull,
|
GraphQLNonNull,
|
||||||
GraphQLID
|
GraphQLID
|
||||||
|
@ -10,11 +9,6 @@ from graphene import relay
|
||||||
schema = graphene.Schema()
|
schema = graphene.Schema()
|
||||||
|
|
||||||
|
|
||||||
class MyType(object):
|
|
||||||
name = 'my'
|
|
||||||
arg = None
|
|
||||||
|
|
||||||
|
|
||||||
class MyConnection(relay.Connection):
|
class MyConnection(relay.Connection):
|
||||||
my_custom_field = graphene.StringField(resolve=lambda instance, *_: 'Custom')
|
my_custom_field = graphene.StringField(resolve=lambda instance, *_: 'Custom')
|
||||||
|
|
||||||
|
@ -24,7 +18,7 @@ class MyNode(relay.Node):
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def get_node(cls, id):
|
def get_node(cls, id):
|
||||||
return MyNode(MyType())
|
return MyNode(name='mo')
|
||||||
|
|
||||||
|
|
||||||
class Query(graphene.ObjectType):
|
class Query(graphene.ObjectType):
|
||||||
|
@ -32,10 +26,9 @@ class Query(graphene.ObjectType):
|
||||||
all_my_nodes = relay.ConnectionField(MyNode, connection_type=MyConnection, customArg=graphene.Argument(graphene.String))
|
all_my_nodes = relay.ConnectionField(MyNode, connection_type=MyConnection, customArg=graphene.Argument(graphene.String))
|
||||||
|
|
||||||
def resolve_all_my_nodes(self, args, info):
|
def resolve_all_my_nodes(self, args, info):
|
||||||
t = MyType()
|
|
||||||
custom_arg = args.get('customArg')
|
custom_arg = args.get('customArg')
|
||||||
assert custom_arg == "1"
|
assert custom_arg == "1"
|
||||||
return [MyNode(t)]
|
return [MyNode(name='my')]
|
||||||
|
|
||||||
schema.query = Query
|
schema.query = Query
|
||||||
|
|
||||||
|
@ -61,7 +54,7 @@ def test_nodefield_query():
|
||||||
'''
|
'''
|
||||||
expected = {
|
expected = {
|
||||||
'myNode': {
|
'myNode': {
|
||||||
'name': 'my'
|
'name': 'mo'
|
||||||
},
|
},
|
||||||
'allMyNodes': {
|
'allMyNodes': {
|
||||||
'edges': [{
|
'edges': [{
|
||||||
|
|
|
@ -1,96 +0,0 @@
|
||||||
from collections import namedtuple
|
|
||||||
|
|
||||||
Human = namedtuple('Human', 'id name friends appearsIn homePlanet')
|
|
||||||
|
|
||||||
luke = Human(
|
|
||||||
id='1000',
|
|
||||||
name='Luke Skywalker',
|
|
||||||
friends=['1002', '1003', '2000', '2001'],
|
|
||||||
appearsIn=[4, 5, 6],
|
|
||||||
homePlanet='Tatooine',
|
|
||||||
)
|
|
||||||
|
|
||||||
vader = Human(
|
|
||||||
id='1001',
|
|
||||||
name='Darth Vader',
|
|
||||||
friends=['1004'],
|
|
||||||
appearsIn=[4, 5, 6],
|
|
||||||
homePlanet='Tatooine',
|
|
||||||
)
|
|
||||||
|
|
||||||
han = Human(
|
|
||||||
id='1002',
|
|
||||||
name='Han Solo',
|
|
||||||
friends=['1000', '1003', '2001'],
|
|
||||||
appearsIn=[4, 5, 6],
|
|
||||||
homePlanet=None,
|
|
||||||
)
|
|
||||||
|
|
||||||
leia = Human(
|
|
||||||
id='1003',
|
|
||||||
name='Leia Organa',
|
|
||||||
friends=['1000', '1002', '2000', '2001'],
|
|
||||||
appearsIn=[4, 5, 6],
|
|
||||||
homePlanet='Alderaan',
|
|
||||||
)
|
|
||||||
|
|
||||||
tarkin = Human(
|
|
||||||
id='1004',
|
|
||||||
name='Wilhuff Tarkin',
|
|
||||||
friends=['1001'],
|
|
||||||
appearsIn=[4],
|
|
||||||
homePlanet=None,
|
|
||||||
)
|
|
||||||
|
|
||||||
humanData = {
|
|
||||||
'1000': luke,
|
|
||||||
'1001': vader,
|
|
||||||
'1002': han,
|
|
||||||
'1003': leia,
|
|
||||||
'1004': tarkin,
|
|
||||||
}
|
|
||||||
|
|
||||||
Droid = namedtuple('Droid', 'id name friends appearsIn primaryFunction')
|
|
||||||
|
|
||||||
threepio = Droid(
|
|
||||||
id='2000',
|
|
||||||
name='C-3PO',
|
|
||||||
friends=['1000', '1002', '1003', '2001'],
|
|
||||||
appearsIn=[4, 5, 6],
|
|
||||||
primaryFunction='Protocol',
|
|
||||||
)
|
|
||||||
|
|
||||||
artoo = Droid(
|
|
||||||
id='2001',
|
|
||||||
name='R2-D2',
|
|
||||||
friends=['1000', '1002', '1003'],
|
|
||||||
appearsIn=[4, 5, 6],
|
|
||||||
primaryFunction='Astromech',
|
|
||||||
)
|
|
||||||
|
|
||||||
droidData = {
|
|
||||||
'2000': threepio,
|
|
||||||
'2001': artoo,
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
def getCharacter(id):
|
|
||||||
return humanData.get(id) or droidData.get(id)
|
|
||||||
|
|
||||||
|
|
||||||
def getFriends(character):
|
|
||||||
return map(getCharacter, character.friends)
|
|
||||||
|
|
||||||
|
|
||||||
def getHero(episode):
|
|
||||||
if episode == 5:
|
|
||||||
return luke
|
|
||||||
return artoo
|
|
||||||
|
|
||||||
|
|
||||||
def getHuman(id):
|
|
||||||
return humanData.get(id)
|
|
||||||
|
|
||||||
|
|
||||||
def getDroid(id):
|
|
||||||
return droidData.get(id)
|
|
|
@ -1,102 +0,0 @@
|
||||||
from collections import namedtuple
|
|
||||||
|
|
||||||
Ship = namedtuple('Ship', ['id', 'name'])
|
|
||||||
Faction = namedtuple('Faction', ['id', 'name', 'ships'])
|
|
||||||
|
|
||||||
xwing = Ship(
|
|
||||||
id='1',
|
|
||||||
name='X-Wing',
|
|
||||||
)
|
|
||||||
|
|
||||||
ywing = Ship(
|
|
||||||
id='2',
|
|
||||||
name='Y-Wing',
|
|
||||||
)
|
|
||||||
|
|
||||||
awing = Ship(
|
|
||||||
id='3',
|
|
||||||
name='A-Wing',
|
|
||||||
)
|
|
||||||
|
|
||||||
# Yeah, technically it's Corellian. But it flew in the service of the rebels,
|
|
||||||
# so for the purposes of this demo it's a rebel ship.
|
|
||||||
falcon = Ship(
|
|
||||||
id='4',
|
|
||||||
name='Millenium Falcon',
|
|
||||||
)
|
|
||||||
|
|
||||||
homeOne = Ship(
|
|
||||||
id='5',
|
|
||||||
name='Home One',
|
|
||||||
)
|
|
||||||
|
|
||||||
tieFighter = Ship(
|
|
||||||
id='6',
|
|
||||||
name='TIE Fighter',
|
|
||||||
)
|
|
||||||
|
|
||||||
tieInterceptor = Ship(
|
|
||||||
id='7',
|
|
||||||
name='TIE Interceptor',
|
|
||||||
)
|
|
||||||
|
|
||||||
executor = Ship(
|
|
||||||
id='8',
|
|
||||||
name='Executor',
|
|
||||||
)
|
|
||||||
|
|
||||||
rebels = Faction(
|
|
||||||
id='1',
|
|
||||||
name='Alliance to Restore the Republic',
|
|
||||||
ships=['1', '2', '3', '4', '5']
|
|
||||||
)
|
|
||||||
|
|
||||||
empire = Faction(
|
|
||||||
id='2',
|
|
||||||
name='Galactic Empire',
|
|
||||||
ships=['6', '7', '8']
|
|
||||||
)
|
|
||||||
|
|
||||||
data = {
|
|
||||||
'Faction': {
|
|
||||||
'1': rebels,
|
|
||||||
'2': empire
|
|
||||||
},
|
|
||||||
'Ship': {
|
|
||||||
'1': xwing,
|
|
||||||
'2': ywing,
|
|
||||||
'3': awing,
|
|
||||||
'4': falcon,
|
|
||||||
'5': homeOne,
|
|
||||||
'6': tieFighter,
|
|
||||||
'7': tieInterceptor,
|
|
||||||
'8': executor
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
def createShip(shipName, factionId):
|
|
||||||
nextShip = len(data['Ship'].keys())+1
|
|
||||||
newShip = Ship(
|
|
||||||
id=str(nextShip),
|
|
||||||
name=shipName
|
|
||||||
)
|
|
||||||
data['Ship'][newShip.id] = newShip
|
|
||||||
data['Faction'][factionId].ships.append(newShip.id)
|
|
||||||
return newShip
|
|
||||||
|
|
||||||
|
|
||||||
def getShip(_id):
|
|
||||||
return data['Ship'][_id]
|
|
||||||
|
|
||||||
|
|
||||||
def getFaction(_id):
|
|
||||||
return data['Faction'][_id]
|
|
||||||
|
|
||||||
|
|
||||||
def getRebels():
|
|
||||||
return rebels
|
|
||||||
|
|
||||||
|
|
||||||
def getEmpire():
|
|
||||||
return empire
|
|
|
@ -1,54 +0,0 @@
|
||||||
import graphene
|
|
||||||
from graphene import resolve_only_args, relay
|
|
||||||
|
|
||||||
from .data import (
|
|
||||||
getFaction,
|
|
||||||
getShip,
|
|
||||||
getRebels,
|
|
||||||
getEmpire,
|
|
||||||
)
|
|
||||||
|
|
||||||
schema = graphene.Schema(name='Starwars Relay Schema')
|
|
||||||
|
|
||||||
|
|
||||||
class Ship(relay.Node):
|
|
||||||
|
|
||||||
'''A ship in the Star Wars saga'''
|
|
||||||
name = graphene.StringField(description='The name of the ship.')
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def get_node(cls, id):
|
|
||||||
return Ship(getShip(id))
|
|
||||||
|
|
||||||
|
|
||||||
class Faction(relay.Node):
|
|
||||||
|
|
||||||
'''A faction in the Star Wars saga'''
|
|
||||||
name = graphene.StringField(description='The name of the faction.')
|
|
||||||
ships = relay.ConnectionField(
|
|
||||||
Ship, description='The ships used by the faction.')
|
|
||||||
|
|
||||||
@resolve_only_args
|
|
||||||
def resolve_ships(self, **kwargs):
|
|
||||||
return [Ship(getShip(ship)) for ship in self.instance.ships]
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def get_node(cls, id):
|
|
||||||
return Faction(getFaction(id))
|
|
||||||
|
|
||||||
|
|
||||||
class Query(graphene.ObjectType):
|
|
||||||
rebels = graphene.Field(Faction)
|
|
||||||
empire = graphene.Field(Faction)
|
|
||||||
node = relay.NodeField()
|
|
||||||
|
|
||||||
@resolve_only_args
|
|
||||||
def resolve_rebels(self):
|
|
||||||
return Faction(getRebels())
|
|
||||||
|
|
||||||
@resolve_only_args
|
|
||||||
def resolve_empire(self):
|
|
||||||
return Faction(getEmpire())
|
|
||||||
|
|
||||||
|
|
||||||
schema.query = Query
|
|
|
@ -1,61 +0,0 @@
|
||||||
import graphene
|
|
||||||
from graphene import resolve_only_args, relay
|
|
||||||
|
|
||||||
from .data import (
|
|
||||||
getHero, getHuman, getCharacter, getDroid,
|
|
||||||
Human as _Human, Droid as _Droid)
|
|
||||||
|
|
||||||
Episode = graphene.Enum('Episode', dict(
|
|
||||||
NEWHOPE=4,
|
|
||||||
EMPIRE=5,
|
|
||||||
JEDI=6
|
|
||||||
))
|
|
||||||
|
|
||||||
|
|
||||||
def wrap_character(character):
|
|
||||||
if isinstance(character, _Human):
|
|
||||||
return Human(character)
|
|
||||||
elif isinstance(character, _Droid):
|
|
||||||
return Droid(character)
|
|
||||||
|
|
||||||
|
|
||||||
class Character(graphene.Interface):
|
|
||||||
name = graphene.StringField()
|
|
||||||
friends = relay.Connection('Character')
|
|
||||||
appearsIn = graphene.ListField(Episode)
|
|
||||||
|
|
||||||
def resolve_friends(self, args, *_):
|
|
||||||
return [wrap_character(getCharacter(f)) for f in self.instance.friends]
|
|
||||||
|
|
||||||
|
|
||||||
class Human(relay.Node, Character):
|
|
||||||
homePlanet = graphene.StringField()
|
|
||||||
|
|
||||||
|
|
||||||
class Droid(relay.Node, Character):
|
|
||||||
primaryFunction = graphene.StringField()
|
|
||||||
|
|
||||||
|
|
||||||
class Query(graphene.ObjectType):
|
|
||||||
hero = graphene.Field(Character,
|
|
||||||
episode=graphene.Argument(Episode))
|
|
||||||
human = graphene.Field(Human,
|
|
||||||
id=graphene.Argument(graphene.String))
|
|
||||||
droid = graphene.Field(Droid,
|
|
||||||
id=graphene.Argument(graphene.String))
|
|
||||||
node = relay.NodeField()
|
|
||||||
|
|
||||||
@resolve_only_args
|
|
||||||
def resolve_hero(self, episode):
|
|
||||||
return wrap_character(getHero(episode))
|
|
||||||
|
|
||||||
@resolve_only_args
|
|
||||||
def resolve_human(self, id):
|
|
||||||
return wrap_character(getHuman(id))
|
|
||||||
|
|
||||||
@resolve_only_args
|
|
||||||
def resolve_droid(self, id):
|
|
||||||
return wrap_character(getDroid(id))
|
|
||||||
|
|
||||||
|
|
||||||
Schema = graphene.Schema(query=Query)
|
|
13
tests/utils/test_misc.py
Normal file
13
tests/utils/test_misc.py
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
import collections
|
||||||
|
from graphql.core.type import GraphQLEnumType
|
||||||
|
from graphene.utils.misc import enum_to_graphql_enum
|
||||||
|
|
||||||
|
item = collections.namedtuple('type', 'name value')
|
||||||
|
|
||||||
|
|
||||||
|
class MyCustomEnum(list):
|
||||||
|
__name__ = 'MyName'
|
||||||
|
|
||||||
|
|
||||||
|
def test_enum_to_graphql_enum():
|
||||||
|
assert isinstance(enum_to_graphql_enum(MyCustomEnum([item('k', 'v')])), GraphQLEnumType)
|
52
tests/utils/test_proxy_snake_dict.py
Normal file
52
tests/utils/test_proxy_snake_dict.py
Normal file
|
@ -0,0 +1,52 @@
|
||||||
|
from py.test import raises
|
||||||
|
from graphene.utils import ProxySnakeDict
|
||||||
|
|
||||||
|
|
||||||
|
def test_proxy_snake_dict():
|
||||||
|
my_data = {'one': 1, 'two': 2, 'none': None, 'threeOrFor': 3, 'inside': {'otherCamelCase': 3}}
|
||||||
|
p = ProxySnakeDict(my_data)
|
||||||
|
assert 'one' in p
|
||||||
|
assert 'two' in p
|
||||||
|
assert 'threeOrFor' in p
|
||||||
|
assert 'none' in p
|
||||||
|
assert len(p) == len(my_data)
|
||||||
|
assert p['none'] is None
|
||||||
|
assert p.get('none') is None
|
||||||
|
assert p.get('none_existent') is None
|
||||||
|
assert 'three_or_for' in p
|
||||||
|
assert p.get('three_or_for') == 3
|
||||||
|
assert 'inside' in p
|
||||||
|
assert 'other_camel_case' in p['inside']
|
||||||
|
assert sorted(p.items()) == sorted(list([('inside', ProxySnakeDict({'other_camel_case': 3})),
|
||||||
|
('none', None),
|
||||||
|
('three_or_for', 3),
|
||||||
|
('two', 2),
|
||||||
|
('one', 1)]))
|
||||||
|
|
||||||
|
|
||||||
|
def test_proxy_snake_dict_as_kwargs():
|
||||||
|
my_data = {'myData': 1}
|
||||||
|
p = ProxySnakeDict(my_data)
|
||||||
|
|
||||||
|
def func(**kwargs):
|
||||||
|
return kwargs.get('my_data')
|
||||||
|
assert func(**p) == 1
|
||||||
|
|
||||||
|
|
||||||
|
def test_proxy_snake_dict_repr():
|
||||||
|
my_data = {'myData': 1}
|
||||||
|
p = ProxySnakeDict(my_data)
|
||||||
|
|
||||||
|
assert repr(p) == "<ProxySnakeDict {'my_data': 1}>"
|
||||||
|
|
||||||
|
|
||||||
|
def test_proxy_snake_dict_set():
|
||||||
|
p = ProxySnakeDict({})
|
||||||
|
with raises(TypeError):
|
||||||
|
p['a'] = 2
|
||||||
|
|
||||||
|
|
||||||
|
def test_proxy_snake_dict_delete():
|
||||||
|
p = ProxySnakeDict({})
|
||||||
|
with raises(TypeError):
|
||||||
|
del p['a']
|
14
tests/utils/test_str_converter.py
Normal file
14
tests/utils/test_str_converter.py
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
from graphene.utils import to_snake_case, to_camel_case
|
||||||
|
|
||||||
|
|
||||||
|
def test_snake_case():
|
||||||
|
assert to_snake_case('snakesOnAPlane') == 'snakes_on_a_plane'
|
||||||
|
assert to_snake_case('SnakesOnAPlane') == 'snakes_on_a_plane'
|
||||||
|
assert to_snake_case('snakes_on_a_plane') == 'snakes_on_a_plane'
|
||||||
|
assert to_snake_case('IPhoneHysteria') == 'i_phone_hysteria'
|
||||||
|
assert to_snake_case('iPhoneHysteria') == 'i_phone_hysteria'
|
||||||
|
|
||||||
|
|
||||||
|
def test_camel_case():
|
||||||
|
assert to_camel_case('snakes_on_a_plane') == 'snakesOnAPlane'
|
||||||
|
assert to_camel_case('i_phone_hysteria') == 'iPhoneHysteria'
|
Loading…
Reference in New Issue
Block a user