mirror of
https://github.com/graphql-python/graphene.git
synced 2025-02-16 19:40:39 +03:00
commit
3e8e65f033
2
.gitignore
vendored
2
.gitignore
vendored
|
@ -59,3 +59,5 @@ docs/_build/
|
|||
# PyBuilder
|
||||
target/
|
||||
|
||||
|
||||
/tests/django.sqlite
|
||||
|
|
|
@ -6,9 +6,10 @@ python:
|
|||
- 3.4
|
||||
- 3.5
|
||||
- pypy
|
||||
cache: pip
|
||||
install:
|
||||
- pip install pytest pytest-cov coveralls flake8 six blinker pytest-django
|
||||
- pip install -e .[django]
|
||||
- pip install --cache-dir $HOME/.cache/pip pytest pytest-cov coveralls flake8 six blinker pytest-django
|
||||
- pip install --cache-dir $HOME/.cache/pip -e .[django]
|
||||
- python setup.py develop
|
||||
script:
|
||||
- py.test --cov=graphene
|
||||
|
|
80
README.md
80
README.md
|
@ -1,10 +1,13 @@
|
|||
# ![Graphene Logo](http://graphene-python.org/favicon.png) [Graphene](http://graphene-python.org) [![Build Status](https://travis-ci.org/graphql-python/graphene.svg?branch=master)](https://travis-ci.org/graphql-python/graphene) [![Coverage Status](https://coveralls.io/repos/graphql-python/graphene/badge.svg?branch=master&service=github)](https://coveralls.io/github/graphql-python/graphene?branch=master)
|
||||
# ![Graphene Logo](http://graphene-python.org/favicon.png) [Graphene](http://graphene-python.org) [![Build Status](https://travis-ci.org/graphql-python/graphene.svg?branch=master)](https://travis-ci.org/graphql-python/graphene) [![PyPI version](https://badge.fury.io/py/graphene.svg)](https://badge.fury.io/py/graphene) [![Coverage Status](https://coveralls.io/repos/graphql-python/graphene/badge.svg?branch=master&service=github)](https://coveralls.io/github/graphql-python/graphene?branch=master)
|
||||
|
||||
|
||||
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
|
||||
* **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
|
||||
|
@ -13,29 +16,26 @@ For instaling graphene, just run this command in your shell
|
|||
|
||||
```bash
|
||||
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:
|
||||
|
||||
### Schema definition
|
||||
Here is one example for get you started:
|
||||
|
||||
```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):
|
||||
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)
|
||||
```
|
||||
|
@ -44,48 +44,20 @@ Then Querying `graphene.Schema` is as simple as:
|
|||
|
||||
```python
|
||||
query = '''
|
||||
query HeroNameQuery {
|
||||
hero {
|
||||
name
|
||||
}
|
||||
query SayHello {
|
||||
hello
|
||||
ping(to:'peter')
|
||||
}
|
||||
'''
|
||||
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
|
||||
|
||||
|
|
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.
|
||||
It maps the models/fields to internal GraphQL objects without effort.
|
||||
Including automatic `Django models`_ conversion.
|
||||
Graphene is a Python library for building GraphQL schemas/types fast and
|
||||
easily. \* **Easy to use:** Graphene helps you use GraphQL in Python
|
||||
without effort. \* **Relay:** Graphene has builtin support for Relay \*
|
||||
**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
|
||||
------------
|
||||
|
@ -13,89 +20,49 @@ For instaling graphene, just run this command in your shell
|
|||
.. code:: bash
|
||||
|
||||
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:
|
||||
|
||||
Schema definition
|
||||
~~~~~~~~~~~~~~~~~
|
||||
Here is one example for get you started:
|
||||
|
||||
.. 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):
|
||||
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)
|
||||
|
||||
Querying
|
||||
~~~~~~~~
|
||||
|
||||
Querying ``graphene.Schema`` is as simple as:
|
||||
Then Querying ``graphene.Schema`` is as simple as:
|
||||
|
||||
.. code:: python
|
||||
|
||||
query = '''
|
||||
query HeroNameQuery {
|
||||
hero {
|
||||
name
|
||||
}
|
||||
query SayHello {
|
||||
hello
|
||||
ping(to:'peter')
|
||||
}
|
||||
'''
|
||||
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`_!
|
||||
|
||||
.. code:: python
|
||||
|
||||
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 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()
|
||||
- **Basic Schema**: `Starwars example <examples/starwars>`__
|
||||
- **Relay Schema**: `Starwars Relay
|
||||
example <examples/starwars_relay>`__
|
||||
- **Django model mapping**: `Starwars Django
|
||||
example <examples/starwars_django>`__
|
||||
|
||||
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
|
||||
|
||||
.. _Django models: #djangorelay-schema
|
||||
.. _Starwars Relay example: tests/starwars_relay
|
||||
.. _Starwars Django example: tests/starwars_django
|
||||
|
||||
.. |Graphene Logo| image:: http://graphene-python.org/favicon.png
|
||||
.. |Build Status| image:: https://travis-ci.org/graphql-python/graphene.svg?branch=master
|
||||
: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
|
||||
: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
|
||||
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(
|
||||
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):
|
||||
id = graphene.IDField()
|
||||
name = graphene.StringField()
|
||||
friends = graphene.ListField('self')
|
||||
appearsIn = graphene.ListField(Episode)
|
||||
appears_in = graphene.ListField(Episode)
|
||||
|
||||
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):
|
||||
homePlanet = graphene.StringField()
|
||||
home_planet = graphene.StringField()
|
||||
|
||||
|
||||
class Droid(Character):
|
||||
primaryFunction = graphene.StringField()
|
||||
primary_function = graphene.StringField()
|
||||
|
||||
|
||||
class Query(graphene.ObjectType):
|
||||
|
@ -47,20 +41,17 @@ class Query(graphene.ObjectType):
|
|||
id=graphene.Argument(graphene.String)
|
||||
)
|
||||
|
||||
class Meta:
|
||||
type_name = 'core_Query'
|
||||
|
||||
@resolve_only_args
|
||||
def resolve_hero(self, episode=None):
|
||||
return wrap_character(getHero(episode))
|
||||
return getHero(episode)
|
||||
|
||||
@resolve_only_args
|
||||
def resolve_human(self, id):
|
||||
return wrap_character(getHuman(id))
|
||||
return getHuman(id)
|
||||
|
||||
@resolve_only_args
|
||||
def resolve_droid(self, id):
|
||||
return wrap_character(getDroid(id))
|
||||
return getDroid(id)
|
||||
|
||||
|
||||
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 ..data import setup
|
||||
|
||||
setup()
|
||||
|
||||
def test_hero_name_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()
|
||||
|
||||
|
||||
def createShip(shipName, factionId):
|
||||
nextShip = len(data['Ship'].keys())+1
|
||||
newShip = Ship(
|
||||
id=str(nextShip),
|
||||
name=shipName
|
||||
def create_ship(ship_name, faction_id):
|
||||
new_ship = Ship(
|
||||
name=ship_name,
|
||||
faction_id=faction_id
|
||||
)
|
||||
newShip.save()
|
||||
return newShip
|
||||
new_ship.save()
|
||||
return new_ship
|
||||
|
||||
|
||||
def getShip(_id):
|
||||
def get_ship(_id):
|
||||
return Ship.objects.get(id=_id)
|
||||
|
||||
|
||||
def getShips():
|
||||
def get_ships():
|
||||
return Ship.objects.all()
|
||||
|
||||
|
||||
def getFaction(_id):
|
||||
def get_faction(_id):
|
||||
return Faction.objects.get(id=_id)
|
||||
|
||||
|
||||
def getRebels():
|
||||
return getFaction(1)
|
||||
def get_rebels():
|
||||
return get_faction(1)
|
||||
|
||||
|
||||
def getEmpire():
|
||||
return getFaction(2)
|
||||
def get_empire():
|
||||
return get_faction(2)
|
|
@ -7,11 +7,12 @@ from graphene.contrib.django import (
|
|||
from .models import (
|
||||
Ship as ShipModel, Faction as FactionModel, Character as CharacterModel)
|
||||
from .data import (
|
||||
getFaction,
|
||||
getShip,
|
||||
getShips,
|
||||
getRebels,
|
||||
getEmpire,
|
||||
get_faction,
|
||||
get_ship,
|
||||
get_ships,
|
||||
get_rebels,
|
||||
get_empire,
|
||||
create_ship
|
||||
)
|
||||
|
||||
schema = graphene.Schema(name='Starwars Django Relay Schema')
|
||||
|
@ -23,11 +24,11 @@ class Ship(DjangoNode):
|
|||
|
||||
@classmethod
|
||||
def get_node(cls, id):
|
||||
return Ship(getShip(id))
|
||||
return Ship(get_ship(id))
|
||||
|
||||
|
||||
@schema.register
|
||||
class CharacterModel(DjangoObjectType):
|
||||
class Character(DjangoObjectType):
|
||||
class Meta:
|
||||
model = CharacterModel
|
||||
|
||||
|
@ -38,7 +39,24 @@ class Faction(DjangoNode):
|
|||
|
||||
@classmethod
|
||||
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):
|
||||
|
@ -49,15 +67,20 @@ class Query(graphene.ObjectType):
|
|||
|
||||
@resolve_only_args
|
||||
def resolve_ships(self):
|
||||
return [Ship(s) for s in getShips()]
|
||||
return [Ship(s) for s in get_ships()]
|
||||
|
||||
@resolve_only_args
|
||||
def resolve_rebels(self):
|
||||
return Faction(getRebels())
|
||||
return Faction(get_rebels())
|
||||
|
||||
@resolve_only_args
|
||||
def resolve_empire(self):
|
||||
return Faction(getEmpire())
|
||||
return Faction(get_empire())
|
||||
|
||||
|
||||
class Mutation(graphene.ObjectType):
|
||||
introduce_ship = graphene.Field(IntroduceShip)
|
||||
|
||||
|
||||
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
|
||||
from graphql.core import graphql
|
||||
|
||||
from .models import *
|
||||
from .schema import schema
|
||||
from .data import initialize
|
||||
from ..models import *
|
||||
from ..schema import schema
|
||||
from ..data import initialize
|
||||
|
||||
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
|
||||
from pytest import raises
|
||||
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
|
||||
|
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 graphql.core import graphql
|
||||
from ..schema import schema
|
||||
from ..data import setup
|
||||
|
||||
from .schema import schema
|
||||
setup()
|
||||
|
||||
|
||||
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 graphql.core import graphql
|
||||
from ..schema import schema
|
||||
from ..data import setup
|
||||
|
||||
from .schema import schema
|
||||
setup()
|
||||
|
||||
|
||||
def test_correctly_fetches_id_name_rebels():
|
|
@ -14,7 +14,8 @@ from graphene.core.schema import (
|
|||
|
||||
from graphene.core.types import (
|
||||
ObjectType,
|
||||
Interface
|
||||
Interface,
|
||||
Mutation,
|
||||
)
|
||||
|
||||
from graphene.core.fields import (
|
||||
|
@ -31,3 +32,8 @@ from graphene.core.fields import (
|
|||
from graphene.decorators import (
|
||||
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.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.contrib.django.utils import get_type_for_model, lazy_map
|
||||
|
||||
|
@ -25,7 +23,6 @@ class LazyListField(ListField):
|
|||
|
||||
|
||||
class ConnectionOrListField(LazyField):
|
||||
@memoize
|
||||
def get_field(self, schema):
|
||||
model_field = self.field_type
|
||||
field_object_type = model_field.get_object_type(schema)
|
||||
|
@ -52,7 +49,6 @@ class DjangoModelField(Field):
|
|||
))
|
||||
return _type(resolved)
|
||||
|
||||
@memoize
|
||||
def internal_type(self, schema):
|
||||
_type = self.get_object_type(schema)
|
||||
if not _type and self.object_type._meta.only_fields:
|
||||
|
@ -66,7 +62,7 @@ class DjangoModelField(Field):
|
|||
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):
|
||||
return get_type_for_model(schema, self.model)
|
||||
|
|
|
@ -37,11 +37,20 @@ class DjangoObjectTypeMeta(ObjectTypeMeta):
|
|||
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
|
||||
|
||||
|
||||
class DjangoInterface(six.with_metaclass(DjangoObjectTypeMeta, BaseObjectType)):
|
||||
class DjangoInterface(six.with_metaclass(DjangoObjectTypeMeta, InstanceObjectType)):
|
||||
pass
|
||||
|
||||
|
||||
|
|
|
@ -2,10 +2,9 @@ from django.db import models
|
|||
from django.db.models.query import QuerySet
|
||||
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):
|
||||
schema = schema
|
||||
types = schema.types.values()
|
||||
|
|
|
@ -1,5 +1,10 @@
|
|||
import inspect
|
||||
import six
|
||||
try:
|
||||
from enum import Enum
|
||||
except ImportError:
|
||||
class Enum(object):
|
||||
pass
|
||||
from functools import total_ordering, wraps
|
||||
from graphql.core.type import (
|
||||
GraphQLField,
|
||||
|
@ -11,9 +16,10 @@ from graphql.core.type import (
|
|||
GraphQLID,
|
||||
GraphQLArgument,
|
||||
GraphQLFloat,
|
||||
GraphQLInputObjectField,
|
||||
)
|
||||
from graphene.utils import memoize, to_camel_case
|
||||
from graphene.core.types import BaseObjectType
|
||||
from graphene.utils import to_camel_case, ProxySnakeDict, enum_to_graphql_enum
|
||||
from graphene.core.types import BaseObjectType, InputObjectType
|
||||
from graphene.core.scalars import GraphQLSkipField
|
||||
|
||||
|
||||
|
@ -27,7 +33,7 @@ class Field(object):
|
|||
creation_counter = 0
|
||||
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.resolve_fn = resolve
|
||||
self.required = self.required or required
|
||||
|
@ -37,9 +43,13 @@ class Field(object):
|
|||
self.name = name
|
||||
self.description = description or self.__doc__
|
||||
self.object_type = None
|
||||
self.default = default
|
||||
self.creation_counter = Field.creation_counter
|
||||
Field.creation_counter += 1
|
||||
|
||||
def get_default(self):
|
||||
return self.default
|
||||
|
||||
def contribute_to_class(self, cls, name, add=True):
|
||||
if not self.name:
|
||||
self.name = to_camel_case(name)
|
||||
|
@ -51,20 +61,18 @@ class Field(object):
|
|||
cls._meta.add_field(self)
|
||||
|
||||
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:
|
||||
return resolve_fn(instance, args, info)
|
||||
return resolve_fn(instance, ProxySnakeDict(args), info)
|
||||
else:
|
||||
if isinstance(instance, BaseObjectType):
|
||||
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)
|
||||
return getattr(instance, self.field_name, self.get_default())
|
||||
|
||||
@memoize
|
||||
def get_resolve_fn(self):
|
||||
if self.resolve_fn:
|
||||
def get_resolve_fn(self, schema):
|
||||
object_type = self.get_object_type(schema)
|
||||
if object_type and object_type._meta.is_mutation:
|
||||
return object_type.mutate
|
||||
elif self.resolve_fn:
|
||||
return self.resolve_fn
|
||||
else:
|
||||
custom_resolve_fn_name = 'resolve_%s' % self.field_name
|
||||
|
@ -96,24 +104,25 @@ class Field(object):
|
|||
field_type = GraphQLNonNull(field_type)
|
||||
return field_type
|
||||
|
||||
@memoize
|
||||
def internal_type(self, schema):
|
||||
field_type = self.field_type
|
||||
_is_class = inspect.isclass(field_type)
|
||||
if isinstance(field_type, Field):
|
||||
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:
|
||||
object_type = self.get_object_type(schema)
|
||||
if object_type:
|
||||
field_type = object_type.internal_type(schema)
|
||||
field_type = schema.T(object_type)
|
||||
|
||||
field_type = self.type_wrapper(field_type)
|
||||
return field_type
|
||||
|
||||
@memoize
|
||||
def internal_field(self, schema):
|
||||
if not self.object_type:
|
||||
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()
|
||||
for arg_name, arg_value in self.extra_args.items():
|
||||
|
@ -128,27 +137,44 @@ class Field(object):
|
|||
','.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)
|
||||
if not internal_type:
|
||||
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:
|
||||
description = resolve_fn.__doc__ or description
|
||||
|
||||
@wraps(resolve_fn)
|
||||
def resolver(*args):
|
||||
return self.resolve(*args)
|
||||
else:
|
||||
resolver = self.resolve
|
||||
|
||||
if issubclass(self.object_type, InputObjectType):
|
||||
return GraphQLInputObjectField(
|
||||
internal_type,
|
||||
description=description,
|
||||
)
|
||||
|
||||
return GraphQLField(
|
||||
internal_type,
|
||||
description=self.description,
|
||||
args=self.args,
|
||||
description=description,
|
||||
args=args,
|
||||
resolver=resolver,
|
||||
)
|
||||
|
||||
def __str__(self):
|
||||
""" 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):
|
||||
"""
|
||||
|
@ -189,7 +215,6 @@ class Field(object):
|
|||
|
||||
class LazyField(Field):
|
||||
|
||||
@memoize
|
||||
def inner_field(self, schema):
|
||||
return self.get_field(schema)
|
||||
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
from graphene.utils import cached_property
|
||||
from collections import OrderedDict, namedtuple
|
||||
|
||||
DEFAULT_NAMES = ('description', 'name', 'interface',
|
||||
DEFAULT_NAMES = ('description', 'name', 'is_interface', 'is_mutation',
|
||||
'type_name', 'interfaces', 'proxy')
|
||||
|
||||
|
||||
|
@ -10,7 +10,8 @@ class Options(object):
|
|||
def __init__(self, meta=None):
|
||||
self.meta = meta
|
||||
self.local_fields = []
|
||||
self.interface = False
|
||||
self.is_interface = False
|
||||
self.is_mutation = False
|
||||
self.proxy = False
|
||||
self.interfaces = []
|
||||
self.parents = []
|
||||
|
@ -54,15 +55,8 @@ class Options(object):
|
|||
else:
|
||||
self.proxy = False
|
||||
|
||||
if self.interfaces != [] and self.interface:
|
||||
raise Exception("A interface cannot inherit from interfaces")
|
||||
|
||||
del self.meta
|
||||
|
||||
@cached_property
|
||||
def object(self):
|
||||
return namedtuple(self.type_name, self.fields_map.keys())
|
||||
|
||||
def add_field(self, field):
|
||||
self.local_fields.append(field)
|
||||
|
||||
|
|
|
@ -25,6 +25,7 @@ class Schema(object):
|
|||
|
||||
def __init__(self, query=None, mutation=None, name='Schema', executor=None):
|
||||
self._internal_types = {}
|
||||
self._types = {}
|
||||
self.mutation = mutation
|
||||
self.query = query
|
||||
self.name = name
|
||||
|
@ -34,6 +35,13 @@ class Schema(object):
|
|||
def __repr__(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
|
||||
def query(self):
|
||||
return self._query
|
||||
|
@ -41,7 +49,14 @@ class Schema(object):
|
|||
@query.setter
|
||||
def query(self, 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
|
||||
def executor(self):
|
||||
|
@ -53,11 +68,11 @@ class Schema(object):
|
|||
def executor(self, value):
|
||||
self._executor = value
|
||||
|
||||
@cached_property
|
||||
@property
|
||||
def schema(self):
|
||||
if not self._query_type:
|
||||
if not self._query:
|
||||
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):
|
||||
self._internal_types[internal_type.name] = object_type
|
||||
|
@ -67,6 +82,7 @@ class Schema(object):
|
|||
return object_type
|
||||
|
||||
def get_type(self, type_name):
|
||||
self.schema._build_type_map()
|
||||
if type_name not in self._internal_types:
|
||||
raise Exception('Type %s not found in %r' % (type_name, self))
|
||||
return self._internal_types[type_name]
|
||||
|
|
|
@ -5,7 +5,9 @@ from collections import OrderedDict
|
|||
|
||||
from graphql.core.type import (
|
||||
GraphQLObjectType,
|
||||
GraphQLInterfaceType
|
||||
GraphQLInputObjectType,
|
||||
GraphQLInterfaceType,
|
||||
GraphQLArgument
|
||||
)
|
||||
|
||||
from graphene import signals
|
||||
|
@ -21,7 +23,7 @@ class ObjectTypeMeta(type):
|
|||
return Interface in parents
|
||||
|
||||
def is_mutation(cls, parents):
|
||||
return Mutation in parents
|
||||
return issubclass(cls, Mutation)
|
||||
|
||||
def __new__(cls, name, bases, attrs):
|
||||
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._meta.interface = new_class.is_interface(parents)
|
||||
new_class._meta.mutation = new_class.is_mutation(parents)
|
||||
new_class._meta.is_interface = new_class.is_interface(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.
|
||||
for obj_name, obj in attrs.items():
|
||||
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_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._meta.parents.append(base)
|
||||
if base._meta.interface:
|
||||
if base._meta.is_interface:
|
||||
new_class._meta.interfaces.append(base)
|
||||
# new_class._meta.parents.extend(base._meta.parents)
|
||||
|
||||
|
@ -113,50 +129,67 @@ class ObjectTypeMeta(type):
|
|||
|
||||
class BaseObjectType(object):
|
||||
|
||||
def __new__(cls, instance=None, **kwargs):
|
||||
if cls._meta.interface:
|
||||
def __new__(cls, *args, **kwargs):
|
||||
if cls._meta.is_interface:
|
||||
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)
|
||||
|
||||
def __init__(self, instance=None, **kwargs):
|
||||
signals.pre_init.send(self.__class__, instance=instance)
|
||||
assert instance or kwargs
|
||||
if not instance:
|
||||
init_kwargs = dict({k: None for k in self._meta.fields_map.keys()}, **kwargs)
|
||||
instance = self._meta.object(**init_kwargs)
|
||||
self.instance = instance
|
||||
def __init__(self, *args, **kwargs):
|
||||
signals.pre_init.send(self.__class__, args=args, kwargs=kwargs)
|
||||
args_len = len(args)
|
||||
fields = self._meta.fields
|
||||
if args_len > len(fields):
|
||||
# Daft, but matches old exception sans the err msg.
|
||||
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)
|
||||
|
||||
def __getattr__(self, name):
|
||||
if self.instance:
|
||||
return getattr(self.instance, name)
|
||||
|
||||
def get_field(self, field):
|
||||
return getattr(self.instance, field, None)
|
||||
@classmethod
|
||||
def fields_as_arguments(cls, schema):
|
||||
return OrderedDict([(f.field_name, GraphQLArgument(f.internal_type(schema)))
|
||||
for f in cls._meta.fields])
|
||||
|
||||
@classmethod
|
||||
def resolve_objecttype(cls, schema, instance, *_):
|
||||
return instance
|
||||
return instance.__class__
|
||||
|
||||
@classmethod
|
||||
def resolve_type(cls, schema, instance, *_):
|
||||
objecttype = cls.resolve_objecttype(schema, instance, *_)
|
||||
return objecttype.internal_type(schema)
|
||||
return schema.T(objecttype)
|
||||
|
||||
@classmethod
|
||||
@memoize
|
||||
@register_internal_type
|
||||
def internal_type(cls, schema):
|
||||
fields_list = cls._meta.fields
|
||||
fields = lambda: OrderedDict([(f.name, f.internal_field(schema))
|
||||
for f in fields_list])
|
||||
if cls._meta.interface:
|
||||
for f in cls._meta.fields])
|
||||
if cls._meta.is_interface:
|
||||
return GraphQLInterfaceType(
|
||||
cls._meta.type_name,
|
||||
description=cls._meta.description,
|
||||
|
@ -167,19 +200,34 @@ class BaseObjectType(object):
|
|||
return GraphQLObjectType(
|
||||
cls._meta.type_name,
|
||||
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,
|
||||
is_type_of=getattr(cls, 'is_type_of', None)
|
||||
)
|
||||
|
||||
|
||||
class Interface(six.with_metaclass(ObjectTypeMeta, BaseObjectType)):
|
||||
pass
|
||||
|
||||
|
||||
class ObjectType(six.with_metaclass(ObjectTypeMeta, BaseObjectType)):
|
||||
pass
|
||||
|
||||
|
||||
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)):
|
||||
pass
|
||||
class InputObjectType(ObjectType):
|
||||
@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,
|
||||
PageInfo,
|
||||
Edge,
|
||||
Connection
|
||||
Connection,
|
||||
ClientIDMutation
|
||||
)
|
||||
|
||||
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.utils import memoize
|
||||
|
||||
|
||||
class ConnectionField(Field):
|
||||
|
@ -51,24 +50,21 @@ class ConnectionField(Field):
|
|||
connection.set_connection_data(resolved)
|
||||
return connection
|
||||
|
||||
@memoize
|
||||
def get_connection_type(self, node):
|
||||
connection_type = self.connection_type or node.get_connection_type()
|
||||
edge_type = self.get_edge_type(node)
|
||||
return connection_type.for_node(node, edge_type=edge_type)
|
||||
|
||||
@memoize
|
||||
def get_edge_type(self, node):
|
||||
return self.edge_type or node.get_edge_type()
|
||||
|
||||
@memoize
|
||||
def internal_type(self, schema):
|
||||
from graphene.relay.utils import is_node
|
||||
node = self.get_object_type(schema)
|
||||
assert is_node(node), 'Only nodes have connections.'
|
||||
schema.register(node)
|
||||
|
||||
return self.get_connection_type(node).internal_type(schema)
|
||||
connection_type = self.get_connection_type(node)
|
||||
return schema.T(connection_type)
|
||||
|
||||
|
||||
class NodeField(Field):
|
||||
|
|
|
@ -2,7 +2,7 @@ from graphql_relay.node.node import (
|
|||
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.relay.fields import GlobalIDField
|
||||
from graphene.utils import memoize
|
||||
|
@ -84,3 +84,32 @@ class BaseNode(object):
|
|||
class Node(BaseNode, Interface):
|
||||
'''An object with an ID'''
|
||||
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(
|
||||
name='graphene',
|
||||
version='0.1.6.1',
|
||||
version='0.3.0',
|
||||
|
||||
description='Graphene: Python DSL for GraphQL',
|
||||
long_description=open('README.rst').read(),
|
||||
|
|
|
@ -46,12 +46,12 @@ schema = Schema()
|
|||
|
||||
|
||||
def test_django_interface():
|
||||
assert DjangoNode._meta.interface is True
|
||||
assert DjangoNode._meta.is_interface is True
|
||||
|
||||
|
||||
def test_pseudo_interface():
|
||||
object_type = Character.internal_type(schema)
|
||||
assert Character._meta.interface is True
|
||||
object_type = schema.T(Character)
|
||||
assert Character._meta.is_interface is True
|
||||
assert isinstance(object_type, GraphQLInterfaceType)
|
||||
assert Character._meta.model == Reporter
|
||||
assert_equal_lists(
|
||||
|
@ -81,9 +81,9 @@ def test_interface_resolve_type():
|
|||
|
||||
|
||||
def test_object_type():
|
||||
object_type = Human.internal_type(schema)
|
||||
object_type = schema.T(Human)
|
||||
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_equal_lists(
|
||||
object_type.get_fields().keys(),
|
||||
|
@ -95,9 +95,9 @@ def test_object_type():
|
|||
# 'reporter': fields_map['reporter'].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():
|
||||
assert Human._meta.interface is False
|
||||
assert Human._meta.is_interface is False
|
||||
assert DjangoNode in Human._meta.interfaces
|
||||
|
|
|
@ -24,19 +24,14 @@ from graphene.core.types import ObjectType
|
|||
class ObjectType(object):
|
||||
_meta = Options()
|
||||
|
||||
def resolve(self, *args, **kwargs):
|
||||
def resolve_customdoc(self, *args, **kwargs):
|
||||
'''Resolver documentation'''
|
||||
return None
|
||||
|
||||
def can_resolve(self, *args):
|
||||
return True
|
||||
|
||||
def __str__(self):
|
||||
return "ObjectType"
|
||||
|
||||
ot = ObjectType()
|
||||
|
||||
ObjectType._meta.contribute_to_class(ObjectType, '_meta')
|
||||
|
||||
ot = ObjectType
|
||||
|
||||
schema = Schema()
|
||||
|
||||
|
@ -89,7 +84,7 @@ def test_field_resolve():
|
|||
f = StringField(required=True, resolve=lambda *args: 'RESOLVED')
|
||||
f.contribute_to_class(ot, 'field_name')
|
||||
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():
|
||||
|
@ -172,3 +167,10 @@ def test_field_repr_contributed():
|
|||
f = StringField()
|
||||
f.contribute_to_class(ot, '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:
|
||||
interface = True
|
||||
is_interface = True
|
||||
type_name = 'Character'
|
||||
|
||||
|
||||
|
|
|
@ -46,11 +46,11 @@ class Human(Character):
|
|||
|
||||
schema = Schema()
|
||||
|
||||
Human_type = Human.internal_type(schema)
|
||||
Human_type = schema.T(Human)
|
||||
|
||||
|
||||
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():
|
||||
|
|
|
@ -125,9 +125,23 @@ def test_schema_register():
|
|||
class MyType(ObjectType):
|
||||
type = StringField(resolve=lambda *_: 'Dog')
|
||||
|
||||
schema.query = 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():
|
||||
schema = Schema(name='My own schema')
|
||||
|
||||
|
@ -138,4 +152,3 @@ def test_schema_introspect():
|
|||
|
||||
introspection = schema.introspect()
|
||||
assert '__schema' in introspection
|
||||
|
||||
|
|
|
@ -34,18 +34,34 @@ class Human(Character):
|
|||
class Meta:
|
||||
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()
|
||||
|
||||
|
||||
def test_interface():
|
||||
object_type = Character.internal_type(schema)
|
||||
assert Character._meta.interface is True
|
||||
object_type = schema.T(Character)
|
||||
assert Character._meta.is_interface is True
|
||||
assert isinstance(object_type, GraphQLInterfaceType)
|
||||
assert Character._meta.type_name == 'core_Character'
|
||||
assert object_type.description == 'Character description'
|
||||
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():
|
||||
|
@ -54,18 +70,39 @@ def test_interface_resolve_type():
|
|||
|
||||
|
||||
def test_object_type():
|
||||
object_type = Human.internal_type(schema)
|
||||
assert Human._meta.interface is False
|
||||
object_type = schema.T(Human)
|
||||
assert Human._meta.is_interface is False
|
||||
assert Human._meta.type_name == 'core_Human'
|
||||
assert isinstance(object_type, GraphQLObjectType)
|
||||
assert object_type.description == 'Human description'
|
||||
assert list(object_type.get_fields().keys()) == ['name', 'friends']
|
||||
# assert object_type.get_fields() == {'name': Human._meta.fields_map['name'].internal_field(
|
||||
# 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
|
||||
|
||||
|
||||
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():
|
||||
with raises(Exception) as excinfo:
|
||||
class Droid(Character):
|
||||
|
|
|
@ -1,8 +1,7 @@
|
|||
SECRET_KEY = 1
|
||||
|
||||
INSTALLED_APPS = [
|
||||
'graphene.contrib.django',
|
||||
'tests.starwars_django',
|
||||
'examples.starwars_django',
|
||||
'tests.contrib_django',
|
||||
]
|
||||
|
||||
|
|
|
@ -32,21 +32,3 @@ def test_node_should_have_same_connection_always():
|
|||
|
||||
def test_node_should_have_id_field():
|
||||
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 (
|
||||
GraphQLNonNull,
|
||||
GraphQLID
|
||||
|
@ -10,11 +9,6 @@ from graphene import relay
|
|||
schema = graphene.Schema()
|
||||
|
||||
|
||||
class MyType(object):
|
||||
name = 'my'
|
||||
arg = None
|
||||
|
||||
|
||||
class MyConnection(relay.Connection):
|
||||
my_custom_field = graphene.StringField(resolve=lambda instance, *_: 'Custom')
|
||||
|
||||
|
@ -24,7 +18,7 @@ class MyNode(relay.Node):
|
|||
|
||||
@classmethod
|
||||
def get_node(cls, id):
|
||||
return MyNode(MyType())
|
||||
return MyNode(name='mo')
|
||||
|
||||
|
||||
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))
|
||||
|
||||
def resolve_all_my_nodes(self, args, info):
|
||||
t = MyType()
|
||||
custom_arg = args.get('customArg')
|
||||
assert custom_arg == "1"
|
||||
return [MyNode(t)]
|
||||
return [MyNode(name='my')]
|
||||
|
||||
schema.query = Query
|
||||
|
||||
|
@ -61,7 +54,7 @@ def test_nodefield_query():
|
|||
'''
|
||||
expected = {
|
||||
'myNode': {
|
||||
'name': 'my'
|
||||
'name': 'mo'
|
||||
},
|
||||
'allMyNodes': {
|
||||
'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