Merge remote-tracking branch 'graphql-python/0.4.0' into use-graphql-django-view

This commit is contained in:
Jake 2015-11-13 17:08:12 -05:00
commit 4e08819094
58 changed files with 1490 additions and 801 deletions

View File

@ -28,9 +28,9 @@ Here is one example for get you started:
```python
class Query(graphene.ObjectType):
hello = graphene.StringField(description='A typical hello world')
ping = graphene.StringField(description='Ping someone',
to=graphene.Argument(graphene.String))
hello = graphene.String(description='A typical hello world')
ping = graphene.String(description='Ping someone',
to=graphene.String())
def resolve_hello(self, args, info):
return 'World'

View File

@ -35,9 +35,9 @@ Here is one example for get you started:
.. code:: python
class Query(graphene.ObjectType):
hello = graphene.StringField(description='A typical hello world')
ping = graphene.StringField(description='Ping someone',
to=graphene.Argument(graphene.String))
hello = graphene.String(description='A typical hello world')
ping = graphene.String(description='Ping someone',
to=graphene.String())
def resolve_hello(self, args, info):
return 'World'

View File

@ -1,4 +1,5 @@
#!/bin/bash
autoflake ./ -r --remove-unused-variables --remove-all-unused-imports --in-place
autoflake ./ -r --remove-unused-variables --remove-all-unused-imports --in-place
autopep8 ./ -r --in-place --experimental --aggressive --max-line-length 120
isort -rc .

View File

@ -12,10 +12,10 @@ Episode = graphene.Enum('Episode', dict(
class Character(graphene.Interface):
id = graphene.IDField()
name = graphene.StringField()
friends = graphene.ListField('self')
appears_in = graphene.ListField(Episode)
id = graphene.ID()
name = graphene.String()
friends = graphene.List('Character')
appears_in = graphene.List(Episode)
def resolve_friends(self, args, *_):
# The character friends is a list of strings
@ -23,11 +23,11 @@ class Character(graphene.Interface):
class Human(Character):
home_planet = graphene.StringField()
home_planet = graphene.String()
class Droid(Character):
primary_function = graphene.StringField()
primary_function = graphene.String()
class Query(graphene.ObjectType):
@ -35,10 +35,10 @@ class Query(graphene.ObjectType):
episode=graphene.Argument(Episode)
)
human = graphene.Field(Human,
id=graphene.Argument(graphene.String)
id=graphene.String()
)
droid = graphene.Field(Droid,
id=graphene.Argument(graphene.String)
id=graphene.String()
)
@resolve_only_args

View File

@ -12,6 +12,7 @@ schema = graphene.Schema(name='Starwars Django Relay Schema')
class Ship(DjangoNode):
class Meta:
model = ShipModel
@ -21,11 +22,13 @@ class Ship(DjangoNode):
class Character(DjangoObjectType):
class Meta:
model = CharacterModel
class Faction(DjangoNode):
class Meta:
model = FactionModel
@ -35,9 +38,10 @@ class Faction(DjangoNode):
class IntroduceShip(relay.ClientIDMutation):
class Input:
ship_name = graphene.StringField(required=True)
faction_id = graphene.StringField(required=True)
ship_name = graphene.String(required=True)
faction_id = graphene.String(required=True)
ship = graphene.Field(Ship)
faction = graphene.Field(Faction)
@ -48,7 +52,7 @@ class IntroduceShip(relay.ClientIDMutation):
faction_id = input.get('faction_id')
ship = create_ship(ship_name, faction_id)
faction = get_faction(faction_id)
return IntroduceShip(ship=ship, faction=faction)
return IntroduceShip(ship=Ship(ship), faction=Faction(faction))
class Query(graphene.ObjectType):

View File

@ -8,7 +8,7 @@ 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.')
name = graphene.String(description='The name of the ship.')
@classmethod
def get_node(cls, id):
@ -17,7 +17,7 @@ class Ship(relay.Node):
class Faction(relay.Node):
'''A faction in the Star Wars saga'''
name = graphene.StringField(description='The name of the faction.')
name = graphene.String(description='The name of the faction.')
ships = relay.ConnectionField(
Ship, description='The ships used by the faction.')
@ -34,8 +34,8 @@ class Faction(relay.Node):
class IntroduceShip(relay.ClientIDMutation):
class Input:
ship_name = graphene.StringField(required=True)
faction_id = graphene.StringField(required=True)
ship_name = graphene.String(required=True)
faction_id = graphene.String(required=True)
ship = graphene.Field(Ship)
faction = graphene.Field(Faction)

View File

@ -1,9 +1,5 @@
from graphql.core.type import (
GraphQLEnumType as Enum,
GraphQLArgument as Argument,
GraphQLString as String,
GraphQLInt as Int,
GraphQLID as ID
GraphQLEnumType as Enum
)
from graphene import signals
@ -14,12 +10,24 @@ from graphene.core.schema import (
from graphene.core.types import (
ObjectType,
InputObjectType,
Interface,
Mutation,
BaseType,
LazyType,
Argument,
Field,
InputField,
String,
Int,
Boolean,
ID,
Float,
List,
NonNull
)
from graphene.core.fields import (
Field,
StringField,
IntField,
BooleanField,
@ -33,7 +41,31 @@ 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']
__all__ = [
'Enum',
'Argument',
'String',
'Int',
'Boolean',
'Float',
'ID',
'List',
'NonNull',
'signals',
'Schema',
'BaseType',
'LazyType',
'ObjectType',
'InputObjectType',
'Interface',
'Mutation',
'Field',
'InputField',
'StringField',
'IntField',
'BooleanField',
'IDField',
'ListField',
'NonNullField',
'FloatField',
'resolve_only_args']

View File

@ -3,8 +3,7 @@ from singledispatch import singledispatch
from graphene.contrib.django.fields import (ConnectionOrListField,
DjangoModelField)
from graphene.core.fields import (BooleanField, FloatField, IDField, IntField,
StringField)
from graphene.core.types.scalars import ID, Boolean, Float, Int, String
try:
UUIDField = models.UUIDField
@ -17,7 +16,8 @@ except AttributeError:
@singledispatch
def convert_django_field(field):
raise Exception(
"Don't know how to convert the Django field %s (%s)" % (field, field.__class__))
"Don't know how to convert the Django field %s (%s)" %
(field, field.__class__))
@convert_django_field.register(models.DateField)
@ -28,12 +28,12 @@ def convert_django_field(field):
@convert_django_field.register(models.URLField)
@convert_django_field.register(UUIDField)
def convert_field_to_string(field):
return StringField(description=field.help_text)
return String(description=field.help_text)
@convert_django_field.register(models.AutoField)
def convert_field_to_id(field):
return IDField(description=field.help_text)
return ID(description=field.help_text)
@convert_django_field.register(models.PositiveIntegerField)
@ -42,23 +42,23 @@ def convert_field_to_id(field):
@convert_django_field.register(models.BigIntegerField)
@convert_django_field.register(models.IntegerField)
def convert_field_to_int(field):
return IntField(description=field.help_text)
return Int(description=field.help_text)
@convert_django_field.register(models.BooleanField)
def convert_field_to_boolean(field):
return BooleanField(description=field.help_text, required=True)
return Boolean(description=field.help_text, required=True)
@convert_django_field.register(models.NullBooleanField)
def convert_field_to_nullboolean(field):
return BooleanField(description=field.help_text)
return Boolean(description=field.help_text)
@convert_django_field.register(models.DecimalField)
@convert_django_field.register(models.FloatField)
def convert_field_to_float(field):
return FloatField(description=field.help_text)
return Float(description=field.help_text)
@convert_django_field.register(models.ManyToManyField)

View File

@ -1,6 +1,9 @@
from graphene import relay
from graphene.contrib.django.utils import get_type_for_model, lazy_map
from graphene.core.fields import Field, LazyField, ListField
from graphene.core.exceptions import SkipField
from graphene.core.fields import Field
from graphene.core.types.base import FieldType
from graphene.core.types.definitions import List
from graphene.relay.utils import is_node
@ -8,60 +11,54 @@ class DjangoConnectionField(relay.ConnectionField):
def wrap_resolved(self, value, instance, args, info):
schema = info.schema.graphene_schema
return lazy_map(value, self.get_object_type(schema))
return lazy_map(value, self.type.get_object_type(schema))
class LazyListField(ListField):
class LazyListField(Field):
def resolve(self, instance, args, info):
def get_type(self, schema):
return List(self.type)
def resolver(self, instance, args, info):
schema = info.schema.graphene_schema
resolved = super(LazyListField, self).resolve(instance, args, info)
resolved = super(LazyListField, self).resolver(instance, args, info)
return lazy_map(resolved, self.get_object_type(schema))
class ConnectionOrListField(LazyField):
class ConnectionOrListField(Field):
def get_field(self, schema):
model_field = self.field_type
def internal_type(self, schema):
model_field = self.type
field_object_type = model_field.get_object_type(schema)
if is_node(field_object_type):
field = DjangoConnectionField(model_field)
else:
field = LazyListField(model_field)
field.contribute_to_class(self.object_type, self.name)
return field
return field.internal_type(schema)
class DjangoModelField(Field):
class DjangoModelField(FieldType):
def __init__(self, model, *args, **kwargs):
super(DjangoModelField, self).__init__(None, *args, **kwargs)
self.model = model
def resolve(self, instance, args, info):
resolved = super(DjangoModelField, self).resolve(instance, args, info)
schema = info.schema.graphene_schema
_type = self.get_object_type(schema)
assert _type, ("Field %s cannot be retrieved as the "
"ObjectType is not registered by the schema" % (
self.attname
))
return _type(resolved)
super(DjangoModelField, self).__init__(*args, **kwargs)
def internal_type(self, schema):
_type = self.get_object_type(schema)
if not _type and self.object_type._meta.only_fields:
if not _type and self.parent._meta.only_fields:
raise Exception(
"Model %r is not accessible by the schema. "
"You can either register the type manually "
"using @schema.register. "
"Or disable the field %s in %s" % (
"Or disable the field in %s" % (
self.model,
self.attname,
self.object_type
self.parent,
)
)
return schema.T(_type) or Field.SKIP
if not _type:
raise SkipField()
return schema.T(_type)
def get_object_type(self, schema):
return get_type_for_model(schema, self.model)

View File

@ -32,6 +32,7 @@ class DjangoOptions(Options):
return
if not self.model:
raise Exception(
'Django ObjectType %s must have a model in the Meta class attr' % cls)
'Django ObjectType %s must have a model in the Meta class attr' %
cls)
elif not inspect.isclass(self.model) or not issubclass(self.model, models.Model):
raise Exception('Provided model in %s is not a Django model' % cls)

View File

@ -1,7 +1,6 @@
from django.db import models
from py.test import raises
from pytest import raises
import graphene
from graphene.contrib.django.converter import convert_django_field
@ -15,8 +14,9 @@ def assert_conversion(django_field, graphene_field, *args):
field = django_field(*args, help_text='Custom Help Text')
graphene_type = convert_django_field(field)
assert isinstance(graphene_type, graphene_field)
assert graphene_type.description == 'Custom Help Text'
return graphene_type
field = graphene_type.as_field()
assert field.description == 'Custom Help Text'
return field
def test_should_unknown_django_field_raise_exception():
@ -26,86 +26,86 @@ def test_should_unknown_django_field_raise_exception():
def test_should_date_convert_string():
assert_conversion(models.DateField, graphene.StringField)
assert_conversion(models.DateField, graphene.String)
def test_should_char_convert_string():
assert_conversion(models.CharField, graphene.StringField)
assert_conversion(models.CharField, graphene.String)
def test_should_text_convert_string():
assert_conversion(models.TextField, graphene.StringField)
assert_conversion(models.TextField, graphene.String)
def test_should_email_convert_string():
assert_conversion(models.EmailField, graphene.StringField)
assert_conversion(models.EmailField, graphene.String)
def test_should_slug_convert_string():
assert_conversion(models.SlugField, graphene.StringField)
assert_conversion(models.SlugField, graphene.String)
def test_should_url_convert_string():
assert_conversion(models.URLField, graphene.StringField)
assert_conversion(models.URLField, graphene.String)
def test_should_auto_convert_id():
assert_conversion(models.AutoField, graphene.IDField)
assert_conversion(models.AutoField, graphene.ID)
def test_should_positive_integer_convert_int():
assert_conversion(models.PositiveIntegerField, graphene.IntField)
assert_conversion(models.PositiveIntegerField, graphene.Int)
def test_should_positive_small_convert_int():
assert_conversion(models.PositiveSmallIntegerField, graphene.IntField)
assert_conversion(models.PositiveSmallIntegerField, graphene.Int)
def test_should_small_integer_convert_int():
assert_conversion(models.SmallIntegerField, graphene.IntField)
assert_conversion(models.SmallIntegerField, graphene.Int)
def test_should_big_integer_convert_int():
assert_conversion(models.BigIntegerField, graphene.IntField)
assert_conversion(models.BigIntegerField, graphene.Int)
def test_should_integer_convert_int():
assert_conversion(models.IntegerField, graphene.IntField)
assert_conversion(models.IntegerField, graphene.Int)
def test_should_boolean_convert_boolean():
field = assert_conversion(models.BooleanField, graphene.BooleanField)
field = assert_conversion(models.BooleanField, graphene.Boolean)
assert field.required is True
def test_should_nullboolean_convert_boolean():
field = assert_conversion(models.NullBooleanField, graphene.BooleanField)
field = assert_conversion(models.NullBooleanField, graphene.Boolean)
assert field.required is False
def test_should_float_convert_float():
assert_conversion(models.FloatField, graphene.FloatField)
assert_conversion(models.FloatField, graphene.Float)
def test_should_manytomany_convert_connectionorlist():
graphene_type = convert_django_field(Reporter._meta.local_many_to_many[0])
assert isinstance(graphene_type, ConnectionOrListField)
assert isinstance(graphene_type.field_type, DjangoModelField)
assert graphene_type.field_type.model == Reporter
assert isinstance(graphene_type.type, DjangoModelField)
assert graphene_type.type.model == Reporter
def test_should_manytoone_convert_connectionorlist():
graphene_type = convert_django_field(Reporter.articles.related)
assert isinstance(graphene_type, ConnectionOrListField)
assert isinstance(graphene_type.field_type, DjangoModelField)
assert graphene_type.field_type.model == Article
assert isinstance(graphene_type.type, DjangoModelField)
assert graphene_type.type.model == Article
def test_should_onetoone_convert_model():
field = assert_conversion(models.OneToOneField, DjangoModelField, Article)
assert field.model == Article
assert field.type.model == Article
def test_should_foreignkey_convert_model():
field = assert_conversion(models.ForeignKey, DjangoModelField, Article)
assert field.model == Article
assert field.type.model == Article

View File

@ -1,40 +1,21 @@
from py.test import raises
from pytest import raises
import graphene
from graphene import relay
from graphene.contrib.django import DjangoNode, DjangoObjectType
from tests.utils import assert_equal_lists
from .models import Article, Reporter
def test_should_raise_if_no_model():
with raises(Exception) as excinfo:
class Character1(DjangoObjectType):
pass
assert 'model in the Meta' in str(excinfo.value)
def test_should_raise_if_model_is_invalid():
with raises(Exception) as excinfo:
class Character2(DjangoObjectType):
class Meta:
model = 1
assert 'not a Django model' in str(excinfo.value)
def test_should_raise_if_model_is_invalid():
with raises(Exception) as excinfo:
class ReporterTypeError(DjangoObjectType):
def test_should_query_only_fields():
with raises(Exception):
class ReporterType(DjangoObjectType):
class Meta:
model = Reporter
only_fields = ('articles', )
schema = graphene.Schema(query=ReporterTypeError)
schema = graphene.Schema(query=ReporterType)
query = '''
query ReporterQuery {
articles
@ -44,24 +25,13 @@ def test_should_raise_if_model_is_invalid():
assert not result.errors
def test_should_map_fields_correctly():
class ReporterType2(DjangoObjectType):
class Meta:
model = Reporter
assert_equal_lists(
ReporterType2._meta.fields_map.keys(),
['articles', 'first_name', 'last_name', 'email', 'pets', 'id']
)
def test_should_map_fields():
def test_should_query_well():
class ReporterType(DjangoObjectType):
class Meta:
model = Reporter
class Query2(graphene.ObjectType):
class Query(graphene.ObjectType):
reporter = graphene.Field(ReporterType)
def resolve_reporter(self, *args, **kwargs):
@ -83,53 +53,42 @@ def test_should_map_fields():
'email': ''
}
}
Schema = graphene.Schema(query=Query2)
result = Schema.execute(query)
schema = graphene.Schema(query=Query)
result = schema.execute(query)
assert not result.errors
assert result.data == expected
def test_should_map_only_few_fields():
class Reporter2(DjangoObjectType):
class Meta:
model = Reporter
only_fields = ('id', 'email')
assert_equal_lists(
Reporter2._meta.fields_map.keys(),
['id', 'email']
)
def test_should_node():
class ReporterNodeType(DjangoNode):
class ReporterNode(DjangoNode):
class Meta:
model = Reporter
@classmethod
def get_node(cls, id):
return ReporterNodeType(Reporter(id=2, first_name='Cookie Monster'))
return ReporterNode(Reporter(id=2, first_name='Cookie Monster'))
def resolve_articles(self, *args, **kwargs):
return [ArticleNodeType(Article(headline='Hi!'))]
return [ArticleNode(Article(headline='Hi!'))]
class ArticleNodeType(DjangoNode):
class ArticleNode(DjangoNode):
class Meta:
model = Article
@classmethod
def get_node(cls, id):
return ArticleNodeType(Article(id=1, headline='Article node'))
return ArticleNode(Article(id=1, headline='Article node'))
class Query1(graphene.ObjectType):
class Query(graphene.ObjectType):
node = relay.NodeField()
reporter = graphene.Field(ReporterNodeType)
article = graphene.Field(ArticleNodeType)
reporter = graphene.Field(ReporterNode)
article = graphene.Field(ArticleNode)
def resolve_reporter(self, *args, **kwargs):
return ReporterNodeType(Reporter(id=1, first_name='ABA', last_name='X'))
return ReporterNode(
Reporter(id=1, first_name='ABA', last_name='X'))
query = '''
query ReporterQuery {
@ -146,12 +105,12 @@ def test_should_node():
lastName,
email
}
myArticle: node(id:"QXJ0aWNsZU5vZGVUeXBlOjE=") {
myArticle: node(id:"QXJ0aWNsZU5vZGU6MQ==") {
id
... on ReporterNodeType {
... on ReporterNode {
firstName
}
... on ArticleNodeType {
... on ArticleNode {
headline
}
}
@ -159,7 +118,7 @@ def test_should_node():
'''
expected = {
'reporter': {
'id': 'UmVwb3J0ZXJOb2RlVHlwZTox',
'id': 'UmVwb3J0ZXJOb2RlOjE=',
'firstName': 'ABA',
'lastName': 'X',
'email': '',
@ -172,11 +131,11 @@ def test_should_node():
},
},
'myArticle': {
'id': 'QXJ0aWNsZU5vZGVUeXBlOjE=',
'id': 'QXJ0aWNsZU5vZGU6MQ==',
'headline': 'Article node'
}
}
Schema = graphene.Schema(query=Query1)
result = Schema.execute(query)
schema = graphene.Schema(query=Query)
result = schema.execute(query)
assert not result.errors
assert result.data == expected

View File

@ -0,0 +1,45 @@
from py.test import raises
from graphene.contrib.django import DjangoObjectType
from tests.utils import assert_equal_lists
from .models import Reporter
def test_should_raise_if_no_model():
with raises(Exception) as excinfo:
class Character1(DjangoObjectType):
pass
assert 'model in the Meta' in str(excinfo.value)
def test_should_raise_if_model_is_invalid():
with raises(Exception) as excinfo:
class Character2(DjangoObjectType):
class Meta:
model = 1
assert 'not a Django model' in str(excinfo.value)
def test_should_map_fields_correctly():
class ReporterType2(DjangoObjectType):
class Meta:
model = Reporter
assert_equal_lists(
ReporterType2._meta.fields_map.keys(),
['articles', 'first_name', 'last_name', 'email', 'pets', 'id']
)
def test_should_map_only_few_fields():
class Reporter2(DjangoObjectType):
class Meta:
model = Reporter
only_fields = ('id', 'email')
assert_equal_lists(
Reporter2._meta.fields_map.keys(),
['id', 'email']
)

View File

@ -2,13 +2,16 @@
from graphene import Schema
from graphene.contrib.django.types import DjangoInterface, DjangoNode
from graphene.core.fields import IntField
from graphene.core.fields import Field
from graphene.core.types.scalars import Int
from graphene.relay.fields import GlobalIDField
from graphql.core.type import GraphQLInterfaceType, GraphQLObjectType
from tests.utils import assert_equal_lists
from .models import Article, Reporter
schema = Schema()
class Character(DjangoInterface):
'''Character description'''
@ -16,10 +19,11 @@ class Character(DjangoInterface):
model = Reporter
@schema.register
class Human(DjangoNode):
'''Human description'''
pub_date = IntField()
pub_date = Int()
def get_node(self, id):
pass
@ -27,14 +31,12 @@ class Human(DjangoNode):
class Meta:
model = Article
schema = Schema()
def test_django_interface():
assert DjangoNode._meta.is_interface is True
def test_pseudo_interface():
def test_pseudo_interface_registered():
object_type = schema.T(Character)
assert Character._meta.is_interface is True
assert isinstance(object_type, GraphQLInterfaceType)
@ -57,7 +59,8 @@ def test_node_idfield():
def test_node_replacedfield():
idfield = Human._meta.fields_map['pub_date']
assert isinstance(idfield, IntField)
assert isinstance(idfield, Field)
assert schema.T(idfield).type == schema.T(Int())
def test_interface_resolve_type():

View File

@ -18,7 +18,7 @@ class Character(DjangoNode):
class Human(DjangoNode):
raises = graphene.StringField()
raises = graphene.String()
class Meta:
model = Article

View File

@ -6,7 +6,7 @@ def format_response(response):
def test_client_get_no_query(settings, client):
settings.ROOT_URLCONF = 'tests.contrib_django.test_urls'
settings.ROOT_URLCONF = 'graphene.contrib.django.tests.test_urls'
response = client.get('/graphql')
json_response = format_response(response)
assert json_response == {'errors': [
@ -14,7 +14,7 @@ def test_client_get_no_query(settings, client):
def test_client_post_no_query(settings, client):
settings.ROOT_URLCONF = 'tests.contrib_django.test_urls'
settings.ROOT_URLCONF = 'graphene.contrib.django.tests.test_urls'
response = client.post('/graphql', {})
json_response = format_response(response)
assert json_response == {'errors': [
@ -22,7 +22,7 @@ def test_client_post_no_query(settings, client):
def test_client_post_malformed_json(settings, client):
settings.ROOT_URLCONF = 'tests.contrib_django.test_urls'
settings.ROOT_URLCONF = 'graphene.contrib.django.tests.test_urls'
response = client.post('/graphql', 'MALFORMED', 'application/json')
json_response = format_response(response)
assert json_response == {'errors': [
@ -30,7 +30,7 @@ def test_client_post_malformed_json(settings, client):
def test_client_post_empty_query_json(settings, client):
settings.ROOT_URLCONF = 'tests.contrib_django.test_urls'
settings.ROOT_URLCONF = 'graphene.contrib.django.tests.test_urls'
response = client.post(
'/graphql', json.dumps({'query': ''}), 'application/json')
json_response = format_response(response)
@ -39,7 +39,7 @@ def test_client_post_empty_query_json(settings, client):
def test_client_post_empty_query_graphql(settings, client):
settings.ROOT_URLCONF = 'tests.contrib_django.test_urls'
settings.ROOT_URLCONF = 'graphene.contrib.django.tests.test_urls'
response = client.post(
'/graphql', '', 'application/graphql')
json_response = format_response(response)
@ -48,7 +48,7 @@ def test_client_post_empty_query_graphql(settings, client):
def test_client_post_bad_query_json(settings, client):
settings.ROOT_URLCONF = 'tests.contrib_django.test_urls'
settings.ROOT_URLCONF = 'graphene.contrib.django.tests.test_urls'
response = client.post(
'/graphql', json.dumps({'query': '{ MALFORMED'}), 'application/json')
json_response = format_response(response)
@ -58,7 +58,7 @@ def test_client_post_bad_query_json(settings, client):
def test_client_post_bad_query_graphql(settings, client):
settings.ROOT_URLCONF = 'tests.contrib_django.test_urls'
settings.ROOT_URLCONF = 'graphene.contrib.django.tests.test_urls'
response = client.post(
'/graphql', '{ MALFORMED', 'application/graphql')
json_response = format_response(response)
@ -68,7 +68,7 @@ def test_client_post_bad_query_graphql(settings, client):
def test_client_get_good_query(settings, client):
settings.ROOT_URLCONF = 'tests.contrib_django.test_urls'
settings.ROOT_URLCONF = 'graphene.contrib.django.tests.test_urls'
response = client.get('/graphql', {'query': '{ headline }'})
json_response = format_response(response)
expected_json = {
@ -80,7 +80,7 @@ def test_client_get_good_query(settings, client):
def test_client_get_good_query_with_raise(settings, client):
settings.ROOT_URLCONF = 'tests.contrib_django.test_urls'
settings.ROOT_URLCONF = 'graphene.contrib.django.tests.test_urls'
response = client.get('/graphql', {'query': '{ raises }'})
json_response = format_response(response)
assert json_response['errors'][0][
@ -89,7 +89,7 @@ def test_client_get_good_query_with_raise(settings, client):
def test_client_post_good_query_json(settings, client):
settings.ROOT_URLCONF = 'tests.contrib_django.test_urls'
settings.ROOT_URLCONF = 'graphene.contrib.django.tests.test_urls'
response = client.post(
'/graphql', json.dumps({'query': '{ headline }'}), 'application/json')
json_response = format_response(response)
@ -102,7 +102,7 @@ def test_client_post_good_query_json(settings, client):
def test_client_post_good_query_graphql(settings, client):
settings.ROOT_URLCONF = 'tests.contrib_django.test_urls'
settings.ROOT_URLCONF = 'graphene.contrib.django.tests.test_urls'
response = client.post(
'/graphql', '{ headline }', 'application/graphql')
json_response = format_response(response)
@ -115,7 +115,7 @@ def test_client_post_good_query_graphql(settings, client):
# def test_client_get_bad_query(settings, client):
# settings.ROOT_URLCONF = 'tests.contrib_django.test_urls'
# settings.ROOT_URLCONF = 'graphene.contrib.django.tests.test_urls'
# response = client.get('/graphql')
# json_response = format_response(response)
# assert json_response == {'errors': [{'message': 'Must provide query string.'}]}

View File

@ -46,11 +46,13 @@ class InstanceObjectType(BaseObjectType):
return getattr(self.instance, attr)
class DjangoObjectType(six.with_metaclass(DjangoObjectTypeMeta, InstanceObjectType)):
class DjangoObjectType(six.with_metaclass(
DjangoObjectTypeMeta, InstanceObjectType)):
pass
class DjangoInterface(six.with_metaclass(DjangoObjectTypeMeta, InstanceObjectType)):
class DjangoInterface(six.with_metaclass(
DjangoObjectTypeMeta, InstanceObjectType)):
pass

View File

@ -0,0 +1,2 @@
class SkipField(Exception):
pass

View File

@ -1,258 +1,49 @@
import inspect
from functools import total_ordering, wraps
import warnings
import six
from graphene.core.scalars import GraphQLSkipField
from graphene.core.types import BaseObjectType, InputObjectType
from graphene.utils import ProxySnakeDict, enum_to_graphql_enum, to_camel_case
from graphql.core.type import (GraphQLArgument, GraphQLBoolean, GraphQLField,
GraphQLFloat, GraphQLID,
GraphQLInputObjectField, GraphQLInt,
GraphQLList, GraphQLNonNull, GraphQLString)
try:
from enum import Enum
except ImportError:
class Enum(object):
pass
from .types.base import FieldType
from .types.definitions import List, NonNull
from .types.field import Field
from .types.scalars import ID, Boolean, Float, Int, String
class Empty(object):
class DeprecatedField(FieldType):
def __init__(self, *args, **kwargs):
cls = self.__class__
warnings.warn("Using {} is not longer supported".format(
cls.__name__), FutureWarning)
if 'resolve' in kwargs:
kwargs['resolver'] = kwargs.pop('resolve')
return super(DeprecatedField, self).__init__(*args, **kwargs)
class StringField(DeprecatedField, String):
pass
@total_ordering
class Field(object):
SKIP = GraphQLSkipField
creation_counter = 0
required = False
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
self.args = args or {}
self.extra_args = extra_args
self._type = None
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)
self.attname = name
self.object_type = cls
if isinstance(self.field_type, Field) and not self.field_type.object_type:
self.field_type.contribute_to_class(cls, name, False)
if add:
cls._meta.add_field(self)
def resolve(self, instance, args, info):
schema = info and getattr(info.schema, 'graphene_schema', None)
resolve_fn = self.get_resolve_fn(schema)
if resolve_fn:
return resolve_fn(instance, ProxySnakeDict(args), info)
else:
return getattr(instance, self.attname, self.get_default())
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.attname
if hasattr(self.object_type, custom_resolve_fn_name):
resolve_fn = getattr(self.object_type, custom_resolve_fn_name)
@wraps(resolve_fn)
def custom_resolve_fn(instance, args, info):
return resolve_fn(instance, args, info)
return custom_resolve_fn
def get_object_type(self, schema):
field_type = self.field_type
if inspect.isfunction(field_type):
field_type = field_type(self)
_is_class = inspect.isclass(field_type)
if isinstance(field_type, Field):
return field_type.get_object_type(schema)
if _is_class and issubclass(field_type, BaseObjectType):
return field_type
elif isinstance(field_type, six.string_types):
if field_type == 'self':
return self.object_type
else:
return schema.get_type(field_type)
def type_wrapper(self, field_type):
if self.required:
field_type = GraphQLNonNull(field_type)
return field_type
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 = schema.T(object_type)
field_type = self.type_wrapper(field_type)
return field_type
def internal_field(self, schema):
if not self.object_type:
raise Exception(
'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():
if isinstance(arg_value, GraphQLArgument):
self.args[arg_name] = arg_value
del extra_args[arg_name]
if extra_args != {}:
raise TypeError("Field %s.%s initiated with invalid args: %s" % (
self.object_type,
self.attname,
','.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)
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=description,
args=args,
resolver=resolver,
)
def __str__(self):
""" Return "object_type.name". """
return '%s.%s' % (self.object_type.__name__, self.attname)
def __repr__(self):
"""
Displays the module, class and name of the field.
"""
path = '%s.%s' % (self.__class__.__module__, self.__class__.__name__)
name = getattr(self, 'attname', None)
if name is not None:
return '<%s: %s>' % (path, name)
return '<%s>' % path
def __eq__(self, other):
# Needed for @total_ordering
if isinstance(other, Field):
return self.creation_counter == other.creation_counter and \
self.object_type == other.object_type
return NotImplemented
def __lt__(self, other):
# This is needed because bisect does not take a comparison function.
if isinstance(other, Field):
return self.creation_counter < other.creation_counter
return NotImplemented
def __hash__(self):
return hash((self.creation_counter, self.object_type))
def __copy__(self):
# We need to avoid hitting __reduce__, so define this
# slightly weird copy construct.
obj = Empty()
obj.__class__ = self.__class__
obj.__dict__ = self.__dict__.copy()
if self.field_type == 'self':
obj.field_type = self.object_type
return obj
class IntField(DeprecatedField, Int):
pass
class LazyField(Field):
def inner_field(self, schema):
return self.get_field(schema)
def internal_type(self, schema):
return self.inner_field(schema).internal_type(schema)
def internal_field(self, schema):
return self.inner_field(schema).internal_field(schema)
class BooleanField(DeprecatedField, Boolean):
pass
class TypeField(Field):
def __init__(self, *args, **kwargs):
super(TypeField, self).__init__(self.field_type, *args, **kwargs)
class IDField(DeprecatedField, ID):
pass
class StringField(TypeField):
field_type = GraphQLString
class FloatField(DeprecatedField, Float):
pass
class IntField(TypeField):
field_type = GraphQLInt
class ListField(DeprecatedField, List):
pass
class BooleanField(TypeField):
field_type = GraphQLBoolean
class NonNullField(DeprecatedField, NonNull):
pass
class IDField(TypeField):
field_type = GraphQLID
class FloatField(TypeField):
field_type = GraphQLFloat
class ListField(Field):
def type_wrapper(self, field_type):
return GraphQLList(field_type)
class NonNullField(Field):
def type_wrapper(self, field_type):
return GraphQLNonNull(field_type)
__all__ = ['Field', 'StringField', 'IntField', 'BooleanField',
'IDField', 'FloatField', 'ListField', 'NonNullField']

View File

@ -52,7 +52,9 @@ class Options(object):
# Any leftover attributes must be invalid.
if meta_attrs != {}:
raise TypeError(
"'class Meta' got invalid attribute(s): %s" % ','.join(meta_attrs.keys()))
"'class Meta' got invalid attribute(s): %s" %
','.join(
meta_attrs.keys()))
else:
self.proxy = False

View File

@ -1,6 +1,9 @@
import inspect
from collections import OrderedDict
from graphene import signals
from graphene.core.types.base import BaseType
from graphene.core.types.objecttype import BaseObjectType
from graphql.core.execution.executor import Executor
from graphql.core.execution.middlewares.sync import \
SynchronousExecutionMiddleware
@ -16,10 +19,10 @@ class GraphQLSchema(_GraphQLSchema):
class Schema(object):
_query = None
_executor = None
def __init__(self, query=None, mutation=None, name='Schema', executor=None):
def __init__(self, query=None, mutation=None,
name='Schema', executor=None):
self._types_names = {}
self._types = {}
self.mutation = mutation
@ -34,32 +37,24 @@ class Schema(object):
def T(self, object_type):
if not object_type:
return
if object_type not in self._types:
internal_type = object_type.internal_type(self)
self._types[object_type] = internal_type
self._types_names[internal_type.name] = object_type
return self._types[object_type]
@property
def query(self):
return self._query
@query.setter
def query(self, query):
self._query = query
@property
def mutation(self):
return self._mutation
@mutation.setter
def mutation(self, mutation):
self._mutation = mutation
if inspect.isclass(object_type) and issubclass(
object_type, BaseType) or isinstance(
object_type, BaseType):
if object_type not in self._types:
internal_type = object_type.internal_type(self)
self._types[object_type] = internal_type
is_objecttype = inspect.isclass(
object_type) and issubclass(object_type, BaseObjectType)
if is_objecttype:
self.register(object_type)
return self._types[object_type]
else:
return object_type
@property
def executor(self):
if not self._executor:
self.executor = Executor(
self._executor = Executor(
[SynchronousExecutionMiddleware()], map_type=OrderedDict)
return self._executor
@ -69,25 +64,45 @@ class Schema(object):
@property
def schema(self):
if not self._query:
if not self.query:
raise Exception('You have to define a base query type')
return GraphQLSchema(self, query=self.T(self._query), mutation=self.T(self._mutation))
return GraphQLSchema(
self, query=self.T(self.query),
mutation=self.T(self.mutation))
def register(self, object_type):
type_name = object_type._meta.type_name
registered_object_type = self._types_names.get(type_name, None)
if registered_object_type:
assert registered_object_type == object_type, 'Type {} already registered with other object type'.format(
type_name)
self._types_names[object_type._meta.type_name] = object_type
return object_type
def objecttype(self, type):
name = getattr(type, 'name', None)
if name:
objecttype = self._types_names.get(name, None)
if objecttype and inspect.isclass(
objecttype) and issubclass(objecttype, BaseObjectType):
return objecttype
def setup(self):
assert self.query, 'The base query type is not set'
self.T(self.query)
def get_type(self, type_name):
self.schema._build_type_map()
self.setup()
if type_name not in self._types_names:
raise Exception('Type %s not found in %r' % (type_name, self))
raise KeyError('Type %r not found in %r' % (type_name, self))
return self._types_names[type_name]
@property
def types(self):
return self._types_names
def execute(self, request='', root=None, vars=None, operation_name=None, **kwargs):
def execute(self, request='', root=None, vars=None,
operation_name=None, **kwargs):
root = root or object()
return self.executor.execute(
self.schema,

View File

@ -1,4 +1,3 @@
import graphene
from graphene.core.schema import Schema
@ -6,15 +5,15 @@ my_id = 0
class Query(graphene.ObjectType):
base = graphene.StringField()
base = graphene.String()
class ChangeNumber(graphene.Mutation):
'''Result mutation'''
class Input:
to = graphene.IntField()
to = graphene.Int()
result = graphene.StringField()
result = graphene.String()
@classmethod
def mutate(cls, instance, args, info):
@ -31,9 +30,7 @@ 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']
assert list(schema.T(ChangeNumber.arguments).keys()) == ['to']
def test_execute_mutations():

View File

@ -0,0 +1,178 @@
from py.test import raises
from graphene.core.fields import (BooleanField, Field, FloatField, IDField,
IntField, NonNullField, StringField)
from graphene.core.schema import Schema
from graphene.core.types import ObjectType
from graphql.core.type import (GraphQLBoolean, GraphQLField, GraphQLFloat,
GraphQLID, GraphQLInt, GraphQLNonNull,
GraphQLString)
class MyOt(ObjectType):
def resolve_customdoc(self, *args, **kwargs):
'''Resolver documentation'''
return None
def __str__(self):
return "ObjectType"
schema = Schema()
def test_field_no_contributed_raises_error():
f = Field(GraphQLString)
with raises(Exception):
schema.T(f)
def test_field_type():
f = Field(GraphQLString)
f.contribute_to_class(MyOt, 'field_name')
assert isinstance(schema.T(f), GraphQLField)
assert schema.T(f).type == GraphQLString
def test_field_name_automatic_camelcase():
f = Field(GraphQLString)
f.contribute_to_class(MyOt, 'field_name')
assert f.name == 'fieldName'
def test_field_name_use_name_if_exists():
f = Field(GraphQLString, name='my_custom_name')
f.contribute_to_class(MyOt, 'field_name')
assert f.name == 'my_custom_name'
def test_stringfield_type():
f = StringField()
f.contribute_to_class(MyOt, 'field_name')
assert schema.T(f) == GraphQLString
def test_idfield_type():
f = IDField()
f.contribute_to_class(MyOt, 'field_name')
assert schema.T(f) == GraphQLID
def test_booleanfield_type():
f = BooleanField()
f.contribute_to_class(MyOt, 'field_name')
assert schema.T(f) == GraphQLBoolean
def test_intfield_type():
f = IntField()
f.contribute_to_class(MyOt, 'field_name')
assert schema.T(f) == GraphQLInt
def test_floatfield_type():
f = FloatField()
f.contribute_to_class(MyOt, 'field_name')
assert schema.T(f) == GraphQLFloat
def test_nonnullfield_type():
f = NonNullField(StringField())
f.contribute_to_class(MyOt, 'field_name')
assert isinstance(schema.T(f), GraphQLNonNull)
def test_stringfield_type_required():
f = StringField(required=True).as_field()
f.contribute_to_class(MyOt, 'field_name')
assert isinstance(schema.T(f), GraphQLField)
assert isinstance(schema.T(f).type, GraphQLNonNull)
def test_field_resolve():
f = StringField(required=True, resolve=lambda *args: 'RESOLVED').as_field()
f.contribute_to_class(MyOt, 'field_name')
field_type = schema.T(f)
assert 'RESOLVED' == field_type.resolver(MyOt, None, None)
def test_field_resolve_type_custom():
class MyCustomType(ObjectType):
pass
f = Field('MyCustomType')
class OtherType(ObjectType):
field_name = f
s = Schema()
s.query = OtherType
s.register(MyCustomType)
assert s.T(f).type == s.T(MyCustomType)
def test_field_orders():
f1 = Field(None)
f2 = Field(None)
assert f1 < f2
def test_field_orders_wrong_type():
field = Field(None)
try:
assert not field < 1
except TypeError:
# Fix exception raising in Python3+
pass
def test_field_eq():
f1 = Field(None)
f2 = Field(None)
assert f1 != f2
def test_field_eq_wrong_type():
field = Field(None)
assert field != 1
def test_field_hash():
f1 = Field(None)
f2 = Field(None)
assert hash(f1) != hash(f2)
def test_field_none_type_raises_error():
s = Schema()
f = Field(None)
f.contribute_to_class(MyOt, 'field_name')
with raises(Exception) as excinfo:
s.T(f)
assert str(
excinfo.value) == "Internal type for field MyOt.field_name is None"
def test_field_str():
f = StringField().as_field()
f.contribute_to_class(MyOt, 'field_name')
assert str(f) == "MyOt.field_name"
def test_field_repr():
f = StringField().as_field()
assert repr(f) == "<graphene.core.types.field.Field>"
def test_field_repr_contributed():
f = StringField().as_field()
f.contribute_to_class(MyOt, 'field_name')
assert repr(f) == "<graphene.core.types.field.Field: field_name>"
def test_field_resolve_objecttype_cos():
f = StringField().as_field()
f.contribute_to_class(MyOt, 'customdoc')
field = schema.T(f)
assert field.description == 'Resolver documentation'

View File

@ -1,8 +1,6 @@
from py.test import raises
from pytest import raises
from graphene.core.fields import StringField
from graphene.core.fields import Field
from graphene.core.options import Options
@ -22,7 +20,7 @@ def test_field_added_in_meta():
pass
opt.contribute_to_class(ObjectType, '_meta')
f = StringField()
f = Field(None)
f.attname = 'string_field'
opt.add_field(f)
assert f in opt.fields

View File

@ -1,23 +1,22 @@
from graphene.core.fields import Field, ListField, StringField
from graphene.core.fields import Field
from graphene.core.schema import Schema
from graphene.core.types import Interface, ObjectType
from graphene.core.types import Interface, List, ObjectType, String
from graphql.core import graphql
from graphql.core.type import (GraphQLInterfaceType, GraphQLObjectType,
GraphQLSchema)
from graphql.core.type import GraphQLSchema
class Character(Interface):
name = StringField()
name = String()
class Pet(ObjectType):
type = StringField(resolve=lambda *_: 'Dog')
type = String(resolver=lambda *_: 'Dog')
class Human(Character):
friends = ListField(Character)
friends = List(Character)
pet = Field(Pet)
def resolve_name(self, *args):
@ -28,8 +27,6 @@ class Human(Character):
def resolve_pet(self, *args):
return Pet(object())
# def resolve_friends(self, *args, **kwargs):
# return 'HEY YOU!'
schema = Schema()
@ -38,8 +35,8 @@ Human_type = schema.T(Human)
def test_type():
assert Human._meta.fields_map['name'].resolve(
Human(object()), None, None) == 'Peter'
assert Human._meta.fields_map['name'].resolver(
Human(object()), {}, None) == 'Peter'
def test_query():

View File

@ -1,27 +1,24 @@
from py.test import raises
from pytest import raises
from graphene import Interface, ObjectType, Schema
from graphene.core.fields import Field, ListField, StringField
from graphene import Interface, List, ObjectType, Schema, String
from graphene.core.fields import Field
from graphene.core.types.base import LazyType
from graphql.core import graphql
from graphql.core.type import (GraphQLInterfaceType, GraphQLObjectType,
GraphQLSchema)
from tests.utils import assert_equal_lists
schema = Schema(name='My own schema')
class Character(Interface):
name = StringField()
name = String()
class Pet(ObjectType):
type = StringField(resolve=lambda *_: 'Dog')
type = String(resolver=lambda *_: 'Dog')
class Human(Character):
friends = ListField(Character)
friends = List(Character)
pet = Field(Pet)
def resolve_name(self, *args):
@ -95,9 +92,9 @@ def test_query_schema_execute():
def test_schema_get_type_map():
assert_equal_lists(
schema.schema.get_type_map().keys(),
['__Field', 'String', 'Pet', 'Character', '__InputValue', '__Directive',
'__TypeKind', '__Schema', '__Type', 'Human', '__EnumValue', 'Boolean']
)
['__Field', 'String', 'Pet', 'Character', '__InputValue',
'__Directive', '__TypeKind', '__Schema', '__Type', 'Human',
'__EnumValue', 'Boolean'])
def test_schema_no_query():
@ -112,19 +109,19 @@ def test_schema_register():
@schema.register
class MyType(ObjectType):
type = StringField(resolve=lambda *_: 'Dog')
type = String(resolver=lambda *_: 'Dog')
schema.query = MyType
assert schema.get_type('MyType') == MyType
def test_schema_register():
def test_schema_register_no_query_type():
schema = Schema(name='My own schema')
@schema.register
class MyType(ObjectType):
type = StringField(resolve=lambda *_: 'Dog')
type = String(resolver=lambda *_: 'Dog')
with raises(Exception) as excinfo:
schema.get_type('MyType')
@ -135,9 +132,23 @@ def test_schema_introspect():
schema = Schema(name='My own schema')
class MyType(ObjectType):
type = StringField(resolve=lambda *_: 'Dog')
type = String(resolver=lambda *_: 'Dog')
schema.query = MyType
introspection = schema.introspect()
assert '__schema' in introspection
def test_lazytype():
schema = Schema(name='My own schema')
t = LazyType('MyType')
@schema.register
class MyType(ObjectType):
type = String(resolver=lambda *_: 'Dog')
schema.query = MyType
assert schema.T(t) == schema.T(MyType)

View File

@ -0,0 +1,30 @@
from .base import BaseType, LazyType, OrderedType
from .argument import Argument, ArgumentsGroup, to_arguments
from .definitions import List, NonNull
from .objecttype import ObjectTypeMeta, BaseObjectType, Interface, ObjectType, Mutation, InputObjectType
from .scalars import String, ID, Boolean, Int, Float, Scalar
from .field import Field, InputField
__all__ = [
'BaseType',
'LazyType',
'OrderedType',
'Argument',
'ArgumentsGroup',
'to_arguments',
'List',
'NonNull',
'Field',
'InputField',
'Interface',
'BaseObjectType',
'ObjectTypeMeta',
'ObjectType',
'Mutation',
'InputObjectType',
'String',
'ID',
'Boolean',
'Int',
'Float',
'Scalar']

View File

@ -0,0 +1,71 @@
from collections import OrderedDict
from itertools import chain
from graphql.core.type import GraphQLArgument
from ...utils import to_camel_case
from .base import ArgumentType, BaseType, OrderedType
class Argument(OrderedType):
def __init__(self, type, description=None, default=None,
name=None, _creation_counter=None):
super(Argument, self).__init__(_creation_counter=_creation_counter)
self.name = name
self.type = type
self.description = description
self.default = default
def internal_type(self, schema):
return GraphQLArgument(
schema.T(self.type),
self.default, self.description)
def __repr__(self):
return self.name
class ArgumentsGroup(BaseType):
def __init__(self, *args, **kwargs):
arguments = to_arguments(*args, **kwargs)
self.arguments = OrderedDict([(arg.name, arg) for arg in arguments])
def internal_type(self, schema):
return OrderedDict([(arg.name, schema.T(arg))
for arg in self.arguments.values()])
def __len__(self):
return len(self.arguments)
def __iter__(self):
return iter(self.arguments)
def __contains__(self, *args):
return self.arguments.__contains__(*args)
def __getitem__(self, *args):
return self.arguments.__getitem__(*args)
def to_arguments(*args, **kwargs):
arguments = {}
iter_arguments = chain(kwargs.items(), [(None, a) for a in args])
for name, arg in iter_arguments:
if isinstance(arg, Argument):
argument = arg
elif isinstance(arg, ArgumentType):
argument = arg.as_argument()
else:
raise ValueError('Unknown argument %s=%r' % (name, arg))
if name:
argument.name = to_camel_case(name)
assert argument.name, 'Argument in field must have a name'
assert argument.name not in arguments, 'Found more than one Argument with same name {}'.format(
argument.name)
arguments[argument.name] = argument
return sorted(arguments.values())

127
graphene/core/types/base.py Normal file
View File

@ -0,0 +1,127 @@
from functools import total_ordering
import six
class BaseType(object):
@classmethod
def internal_type(cls, schema):
return getattr(cls, 'T', None)
class MountType(BaseType):
parent = None
def mount(self, cls):
self.parent = cls
class LazyType(MountType):
def __init__(self, type):
self.type = type
@property
def is_self(self):
return self.type == 'self'
def internal_type(self, schema):
type = None
if callable(self.type):
type = self.type(self.parent)
elif isinstance(self.type, six.string_types):
if self.is_self:
type = self.parent
else:
type = schema.get_type(self.type)
assert type, 'Type in %s %r cannot be none' % (self.type, self.parent)
return schema.T(type)
@total_ordering
class OrderedType(MountType):
creation_counter = 0
def __init__(self, _creation_counter=None):
self.creation_counter = _creation_counter or self.gen_counter()
@staticmethod
def gen_counter():
counter = OrderedType.creation_counter
OrderedType.creation_counter += 1
return counter
def __eq__(self, other):
# Needed for @total_ordering
if isinstance(self, type(other)):
return self.creation_counter == other.creation_counter
return NotImplemented
def __lt__(self, other):
# This is needed because bisect does not take a comparison function.
if isinstance(other, OrderedType):
return self.creation_counter < other.creation_counter
return NotImplemented
def __gt__(self, other):
# This is needed because bisect does not take a comparison function.
if isinstance(other, OrderedType):
return self.creation_counter > other.creation_counter
return NotImplemented
def __hash__(self):
return hash((self.creation_counter))
class MirroredType(OrderedType):
def __init__(self, *args, **kwargs):
_creation_counter = kwargs.pop('_creation_counter', None)
super(MirroredType, self).__init__(_creation_counter=_creation_counter)
self.args = args
self.kwargs = kwargs
@property
def List(self): # noqa
from .definitions import List
return List(self, *self.args, **self.kwargs)
@property
def NonNull(self): # noqa
from .definitions import NonNull
return NonNull(self, *self.args, **self.kwargs)
class ArgumentType(MirroredType):
def as_argument(self):
from .argument import Argument
return Argument(
self, _creation_counter=self.creation_counter, *self.args, **self.kwargs)
class FieldType(MirroredType):
def contribute_to_class(self, cls, name):
from ..types import BaseObjectType, InputObjectType
if issubclass(cls, InputObjectType):
inputfield = self.as_inputfield()
return inputfield.contribute_to_class(cls, name)
elif issubclass(cls, BaseObjectType):
field = self.as_field()
return field.contribute_to_class(cls, name)
def as_field(self):
from .field import Field
return Field(self, _creation_counter=self.creation_counter,
*self.args, **self.kwargs)
def as_inputfield(self):
from .field import InputField
return InputField(
self, _creation_counter=self.creation_counter, *self.args, **self.kwargs)
class MountedType(FieldType, ArgumentType):
pass

View File

@ -0,0 +1,30 @@
import six
from graphql.core.type import GraphQLList, GraphQLNonNull
from .base import LazyType, MountedType, MountType
class OfType(MountedType):
def __init__(self, of_type, *args, **kwargs):
if isinstance(of_type, six.string_types):
of_type = LazyType(of_type)
self.of_type = of_type
super(OfType, self).__init__(*args, **kwargs)
def internal_type(self, schema):
return self.T(schema.T(self.of_type))
def mount(self, cls):
self.parent = cls
if isinstance(self.of_type, MountType):
self.of_type.mount(cls)
class List(OfType):
T = GraphQLList
class NonNull(OfType):
T = GraphQLNonNull

View File

@ -0,0 +1,151 @@
from collections import OrderedDict
from functools import wraps
import six
from graphql.core.type import GraphQLField, GraphQLInputObjectField
from ...utils import ProxySnakeDict, to_camel_case
from ..types import BaseObjectType, InputObjectType
from .argument import ArgumentsGroup
from .base import LazyType, MountType, OrderedType
from .definitions import NonNull
def make_args_snake_case(resolver):
@wraps(resolver)
def wrapped_resolver(instance, args, info):
return resolver(instance, ProxySnakeDict(args), info)
return wrapped_resolver
class Empty(object):
pass
class Field(OrderedType):
def __init__(
self, type, description=None, args=None, name=None, resolver=None,
required=False, default=None, *args_list, **kwargs):
_creation_counter = kwargs.pop('_creation_counter', None)
super(Field, self).__init__(_creation_counter=_creation_counter)
self.name = name
if isinstance(type, six.string_types):
type = LazyType(type)
self.required = required
self.type = type
self.description = description
args = OrderedDict(args or {}, **kwargs)
self.arguments = ArgumentsGroup(*args_list, **args)
self.object_type = None
self.resolver_fn = resolver
self.default = default
def contribute_to_class(self, cls, attname):
assert issubclass(
cls, BaseObjectType), 'Field {} cannot be mounted in {}'.format(
self, cls)
if not self.name:
self.name = to_camel_case(attname)
self.attname = attname
self.object_type = cls
self.mount(cls)
if isinstance(self.type, MountType):
self.type.mount(cls)
cls._meta.add_field(self)
@property
def resolver(self):
return self.resolver_fn or self.get_resolver_fn()
@resolver.setter
def resolver(self, value):
self.resolver_fn = value
def get_resolver_fn(self):
resolve_fn_name = 'resolve_%s' % self.attname
if hasattr(self.object_type, resolve_fn_name):
return getattr(self.object_type, resolve_fn_name)
def default_getter(instance, args, info):
return getattr(instance, self.attname, self.default)
return default_getter
def get_type(self, schema):
if self.required:
return NonNull(self.type)
return self.type
def internal_type(self, schema):
resolver = self.resolver
description = self.description
arguments = self.arguments
if not description and resolver:
description = resolver.__doc__
type = schema.T(self.get_type(schema))
type_objecttype = schema.objecttype(type)
if type_objecttype and type_objecttype._meta.is_mutation:
assert len(arguments) == 0
arguments = type_objecttype.get_arguments()
resolver = getattr(type_objecttype, 'mutate')
resolver = make_args_snake_case(resolver)
assert type, 'Internal type for field %s is None' % str(self)
return GraphQLField(type, args=schema.T(arguments), resolver=resolver,
description=description,)
def __repr__(self):
"""
Displays the module, class and name of the field.
"""
path = '%s.%s' % (self.__class__.__module__, self.__class__.__name__)
name = getattr(self, 'attname', None)
if name is not None:
return '<%s: %s>' % (path, name)
return '<%s>' % path
def __str__(self):
""" Return "object_type.field_name". """
return '%s.%s' % (self.object_type.__name__, self.attname)
def __eq__(self, other):
eq = super(Field, self).__eq__(other)
if isinstance(self, type(other)):
return eq and self.object_type == other.object_type
return NotImplemented
def __hash__(self):
return hash((self.creation_counter, self.object_type))
class InputField(OrderedType):
def __init__(self, type, description=None, default=None,
name=None, _creation_counter=None, required=False):
super(InputField, self).__init__(_creation_counter=_creation_counter)
self.name = name
if required:
type = NonNull(type)
self.type = type
self.description = description
self.default = default
def contribute_to_class(self, cls, attname):
assert issubclass(
cls, InputObjectType), 'InputField {} cannot be mounted in {}'.format(
self, cls)
if not self.name:
self.name = to_camel_case(attname)
self.attname = attname
self.object_type = cls
self.mount(cls)
if isinstance(self.type, MountType):
self.type.mount(cls)
cls._meta.add_field(self)
def internal_type(self, schema):
return GraphQLInputObjectField(
schema.T(self.type),
default_value=self.default, description=self.description)

View File

@ -6,7 +6,11 @@ from functools import partial
import six
from graphene import signals
from graphene.core.exceptions import SkipField
from graphene.core.options import Options
from graphene.core.types.argument import ArgumentsGroup
from graphene.core.types.base import BaseType
from graphene.core.types.definitions import List, NonNull
from graphql.core.type import (GraphQLArgument, GraphQLInputObjectType,
GraphQLInterfaceType, GraphQLObjectType)
@ -50,10 +54,6 @@ class ObjectTypeMeta(type):
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)
@ -62,13 +62,6 @@ class ObjectTypeMeta(type):
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
@ -87,7 +80,8 @@ class ObjectTypeMeta(type):
# on the base classes (we cannot handle shadowed fields at the
# moment).
for field in parent_fields:
if field.name in field_names and field.__class__ != field_names[field.name].__class__:
if field.name in field_names and field.type.__class__ != field_names[
field.name].type.__class__:
raise Exception(
'Local field %r in class %r (%r) clashes '
'with field with similar name from '
@ -106,6 +100,9 @@ class ObjectTypeMeta(type):
new_class._meta.interfaces.append(base)
# new_class._meta.parents.extend(base._meta.parents)
setattr(new_class, 'NonNull', NonNull(new_class))
setattr(new_class, 'List', List(new_class))
new_class._prepare()
return new_class
@ -119,13 +116,14 @@ class ObjectTypeMeta(type):
def add_to_class(cls, name, value):
# We should call the contribute_to_class method only if it's bound
if not inspect.isclass(value) and hasattr(value, 'contribute_to_class'):
if not inspect.isclass(value) and hasattr(
value, 'contribute_to_class'):
value.contribute_to_class(cls, name)
else:
setattr(cls, name, value)
class BaseObjectType(object):
class BaseObjectType(BaseType):
def __new__(cls, *args, **kwargs):
if cls._meta.is_interface:
@ -165,14 +163,16 @@ class BaseObjectType(object):
pass
if kwargs:
raise TypeError(
"'%s' is an invalid keyword argument for this function" % list(kwargs)[0])
"'%s' is an invalid keyword argument for this function" %
list(kwargs)[0])
signals.post_init.send(self.__class__, instance=self)
@classmethod
def fields_as_arguments(cls, schema):
return OrderedDict([(f.attname, GraphQLArgument(f.internal_type(schema)))
for f in cls._meta.fields])
return OrderedDict(
[(f.attname, GraphQLArgument(f.internal_type(schema)))
for f in cls._meta.fields])
@classmethod
def resolve_objecttype(cls, schema, instance, *args):
@ -185,23 +185,32 @@ class BaseObjectType(object):
@classmethod
def internal_type(cls, schema):
fields = lambda: OrderedDict([(f.name, f.internal_field(schema))
for f in cls._meta.fields])
if cls._meta.is_interface:
return GraphQLInterfaceType(
cls._meta.type_name,
description=cls._meta.description,
resolve_type=partial(cls.resolve_type, schema),
fields=fields
fields=partial(cls.get_fields, schema)
)
return GraphQLObjectType(
cls._meta.type_name,
description=cls._meta.description,
interfaces=[schema.T(i) for i in cls._meta.interfaces],
fields=fields,
fields=partial(cls.get_fields, schema),
is_type_of=getattr(cls, 'is_type_of', None)
)
@classmethod
def get_fields(cls, schema):
fields = []
for field in cls._meta.fields:
try:
fields.append((field.name, schema.T(field)))
except SkipField:
continue
return OrderedDict(fields)
class Interface(six.with_metaclass(ObjectTypeMeta, BaseObjectType)):
pass
@ -212,20 +221,33 @@ class ObjectType(six.with_metaclass(ObjectTypeMeta, BaseObjectType)):
class Mutation(six.with_metaclass(ObjectTypeMeta, BaseObjectType)):
@classmethod
def _construct_arguments(cls, items):
return ArgumentsGroup(**items)
@classmethod
def get_input_type(cls):
return getattr(cls, 'input_type', None)
def _prepare_class(cls):
input_class = getattr(cls, 'Input', None)
if input_class:
items = dict(vars(input_class))
items.pop('__dict__', None)
items.pop('__doc__', None)
items.pop('__module__', None)
items.pop('__weakref__', None)
cls.add_to_class('arguments', cls._construct_arguments(items))
delattr(cls, 'Input')
@classmethod
def get_arguments(cls):
return cls.arguments
class InputObjectType(ObjectType):
@classmethod
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,
fields=partial(cls.get_fields, schema),
)

View File

@ -0,0 +1,41 @@
from graphql.core.type import (GraphQLBoolean, GraphQLFloat, GraphQLID,
GraphQLInt, GraphQLScalarType, GraphQLString)
from .base import MountedType
class String(MountedType):
T = GraphQLString
class Int(MountedType):
T = GraphQLInt
class Boolean(MountedType):
T = GraphQLBoolean
class ID(MountedType):
T = GraphQLID
class Float(MountedType):
T = GraphQLFloat
class Scalar(MountedType):
@classmethod
def internal_type(cls, schema):
serialize = getattr(cls, 'serialize')
parse_literal = getattr(cls, 'parse_literal')
parse_value = getattr(cls, 'parse_value')
return GraphQLScalarType(
name=cls.__name__,
description=cls.__doc__,
serialize=serialize,
parse_value=parse_value,
parse_literal=parse_literal
)

View File

View File

@ -0,0 +1,47 @@
from pytest import raises
from graphene.core.schema import Schema
from graphene.core.types import ObjectType
from graphql.core.type import GraphQLArgument
from ..argument import Argument, to_arguments
from ..scalars import String
def test_argument_internal_type():
class MyObjectType(ObjectType):
pass
schema = Schema(query=MyObjectType)
a = Argument(MyObjectType, description='My argument', default='3')
type = schema.T(a)
assert isinstance(type, GraphQLArgument)
assert type.description == 'My argument'
assert type.default_value == '3'
def test_to_arguments():
arguments = to_arguments(
Argument(String, name='myArg'),
String(name='otherArg'),
my_kwarg=String(),
other_kwarg=String(),
)
assert [a.name for a in arguments] == [
'myArg', 'otherArg', 'myKwarg', 'otherKwarg']
def test_to_arguments_no_name():
with raises(AssertionError) as excinfo:
to_arguments(
String(),
)
assert 'must have a name' in str(excinfo.value)
def test_to_arguments_wrong_type():
with raises(ValueError) as excinfo:
to_arguments(
p=3
)
assert 'Unknown argument p=3' == str(excinfo.value)

View File

@ -0,0 +1,94 @@
from mock import patch
from graphene.core.types import InputObjectType, ObjectType
from ..argument import Argument
from ..base import MountedType, OrderedType
from ..field import Field, InputField
from ..definitions import List, NonNull
def test_orderedtype_equal():
a = OrderedType()
assert a == a
assert hash(a) == hash(a)
def test_orderedtype_different():
a = OrderedType()
b = OrderedType()
assert a != b
assert hash(a) != hash(b)
assert a < b
assert b > a
@patch('graphene.core.types.field.Field')
def test_type_as_field_called(Field):
resolver = lambda x: x
a = MountedType(2, description='A', resolver=resolver)
a.as_field()
Field.assert_called_with(
a,
2,
_creation_counter=a.creation_counter,
description='A',
resolver=resolver)
@patch('graphene.core.types.argument.Argument')
def test_type_as_argument_called(Argument):
a = MountedType(2, description='A')
a.as_argument()
Argument.assert_called_with(
a, 2, _creation_counter=a.creation_counter, description='A')
def test_type_as_field():
resolver = lambda x: x
class MyObjectType(ObjectType):
t = MountedType(description='A', resolver=resolver)
fields_map = MyObjectType._meta.fields_map
field = fields_map.get('t')
assert isinstance(field, Field)
assert field.description == 'A'
assert field.object_type == MyObjectType
def test_type_as_inputfield():
class MyObjectType(InputObjectType):
t = MountedType(description='A')
fields_map = MyObjectType._meta.fields_map
field = fields_map.get('t')
assert isinstance(field, InputField)
assert field.description == 'A'
assert field.object_type == MyObjectType
def test_type_as_argument():
a = MountedType(description='A')
argument = a.as_argument()
assert isinstance(argument, Argument)
def test_type_as_list():
m = MountedType(2, 3, my_c='A')
a = m.List
assert isinstance(a, List)
assert a.of_type == m
assert a.args == (2, 3)
assert a.kwargs == {'my_c': 'A'}
def test_type_as_nonnull():
m = MountedType(2, 3, my_c='A')
a = m.NonNull
assert isinstance(a, NonNull)
assert a.of_type == m
assert a.args == (2, 3)
assert a.kwargs == {'my_c': 'A'}

View File

@ -0,0 +1,26 @@
from graphene.core.schema import Schema
from graphql.core.type import GraphQLList, GraphQLNonNull, GraphQLString
from ..definitions import List, NonNull
from ..scalars import String
schema = Schema()
def test_list_scalar():
type = schema.T(List(String()))
assert isinstance(type, GraphQLList)
assert type.of_type == GraphQLString
def test_nonnull_scalar():
type = schema.T(NonNull(String()))
assert isinstance(type, GraphQLNonNull)
assert type.of_type == GraphQLString
def test_nested_scalars():
type = schema.T(NonNull(List(String())))
assert isinstance(type, GraphQLNonNull)
assert isinstance(type.of_type, GraphQLList)
assert type.of_type.of_type == GraphQLString

View File

@ -0,0 +1,114 @@
from graphene.core.schema import Schema
from graphene.core.types import InputObjectType, ObjectType
from graphql.core.type import (GraphQLField, GraphQLInputObjectField,
GraphQLString)
from ..base import LazyType
from ..definitions import List
from ..field import Field, InputField
from ..scalars import String
def test_field_internal_type():
resolver = lambda *args: 'RESOLVED'
field = Field(String, description='My argument', resolver=resolver)
class Query(ObjectType):
my_field = field
schema = Schema(query=Query)
type = schema.T(field)
assert field.name == 'myField'
assert field.attname == 'my_field'
assert isinstance(type, GraphQLField)
assert type.description == 'My argument'
assert type.resolver(None, {}, None) == 'RESOLVED'
assert type.type == GraphQLString
def test_field_objectype_resolver():
field = Field(String)
class Query(ObjectType):
my_field = field
def resolve_my_field(self, *args, **kwargs):
'''Custom description'''
return 'RESOLVED'
schema = Schema(query=Query)
type = schema.T(field)
assert isinstance(type, GraphQLField)
assert type.description == 'Custom description'
assert type.resolver(Query(), {}, None) == 'RESOLVED'
def test_field_custom_name():
field = Field(None, name='my_customName')
class MyObjectType(ObjectType):
my_field = field
assert field.name == 'my_customName'
assert field.attname == 'my_field'
def test_field_self():
field = Field('self', name='my_customName')
class MyObjectType(ObjectType):
my_field = field
schema = Schema()
assert schema.T(field).type == schema.T(MyObjectType)
def test_field_mounted():
field = Field(List('MyObjectType'), name='my_customName')
class MyObjectType(ObjectType):
my_field = field
assert field.parent == MyObjectType
assert field.type.parent == MyObjectType
def test_field_string_reference():
field = Field('MyObjectType', name='my_customName')
class MyObjectType(ObjectType):
my_field = field
schema = Schema(query=MyObjectType)
assert isinstance(field.type, LazyType)
assert schema.T(field.type) == schema.T(MyObjectType)
def test_field_custom_arguments():
field = Field(None, name='my_customName', p=String())
args = field.arguments
assert 'p' in args
def test_inputfield_internal_type():
field = InputField(String, description='My input field', default='3')
class MyObjectType(InputObjectType):
my_field = field
class Query(ObjectType):
input_ot = Field(MyObjectType)
schema = Schema(query=MyObjectType)
type = schema.T(field)
assert field.name == 'myField'
assert field.attname == 'my_field'
assert isinstance(type, GraphQLInputObjectField)
assert type.description == 'My input field'
assert type.default_value == '3'

View File

@ -1,9 +1,7 @@
from py.test import raises
from pytest import raises
from graphene.core.fields import IntField, StringField
from graphene.core.schema import Schema
from graphene.core.types import Interface
from graphene.core.types import Int, Interface, String
from graphql.core.execution.middlewares.utils import (resolver_has_tag,
tag_resolver)
from graphql.core.type import GraphQLInterfaceType, GraphQLObjectType
@ -11,7 +9,7 @@ from graphql.core.type import GraphQLInterfaceType, GraphQLObjectType
class Character(Interface):
'''Character description'''
name = StringField()
name = String()
class Meta:
type_name = 'core_Character'
@ -19,7 +17,7 @@ class Character(Interface):
class Human(Character):
'''Human description'''
friends = StringField()
friends = String()
class Meta:
type_name = 'core_Human'
@ -66,9 +64,6 @@ def test_object_type():
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() == [schema.T(Character)]
assert Human._meta.fields_map['name'].object_type == Human
@ -97,7 +92,7 @@ def test_object_type_container_too_many_args():
def test_field_clashes():
with raises(Exception) as excinfo:
class Droid(Character):
name = IntField()
name = Int()
assert 'clashes' in str(excinfo.value)
@ -108,12 +103,26 @@ def test_fields_inherited_should_be_different():
def test_field_mantain_resolver_tags():
class Droid(Character):
name = StringField()
name = String()
def resolve_name(self, *args):
return 'My Droid'
tag_resolver(resolve_name, 'test')
field = Droid._meta.fields_map['name'].internal_field(schema)
field = schema.T(Droid._meta.fields_map['name'])
assert resolver_has_tag(field.resolver, 'test')
def test_type_has_nonnull():
class Droid(Character):
name = String()
assert Droid.NonNull.of_type == Droid
def test_type_has_list():
class Droid(Character):
name = String()
assert Droid.List.of_type == Droid

View File

@ -0,0 +1,53 @@
from graphene.core.schema import Schema
from graphql.core.type import (GraphQLBoolean, GraphQLFloat, GraphQLID,
GraphQLInt, GraphQLScalarType, GraphQLString)
from ..scalars import ID, Boolean, Float, Int, Scalar, String
schema = Schema()
def test_string_scalar():
assert schema.T(String()) == GraphQLString
def test_int_scalar():
assert schema.T(Int()) == GraphQLInt
def test_boolean_scalar():
assert schema.T(Boolean()) == GraphQLBoolean
def test_id_scalar():
assert schema.T(ID()) == GraphQLID
def test_float_scalar():
assert schema.T(Float()) == GraphQLFloat
def test_custom_scalar():
import datetime
from graphql.core.language import ast
class DateTimeScalar(Scalar):
'''DateTimeScalar Documentation'''
@staticmethod
def serialize(dt):
return dt.isoformat()
@staticmethod
def parse_literal(node):
if isinstance(node, ast.StringValue):
return datetime.datetime.strptime(
node.value, "%Y-%m-%dT%H:%M:%S.%f")
@staticmethod
def parse_value(value):
return datetime.datetime.strptime(value, "%Y-%m-%dT%H:%M:%S.%f")
scalar_type = schema.T(DateTimeScalar)
assert isinstance(scalar_type, GraphQLScalarType)
assert scalar_type.name == 'DateTimeScalar'
assert scalar_type.description == 'DateTimeScalar Documentation'

View File

@ -1,41 +1,50 @@
from collections import Iterable
from graphene.core.fields import Field, IDField
from graphql.core.type import GraphQLArgument, GraphQLID, GraphQLNonNull
from graphene.core.fields import Field
from graphene.core.types.scalars import ID, Int, String
from graphql_relay.connection.arrayconnection import connection_from_list
from graphql_relay.connection.connection import connection_args
from graphql_relay.node.node import from_global_id
class ConnectionField(Field):
def __init__(self, field_type, resolve=None, description='',
def __init__(self, field_type, resolver=None, description='',
connection_type=None, edge_type=None, **kwargs):
super(ConnectionField, self).__init__(field_type, resolve=resolve,
args=connection_args,
description=description, **kwargs)
super(
ConnectionField,
self).__init__(
field_type,
resolver=resolver,
before=String(),
after=String(),
first=Int(),
last=Int(),
description=description,
**kwargs)
self.connection_type = connection_type
self.edge_type = edge_type
def wrap_resolved(self, value, instance, args, info):
return value
def resolve(self, instance, args, info):
def resolver(self, instance, args, info):
from graphene.relay.types import PageInfo
schema = info.schema.graphene_schema
resolved = super(ConnectionField, self).resolve(instance, args, info)
resolved = super(ConnectionField, self).resolver(instance, args, info)
if resolved:
resolved = self.wrap_resolved(resolved, instance, args, info)
assert isinstance(
resolved, Iterable), 'Resolved value from the connection field have to be iterable'
node = self.get_object_type(schema)
type = schema.T(self.type)
node = schema.objecttype(type)
connection_type = self.get_connection_type(node)
edge_type = self.get_edge_type(node)
connection = connection_from_list(resolved, args, connection_type=connection_type,
edge_type=edge_type, pageinfo_type=PageInfo)
connection = connection_from_list(
resolved, args, connection_type=connection_type,
edge_type=edge_type, pageinfo_type=PageInfo)
connection.set_connection_data(resolved)
return connection
@ -47,25 +56,24 @@ class ConnectionField(Field):
def get_edge_type(self, node):
return self.edge_type or node.get_edge_type()
def internal_type(self, schema):
def get_type(self, schema):
from graphene.relay.utils import is_node
node = self.get_object_type(schema)
type = schema.T(self.type)
node = schema.objecttype(type)
assert is_node(node), 'Only nodes have connections.'
schema.register(node)
connection_type = self.get_connection_type(node)
return schema.T(connection_type)
return connection_type
class NodeField(Field):
def __init__(self, object_type=None, *args, **kwargs):
from graphene.relay.types import Node
super(NodeField, self).__init__(object_type or Node, *args, **kwargs)
id = kwargs.pop('id', None) or ID(description='The ID of an object')
super(NodeField, self).__init__(
object_type or Node, id=id, *args, **kwargs)
self.field_object_type = object_type
self.args['id'] = GraphQLArgument(
GraphQLNonNull(GraphQLID),
description='The ID of an object'
)
def id_fetcher(self, global_id, info):
from graphene.relay.utils import is_node
@ -79,20 +87,23 @@ class NodeField(Field):
return object_type.get_node(_id)
def resolve(self, instance, args, info):
def resolver(self, instance, args, info):
global_id = args.get('id')
return self.id_fetcher(global_id, info)
class GlobalIDField(IDField):
class GlobalIDField(Field):
'''The ID of an object'''
required = True
def contribute_to_class(self, cls, name, add=True):
def __init__(self, *args, **kwargs):
super(GlobalIDField, self).__init__(ID(), *args, **kwargs)
self.required = True
def contribute_to_class(self, cls, name):
from graphene.relay.utils import is_node, is_node_type
in_node = is_node(cls) or is_node_type(cls)
assert in_node, 'GlobalIDField could only be inside a Node, but got %r' % cls
super(GlobalIDField, self).contribute_to_class(cls, name, add)
super(GlobalIDField, self).contribute_to_class(cls, name)
def resolve(self, instance, args, info):
return self.object_type.to_global_id(instance, args, info)
def resolver(self, instance, args, info):
return instance.to_global_id()

View File

View File

@ -1,22 +1,21 @@
import graphene
from graphene import relay
from graphene.core.schema import Schema
from graphene.core.types import InputObjectType
from graphql.core.type import GraphQLInputObjectField
my_id = 0
class Query(graphene.ObjectType):
base = graphene.StringField()
base = graphene.String()
class ChangeNumber(relay.ClientIDMutation):
'''Result mutation'''
class Input:
to = graphene.IntField()
to = graphene.Int()
result = graphene.StringField()
result = graphene.String()
@classmethod
def mutate_and_get_payload(cls, input, info):
@ -32,19 +31,18 @@ class MyResultMutation(graphene.ObjectType):
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)
def test_mutation_arguments():
assert ChangeNumber.arguments
assert list(ChangeNumber.arguments) == ['input']
assert 'input' in ChangeNumber.arguments
inner_type = ChangeNumber.input_type
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 issubclass(inner_type, graphene.InputObjectType)
assert isinstance(client_mutation_id_field.type, graphene.NonNull)
assert isinstance(client_mutation_id_field.type.of_type, graphene.String)
assert client_mutation_id_field.object_type == inner_type
assert isinstance(client_mutation_id_field.internal_field(
schema), GraphQLInputObjectField)
assert isinstance(schema.T(client_mutation_id_field), GraphQLInputObjectField)
def test_execute_mutations():

View File

@ -6,22 +6,22 @@ schema = graphene.Schema()
class MyConnection(relay.Connection):
my_custom_field = graphene.StringField(
resolve=lambda instance, *_: 'Custom')
my_custom_field = graphene.String(
resolver=lambda instance, *_: 'Custom')
class MyNode(relay.Node):
name = graphene.StringField()
name = graphene.String()
@classmethod
def get_node(cls, id):
return MyNode(name='mo')
return MyNode(id=id, name='mo')
class Query(graphene.ObjectType):
my_node = relay.NodeField(MyNode)
all_my_nodes = relay.ConnectionField(
MyNode, connection_type=MyConnection, customArg=graphene.Argument(graphene.String))
MyNode, connection_type=MyConnection, customArg=graphene.String())
def resolve_all_my_nodes(self, args, info):
custom_arg = args.get('customArg')
@ -35,6 +35,7 @@ def test_nodefield_query():
query = '''
query RebelsShipsQuery {
myNode(id:"TXlOb2RlOjE=") {
id
name
},
allMyNodes (customArg:"1") {
@ -52,6 +53,7 @@ def test_nodefield_query():
'''
expected = {
'myNode': {
'id': 'TXlOb2RlOjE=',
'name': 'mo'
},
'allMyNodes': {
@ -73,5 +75,6 @@ def test_nodefield_query():
def test_nodeidfield():
id_field = MyNode._meta.fields_map['id']
assert isinstance(id_field.internal_field(schema).type, GraphQLNonNull)
assert id_field.internal_field(schema).type.of_type == GraphQLID
id_field_type = schema.T(id_field)
assert isinstance(id_field_type.type, GraphQLNonNull)
assert id_field_type.type.of_type == GraphQLID

View File

@ -7,7 +7,7 @@ schema = graphene.Schema()
class OtherNode(relay.Node):
name = graphene.StringField()
name = graphene.String()
@classmethod
def get_node(cls, id):
@ -17,7 +17,7 @@ class OtherNode(relay.Node):
def test_field_no_contributed_raises_error():
with raises(Exception) as excinfo:
class Part(relay.Node):
x = graphene.StringField()
x = graphene.String()
assert 'get_node' in str(excinfo.value)

View File

@ -1,19 +1,23 @@
from graphene.core.fields import BooleanField, Field, ListField, StringField
from graphene.core.types import (InputObjectType, Interface, Mutation,
ObjectType)
from graphene.core.types import (Boolean, Field, InputObjectType, Interface,
List, Mutation, ObjectType, String)
from graphene.core.types.argument import ArgumentsGroup
from graphene.core.types.base import LazyType
from graphene.core.types.definitions import NonNull
from graphene.relay.fields import GlobalIDField
from graphene.utils import memoize
from graphql_relay.node.node import to_global_id
class PageInfo(ObjectType):
has_next_page = BooleanField(
required=True, description='When paginating forwards, are there more items?')
has_previous_page = BooleanField(
required=True, description='When paginating backwards, are there more items?')
start_cursor = StringField(
has_next_page = Boolean(
required=True,
description='When paginating forwards, are there more items?')
has_previous_page = Boolean(
required=True,
description='When paginating backwards, are there more items?')
start_cursor = String(
description='When paginating backwards, the cursor to continue.')
end_cursor = StringField(
end_cursor = String(
description='When paginating forwards, the cursor to continue.')
@ -22,9 +26,9 @@ class Edge(ObjectType):
class Meta:
type_name = 'DefaultEdge'
node = Field(lambda field: field.object_type.node_type,
node = Field(LazyType(lambda object_type: object_type.node_type),
description='The item at the end of the edge')
cursor = StringField(
cursor = String(
required=True, description='A cursor for use in pagination')
@classmethod
@ -32,7 +36,10 @@ class Edge(ObjectType):
def for_node(cls, node):
from graphene.relay.utils import is_node
assert is_node(node), 'ObjectTypes in a edge have to be Nodes'
return type('%s%s' % (node._meta.type_name, cls._meta.type_name), (cls, ), {'node_type': node})
return type(
'%s%s' % (node._meta.type_name, cls._meta.type_name),
(cls,),
{'node_type': node})
class Connection(ObjectType):
@ -42,8 +49,8 @@ class Connection(ObjectType):
page_info = Field(PageInfo, required=True,
description='The Information to aid in pagination')
edges = ListField(lambda field: field.object_type.edge_type,
description='Information to aid in pagination.')
edges = List(LazyType(lambda object_type: object_type.edge_type),
description='Information to aid in pagination.')
_connection_data = None
@ -53,7 +60,10 @@ class Connection(ObjectType):
from graphene.relay.utils import is_node
edge_type = edge_type or Edge
assert is_node(node), 'ObjectTypes in a connection have to be Nodes'
return type('%s%s' % (node._meta.type_name, cls._meta.type_name), (cls, ), {'edge_type': edge_type.for_node(node)})
return type(
'%s%s' % (node._meta.type_name, cls._meta.type_name),
(cls,),
{'edge_type': edge_type.for_node(node)})
def set_connection_data(self, data):
self._connection_data = data
@ -71,10 +81,9 @@ class BaseNode(object):
assert hasattr(
cls, 'get_node'), 'get_node classmethod not found in %s Node' % cls
@classmethod
def to_global_id(cls, instance, args, info):
type_name = cls._meta.type_name
return to_global_id(type_name, instance.id)
def to_global_id(self):
type_name = self._meta.type_name
return to_global_id(type_name, self.id)
connection_type = Connection
edge_type = Edge
@ -90,31 +99,24 @@ class BaseNode(object):
class Node(BaseNode, Interface):
'''An object with an ID'''
id = GlobalIDField()
id = GlobalIDField(required=True)
class MutationInputType(InputObjectType):
client_mutation_id = StringField(required=True)
client_mutation_id = String(required=True)
class ClientIDMutation(Mutation):
client_mutation_id = StringField(required=True)
client_mutation_id = String(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)
def _construct_arguments(cls, items):
assert hasattr(
cls, 'mutate_and_get_payload'), 'You have to implement mutate_and_get_payload'
new_input_type = type('{}Input'.format(
cls._meta.type_name), (MutationInputType, ), items)
cls.add_to_class('input_type', new_input_type)
return ArgumentsGroup(input=NonNull(new_input_type))
@classmethod
def mutate(cls, instance, args, info):

View File

@ -2,7 +2,8 @@ from graphene.relay.types import BaseNode
def is_node(object_type):
return object_type and issubclass(object_type, BaseNode) and not is_node_type(object_type)
return object_type and issubclass(
object_type, BaseNode) and not is_node_type(object_type)
def is_node_type(object_type):

View File

@ -19,11 +19,14 @@ def test_proxy_snake_dict():
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)]))
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():

View File

@ -1,3 +1,6 @@
[flake8]
exclude = tests/*,setup.py
max-line-length = 160
exclude = setup.py
max-line-length = 120
[coverage:run]
omit = core/ntypes/tests/*

View File

@ -62,6 +62,7 @@ setup(
tests_require=[
'pytest>=2.7.2',
'pytest-django',
'mock',
],
extras_require={
'django': [

View File

@ -1,166 +0,0 @@
from py.test import raises
from pytest import raises
from graphene.core.fields import Field, NonNullField, StringField
from graphene.core.options import Options
from graphene.core.schema import Schema
from graphene.core.types import ObjectType
from graphql.core.type import (GraphQLBoolean, GraphQLField, GraphQLID,
GraphQLInt, GraphQLNonNull, GraphQLString)
class ObjectType(object):
_meta = Options()
def resolve_customdoc(self, *args, **kwargs):
'''Resolver documentation'''
return None
def __str__(self):
return "ObjectType"
ot = ObjectType
schema = Schema()
def test_field_no_contributed_raises_error():
f = Field(GraphQLString)
with raises(Exception) as excinfo:
f.internal_field(schema)
def test_field_type():
f = Field(GraphQLString)
f.contribute_to_class(ot, 'field_name')
assert isinstance(f.internal_field(schema), GraphQLField)
assert f.internal_type(schema) == GraphQLString
def test_field_name_automatic_camelcase():
f = Field(GraphQLString)
f.contribute_to_class(ot, 'field_name')
assert f.name == 'fieldName'
def test_field_name_use_name_if_exists():
f = Field(GraphQLString, name='my_custom_name')
f.contribute_to_class(ot, 'field_name')
assert f.name == 'my_custom_name'
def test_stringfield_type():
f = StringField()
f.contribute_to_class(ot, 'field_name')
assert f.internal_type(schema) == GraphQLString
def test_nonnullfield_type():
f = NonNullField(StringField())
f.contribute_to_class(ot, 'field_name')
assert isinstance(f.internal_type(schema), GraphQLNonNull)
def test_stringfield_type_required():
f = StringField(required=True)
f.contribute_to_class(ot, 'field_name')
assert isinstance(f.internal_field(schema), GraphQLField)
assert isinstance(f.internal_type(schema), GraphQLNonNull)
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, None, None)
def test_field_resolve_type_custom():
class MyCustomType(ObjectType):
pass
class OtherType(ObjectType):
pass
s = Schema()
f = Field('MyCustomType')
f.contribute_to_class(OtherType, 'field_name')
field_type = f.get_object_type(s)
assert field_type == MyCustomType
def test_field_resolve_type_custom():
s = Schema()
f = Field('self')
f.contribute_to_class(ot, 'field_name')
field_type = f.get_object_type(s)
assert field_type == ot
def test_field_orders():
f1 = Field(None)
f2 = Field(None)
assert f1 < f2
def test_field_orders_wrong_type():
field = Field(None)
try:
assert not field < 1
except TypeError:
# Fix exception raising in Python3+
pass
def test_field_eq():
f1 = Field(None)
f2 = Field(None)
assert f1 != f2
def test_field_eq_wrong_type():
field = Field(None)
assert field != 1
def test_field_hash():
f1 = Field(None)
f2 = Field(None)
assert hash(f1) != hash(f2)
def test_field_none_type_raises_error():
s = Schema()
f = Field(None)
f.contribute_to_class(ot, 'field_name')
with raises(Exception) as excinfo:
f.internal_field(s)
assert str(
excinfo.value) == "Internal type for field ObjectType.field_name is None"
def test_field_str():
f = StringField()
f.contribute_to_class(ot, 'field_name')
assert str(f) == "ObjectType.field_name"
def test_field_repr():
f = StringField()
assert repr(f) == "<graphene.core.fields.StringField>"
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'

View File

@ -1,16 +0,0 @@
from graphene.core.scalars import GraphQLSkipField
def test_skipfield_serialize():
f = GraphQLSkipField
assert f.serialize('a') is None
def test_skipfield_parse_value():
f = GraphQLSkipField
assert f.parse_value('a') is None
def test_skipfield_parse_literal():
f = GraphQLSkipField
assert f.parse_literal('a') is None

View File

@ -2,7 +2,6 @@ SECRET_KEY = 1
INSTALLED_APPS = [
'examples.starwars_django',
'tests.contrib_django',
]
DATABASES = {