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 ```python
class Query(graphene.ObjectType): class Query(graphene.ObjectType):
hello = graphene.StringField(description='A typical hello world') hello = graphene.String(description='A typical hello world')
ping = graphene.StringField(description='Ping someone', ping = graphene.String(description='Ping someone',
to=graphene.Argument(graphene.String)) to=graphene.String())
def resolve_hello(self, args, info): def resolve_hello(self, args, info):
return 'World' return 'World'

View File

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

View File

@ -1,4 +1,5 @@
#!/bin/bash #!/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 . isort -rc .

View File

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

View File

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

View File

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

View File

@ -1,9 +1,5 @@
from graphql.core.type import ( from graphql.core.type import (
GraphQLEnumType as Enum, GraphQLEnumType as Enum
GraphQLArgument as Argument,
GraphQLString as String,
GraphQLInt as Int,
GraphQLID as ID
) )
from graphene import signals from graphene import signals
@ -14,12 +10,24 @@ from graphene.core.schema import (
from graphene.core.types import ( from graphene.core.types import (
ObjectType, ObjectType,
InputObjectType,
Interface, Interface,
Mutation, Mutation,
BaseType,
LazyType,
Argument,
Field,
InputField,
String,
Int,
Boolean,
ID,
Float,
List,
NonNull
) )
from graphene.core.fields import ( from graphene.core.fields import (
Field,
StringField, StringField,
IntField, IntField,
BooleanField, BooleanField,
@ -33,7 +41,31 @@ from graphene.decorators import (
resolve_only_args resolve_only_args
) )
__all__ = ['Enum', 'Argument', 'String', 'Int', 'ID', 'signals', 'Schema', __all__ = [
'ObjectType', 'Interface', 'Mutation', 'Field', 'StringField', 'Enum',
'IntField', 'BooleanField', 'IDField', 'ListField', 'NonNullField', 'Argument',
'FloatField', 'resolve_only_args'] '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, from graphene.contrib.django.fields import (ConnectionOrListField,
DjangoModelField) DjangoModelField)
from graphene.core.fields import (BooleanField, FloatField, IDField, IntField, from graphene.core.types.scalars import ID, Boolean, Float, Int, String
StringField)
try: try:
UUIDField = models.UUIDField UUIDField = models.UUIDField
@ -17,7 +16,8 @@ except AttributeError:
@singledispatch @singledispatch
def convert_django_field(field): def convert_django_field(field):
raise Exception( 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) @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(models.URLField)
@convert_django_field.register(UUIDField) @convert_django_field.register(UUIDField)
def convert_field_to_string(field): def convert_field_to_string(field):
return StringField(description=field.help_text) return String(description=field.help_text)
@convert_django_field.register(models.AutoField) @convert_django_field.register(models.AutoField)
def convert_field_to_id(field): def convert_field_to_id(field):
return IDField(description=field.help_text) return ID(description=field.help_text)
@convert_django_field.register(models.PositiveIntegerField) @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.BigIntegerField)
@convert_django_field.register(models.IntegerField) @convert_django_field.register(models.IntegerField)
def convert_field_to_int(field): def convert_field_to_int(field):
return IntField(description=field.help_text) return Int(description=field.help_text)
@convert_django_field.register(models.BooleanField) @convert_django_field.register(models.BooleanField)
def convert_field_to_boolean(field): 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) @convert_django_field.register(models.NullBooleanField)
def convert_field_to_nullboolean(field): 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.DecimalField)
@convert_django_field.register(models.FloatField) @convert_django_field.register(models.FloatField)
def convert_field_to_float(field): def convert_field_to_float(field):
return FloatField(description=field.help_text) return Float(description=field.help_text)
@convert_django_field.register(models.ManyToManyField) @convert_django_field.register(models.ManyToManyField)

View File

@ -1,6 +1,9 @@
from graphene import relay from graphene import relay
from graphene.contrib.django.utils import get_type_for_model, lazy_map 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 from graphene.relay.utils import is_node
@ -8,60 +11,54 @@ class DjangoConnectionField(relay.ConnectionField):
def wrap_resolved(self, value, instance, args, info): def wrap_resolved(self, value, instance, args, info):
schema = info.schema.graphene_schema 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 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)) return lazy_map(resolved, self.get_object_type(schema))
class ConnectionOrListField(LazyField): class ConnectionOrListField(Field):
def get_field(self, schema): def internal_type(self, schema):
model_field = self.field_type model_field = self.type
field_object_type = model_field.get_object_type(schema) field_object_type = model_field.get_object_type(schema)
if is_node(field_object_type): if is_node(field_object_type):
field = DjangoConnectionField(model_field) field = DjangoConnectionField(model_field)
else: else:
field = LazyListField(model_field) field = LazyListField(model_field)
field.contribute_to_class(self.object_type, self.name) 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): def __init__(self, model, *args, **kwargs):
super(DjangoModelField, self).__init__(None, *args, **kwargs)
self.model = model self.model = model
super(DjangoModelField, self).__init__(*args, **kwargs)
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)
def internal_type(self, schema): def internal_type(self, schema):
_type = self.get_object_type(schema) _type = self.get_object_type(schema)
if not _type and self.object_type._meta.only_fields: if not _type and self.parent._meta.only_fields:
raise Exception( raise Exception(
"Model %r is not accessible by the schema. " "Model %r is not accessible by the schema. "
"You can either register the type manually " "You can either register the type manually "
"using @schema.register. " "using @schema.register. "
"Or disable the field %s in %s" % ( "Or disable the field in %s" % (
self.model, self.model,
self.attname, self.parent,
self.object_type
) )
) )
return schema.T(_type) or Field.SKIP if not _type:
raise SkipField()
return schema.T(_type)
def get_object_type(self, schema): def get_object_type(self, schema):
return get_type_for_model(schema, self.model) return get_type_for_model(schema, self.model)

View File

@ -32,6 +32,7 @@ class DjangoOptions(Options):
return return
if not self.model: if not self.model:
raise Exception( 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): 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) raise Exception('Provided model in %s is not a Django model' % cls)

View File

@ -1,7 +1,6 @@
from django.db import models from django.db import models
from py.test import raises from py.test import raises
from pytest import raises
import graphene import graphene
from graphene.contrib.django.converter import convert_django_field 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') field = django_field(*args, help_text='Custom Help Text')
graphene_type = convert_django_field(field) graphene_type = convert_django_field(field)
assert isinstance(graphene_type, graphene_field) assert isinstance(graphene_type, graphene_field)
assert graphene_type.description == 'Custom Help Text' field = graphene_type.as_field()
return graphene_type assert field.description == 'Custom Help Text'
return field
def test_should_unknown_django_field_raise_exception(): 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(): def test_should_date_convert_string():
assert_conversion(models.DateField, graphene.StringField) assert_conversion(models.DateField, graphene.String)
def test_should_char_convert_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(): def test_should_text_convert_string():
assert_conversion(models.TextField, graphene.StringField) assert_conversion(models.TextField, graphene.String)
def test_should_email_convert_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(): def test_should_slug_convert_string():
assert_conversion(models.SlugField, graphene.StringField) assert_conversion(models.SlugField, graphene.String)
def test_should_url_convert_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(): 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(): 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(): 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(): 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(): 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(): def test_should_integer_convert_int():
assert_conversion(models.IntegerField, graphene.IntField) assert_conversion(models.IntegerField, graphene.Int)
def test_should_boolean_convert_boolean(): 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 assert field.required is True
def test_should_nullboolean_convert_boolean(): 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 assert field.required is False
def test_should_float_convert_float(): def test_should_float_convert_float():
assert_conversion(models.FloatField, graphene.FloatField) assert_conversion(models.FloatField, graphene.Float)
def test_should_manytomany_convert_connectionorlist(): def test_should_manytomany_convert_connectionorlist():
graphene_type = convert_django_field(Reporter._meta.local_many_to_many[0]) graphene_type = convert_django_field(Reporter._meta.local_many_to_many[0])
assert isinstance(graphene_type, ConnectionOrListField) assert isinstance(graphene_type, ConnectionOrListField)
assert isinstance(graphene_type.field_type, DjangoModelField) assert isinstance(graphene_type.type, DjangoModelField)
assert graphene_type.field_type.model == Reporter assert graphene_type.type.model == Reporter
def test_should_manytoone_convert_connectionorlist(): def test_should_manytoone_convert_connectionorlist():
graphene_type = convert_django_field(Reporter.articles.related) graphene_type = convert_django_field(Reporter.articles.related)
assert isinstance(graphene_type, ConnectionOrListField) assert isinstance(graphene_type, ConnectionOrListField)
assert isinstance(graphene_type.field_type, DjangoModelField) assert isinstance(graphene_type.type, DjangoModelField)
assert graphene_type.field_type.model == Article assert graphene_type.type.model == Article
def test_should_onetoone_convert_model(): def test_should_onetoone_convert_model():
field = assert_conversion(models.OneToOneField, DjangoModelField, Article) field = assert_conversion(models.OneToOneField, DjangoModelField, Article)
assert field.model == Article assert field.type.model == Article
def test_should_foreignkey_convert_model(): def test_should_foreignkey_convert_model():
field = assert_conversion(models.ForeignKey, DjangoModelField, Article) 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 py.test import raises
from pytest import raises
import graphene import graphene
from graphene import relay from graphene import relay
from graphene.contrib.django import DjangoNode, DjangoObjectType from graphene.contrib.django import DjangoNode, DjangoObjectType
from tests.utils import assert_equal_lists
from .models import Article, Reporter from .models import Article, Reporter
def test_should_raise_if_no_model(): def test_should_query_only_fields():
with raises(Exception) as excinfo: with raises(Exception):
class Character1(DjangoObjectType): class ReporterType(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):
class Meta: class Meta:
model = Reporter model = Reporter
only_fields = ('articles', ) only_fields = ('articles', )
schema = graphene.Schema(query=ReporterTypeError) schema = graphene.Schema(query=ReporterType)
query = ''' query = '''
query ReporterQuery { query ReporterQuery {
articles articles
@ -44,24 +25,13 @@ def test_should_raise_if_model_is_invalid():
assert not result.errors assert not result.errors
def test_should_map_fields_correctly(): def test_should_query_well():
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():
class ReporterType(DjangoObjectType): class ReporterType(DjangoObjectType):
class Meta: class Meta:
model = Reporter model = Reporter
class Query2(graphene.ObjectType): class Query(graphene.ObjectType):
reporter = graphene.Field(ReporterType) reporter = graphene.Field(ReporterType)
def resolve_reporter(self, *args, **kwargs): def resolve_reporter(self, *args, **kwargs):
@ -83,53 +53,42 @@ def test_should_map_fields():
'email': '' 'email': ''
} }
} }
Schema = graphene.Schema(query=Query2) schema = graphene.Schema(query=Query)
result = Schema.execute(query) result = schema.execute(query)
assert not result.errors assert not result.errors
assert result.data == expected 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(): def test_should_node():
class ReporterNodeType(DjangoNode): class ReporterNode(DjangoNode):
class Meta: class Meta:
model = Reporter model = Reporter
@classmethod @classmethod
def get_node(cls, id): 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): def resolve_articles(self, *args, **kwargs):
return [ArticleNodeType(Article(headline='Hi!'))] return [ArticleNode(Article(headline='Hi!'))]
class ArticleNodeType(DjangoNode): class ArticleNode(DjangoNode):
class Meta: class Meta:
model = Article model = Article
@classmethod @classmethod
def get_node(cls, id): 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() node = relay.NodeField()
reporter = graphene.Field(ReporterNodeType) reporter = graphene.Field(ReporterNode)
article = graphene.Field(ArticleNodeType) article = graphene.Field(ArticleNode)
def resolve_reporter(self, *args, **kwargs): 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 = '''
query ReporterQuery { query ReporterQuery {
@ -146,12 +105,12 @@ def test_should_node():
lastName, lastName,
email email
} }
myArticle: node(id:"QXJ0aWNsZU5vZGVUeXBlOjE=") { myArticle: node(id:"QXJ0aWNsZU5vZGU6MQ==") {
id id
... on ReporterNodeType { ... on ReporterNode {
firstName firstName
} }
... on ArticleNodeType { ... on ArticleNode {
headline headline
} }
} }
@ -159,7 +118,7 @@ def test_should_node():
''' '''
expected = { expected = {
'reporter': { 'reporter': {
'id': 'UmVwb3J0ZXJOb2RlVHlwZTox', 'id': 'UmVwb3J0ZXJOb2RlOjE=',
'firstName': 'ABA', 'firstName': 'ABA',
'lastName': 'X', 'lastName': 'X',
'email': '', 'email': '',
@ -172,11 +131,11 @@ def test_should_node():
}, },
}, },
'myArticle': { 'myArticle': {
'id': 'QXJ0aWNsZU5vZGVUeXBlOjE=', 'id': 'QXJ0aWNsZU5vZGU6MQ==',
'headline': 'Article node' 'headline': 'Article node'
} }
} }
Schema = graphene.Schema(query=Query1) schema = graphene.Schema(query=Query)
result = Schema.execute(query) result = schema.execute(query)
assert not result.errors assert not result.errors
assert result.data == expected 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 import Schema
from graphene.contrib.django.types import DjangoInterface, DjangoNode 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 graphene.relay.fields import GlobalIDField
from graphql.core.type import GraphQLInterfaceType, GraphQLObjectType from graphql.core.type import GraphQLInterfaceType, GraphQLObjectType
from tests.utils import assert_equal_lists from tests.utils import assert_equal_lists
from .models import Article, Reporter from .models import Article, Reporter
schema = Schema()
class Character(DjangoInterface): class Character(DjangoInterface):
'''Character description''' '''Character description'''
@ -16,10 +19,11 @@ class Character(DjangoInterface):
model = Reporter model = Reporter
@schema.register
class Human(DjangoNode): class Human(DjangoNode):
'''Human description''' '''Human description'''
pub_date = IntField() pub_date = Int()
def get_node(self, id): def get_node(self, id):
pass pass
@ -27,14 +31,12 @@ class Human(DjangoNode):
class Meta: class Meta:
model = Article model = Article
schema = Schema()
def test_django_interface(): def test_django_interface():
assert DjangoNode._meta.is_interface is True assert DjangoNode._meta.is_interface is True
def test_pseudo_interface(): def test_pseudo_interface_registered():
object_type = schema.T(Character) object_type = schema.T(Character)
assert Character._meta.is_interface is True assert Character._meta.is_interface is True
assert isinstance(object_type, GraphQLInterfaceType) assert isinstance(object_type, GraphQLInterfaceType)
@ -57,7 +59,8 @@ def test_node_idfield():
def test_node_replacedfield(): def test_node_replacedfield():
idfield = Human._meta.fields_map['pub_date'] 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(): def test_interface_resolve_type():

View File

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

View File

@ -6,7 +6,7 @@ def format_response(response):
def test_client_get_no_query(settings, client): 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') response = client.get('/graphql')
json_response = format_response(response) json_response = format_response(response)
assert json_response == {'errors': [ assert json_response == {'errors': [
@ -14,7 +14,7 @@ def test_client_get_no_query(settings, client):
def test_client_post_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', {}) response = client.post('/graphql', {})
json_response = format_response(response) json_response = format_response(response)
assert json_response == {'errors': [ assert json_response == {'errors': [
@ -22,7 +22,7 @@ def test_client_post_no_query(settings, client):
def test_client_post_malformed_json(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') response = client.post('/graphql', 'MALFORMED', 'application/json')
json_response = format_response(response) json_response = format_response(response)
assert json_response == {'errors': [ 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): 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( response = client.post(
'/graphql', json.dumps({'query': ''}), 'application/json') '/graphql', json.dumps({'query': ''}), 'application/json')
json_response = format_response(response) 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): 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( response = client.post(
'/graphql', '', 'application/graphql') '/graphql', '', 'application/graphql')
json_response = format_response(response) 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): 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( response = client.post(
'/graphql', json.dumps({'query': '{ MALFORMED'}), 'application/json') '/graphql', json.dumps({'query': '{ MALFORMED'}), 'application/json')
json_response = format_response(response) 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): 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( response = client.post(
'/graphql', '{ MALFORMED', 'application/graphql') '/graphql', '{ MALFORMED', 'application/graphql')
json_response = format_response(response) 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): 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 }'}) response = client.get('/graphql', {'query': '{ headline }'})
json_response = format_response(response) json_response = format_response(response)
expected_json = { expected_json = {
@ -80,7 +80,7 @@ def test_client_get_good_query(settings, client):
def test_client_get_good_query_with_raise(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 }'}) response = client.get('/graphql', {'query': '{ raises }'})
json_response = format_response(response) json_response = format_response(response)
assert json_response['errors'][0][ 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): 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( response = client.post(
'/graphql', json.dumps({'query': '{ headline }'}), 'application/json') '/graphql', json.dumps({'query': '{ headline }'}), 'application/json')
json_response = format_response(response) 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): 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( response = client.post(
'/graphql', '{ headline }', 'application/graphql') '/graphql', '{ headline }', 'application/graphql')
json_response = format_response(response) 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): # 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') # response = client.get('/graphql')
# json_response = format_response(response) # json_response = format_response(response)
# assert json_response == {'errors': [{'message': 'Must provide query string.'}]} # assert json_response == {'errors': [{'message': 'Must provide query string.'}]}

View File

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

View File

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

View File

@ -1,258 +1,49 @@
import inspect import warnings
from functools import total_ordering, wraps
import six from .types.base import FieldType
from .types.definitions import List, NonNull
from graphene.core.scalars import GraphQLSkipField from .types.field import Field
from graphene.core.types import BaseObjectType, InputObjectType from .types.scalars import ID, Boolean, Float, Int, String
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
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 pass
@total_ordering class IntField(DeprecatedField, Int):
class Field(object): pass
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 LazyField(Field): class BooleanField(DeprecatedField, Boolean):
pass
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 TypeField(Field): class IDField(DeprecatedField, ID):
pass
def __init__(self, *args, **kwargs):
super(TypeField, self).__init__(self.field_type, *args, **kwargs)
class StringField(TypeField): class FloatField(DeprecatedField, Float):
field_type = GraphQLString pass
class IntField(TypeField): class ListField(DeprecatedField, List):
field_type = GraphQLInt pass
class BooleanField(TypeField): class NonNullField(DeprecatedField, NonNull):
field_type = GraphQLBoolean pass
class IDField(TypeField): __all__ = ['Field', 'StringField', 'IntField', 'BooleanField',
field_type = GraphQLID 'IDField', 'FloatField', 'ListField', 'NonNullField']
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)

View File

@ -52,7 +52,9 @@ class Options(object):
# Any leftover attributes must be invalid. # Any leftover attributes must be invalid.
if meta_attrs != {}: if meta_attrs != {}:
raise TypeError( 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: else:
self.proxy = False self.proxy = False

View File

@ -1,6 +1,9 @@
import inspect
from collections import OrderedDict from collections import OrderedDict
from graphene import signals 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.executor import Executor
from graphql.core.execution.middlewares.sync import \ from graphql.core.execution.middlewares.sync import \
SynchronousExecutionMiddleware SynchronousExecutionMiddleware
@ -16,10 +19,10 @@ class GraphQLSchema(_GraphQLSchema):
class Schema(object): class Schema(object):
_query = None
_executor = 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_names = {}
self._types = {} self._types = {}
self.mutation = mutation self.mutation = mutation
@ -34,32 +37,24 @@ class Schema(object):
def T(self, object_type): def T(self, object_type):
if not object_type: if not object_type:
return return
if object_type not in self._types: if inspect.isclass(object_type) and issubclass(
internal_type = object_type.internal_type(self) object_type, BaseType) or isinstance(
self._types[object_type] = internal_type object_type, BaseType):
self._types_names[internal_type.name] = object_type if object_type not in self._types:
return self._types[object_type] internal_type = object_type.internal_type(self)
self._types[object_type] = internal_type
@property is_objecttype = inspect.isclass(
def query(self): object_type) and issubclass(object_type, BaseObjectType)
return self._query if is_objecttype:
self.register(object_type)
@query.setter return self._types[object_type]
def query(self, query): else:
self._query = query return object_type
@property
def mutation(self):
return self._mutation
@mutation.setter
def mutation(self, mutation):
self._mutation = mutation
@property @property
def executor(self): def executor(self):
if not self._executor: if not self._executor:
self.executor = Executor( self._executor = Executor(
[SynchronousExecutionMiddleware()], map_type=OrderedDict) [SynchronousExecutionMiddleware()], map_type=OrderedDict)
return self._executor return self._executor
@ -69,25 +64,45 @@ class Schema(object):
@property @property
def schema(self): def schema(self):
if not self._query: if not self.query:
raise Exception('You have to define a base query type') raise Exception('You have to define a base query type')
return GraphQLSchema(self, query=self.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): 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 self._types_names[object_type._meta.type_name] = object_type
return 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): def get_type(self, type_name):
self.schema._build_type_map() self.setup()
if type_name not in self._types_names: 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] return self._types_names[type_name]
@property @property
def types(self): def types(self):
return self._types_names 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() root = root or object()
return self.executor.execute( return self.executor.execute(
self.schema, self.schema,

View File

@ -1,4 +1,3 @@
import graphene import graphene
from graphene.core.schema import Schema from graphene.core.schema import Schema
@ -6,15 +5,15 @@ my_id = 0
class Query(graphene.ObjectType): class Query(graphene.ObjectType):
base = graphene.StringField() base = graphene.String()
class ChangeNumber(graphene.Mutation): class ChangeNumber(graphene.Mutation):
'''Result mutation''' '''Result mutation'''
class Input: class Input:
to = graphene.IntField() to = graphene.Int()
result = graphene.StringField() result = graphene.String()
@classmethod @classmethod
def mutate(cls, instance, args, info): def mutate(cls, instance, args, info):
@ -31,9 +30,7 @@ schema = Schema(query=Query, mutation=MyResultMutation)
def test_mutation_input(): def test_mutation_input():
assert ChangeNumber.input_type assert list(schema.T(ChangeNumber.arguments).keys()) == ['to']
assert ChangeNumber.input_type._meta.type_name == 'ChangeNumberInput'
assert list(ChangeNumber.input_type._meta.fields_map.keys()) == ['to']
def test_execute_mutations(): 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 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 from graphene.core.options import Options
@ -22,7 +20,7 @@ def test_field_added_in_meta():
pass pass
opt.contribute_to_class(ObjectType, '_meta') opt.contribute_to_class(ObjectType, '_meta')
f = StringField() f = Field(None)
f.attname = 'string_field' f.attname = 'string_field'
opt.add_field(f) opt.add_field(f)
assert f in opt.fields 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.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 import graphql
from graphql.core.type import (GraphQLInterfaceType, GraphQLObjectType, from graphql.core.type import GraphQLSchema
GraphQLSchema)
class Character(Interface): class Character(Interface):
name = StringField() name = String()
class Pet(ObjectType): class Pet(ObjectType):
type = StringField(resolve=lambda *_: 'Dog') type = String(resolver=lambda *_: 'Dog')
class Human(Character): class Human(Character):
friends = ListField(Character) friends = List(Character)
pet = Field(Pet) pet = Field(Pet)
def resolve_name(self, *args): def resolve_name(self, *args):
@ -28,8 +27,6 @@ class Human(Character):
def resolve_pet(self, *args): def resolve_pet(self, *args):
return Pet(object()) return Pet(object())
# def resolve_friends(self, *args, **kwargs):
# return 'HEY YOU!'
schema = Schema() schema = Schema()
@ -38,8 +35,8 @@ Human_type = schema.T(Human)
def test_type(): def test_type():
assert Human._meta.fields_map['name'].resolve( assert Human._meta.fields_map['name'].resolver(
Human(object()), None, None) == 'Peter' Human(object()), {}, None) == 'Peter'
def test_query(): def test_query():

View File

@ -1,27 +1,24 @@
from py.test import raises from py.test import raises
from pytest import raises
from graphene import Interface, ObjectType, Schema from graphene import Interface, List, ObjectType, Schema, String
from graphene.core.fields import Field, ListField, StringField from graphene.core.fields import Field
from graphene.core.types.base import LazyType
from graphql.core import graphql from graphql.core import graphql
from graphql.core.type import (GraphQLInterfaceType, GraphQLObjectType,
GraphQLSchema)
from tests.utils import assert_equal_lists from tests.utils import assert_equal_lists
schema = Schema(name='My own schema') schema = Schema(name='My own schema')
class Character(Interface): class Character(Interface):
name = StringField() name = String()
class Pet(ObjectType): class Pet(ObjectType):
type = StringField(resolve=lambda *_: 'Dog') type = String(resolver=lambda *_: 'Dog')
class Human(Character): class Human(Character):
friends = ListField(Character) friends = List(Character)
pet = Field(Pet) pet = Field(Pet)
def resolve_name(self, *args): def resolve_name(self, *args):
@ -95,9 +92,9 @@ def test_query_schema_execute():
def test_schema_get_type_map(): def test_schema_get_type_map():
assert_equal_lists( assert_equal_lists(
schema.schema.get_type_map().keys(), schema.schema.get_type_map().keys(),
['__Field', 'String', 'Pet', 'Character', '__InputValue', '__Directive', ['__Field', 'String', 'Pet', 'Character', '__InputValue',
'__TypeKind', '__Schema', '__Type', 'Human', '__EnumValue', 'Boolean'] '__Directive', '__TypeKind', '__Schema', '__Type', 'Human',
) '__EnumValue', 'Boolean'])
def test_schema_no_query(): def test_schema_no_query():
@ -112,19 +109,19 @@ def test_schema_register():
@schema.register @schema.register
class MyType(ObjectType): class MyType(ObjectType):
type = StringField(resolve=lambda *_: 'Dog') type = String(resolver=lambda *_: 'Dog')
schema.query = MyType schema.query = MyType
assert schema.get_type('MyType') == 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 = Schema(name='My own schema')
@schema.register @schema.register
class MyType(ObjectType): class MyType(ObjectType):
type = StringField(resolve=lambda *_: 'Dog') type = String(resolver=lambda *_: 'Dog')
with raises(Exception) as excinfo: with raises(Exception) as excinfo:
schema.get_type('MyType') schema.get_type('MyType')
@ -135,9 +132,23 @@ def test_schema_introspect():
schema = Schema(name='My own schema') schema = Schema(name='My own schema')
class MyType(ObjectType): class MyType(ObjectType):
type = StringField(resolve=lambda *_: 'Dog') type = String(resolver=lambda *_: 'Dog')
schema.query = MyType schema.query = MyType
introspection = schema.introspect() introspection = schema.introspect()
assert '__schema' in introspection 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 import six
from graphene import signals from graphene import signals
from graphene.core.exceptions import SkipField
from graphene.core.options import Options 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, from graphql.core.type import (GraphQLArgument, GraphQLInputObjectType,
GraphQLInterfaceType, GraphQLObjectType) GraphQLInterfaceType, GraphQLObjectType)
@ -50,10 +54,6 @@ class ObjectTypeMeta(type):
assert not ( assert not (
new_class._meta.is_interface and new_class._meta.is_mutation) new_class._meta.is_interface and new_class._meta.is_mutation)
input_class = None
if new_class._meta.is_mutation:
input_class = attrs.pop('Input', None)
# Add all attributes to the class. # Add all attributes to the class.
for obj_name, obj in attrs.items(): for obj_name, obj in attrs.items():
new_class.add_to_class(obj_name, obj) new_class.add_to_class(obj_name, obj)
@ -62,13 +62,6 @@ class ObjectTypeMeta(type):
assert hasattr( assert hasattr(
new_class, 'mutate'), "All mutations must implement mutate method" new_class, 'mutate'), "All mutations must implement mutate method"
if input_class:
items = dict(input_class.__dict__)
items.pop('__dict__', None)
input_type = type('{}Input'.format(
new_class._meta.type_name), (ObjectType, ), items)
new_class.add_to_class('input_type', input_type)
new_class.add_extra_fields() new_class.add_extra_fields()
new_fields = new_class._meta.local_fields new_fields = new_class._meta.local_fields
@ -87,7 +80,8 @@ class ObjectTypeMeta(type):
# on the base classes (we cannot handle shadowed fields at the # on the base classes (we cannot handle shadowed fields at the
# moment). # moment).
for field in parent_fields: 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( raise Exception(
'Local field %r in class %r (%r) clashes ' 'Local field %r in class %r (%r) clashes '
'with field with similar name from ' 'with field with similar name from '
@ -106,6 +100,9 @@ class ObjectTypeMeta(type):
new_class._meta.interfaces.append(base) new_class._meta.interfaces.append(base)
# new_class._meta.parents.extend(base._meta.parents) # new_class._meta.parents.extend(base._meta.parents)
setattr(new_class, 'NonNull', NonNull(new_class))
setattr(new_class, 'List', List(new_class))
new_class._prepare() new_class._prepare()
return new_class return new_class
@ -119,13 +116,14 @@ class ObjectTypeMeta(type):
def add_to_class(cls, name, value): def add_to_class(cls, name, value):
# We should call the contribute_to_class method only if it's bound # 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) value.contribute_to_class(cls, name)
else: else:
setattr(cls, name, value) setattr(cls, name, value)
class BaseObjectType(object): class BaseObjectType(BaseType):
def __new__(cls, *args, **kwargs): def __new__(cls, *args, **kwargs):
if cls._meta.is_interface: if cls._meta.is_interface:
@ -165,14 +163,16 @@ class BaseObjectType(object):
pass pass
if kwargs: if kwargs:
raise TypeError( 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) signals.post_init.send(self.__class__, instance=self)
@classmethod @classmethod
def fields_as_arguments(cls, schema): def fields_as_arguments(cls, schema):
return OrderedDict([(f.attname, GraphQLArgument(f.internal_type(schema))) return OrderedDict(
for f in cls._meta.fields]) [(f.attname, GraphQLArgument(f.internal_type(schema)))
for f in cls._meta.fields])
@classmethod @classmethod
def resolve_objecttype(cls, schema, instance, *args): def resolve_objecttype(cls, schema, instance, *args):
@ -185,23 +185,32 @@ class BaseObjectType(object):
@classmethod @classmethod
def internal_type(cls, schema): 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: if cls._meta.is_interface:
return GraphQLInterfaceType( return GraphQLInterfaceType(
cls._meta.type_name, cls._meta.type_name,
description=cls._meta.description, description=cls._meta.description,
resolve_type=partial(cls.resolve_type, schema), resolve_type=partial(cls.resolve_type, schema),
fields=fields fields=partial(cls.get_fields, schema)
) )
return GraphQLObjectType( return GraphQLObjectType(
cls._meta.type_name, cls._meta.type_name,
description=cls._meta.description, description=cls._meta.description,
interfaces=[schema.T(i) for i in cls._meta.interfaces], 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) 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)): class Interface(six.with_metaclass(ObjectTypeMeta, BaseObjectType)):
pass pass
@ -212,20 +221,33 @@ class ObjectType(six.with_metaclass(ObjectTypeMeta, BaseObjectType)):
class Mutation(six.with_metaclass(ObjectTypeMeta, BaseObjectType)): class Mutation(six.with_metaclass(ObjectTypeMeta, BaseObjectType)):
@classmethod
def _construct_arguments(cls, items):
return ArgumentsGroup(**items)
@classmethod @classmethod
def get_input_type(cls): def _prepare_class(cls):
return getattr(cls, 'input_type', None) 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): class InputObjectType(ObjectType):
@classmethod @classmethod
def internal_type(cls, schema): def internal_type(cls, schema):
fields = lambda: OrderedDict([(f.name, f.internal_field(schema))
for f in cls._meta.fields])
return GraphQLInputObjectType( return GraphQLInputObjectType(
cls._meta.type_name, cls._meta.type_name,
description=cls._meta.description, 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 py.test import raises
from pytest import raises
from graphene.core.fields import IntField, StringField
from graphene.core.schema import Schema 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, from graphql.core.execution.middlewares.utils import (resolver_has_tag,
tag_resolver) tag_resolver)
from graphql.core.type import GraphQLInterfaceType, GraphQLObjectType from graphql.core.type import GraphQLInterfaceType, GraphQLObjectType
@ -11,7 +9,7 @@ from graphql.core.type import GraphQLInterfaceType, GraphQLObjectType
class Character(Interface): class Character(Interface):
'''Character description''' '''Character description'''
name = StringField() name = String()
class Meta: class Meta:
type_name = 'core_Character' type_name = 'core_Character'
@ -19,7 +17,7 @@ class Character(Interface):
class Human(Character): class Human(Character):
'''Human description''' '''Human description'''
friends = StringField() friends = String()
class Meta: class Meta:
type_name = 'core_Human' type_name = 'core_Human'
@ -66,9 +64,6 @@ def test_object_type():
assert isinstance(object_type, GraphQLObjectType) assert isinstance(object_type, GraphQLObjectType)
assert object_type.description == 'Human description' assert object_type.description == 'Human description'
assert list(object_type.get_fields().keys()) == ['name', 'friends'] assert list(object_type.get_fields().keys()) == ['name', 'friends']
# assert object_type.get_fields() == {'name': Human._meta.fields_map['name'].internal_field(
# schema), 'friends':
# Human._meta.fields_map['friends'].internal_field(schema)}
assert object_type.get_interfaces() == [schema.T(Character)] assert object_type.get_interfaces() == [schema.T(Character)]
assert Human._meta.fields_map['name'].object_type == Human assert Human._meta.fields_map['name'].object_type == Human
@ -97,7 +92,7 @@ def test_object_type_container_too_many_args():
def test_field_clashes(): def test_field_clashes():
with raises(Exception) as excinfo: with raises(Exception) as excinfo:
class Droid(Character): class Droid(Character):
name = IntField() name = Int()
assert 'clashes' in str(excinfo.value) assert 'clashes' in str(excinfo.value)
@ -108,12 +103,26 @@ def test_fields_inherited_should_be_different():
def test_field_mantain_resolver_tags(): def test_field_mantain_resolver_tags():
class Droid(Character): class Droid(Character):
name = StringField() name = String()
def resolve_name(self, *args): def resolve_name(self, *args):
return 'My Droid' return 'My Droid'
tag_resolver(resolve_name, 'test') 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') 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 collections import Iterable
from graphene.core.fields import Field, IDField from graphene.core.fields import Field
from graphql.core.type import GraphQLArgument, GraphQLID, GraphQLNonNull from graphene.core.types.scalars import ID, Int, String
from graphql_relay.connection.arrayconnection import connection_from_list 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 from graphql_relay.node.node import from_global_id
class ConnectionField(Field): 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): connection_type=None, edge_type=None, **kwargs):
super(ConnectionField, self).__init__(field_type, resolve=resolve, super(
args=connection_args, ConnectionField,
description=description, **kwargs) self).__init__(
field_type,
resolver=resolver,
before=String(),
after=String(),
first=Int(),
last=Int(),
description=description,
**kwargs)
self.connection_type = connection_type self.connection_type = connection_type
self.edge_type = edge_type self.edge_type = edge_type
def wrap_resolved(self, value, instance, args, info): def wrap_resolved(self, value, instance, args, info):
return value return value
def resolve(self, instance, args, info): def resolver(self, instance, args, info):
from graphene.relay.types import PageInfo from graphene.relay.types import PageInfo
schema = info.schema.graphene_schema schema = info.schema.graphene_schema
resolved = super(ConnectionField, self).resolve(instance, args, info) resolved = super(ConnectionField, self).resolver(instance, args, info)
if resolved: if resolved:
resolved = self.wrap_resolved(resolved, instance, args, info) resolved = self.wrap_resolved(resolved, instance, args, info)
assert isinstance( assert isinstance(
resolved, Iterable), 'Resolved value from the connection field have to be iterable' 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) connection_type = self.get_connection_type(node)
edge_type = self.get_edge_type(node) edge_type = self.get_edge_type(node)
connection = connection_from_list(resolved, args, connection_type=connection_type, connection = connection_from_list(
edge_type=edge_type, pageinfo_type=PageInfo) resolved, args, connection_type=connection_type,
edge_type=edge_type, pageinfo_type=PageInfo)
connection.set_connection_data(resolved) connection.set_connection_data(resolved)
return connection return connection
@ -47,25 +56,24 @@ class ConnectionField(Field):
def get_edge_type(self, node): def get_edge_type(self, node):
return self.edge_type or node.get_edge_type() return self.edge_type or node.get_edge_type()
def internal_type(self, schema): def get_type(self, schema):
from graphene.relay.utils import is_node 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.' assert is_node(node), 'Only nodes have connections.'
schema.register(node) schema.register(node)
connection_type = self.get_connection_type(node) connection_type = self.get_connection_type(node)
return schema.T(connection_type) return connection_type
class NodeField(Field): class NodeField(Field):
def __init__(self, object_type=None, *args, **kwargs): def __init__(self, object_type=None, *args, **kwargs):
from graphene.relay.types import Node 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.field_object_type = object_type
self.args['id'] = GraphQLArgument(
GraphQLNonNull(GraphQLID),
description='The ID of an object'
)
def id_fetcher(self, global_id, info): def id_fetcher(self, global_id, info):
from graphene.relay.utils import is_node from graphene.relay.utils import is_node
@ -79,20 +87,23 @@ class NodeField(Field):
return object_type.get_node(_id) return object_type.get_node(_id)
def resolve(self, instance, args, info): def resolver(self, instance, args, info):
global_id = args.get('id') global_id = args.get('id')
return self.id_fetcher(global_id, info) return self.id_fetcher(global_id, info)
class GlobalIDField(IDField): class GlobalIDField(Field):
'''The ID of an object''' '''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 from graphene.relay.utils import is_node, is_node_type
in_node = is_node(cls) or is_node_type(cls) in_node = is_node(cls) or is_node_type(cls)
assert in_node, 'GlobalIDField could only be inside a Node, but got %r' % 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): def resolver(self, instance, args, info):
return self.object_type.to_global_id(instance, args, info) return instance.to_global_id()

View File

View File

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

View File

@ -6,22 +6,22 @@ schema = graphene.Schema()
class MyConnection(relay.Connection): class MyConnection(relay.Connection):
my_custom_field = graphene.StringField( my_custom_field = graphene.String(
resolve=lambda instance, *_: 'Custom') resolver=lambda instance, *_: 'Custom')
class MyNode(relay.Node): class MyNode(relay.Node):
name = graphene.StringField() name = graphene.String()
@classmethod @classmethod
def get_node(cls, id): def get_node(cls, id):
return MyNode(name='mo') return MyNode(id=id, name='mo')
class Query(graphene.ObjectType): class Query(graphene.ObjectType):
my_node = relay.NodeField(MyNode) my_node = relay.NodeField(MyNode)
all_my_nodes = relay.ConnectionField( 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): def resolve_all_my_nodes(self, args, info):
custom_arg = args.get('customArg') custom_arg = args.get('customArg')
@ -35,6 +35,7 @@ def test_nodefield_query():
query = ''' query = '''
query RebelsShipsQuery { query RebelsShipsQuery {
myNode(id:"TXlOb2RlOjE=") { myNode(id:"TXlOb2RlOjE=") {
id
name name
}, },
allMyNodes (customArg:"1") { allMyNodes (customArg:"1") {
@ -52,6 +53,7 @@ def test_nodefield_query():
''' '''
expected = { expected = {
'myNode': { 'myNode': {
'id': 'TXlOb2RlOjE=',
'name': 'mo' 'name': 'mo'
}, },
'allMyNodes': { 'allMyNodes': {
@ -73,5 +75,6 @@ def test_nodefield_query():
def test_nodeidfield(): def test_nodeidfield():
id_field = MyNode._meta.fields_map['id'] id_field = MyNode._meta.fields_map['id']
assert isinstance(id_field.internal_field(schema).type, GraphQLNonNull) id_field_type = schema.T(id_field)
assert id_field.internal_field(schema).type.of_type == GraphQLID 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): class OtherNode(relay.Node):
name = graphene.StringField() name = graphene.String()
@classmethod @classmethod
def get_node(cls, id): def get_node(cls, id):
@ -17,7 +17,7 @@ class OtherNode(relay.Node):
def test_field_no_contributed_raises_error(): def test_field_no_contributed_raises_error():
with raises(Exception) as excinfo: with raises(Exception) as excinfo:
class Part(relay.Node): class Part(relay.Node):
x = graphene.StringField() x = graphene.String()
assert 'get_node' in str(excinfo.value) 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 (Boolean, Field, InputObjectType, Interface,
from graphene.core.types import (InputObjectType, Interface, Mutation, List, Mutation, ObjectType, String)
ObjectType) 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.relay.fields import GlobalIDField
from graphene.utils import memoize from graphene.utils import memoize
from graphql_relay.node.node import to_global_id from graphql_relay.node.node import to_global_id
class PageInfo(ObjectType): class PageInfo(ObjectType):
has_next_page = BooleanField( has_next_page = Boolean(
required=True, description='When paginating forwards, are there more items?') required=True,
has_previous_page = BooleanField( description='When paginating forwards, are there more items?')
required=True, description='When paginating backwards, are there more items?') has_previous_page = Boolean(
start_cursor = StringField( required=True,
description='When paginating backwards, are there more items?')
start_cursor = String(
description='When paginating backwards, the cursor to continue.') description='When paginating backwards, the cursor to continue.')
end_cursor = StringField( end_cursor = String(
description='When paginating forwards, the cursor to continue.') description='When paginating forwards, the cursor to continue.')
@ -22,9 +26,9 @@ class Edge(ObjectType):
class Meta: class Meta:
type_name = 'DefaultEdge' 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') description='The item at the end of the edge')
cursor = StringField( cursor = String(
required=True, description='A cursor for use in pagination') required=True, description='A cursor for use in pagination')
@classmethod @classmethod
@ -32,7 +36,10 @@ class Edge(ObjectType):
def for_node(cls, node): def for_node(cls, node):
from graphene.relay.utils import is_node from graphene.relay.utils import is_node
assert is_node(node), 'ObjectTypes in a edge have to be Nodes' 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): class Connection(ObjectType):
@ -42,8 +49,8 @@ class Connection(ObjectType):
page_info = Field(PageInfo, required=True, page_info = Field(PageInfo, required=True,
description='The Information to aid in pagination') description='The Information to aid in pagination')
edges = ListField(lambda field: field.object_type.edge_type, edges = List(LazyType(lambda object_type: object_type.edge_type),
description='Information to aid in pagination.') description='Information to aid in pagination.')
_connection_data = None _connection_data = None
@ -53,7 +60,10 @@ class Connection(ObjectType):
from graphene.relay.utils import is_node from graphene.relay.utils import is_node
edge_type = edge_type or Edge edge_type = edge_type or Edge
assert is_node(node), 'ObjectTypes in a connection have to be Nodes' 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): def set_connection_data(self, data):
self._connection_data = data self._connection_data = data
@ -71,10 +81,9 @@ class BaseNode(object):
assert hasattr( assert hasattr(
cls, 'get_node'), 'get_node classmethod not found in %s Node' % cls cls, 'get_node'), 'get_node classmethod not found in %s Node' % cls
@classmethod def to_global_id(self):
def to_global_id(cls, instance, args, info): type_name = self._meta.type_name
type_name = cls._meta.type_name return to_global_id(type_name, self.id)
return to_global_id(type_name, instance.id)
connection_type = Connection connection_type = Connection
edge_type = Edge edge_type = Edge
@ -90,31 +99,24 @@ class BaseNode(object):
class Node(BaseNode, Interface): class Node(BaseNode, Interface):
'''An object with an ID''' '''An object with an ID'''
id = GlobalIDField() id = GlobalIDField(required=True)
class MutationInputType(InputObjectType): class MutationInputType(InputObjectType):
client_mutation_id = StringField(required=True) client_mutation_id = String(required=True)
class ClientIDMutation(Mutation): class ClientIDMutation(Mutation):
client_mutation_id = StringField(required=True) client_mutation_id = String(required=True)
@classmethod @classmethod
def _prepare_class(cls): def _construct_arguments(cls, items):
input_type = getattr(cls, 'input_type', None) assert hasattr(
if input_type: cls, 'mutate_and_get_payload'), 'You have to implement mutate_and_get_payload'
assert hasattr( new_input_type = type('{}Input'.format(
cls, 'mutate_and_get_payload'), 'You have to implement mutate_and_get_payload' cls._meta.type_name), (MutationInputType, ), items)
new_input_inner_type = type('{}InnerInput'.format( cls.add_to_class('input_type', new_input_type)
cls._meta.type_name), (MutationInputType, input_type, ), {}) return ArgumentsGroup(input=NonNull(new_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 @classmethod
def mutate(cls, instance, args, info): def mutate(cls, instance, args, info):

View File

@ -2,7 +2,8 @@ from graphene.relay.types import BaseNode
def is_node(object_type): 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): 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 p.get('three_or_for') == 3
assert 'inside' in p assert 'inside' in p
assert 'other_camel_case' in p['inside'] assert 'other_camel_case' in p['inside']
assert sorted(p.items()) == sorted(list([('inside', ProxySnakeDict({'other_camel_case': 3})), assert sorted(
('none', None), p.items()) == sorted(
('three_or_for', 3), list(
('two', 2), [('inside', ProxySnakeDict({'other_camel_case': 3})),
('one', 1)])) ('none', None),
('three_or_for', 3),
('two', 2),
('one', 1)]))
def test_proxy_snake_dict_as_kwargs(): def test_proxy_snake_dict_as_kwargs():

View File

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

View File

@ -62,6 +62,7 @@ setup(
tests_require=[ tests_require=[
'pytest>=2.7.2', 'pytest>=2.7.2',
'pytest-django', 'pytest-django',
'mock',
], ],
extras_require={ extras_require={
'django': [ '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 = [ INSTALLED_APPS = [
'examples.starwars_django', 'examples.starwars_django',
'tests.contrib_django',
] ]
DATABASES = { DATABASES = {