mirror of
https://github.com/graphql-python/graphene.git
synced 2024-11-23 01:56:54 +03:00
Merge remote-tracking branch 'graphql-python/0.4.0' into use-graphql-django-view
This commit is contained in:
commit
4e08819094
|
@ -28,9 +28,9 @@ Here is one example for get you started:
|
|||
|
||||
```python
|
||||
class Query(graphene.ObjectType):
|
||||
hello = graphene.StringField(description='A typical hello world')
|
||||
ping = graphene.StringField(description='Ping someone',
|
||||
to=graphene.Argument(graphene.String))
|
||||
hello = graphene.String(description='A typical hello world')
|
||||
ping = graphene.String(description='Ping someone',
|
||||
to=graphene.String())
|
||||
|
||||
def resolve_hello(self, args, info):
|
||||
return 'World'
|
||||
|
|
|
@ -35,9 +35,9 @@ Here is one example for get you started:
|
|||
.. code:: python
|
||||
|
||||
class Query(graphene.ObjectType):
|
||||
hello = graphene.StringField(description='A typical hello world')
|
||||
ping = graphene.StringField(description='Ping someone',
|
||||
to=graphene.Argument(graphene.String))
|
||||
hello = graphene.String(description='A typical hello world')
|
||||
ping = graphene.String(description='Ping someone',
|
||||
to=graphene.String())
|
||||
|
||||
def resolve_hello(self, args, info):
|
||||
return 'World'
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
#!/bin/bash
|
||||
|
||||
autoflake ./ -r --remove-unused-variables --remove-all-unused-imports --in-place
|
||||
autoflake ./ -r --remove-unused-variables --remove-all-unused-imports --in-place
|
||||
autopep8 ./ -r --in-place --experimental --aggressive --max-line-length 120
|
||||
isort -rc .
|
||||
|
|
|
@ -12,10 +12,10 @@ Episode = graphene.Enum('Episode', dict(
|
|||
|
||||
|
||||
class Character(graphene.Interface):
|
||||
id = graphene.IDField()
|
||||
name = graphene.StringField()
|
||||
friends = graphene.ListField('self')
|
||||
appears_in = graphene.ListField(Episode)
|
||||
id = graphene.ID()
|
||||
name = graphene.String()
|
||||
friends = graphene.List('Character')
|
||||
appears_in = graphene.List(Episode)
|
||||
|
||||
def resolve_friends(self, args, *_):
|
||||
# The character friends is a list of strings
|
||||
|
@ -23,11 +23,11 @@ class Character(graphene.Interface):
|
|||
|
||||
|
||||
class Human(Character):
|
||||
home_planet = graphene.StringField()
|
||||
home_planet = graphene.String()
|
||||
|
||||
|
||||
class Droid(Character):
|
||||
primary_function = graphene.StringField()
|
||||
primary_function = graphene.String()
|
||||
|
||||
|
||||
class Query(graphene.ObjectType):
|
||||
|
@ -35,10 +35,10 @@ class Query(graphene.ObjectType):
|
|||
episode=graphene.Argument(Episode)
|
||||
)
|
||||
human = graphene.Field(Human,
|
||||
id=graphene.Argument(graphene.String)
|
||||
id=graphene.String()
|
||||
)
|
||||
droid = graphene.Field(Droid,
|
||||
id=graphene.Argument(graphene.String)
|
||||
id=graphene.String()
|
||||
)
|
||||
|
||||
@resolve_only_args
|
||||
|
|
|
@ -12,6 +12,7 @@ schema = graphene.Schema(name='Starwars Django Relay Schema')
|
|||
|
||||
|
||||
class Ship(DjangoNode):
|
||||
|
||||
class Meta:
|
||||
model = ShipModel
|
||||
|
||||
|
@ -21,11 +22,13 @@ class Ship(DjangoNode):
|
|||
|
||||
|
||||
class Character(DjangoObjectType):
|
||||
|
||||
class Meta:
|
||||
model = CharacterModel
|
||||
|
||||
|
||||
class Faction(DjangoNode):
|
||||
|
||||
class Meta:
|
||||
model = FactionModel
|
||||
|
||||
|
@ -35,9 +38,10 @@ class Faction(DjangoNode):
|
|||
|
||||
|
||||
class IntroduceShip(relay.ClientIDMutation):
|
||||
|
||||
class Input:
|
||||
ship_name = graphene.StringField(required=True)
|
||||
faction_id = graphene.StringField(required=True)
|
||||
ship_name = graphene.String(required=True)
|
||||
faction_id = graphene.String(required=True)
|
||||
|
||||
ship = graphene.Field(Ship)
|
||||
faction = graphene.Field(Faction)
|
||||
|
@ -48,7 +52,7 @@ class IntroduceShip(relay.ClientIDMutation):
|
|||
faction_id = input.get('faction_id')
|
||||
ship = create_ship(ship_name, faction_id)
|
||||
faction = get_faction(faction_id)
|
||||
return IntroduceShip(ship=ship, faction=faction)
|
||||
return IntroduceShip(ship=Ship(ship), faction=Faction(faction))
|
||||
|
||||
|
||||
class Query(graphene.ObjectType):
|
||||
|
|
|
@ -8,7 +8,7 @@ schema = graphene.Schema(name='Starwars Relay Schema')
|
|||
|
||||
class Ship(relay.Node):
|
||||
'''A ship in the Star Wars saga'''
|
||||
name = graphene.StringField(description='The name of the ship.')
|
||||
name = graphene.String(description='The name of the ship.')
|
||||
|
||||
@classmethod
|
||||
def get_node(cls, id):
|
||||
|
@ -17,7 +17,7 @@ class Ship(relay.Node):
|
|||
|
||||
class Faction(relay.Node):
|
||||
'''A faction in the Star Wars saga'''
|
||||
name = graphene.StringField(description='The name of the faction.')
|
||||
name = graphene.String(description='The name of the faction.')
|
||||
ships = relay.ConnectionField(
|
||||
Ship, description='The ships used by the faction.')
|
||||
|
||||
|
@ -34,8 +34,8 @@ class Faction(relay.Node):
|
|||
class IntroduceShip(relay.ClientIDMutation):
|
||||
|
||||
class Input:
|
||||
ship_name = graphene.StringField(required=True)
|
||||
faction_id = graphene.StringField(required=True)
|
||||
ship_name = graphene.String(required=True)
|
||||
faction_id = graphene.String(required=True)
|
||||
|
||||
ship = graphene.Field(Ship)
|
||||
faction = graphene.Field(Faction)
|
||||
|
|
|
@ -1,9 +1,5 @@
|
|||
from graphql.core.type import (
|
||||
GraphQLEnumType as Enum,
|
||||
GraphQLArgument as Argument,
|
||||
GraphQLString as String,
|
||||
GraphQLInt as Int,
|
||||
GraphQLID as ID
|
||||
GraphQLEnumType as Enum
|
||||
)
|
||||
|
||||
from graphene import signals
|
||||
|
@ -14,12 +10,24 @@ from graphene.core.schema import (
|
|||
|
||||
from graphene.core.types import (
|
||||
ObjectType,
|
||||
InputObjectType,
|
||||
Interface,
|
||||
Mutation,
|
||||
BaseType,
|
||||
LazyType,
|
||||
Argument,
|
||||
Field,
|
||||
InputField,
|
||||
String,
|
||||
Int,
|
||||
Boolean,
|
||||
ID,
|
||||
Float,
|
||||
List,
|
||||
NonNull
|
||||
)
|
||||
|
||||
from graphene.core.fields import (
|
||||
Field,
|
||||
StringField,
|
||||
IntField,
|
||||
BooleanField,
|
||||
|
@ -33,7 +41,31 @@ from graphene.decorators import (
|
|||
resolve_only_args
|
||||
)
|
||||
|
||||
__all__ = ['Enum', 'Argument', 'String', 'Int', 'ID', 'signals', 'Schema',
|
||||
'ObjectType', 'Interface', 'Mutation', 'Field', 'StringField',
|
||||
'IntField', 'BooleanField', 'IDField', 'ListField', 'NonNullField',
|
||||
'FloatField', 'resolve_only_args']
|
||||
__all__ = [
|
||||
'Enum',
|
||||
'Argument',
|
||||
'String',
|
||||
'Int',
|
||||
'Boolean',
|
||||
'Float',
|
||||
'ID',
|
||||
'List',
|
||||
'NonNull',
|
||||
'signals',
|
||||
'Schema',
|
||||
'BaseType',
|
||||
'LazyType',
|
||||
'ObjectType',
|
||||
'InputObjectType',
|
||||
'Interface',
|
||||
'Mutation',
|
||||
'Field',
|
||||
'InputField',
|
||||
'StringField',
|
||||
'IntField',
|
||||
'BooleanField',
|
||||
'IDField',
|
||||
'ListField',
|
||||
'NonNullField',
|
||||
'FloatField',
|
||||
'resolve_only_args']
|
||||
|
|
|
@ -3,8 +3,7 @@ from singledispatch import singledispatch
|
|||
|
||||
from graphene.contrib.django.fields import (ConnectionOrListField,
|
||||
DjangoModelField)
|
||||
from graphene.core.fields import (BooleanField, FloatField, IDField, IntField,
|
||||
StringField)
|
||||
from graphene.core.types.scalars import ID, Boolean, Float, Int, String
|
||||
|
||||
try:
|
||||
UUIDField = models.UUIDField
|
||||
|
@ -17,7 +16,8 @@ except AttributeError:
|
|||
@singledispatch
|
||||
def convert_django_field(field):
|
||||
raise Exception(
|
||||
"Don't know how to convert the Django field %s (%s)" % (field, field.__class__))
|
||||
"Don't know how to convert the Django field %s (%s)" %
|
||||
(field, field.__class__))
|
||||
|
||||
|
||||
@convert_django_field.register(models.DateField)
|
||||
|
@ -28,12 +28,12 @@ def convert_django_field(field):
|
|||
@convert_django_field.register(models.URLField)
|
||||
@convert_django_field.register(UUIDField)
|
||||
def convert_field_to_string(field):
|
||||
return StringField(description=field.help_text)
|
||||
return String(description=field.help_text)
|
||||
|
||||
|
||||
@convert_django_field.register(models.AutoField)
|
||||
def convert_field_to_id(field):
|
||||
return IDField(description=field.help_text)
|
||||
return ID(description=field.help_text)
|
||||
|
||||
|
||||
@convert_django_field.register(models.PositiveIntegerField)
|
||||
|
@ -42,23 +42,23 @@ def convert_field_to_id(field):
|
|||
@convert_django_field.register(models.BigIntegerField)
|
||||
@convert_django_field.register(models.IntegerField)
|
||||
def convert_field_to_int(field):
|
||||
return IntField(description=field.help_text)
|
||||
return Int(description=field.help_text)
|
||||
|
||||
|
||||
@convert_django_field.register(models.BooleanField)
|
||||
def convert_field_to_boolean(field):
|
||||
return BooleanField(description=field.help_text, required=True)
|
||||
return Boolean(description=field.help_text, required=True)
|
||||
|
||||
|
||||
@convert_django_field.register(models.NullBooleanField)
|
||||
def convert_field_to_nullboolean(field):
|
||||
return BooleanField(description=field.help_text)
|
||||
return Boolean(description=field.help_text)
|
||||
|
||||
|
||||
@convert_django_field.register(models.DecimalField)
|
||||
@convert_django_field.register(models.FloatField)
|
||||
def convert_field_to_float(field):
|
||||
return FloatField(description=field.help_text)
|
||||
return Float(description=field.help_text)
|
||||
|
||||
|
||||
@convert_django_field.register(models.ManyToManyField)
|
||||
|
|
|
@ -1,6 +1,9 @@
|
|||
from graphene import relay
|
||||
from graphene.contrib.django.utils import get_type_for_model, lazy_map
|
||||
from graphene.core.fields import Field, LazyField, ListField
|
||||
from graphene.core.exceptions import SkipField
|
||||
from graphene.core.fields import Field
|
||||
from graphene.core.types.base import FieldType
|
||||
from graphene.core.types.definitions import List
|
||||
from graphene.relay.utils import is_node
|
||||
|
||||
|
||||
|
@ -8,60 +11,54 @@ class DjangoConnectionField(relay.ConnectionField):
|
|||
|
||||
def wrap_resolved(self, value, instance, args, info):
|
||||
schema = info.schema.graphene_schema
|
||||
return lazy_map(value, self.get_object_type(schema))
|
||||
return lazy_map(value, self.type.get_object_type(schema))
|
||||
|
||||
|
||||
class LazyListField(ListField):
|
||||
class LazyListField(Field):
|
||||
|
||||
def resolve(self, instance, args, info):
|
||||
def get_type(self, schema):
|
||||
return List(self.type)
|
||||
|
||||
def resolver(self, instance, args, info):
|
||||
schema = info.schema.graphene_schema
|
||||
resolved = super(LazyListField, self).resolve(instance, args, info)
|
||||
resolved = super(LazyListField, self).resolver(instance, args, info)
|
||||
return lazy_map(resolved, self.get_object_type(schema))
|
||||
|
||||
|
||||
class ConnectionOrListField(LazyField):
|
||||
class ConnectionOrListField(Field):
|
||||
|
||||
def get_field(self, schema):
|
||||
model_field = self.field_type
|
||||
def internal_type(self, schema):
|
||||
model_field = self.type
|
||||
field_object_type = model_field.get_object_type(schema)
|
||||
if is_node(field_object_type):
|
||||
field = DjangoConnectionField(model_field)
|
||||
else:
|
||||
field = LazyListField(model_field)
|
||||
field.contribute_to_class(self.object_type, self.name)
|
||||
return field
|
||||
return field.internal_type(schema)
|
||||
|
||||
|
||||
class DjangoModelField(Field):
|
||||
class DjangoModelField(FieldType):
|
||||
|
||||
def __init__(self, model, *args, **kwargs):
|
||||
super(DjangoModelField, self).__init__(None, *args, **kwargs)
|
||||
self.model = model
|
||||
|
||||
def resolve(self, instance, args, info):
|
||||
resolved = super(DjangoModelField, self).resolve(instance, args, info)
|
||||
schema = info.schema.graphene_schema
|
||||
_type = self.get_object_type(schema)
|
||||
assert _type, ("Field %s cannot be retrieved as the "
|
||||
"ObjectType is not registered by the schema" % (
|
||||
self.attname
|
||||
))
|
||||
return _type(resolved)
|
||||
super(DjangoModelField, self).__init__(*args, **kwargs)
|
||||
|
||||
def internal_type(self, schema):
|
||||
_type = self.get_object_type(schema)
|
||||
if not _type and self.object_type._meta.only_fields:
|
||||
if not _type and self.parent._meta.only_fields:
|
||||
raise Exception(
|
||||
"Model %r is not accessible by the schema. "
|
||||
"You can either register the type manually "
|
||||
"using @schema.register. "
|
||||
"Or disable the field %s in %s" % (
|
||||
"Or disable the field in %s" % (
|
||||
self.model,
|
||||
self.attname,
|
||||
self.object_type
|
||||
self.parent,
|
||||
)
|
||||
)
|
||||
return schema.T(_type) or Field.SKIP
|
||||
if not _type:
|
||||
raise SkipField()
|
||||
return schema.T(_type)
|
||||
|
||||
def get_object_type(self, schema):
|
||||
return get_type_for_model(schema, self.model)
|
||||
|
|
|
@ -32,6 +32,7 @@ class DjangoOptions(Options):
|
|||
return
|
||||
if not self.model:
|
||||
raise Exception(
|
||||
'Django ObjectType %s must have a model in the Meta class attr' % cls)
|
||||
'Django ObjectType %s must have a model in the Meta class attr' %
|
||||
cls)
|
||||
elif not inspect.isclass(self.model) or not issubclass(self.model, models.Model):
|
||||
raise Exception('Provided model in %s is not a Django model' % cls)
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
|
||||
from django.db import models
|
||||
from py.test import raises
|
||||
from pytest import raises
|
||||
|
||||
import graphene
|
||||
from graphene.contrib.django.converter import convert_django_field
|
||||
|
@ -15,8 +14,9 @@ def assert_conversion(django_field, graphene_field, *args):
|
|||
field = django_field(*args, help_text='Custom Help Text')
|
||||
graphene_type = convert_django_field(field)
|
||||
assert isinstance(graphene_type, graphene_field)
|
||||
assert graphene_type.description == 'Custom Help Text'
|
||||
return graphene_type
|
||||
field = graphene_type.as_field()
|
||||
assert field.description == 'Custom Help Text'
|
||||
return field
|
||||
|
||||
|
||||
def test_should_unknown_django_field_raise_exception():
|
||||
|
@ -26,86 +26,86 @@ def test_should_unknown_django_field_raise_exception():
|
|||
|
||||
|
||||
def test_should_date_convert_string():
|
||||
assert_conversion(models.DateField, graphene.StringField)
|
||||
assert_conversion(models.DateField, graphene.String)
|
||||
|
||||
|
||||
def test_should_char_convert_string():
|
||||
assert_conversion(models.CharField, graphene.StringField)
|
||||
assert_conversion(models.CharField, graphene.String)
|
||||
|
||||
|
||||
def test_should_text_convert_string():
|
||||
assert_conversion(models.TextField, graphene.StringField)
|
||||
assert_conversion(models.TextField, graphene.String)
|
||||
|
||||
|
||||
def test_should_email_convert_string():
|
||||
assert_conversion(models.EmailField, graphene.StringField)
|
||||
assert_conversion(models.EmailField, graphene.String)
|
||||
|
||||
|
||||
def test_should_slug_convert_string():
|
||||
assert_conversion(models.SlugField, graphene.StringField)
|
||||
assert_conversion(models.SlugField, graphene.String)
|
||||
|
||||
|
||||
def test_should_url_convert_string():
|
||||
assert_conversion(models.URLField, graphene.StringField)
|
||||
assert_conversion(models.URLField, graphene.String)
|
||||
|
||||
|
||||
def test_should_auto_convert_id():
|
||||
assert_conversion(models.AutoField, graphene.IDField)
|
||||
assert_conversion(models.AutoField, graphene.ID)
|
||||
|
||||
|
||||
def test_should_positive_integer_convert_int():
|
||||
assert_conversion(models.PositiveIntegerField, graphene.IntField)
|
||||
assert_conversion(models.PositiveIntegerField, graphene.Int)
|
||||
|
||||
|
||||
def test_should_positive_small_convert_int():
|
||||
assert_conversion(models.PositiveSmallIntegerField, graphene.IntField)
|
||||
assert_conversion(models.PositiveSmallIntegerField, graphene.Int)
|
||||
|
||||
|
||||
def test_should_small_integer_convert_int():
|
||||
assert_conversion(models.SmallIntegerField, graphene.IntField)
|
||||
assert_conversion(models.SmallIntegerField, graphene.Int)
|
||||
|
||||
|
||||
def test_should_big_integer_convert_int():
|
||||
assert_conversion(models.BigIntegerField, graphene.IntField)
|
||||
assert_conversion(models.BigIntegerField, graphene.Int)
|
||||
|
||||
|
||||
def test_should_integer_convert_int():
|
||||
assert_conversion(models.IntegerField, graphene.IntField)
|
||||
assert_conversion(models.IntegerField, graphene.Int)
|
||||
|
||||
|
||||
def test_should_boolean_convert_boolean():
|
||||
field = assert_conversion(models.BooleanField, graphene.BooleanField)
|
||||
field = assert_conversion(models.BooleanField, graphene.Boolean)
|
||||
assert field.required is True
|
||||
|
||||
|
||||
def test_should_nullboolean_convert_boolean():
|
||||
field = assert_conversion(models.NullBooleanField, graphene.BooleanField)
|
||||
field = assert_conversion(models.NullBooleanField, graphene.Boolean)
|
||||
assert field.required is False
|
||||
|
||||
|
||||
def test_should_float_convert_float():
|
||||
assert_conversion(models.FloatField, graphene.FloatField)
|
||||
assert_conversion(models.FloatField, graphene.Float)
|
||||
|
||||
|
||||
def test_should_manytomany_convert_connectionorlist():
|
||||
graphene_type = convert_django_field(Reporter._meta.local_many_to_many[0])
|
||||
assert isinstance(graphene_type, ConnectionOrListField)
|
||||
assert isinstance(graphene_type.field_type, DjangoModelField)
|
||||
assert graphene_type.field_type.model == Reporter
|
||||
assert isinstance(graphene_type.type, DjangoModelField)
|
||||
assert graphene_type.type.model == Reporter
|
||||
|
||||
|
||||
def test_should_manytoone_convert_connectionorlist():
|
||||
graphene_type = convert_django_field(Reporter.articles.related)
|
||||
assert isinstance(graphene_type, ConnectionOrListField)
|
||||
assert isinstance(graphene_type.field_type, DjangoModelField)
|
||||
assert graphene_type.field_type.model == Article
|
||||
assert isinstance(graphene_type.type, DjangoModelField)
|
||||
assert graphene_type.type.model == Article
|
||||
|
||||
|
||||
def test_should_onetoone_convert_model():
|
||||
field = assert_conversion(models.OneToOneField, DjangoModelField, Article)
|
||||
assert field.model == Article
|
||||
assert field.type.model == Article
|
||||
|
||||
|
||||
def test_should_foreignkey_convert_model():
|
||||
field = assert_conversion(models.ForeignKey, DjangoModelField, Article)
|
||||
assert field.model == Article
|
||||
assert field.type.model == Article
|
|
@ -1,40 +1,21 @@
|
|||
|
||||
from py.test import raises
|
||||
from pytest import raises
|
||||
|
||||
import graphene
|
||||
from graphene import relay
|
||||
from graphene.contrib.django import DjangoNode, DjangoObjectType
|
||||
from tests.utils import assert_equal_lists
|
||||
|
||||
from .models import Article, Reporter
|
||||
|
||||
|
||||
def test_should_raise_if_no_model():
|
||||
with raises(Exception) as excinfo:
|
||||
class Character1(DjangoObjectType):
|
||||
pass
|
||||
assert 'model in the Meta' in str(excinfo.value)
|
||||
|
||||
|
||||
def test_should_raise_if_model_is_invalid():
|
||||
with raises(Exception) as excinfo:
|
||||
class Character2(DjangoObjectType):
|
||||
|
||||
class Meta:
|
||||
model = 1
|
||||
assert 'not a Django model' in str(excinfo.value)
|
||||
|
||||
|
||||
def test_should_raise_if_model_is_invalid():
|
||||
with raises(Exception) as excinfo:
|
||||
class ReporterTypeError(DjangoObjectType):
|
||||
def test_should_query_only_fields():
|
||||
with raises(Exception):
|
||||
class ReporterType(DjangoObjectType):
|
||||
|
||||
class Meta:
|
||||
model = Reporter
|
||||
only_fields = ('articles', )
|
||||
|
||||
schema = graphene.Schema(query=ReporterTypeError)
|
||||
schema = graphene.Schema(query=ReporterType)
|
||||
query = '''
|
||||
query ReporterQuery {
|
||||
articles
|
||||
|
@ -44,24 +25,13 @@ def test_should_raise_if_model_is_invalid():
|
|||
assert not result.errors
|
||||
|
||||
|
||||
def test_should_map_fields_correctly():
|
||||
class ReporterType2(DjangoObjectType):
|
||||
|
||||
class Meta:
|
||||
model = Reporter
|
||||
assert_equal_lists(
|
||||
ReporterType2._meta.fields_map.keys(),
|
||||
['articles', 'first_name', 'last_name', 'email', 'pets', 'id']
|
||||
)
|
||||
|
||||
|
||||
def test_should_map_fields():
|
||||
def test_should_query_well():
|
||||
class ReporterType(DjangoObjectType):
|
||||
|
||||
class Meta:
|
||||
model = Reporter
|
||||
|
||||
class Query2(graphene.ObjectType):
|
||||
class Query(graphene.ObjectType):
|
||||
reporter = graphene.Field(ReporterType)
|
||||
|
||||
def resolve_reporter(self, *args, **kwargs):
|
||||
|
@ -83,53 +53,42 @@ def test_should_map_fields():
|
|||
'email': ''
|
||||
}
|
||||
}
|
||||
Schema = graphene.Schema(query=Query2)
|
||||
result = Schema.execute(query)
|
||||
schema = graphene.Schema(query=Query)
|
||||
result = schema.execute(query)
|
||||
assert not result.errors
|
||||
assert result.data == expected
|
||||
|
||||
|
||||
def test_should_map_only_few_fields():
|
||||
class Reporter2(DjangoObjectType):
|
||||
|
||||
class Meta:
|
||||
model = Reporter
|
||||
only_fields = ('id', 'email')
|
||||
assert_equal_lists(
|
||||
Reporter2._meta.fields_map.keys(),
|
||||
['id', 'email']
|
||||
)
|
||||
|
||||
|
||||
def test_should_node():
|
||||
class ReporterNodeType(DjangoNode):
|
||||
class ReporterNode(DjangoNode):
|
||||
|
||||
class Meta:
|
||||
model = Reporter
|
||||
|
||||
@classmethod
|
||||
def get_node(cls, id):
|
||||
return ReporterNodeType(Reporter(id=2, first_name='Cookie Monster'))
|
||||
return ReporterNode(Reporter(id=2, first_name='Cookie Monster'))
|
||||
|
||||
def resolve_articles(self, *args, **kwargs):
|
||||
return [ArticleNodeType(Article(headline='Hi!'))]
|
||||
return [ArticleNode(Article(headline='Hi!'))]
|
||||
|
||||
class ArticleNodeType(DjangoNode):
|
||||
class ArticleNode(DjangoNode):
|
||||
|
||||
class Meta:
|
||||
model = Article
|
||||
|
||||
@classmethod
|
||||
def get_node(cls, id):
|
||||
return ArticleNodeType(Article(id=1, headline='Article node'))
|
||||
return ArticleNode(Article(id=1, headline='Article node'))
|
||||
|
||||
class Query1(graphene.ObjectType):
|
||||
class Query(graphene.ObjectType):
|
||||
node = relay.NodeField()
|
||||
reporter = graphene.Field(ReporterNodeType)
|
||||
article = graphene.Field(ArticleNodeType)
|
||||
reporter = graphene.Field(ReporterNode)
|
||||
article = graphene.Field(ArticleNode)
|
||||
|
||||
def resolve_reporter(self, *args, **kwargs):
|
||||
return ReporterNodeType(Reporter(id=1, first_name='ABA', last_name='X'))
|
||||
return ReporterNode(
|
||||
Reporter(id=1, first_name='ABA', last_name='X'))
|
||||
|
||||
query = '''
|
||||
query ReporterQuery {
|
||||
|
@ -146,12 +105,12 @@ def test_should_node():
|
|||
lastName,
|
||||
email
|
||||
}
|
||||
myArticle: node(id:"QXJ0aWNsZU5vZGVUeXBlOjE=") {
|
||||
myArticle: node(id:"QXJ0aWNsZU5vZGU6MQ==") {
|
||||
id
|
||||
... on ReporterNodeType {
|
||||
... on ReporterNode {
|
||||
firstName
|
||||
}
|
||||
... on ArticleNodeType {
|
||||
... on ArticleNode {
|
||||
headline
|
||||
}
|
||||
}
|
||||
|
@ -159,7 +118,7 @@ def test_should_node():
|
|||
'''
|
||||
expected = {
|
||||
'reporter': {
|
||||
'id': 'UmVwb3J0ZXJOb2RlVHlwZTox',
|
||||
'id': 'UmVwb3J0ZXJOb2RlOjE=',
|
||||
'firstName': 'ABA',
|
||||
'lastName': 'X',
|
||||
'email': '',
|
||||
|
@ -172,11 +131,11 @@ def test_should_node():
|
|||
},
|
||||
},
|
||||
'myArticle': {
|
||||
'id': 'QXJ0aWNsZU5vZGVUeXBlOjE=',
|
||||
'id': 'QXJ0aWNsZU5vZGU6MQ==',
|
||||
'headline': 'Article node'
|
||||
}
|
||||
}
|
||||
Schema = graphene.Schema(query=Query1)
|
||||
result = Schema.execute(query)
|
||||
schema = graphene.Schema(query=Query)
|
||||
result = schema.execute(query)
|
||||
assert not result.errors
|
||||
assert result.data == expected
|
45
graphene/contrib/django/tests/test_schema.py
Normal file
45
graphene/contrib/django/tests/test_schema.py
Normal 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']
|
||||
)
|
|
@ -2,13 +2,16 @@
|
|||
|
||||
from graphene import Schema
|
||||
from graphene.contrib.django.types import DjangoInterface, DjangoNode
|
||||
from graphene.core.fields import IntField
|
||||
from graphene.core.fields import Field
|
||||
from graphene.core.types.scalars import Int
|
||||
from graphene.relay.fields import GlobalIDField
|
||||
from graphql.core.type import GraphQLInterfaceType, GraphQLObjectType
|
||||
from tests.utils import assert_equal_lists
|
||||
|
||||
from .models import Article, Reporter
|
||||
|
||||
schema = Schema()
|
||||
|
||||
|
||||
class Character(DjangoInterface):
|
||||
'''Character description'''
|
||||
|
@ -16,10 +19,11 @@ class Character(DjangoInterface):
|
|||
model = Reporter
|
||||
|
||||
|
||||
@schema.register
|
||||
class Human(DjangoNode):
|
||||
'''Human description'''
|
||||
|
||||
pub_date = IntField()
|
||||
pub_date = Int()
|
||||
|
||||
def get_node(self, id):
|
||||
pass
|
||||
|
@ -27,14 +31,12 @@ class Human(DjangoNode):
|
|||
class Meta:
|
||||
model = Article
|
||||
|
||||
schema = Schema()
|
||||
|
||||
|
||||
def test_django_interface():
|
||||
assert DjangoNode._meta.is_interface is True
|
||||
|
||||
|
||||
def test_pseudo_interface():
|
||||
def test_pseudo_interface_registered():
|
||||
object_type = schema.T(Character)
|
||||
assert Character._meta.is_interface is True
|
||||
assert isinstance(object_type, GraphQLInterfaceType)
|
||||
|
@ -57,7 +59,8 @@ def test_node_idfield():
|
|||
|
||||
def test_node_replacedfield():
|
||||
idfield = Human._meta.fields_map['pub_date']
|
||||
assert isinstance(idfield, IntField)
|
||||
assert isinstance(idfield, Field)
|
||||
assert schema.T(idfield).type == schema.T(Int())
|
||||
|
||||
|
||||
def test_interface_resolve_type():
|
|
@ -18,7 +18,7 @@ class Character(DjangoNode):
|
|||
|
||||
|
||||
class Human(DjangoNode):
|
||||
raises = graphene.StringField()
|
||||
raises = graphene.String()
|
||||
|
||||
class Meta:
|
||||
model = Article
|
|
@ -6,7 +6,7 @@ def format_response(response):
|
|||
|
||||
|
||||
def test_client_get_no_query(settings, client):
|
||||
settings.ROOT_URLCONF = 'tests.contrib_django.test_urls'
|
||||
settings.ROOT_URLCONF = 'graphene.contrib.django.tests.test_urls'
|
||||
response = client.get('/graphql')
|
||||
json_response = format_response(response)
|
||||
assert json_response == {'errors': [
|
||||
|
@ -14,7 +14,7 @@ def test_client_get_no_query(settings, client):
|
|||
|
||||
|
||||
def test_client_post_no_query(settings, client):
|
||||
settings.ROOT_URLCONF = 'tests.contrib_django.test_urls'
|
||||
settings.ROOT_URLCONF = 'graphene.contrib.django.tests.test_urls'
|
||||
response = client.post('/graphql', {})
|
||||
json_response = format_response(response)
|
||||
assert json_response == {'errors': [
|
||||
|
@ -22,7 +22,7 @@ def test_client_post_no_query(settings, client):
|
|||
|
||||
|
||||
def test_client_post_malformed_json(settings, client):
|
||||
settings.ROOT_URLCONF = 'tests.contrib_django.test_urls'
|
||||
settings.ROOT_URLCONF = 'graphene.contrib.django.tests.test_urls'
|
||||
response = client.post('/graphql', 'MALFORMED', 'application/json')
|
||||
json_response = format_response(response)
|
||||
assert json_response == {'errors': [
|
||||
|
@ -30,7 +30,7 @@ def test_client_post_malformed_json(settings, client):
|
|||
|
||||
|
||||
def test_client_post_empty_query_json(settings, client):
|
||||
settings.ROOT_URLCONF = 'tests.contrib_django.test_urls'
|
||||
settings.ROOT_URLCONF = 'graphene.contrib.django.tests.test_urls'
|
||||
response = client.post(
|
||||
'/graphql', json.dumps({'query': ''}), 'application/json')
|
||||
json_response = format_response(response)
|
||||
|
@ -39,7 +39,7 @@ def test_client_post_empty_query_json(settings, client):
|
|||
|
||||
|
||||
def test_client_post_empty_query_graphql(settings, client):
|
||||
settings.ROOT_URLCONF = 'tests.contrib_django.test_urls'
|
||||
settings.ROOT_URLCONF = 'graphene.contrib.django.tests.test_urls'
|
||||
response = client.post(
|
||||
'/graphql', '', 'application/graphql')
|
||||
json_response = format_response(response)
|
||||
|
@ -48,7 +48,7 @@ def test_client_post_empty_query_graphql(settings, client):
|
|||
|
||||
|
||||
def test_client_post_bad_query_json(settings, client):
|
||||
settings.ROOT_URLCONF = 'tests.contrib_django.test_urls'
|
||||
settings.ROOT_URLCONF = 'graphene.contrib.django.tests.test_urls'
|
||||
response = client.post(
|
||||
'/graphql', json.dumps({'query': '{ MALFORMED'}), 'application/json')
|
||||
json_response = format_response(response)
|
||||
|
@ -58,7 +58,7 @@ def test_client_post_bad_query_json(settings, client):
|
|||
|
||||
|
||||
def test_client_post_bad_query_graphql(settings, client):
|
||||
settings.ROOT_URLCONF = 'tests.contrib_django.test_urls'
|
||||
settings.ROOT_URLCONF = 'graphene.contrib.django.tests.test_urls'
|
||||
response = client.post(
|
||||
'/graphql', '{ MALFORMED', 'application/graphql')
|
||||
json_response = format_response(response)
|
||||
|
@ -68,7 +68,7 @@ def test_client_post_bad_query_graphql(settings, client):
|
|||
|
||||
|
||||
def test_client_get_good_query(settings, client):
|
||||
settings.ROOT_URLCONF = 'tests.contrib_django.test_urls'
|
||||
settings.ROOT_URLCONF = 'graphene.contrib.django.tests.test_urls'
|
||||
response = client.get('/graphql', {'query': '{ headline }'})
|
||||
json_response = format_response(response)
|
||||
expected_json = {
|
||||
|
@ -80,7 +80,7 @@ def test_client_get_good_query(settings, client):
|
|||
|
||||
|
||||
def test_client_get_good_query_with_raise(settings, client):
|
||||
settings.ROOT_URLCONF = 'tests.contrib_django.test_urls'
|
||||
settings.ROOT_URLCONF = 'graphene.contrib.django.tests.test_urls'
|
||||
response = client.get('/graphql', {'query': '{ raises }'})
|
||||
json_response = format_response(response)
|
||||
assert json_response['errors'][0][
|
||||
|
@ -89,7 +89,7 @@ def test_client_get_good_query_with_raise(settings, client):
|
|||
|
||||
|
||||
def test_client_post_good_query_json(settings, client):
|
||||
settings.ROOT_URLCONF = 'tests.contrib_django.test_urls'
|
||||
settings.ROOT_URLCONF = 'graphene.contrib.django.tests.test_urls'
|
||||
response = client.post(
|
||||
'/graphql', json.dumps({'query': '{ headline }'}), 'application/json')
|
||||
json_response = format_response(response)
|
||||
|
@ -102,7 +102,7 @@ def test_client_post_good_query_json(settings, client):
|
|||
|
||||
|
||||
def test_client_post_good_query_graphql(settings, client):
|
||||
settings.ROOT_URLCONF = 'tests.contrib_django.test_urls'
|
||||
settings.ROOT_URLCONF = 'graphene.contrib.django.tests.test_urls'
|
||||
response = client.post(
|
||||
'/graphql', '{ headline }', 'application/graphql')
|
||||
json_response = format_response(response)
|
||||
|
@ -115,7 +115,7 @@ def test_client_post_good_query_graphql(settings, client):
|
|||
|
||||
|
||||
# def test_client_get_bad_query(settings, client):
|
||||
# settings.ROOT_URLCONF = 'tests.contrib_django.test_urls'
|
||||
# settings.ROOT_URLCONF = 'graphene.contrib.django.tests.test_urls'
|
||||
# response = client.get('/graphql')
|
||||
# json_response = format_response(response)
|
||||
# assert json_response == {'errors': [{'message': 'Must provide query string.'}]}
|
|
@ -46,11 +46,13 @@ class InstanceObjectType(BaseObjectType):
|
|||
return getattr(self.instance, attr)
|
||||
|
||||
|
||||
class DjangoObjectType(six.with_metaclass(DjangoObjectTypeMeta, InstanceObjectType)):
|
||||
class DjangoObjectType(six.with_metaclass(
|
||||
DjangoObjectTypeMeta, InstanceObjectType)):
|
||||
pass
|
||||
|
||||
|
||||
class DjangoInterface(six.with_metaclass(DjangoObjectTypeMeta, InstanceObjectType)):
|
||||
class DjangoInterface(six.with_metaclass(
|
||||
DjangoObjectTypeMeta, InstanceObjectType)):
|
||||
pass
|
||||
|
||||
|
||||
|
|
2
graphene/core/exceptions.py
Normal file
2
graphene/core/exceptions.py
Normal file
|
@ -0,0 +1,2 @@
|
|||
class SkipField(Exception):
|
||||
pass
|
|
@ -1,258 +1,49 @@
|
|||
import inspect
|
||||
from functools import total_ordering, wraps
|
||||
import warnings
|
||||
|
||||
import six
|
||||
|
||||
from graphene.core.scalars import GraphQLSkipField
|
||||
from graphene.core.types import BaseObjectType, InputObjectType
|
||||
from graphene.utils import ProxySnakeDict, enum_to_graphql_enum, to_camel_case
|
||||
from graphql.core.type import (GraphQLArgument, GraphQLBoolean, GraphQLField,
|
||||
GraphQLFloat, GraphQLID,
|
||||
GraphQLInputObjectField, GraphQLInt,
|
||||
GraphQLList, GraphQLNonNull, GraphQLString)
|
||||
|
||||
try:
|
||||
from enum import Enum
|
||||
except ImportError:
|
||||
class Enum(object):
|
||||
pass
|
||||
from .types.base import FieldType
|
||||
from .types.definitions import List, NonNull
|
||||
from .types.field import Field
|
||||
from .types.scalars import ID, Boolean, Float, Int, String
|
||||
|
||||
|
||||
class Empty(object):
|
||||
class DeprecatedField(FieldType):
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
cls = self.__class__
|
||||
warnings.warn("Using {} is not longer supported".format(
|
||||
cls.__name__), FutureWarning)
|
||||
if 'resolve' in kwargs:
|
||||
kwargs['resolver'] = kwargs.pop('resolve')
|
||||
return super(DeprecatedField, self).__init__(*args, **kwargs)
|
||||
|
||||
|
||||
class StringField(DeprecatedField, String):
|
||||
pass
|
||||
|
||||
|
||||
@total_ordering
|
||||
class Field(object):
|
||||
SKIP = GraphQLSkipField
|
||||
creation_counter = 0
|
||||
required = False
|
||||
|
||||
def __init__(self, field_type, name=None, resolve=None, required=False, args=None, description='', default=None, **extra_args):
|
||||
self.field_type = field_type
|
||||
self.resolve_fn = resolve
|
||||
self.required = self.required or required
|
||||
self.args = args or {}
|
||||
self.extra_args = extra_args
|
||||
self._type = None
|
||||
self.name = name
|
||||
self.description = description or self.__doc__
|
||||
self.object_type = None
|
||||
self.default = default
|
||||
self.creation_counter = Field.creation_counter
|
||||
Field.creation_counter += 1
|
||||
|
||||
def get_default(self):
|
||||
return self.default
|
||||
|
||||
def contribute_to_class(self, cls, name, add=True):
|
||||
if not self.name:
|
||||
self.name = to_camel_case(name)
|
||||
self.attname = name
|
||||
self.object_type = cls
|
||||
if isinstance(self.field_type, Field) and not self.field_type.object_type:
|
||||
self.field_type.contribute_to_class(cls, name, False)
|
||||
if add:
|
||||
cls._meta.add_field(self)
|
||||
|
||||
def resolve(self, instance, args, info):
|
||||
schema = info and getattr(info.schema, 'graphene_schema', None)
|
||||
resolve_fn = self.get_resolve_fn(schema)
|
||||
if resolve_fn:
|
||||
return resolve_fn(instance, ProxySnakeDict(args), info)
|
||||
else:
|
||||
return getattr(instance, self.attname, self.get_default())
|
||||
|
||||
def get_resolve_fn(self, schema):
|
||||
object_type = self.get_object_type(schema)
|
||||
if object_type and object_type._meta.is_mutation:
|
||||
return object_type.mutate
|
||||
elif self.resolve_fn:
|
||||
return self.resolve_fn
|
||||
else:
|
||||
custom_resolve_fn_name = 'resolve_%s' % self.attname
|
||||
if hasattr(self.object_type, custom_resolve_fn_name):
|
||||
resolve_fn = getattr(self.object_type, custom_resolve_fn_name)
|
||||
|
||||
@wraps(resolve_fn)
|
||||
def custom_resolve_fn(instance, args, info):
|
||||
return resolve_fn(instance, args, info)
|
||||
return custom_resolve_fn
|
||||
|
||||
def get_object_type(self, schema):
|
||||
field_type = self.field_type
|
||||
if inspect.isfunction(field_type):
|
||||
field_type = field_type(self)
|
||||
_is_class = inspect.isclass(field_type)
|
||||
if isinstance(field_type, Field):
|
||||
return field_type.get_object_type(schema)
|
||||
if _is_class and issubclass(field_type, BaseObjectType):
|
||||
return field_type
|
||||
elif isinstance(field_type, six.string_types):
|
||||
if field_type == 'self':
|
||||
return self.object_type
|
||||
else:
|
||||
return schema.get_type(field_type)
|
||||
|
||||
def type_wrapper(self, field_type):
|
||||
if self.required:
|
||||
field_type = GraphQLNonNull(field_type)
|
||||
return field_type
|
||||
|
||||
def internal_type(self, schema):
|
||||
field_type = self.field_type
|
||||
_is_class = inspect.isclass(field_type)
|
||||
if isinstance(field_type, Field):
|
||||
field_type = self.field_type.internal_type(schema)
|
||||
elif _is_class and issubclass(field_type, Enum):
|
||||
field_type = enum_to_graphql_enum(field_type)
|
||||
else:
|
||||
object_type = self.get_object_type(schema)
|
||||
if object_type:
|
||||
field_type = schema.T(object_type)
|
||||
|
||||
field_type = self.type_wrapper(field_type)
|
||||
return field_type
|
||||
|
||||
def internal_field(self, schema):
|
||||
if not self.object_type:
|
||||
raise Exception(
|
||||
'Field could not be constructed in a non graphene.ObjectType or graphene.Interface')
|
||||
|
||||
extra_args = self.extra_args.copy()
|
||||
for arg_name, arg_value in self.extra_args.items():
|
||||
if isinstance(arg_value, GraphQLArgument):
|
||||
self.args[arg_name] = arg_value
|
||||
del extra_args[arg_name]
|
||||
|
||||
if extra_args != {}:
|
||||
raise TypeError("Field %s.%s initiated with invalid args: %s" % (
|
||||
self.object_type,
|
||||
self.attname,
|
||||
','.join(extra_args.keys())
|
||||
))
|
||||
|
||||
args = self.args
|
||||
|
||||
object_type = self.get_object_type(schema)
|
||||
if object_type and object_type._meta.is_mutation:
|
||||
assert not self.args, 'Arguments provided for mutations are defined in Input class in Mutation'
|
||||
args = object_type.get_input_type().fields_as_arguments(schema)
|
||||
|
||||
internal_type = self.internal_type(schema)
|
||||
if not internal_type:
|
||||
raise Exception("Internal type for field %s is None" % self)
|
||||
|
||||
description = self.description
|
||||
resolve_fn = self.get_resolve_fn(schema)
|
||||
if resolve_fn:
|
||||
description = resolve_fn.__doc__ or description
|
||||
|
||||
@wraps(resolve_fn)
|
||||
def resolver(*args):
|
||||
return self.resolve(*args)
|
||||
else:
|
||||
resolver = self.resolve
|
||||
|
||||
if issubclass(self.object_type, InputObjectType):
|
||||
return GraphQLInputObjectField(
|
||||
internal_type,
|
||||
description=description,
|
||||
)
|
||||
|
||||
return GraphQLField(
|
||||
internal_type,
|
||||
description=description,
|
||||
args=args,
|
||||
resolver=resolver,
|
||||
)
|
||||
|
||||
def __str__(self):
|
||||
""" Return "object_type.name". """
|
||||
return '%s.%s' % (self.object_type.__name__, self.attname)
|
||||
|
||||
def __repr__(self):
|
||||
"""
|
||||
Displays the module, class and name of the field.
|
||||
"""
|
||||
path = '%s.%s' % (self.__class__.__module__, self.__class__.__name__)
|
||||
name = getattr(self, 'attname', None)
|
||||
if name is not None:
|
||||
return '<%s: %s>' % (path, name)
|
||||
return '<%s>' % path
|
||||
|
||||
def __eq__(self, other):
|
||||
# Needed for @total_ordering
|
||||
if isinstance(other, Field):
|
||||
return self.creation_counter == other.creation_counter and \
|
||||
self.object_type == other.object_type
|
||||
return NotImplemented
|
||||
|
||||
def __lt__(self, other):
|
||||
# This is needed because bisect does not take a comparison function.
|
||||
if isinstance(other, Field):
|
||||
return self.creation_counter < other.creation_counter
|
||||
return NotImplemented
|
||||
|
||||
def __hash__(self):
|
||||
return hash((self.creation_counter, self.object_type))
|
||||
|
||||
def __copy__(self):
|
||||
# We need to avoid hitting __reduce__, so define this
|
||||
# slightly weird copy construct.
|
||||
obj = Empty()
|
||||
obj.__class__ = self.__class__
|
||||
obj.__dict__ = self.__dict__.copy()
|
||||
if self.field_type == 'self':
|
||||
obj.field_type = self.object_type
|
||||
return obj
|
||||
class IntField(DeprecatedField, Int):
|
||||
pass
|
||||
|
||||
|
||||
class LazyField(Field):
|
||||
|
||||
def inner_field(self, schema):
|
||||
return self.get_field(schema)
|
||||
|
||||
def internal_type(self, schema):
|
||||
return self.inner_field(schema).internal_type(schema)
|
||||
|
||||
def internal_field(self, schema):
|
||||
return self.inner_field(schema).internal_field(schema)
|
||||
class BooleanField(DeprecatedField, Boolean):
|
||||
pass
|
||||
|
||||
|
||||
class TypeField(Field):
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(TypeField, self).__init__(self.field_type, *args, **kwargs)
|
||||
class IDField(DeprecatedField, ID):
|
||||
pass
|
||||
|
||||
|
||||
class StringField(TypeField):
|
||||
field_type = GraphQLString
|
||||
class FloatField(DeprecatedField, Float):
|
||||
pass
|
||||
|
||||
|
||||
class IntField(TypeField):
|
||||
field_type = GraphQLInt
|
||||
class ListField(DeprecatedField, List):
|
||||
pass
|
||||
|
||||
|
||||
class BooleanField(TypeField):
|
||||
field_type = GraphQLBoolean
|
||||
class NonNullField(DeprecatedField, NonNull):
|
||||
pass
|
||||
|
||||
|
||||
class IDField(TypeField):
|
||||
field_type = GraphQLID
|
||||
|
||||
|
||||
class FloatField(TypeField):
|
||||
field_type = GraphQLFloat
|
||||
|
||||
|
||||
class ListField(Field):
|
||||
|
||||
def type_wrapper(self, field_type):
|
||||
return GraphQLList(field_type)
|
||||
|
||||
|
||||
class NonNullField(Field):
|
||||
|
||||
def type_wrapper(self, field_type):
|
||||
return GraphQLNonNull(field_type)
|
||||
__all__ = ['Field', 'StringField', 'IntField', 'BooleanField',
|
||||
'IDField', 'FloatField', 'ListField', 'NonNullField']
|
||||
|
|
|
@ -52,7 +52,9 @@ class Options(object):
|
|||
# Any leftover attributes must be invalid.
|
||||
if meta_attrs != {}:
|
||||
raise TypeError(
|
||||
"'class Meta' got invalid attribute(s): %s" % ','.join(meta_attrs.keys()))
|
||||
"'class Meta' got invalid attribute(s): %s" %
|
||||
','.join(
|
||||
meta_attrs.keys()))
|
||||
else:
|
||||
self.proxy = False
|
||||
|
||||
|
|
|
@ -1,6 +1,9 @@
|
|||
import inspect
|
||||
from collections import OrderedDict
|
||||
|
||||
from graphene import signals
|
||||
from graphene.core.types.base import BaseType
|
||||
from graphene.core.types.objecttype import BaseObjectType
|
||||
from graphql.core.execution.executor import Executor
|
||||
from graphql.core.execution.middlewares.sync import \
|
||||
SynchronousExecutionMiddleware
|
||||
|
@ -16,10 +19,10 @@ class GraphQLSchema(_GraphQLSchema):
|
|||
|
||||
|
||||
class Schema(object):
|
||||
_query = None
|
||||
_executor = None
|
||||
|
||||
def __init__(self, query=None, mutation=None, name='Schema', executor=None):
|
||||
def __init__(self, query=None, mutation=None,
|
||||
name='Schema', executor=None):
|
||||
self._types_names = {}
|
||||
self._types = {}
|
||||
self.mutation = mutation
|
||||
|
@ -34,32 +37,24 @@ class Schema(object):
|
|||
def T(self, object_type):
|
||||
if not object_type:
|
||||
return
|
||||
if object_type not in self._types:
|
||||
internal_type = object_type.internal_type(self)
|
||||
self._types[object_type] = internal_type
|
||||
self._types_names[internal_type.name] = object_type
|
||||
return self._types[object_type]
|
||||
|
||||
@property
|
||||
def query(self):
|
||||
return self._query
|
||||
|
||||
@query.setter
|
||||
def query(self, query):
|
||||
self._query = query
|
||||
|
||||
@property
|
||||
def mutation(self):
|
||||
return self._mutation
|
||||
|
||||
@mutation.setter
|
||||
def mutation(self, mutation):
|
||||
self._mutation = mutation
|
||||
if inspect.isclass(object_type) and issubclass(
|
||||
object_type, BaseType) or isinstance(
|
||||
object_type, BaseType):
|
||||
if object_type not in self._types:
|
||||
internal_type = object_type.internal_type(self)
|
||||
self._types[object_type] = internal_type
|
||||
is_objecttype = inspect.isclass(
|
||||
object_type) and issubclass(object_type, BaseObjectType)
|
||||
if is_objecttype:
|
||||
self.register(object_type)
|
||||
return self._types[object_type]
|
||||
else:
|
||||
return object_type
|
||||
|
||||
@property
|
||||
def executor(self):
|
||||
if not self._executor:
|
||||
self.executor = Executor(
|
||||
self._executor = Executor(
|
||||
[SynchronousExecutionMiddleware()], map_type=OrderedDict)
|
||||
return self._executor
|
||||
|
||||
|
@ -69,25 +64,45 @@ class Schema(object):
|
|||
|
||||
@property
|
||||
def schema(self):
|
||||
if not self._query:
|
||||
if not self.query:
|
||||
raise Exception('You have to define a base query type')
|
||||
return GraphQLSchema(self, query=self.T(self._query), mutation=self.T(self._mutation))
|
||||
return GraphQLSchema(
|
||||
self, query=self.T(self.query),
|
||||
mutation=self.T(self.mutation))
|
||||
|
||||
def register(self, object_type):
|
||||
type_name = object_type._meta.type_name
|
||||
registered_object_type = self._types_names.get(type_name, None)
|
||||
if registered_object_type:
|
||||
assert registered_object_type == object_type, 'Type {} already registered with other object type'.format(
|
||||
type_name)
|
||||
self._types_names[object_type._meta.type_name] = object_type
|
||||
return object_type
|
||||
|
||||
def objecttype(self, type):
|
||||
name = getattr(type, 'name', None)
|
||||
if name:
|
||||
objecttype = self._types_names.get(name, None)
|
||||
if objecttype and inspect.isclass(
|
||||
objecttype) and issubclass(objecttype, BaseObjectType):
|
||||
return objecttype
|
||||
|
||||
def setup(self):
|
||||
assert self.query, 'The base query type is not set'
|
||||
self.T(self.query)
|
||||
|
||||
def get_type(self, type_name):
|
||||
self.schema._build_type_map()
|
||||
self.setup()
|
||||
if type_name not in self._types_names:
|
||||
raise Exception('Type %s not found in %r' % (type_name, self))
|
||||
raise KeyError('Type %r not found in %r' % (type_name, self))
|
||||
return self._types_names[type_name]
|
||||
|
||||
@property
|
||||
def types(self):
|
||||
return self._types_names
|
||||
|
||||
def execute(self, request='', root=None, vars=None, operation_name=None, **kwargs):
|
||||
def execute(self, request='', root=None, vars=None,
|
||||
operation_name=None, **kwargs):
|
||||
root = root or object()
|
||||
return self.executor.execute(
|
||||
self.schema,
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
|
||||
import graphene
|
||||
from graphene.core.schema import Schema
|
||||
|
||||
|
@ -6,15 +5,15 @@ my_id = 0
|
|||
|
||||
|
||||
class Query(graphene.ObjectType):
|
||||
base = graphene.StringField()
|
||||
base = graphene.String()
|
||||
|
||||
|
||||
class ChangeNumber(graphene.Mutation):
|
||||
'''Result mutation'''
|
||||
class Input:
|
||||
to = graphene.IntField()
|
||||
to = graphene.Int()
|
||||
|
||||
result = graphene.StringField()
|
||||
result = graphene.String()
|
||||
|
||||
@classmethod
|
||||
def mutate(cls, instance, args, info):
|
||||
|
@ -31,9 +30,7 @@ schema = Schema(query=Query, mutation=MyResultMutation)
|
|||
|
||||
|
||||
def test_mutation_input():
|
||||
assert ChangeNumber.input_type
|
||||
assert ChangeNumber.input_type._meta.type_name == 'ChangeNumberInput'
|
||||
assert list(ChangeNumber.input_type._meta.fields_map.keys()) == ['to']
|
||||
assert list(schema.T(ChangeNumber.arguments).keys()) == ['to']
|
||||
|
||||
|
||||
def test_execute_mutations():
|
178
graphene/core/tests/test_old_fields.py
Normal file
178
graphene/core/tests/test_old_fields.py
Normal 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'
|
|
@ -1,8 +1,6 @@
|
|||
|
||||
from py.test import raises
|
||||
from pytest import raises
|
||||
|
||||
from graphene.core.fields import StringField
|
||||
from graphene.core.fields import Field
|
||||
from graphene.core.options import Options
|
||||
|
||||
|
||||
|
@ -22,7 +20,7 @@ def test_field_added_in_meta():
|
|||
pass
|
||||
|
||||
opt.contribute_to_class(ObjectType, '_meta')
|
||||
f = StringField()
|
||||
f = Field(None)
|
||||
f.attname = 'string_field'
|
||||
opt.add_field(f)
|
||||
assert f in opt.fields
|
|
@ -1,23 +1,22 @@
|
|||
|
||||
|
||||
from graphene.core.fields import Field, ListField, StringField
|
||||
from graphene.core.fields import Field
|
||||
from graphene.core.schema import Schema
|
||||
from graphene.core.types import Interface, ObjectType
|
||||
from graphene.core.types import Interface, List, ObjectType, String
|
||||
from graphql.core import graphql
|
||||
from graphql.core.type import (GraphQLInterfaceType, GraphQLObjectType,
|
||||
GraphQLSchema)
|
||||
from graphql.core.type import GraphQLSchema
|
||||
|
||||
|
||||
class Character(Interface):
|
||||
name = StringField()
|
||||
name = String()
|
||||
|
||||
|
||||
class Pet(ObjectType):
|
||||
type = StringField(resolve=lambda *_: 'Dog')
|
||||
type = String(resolver=lambda *_: 'Dog')
|
||||
|
||||
|
||||
class Human(Character):
|
||||
friends = ListField(Character)
|
||||
friends = List(Character)
|
||||
pet = Field(Pet)
|
||||
|
||||
def resolve_name(self, *args):
|
||||
|
@ -28,8 +27,6 @@ class Human(Character):
|
|||
|
||||
def resolve_pet(self, *args):
|
||||
return Pet(object())
|
||||
# def resolve_friends(self, *args, **kwargs):
|
||||
# return 'HEY YOU!'
|
||||
|
||||
|
||||
schema = Schema()
|
||||
|
@ -38,8 +35,8 @@ Human_type = schema.T(Human)
|
|||
|
||||
|
||||
def test_type():
|
||||
assert Human._meta.fields_map['name'].resolve(
|
||||
Human(object()), None, None) == 'Peter'
|
||||
assert Human._meta.fields_map['name'].resolver(
|
||||
Human(object()), {}, None) == 'Peter'
|
||||
|
||||
|
||||
def test_query():
|
|
@ -1,27 +1,24 @@
|
|||
|
||||
from py.test import raises
|
||||
from pytest import raises
|
||||
|
||||
from graphene import Interface, ObjectType, Schema
|
||||
from graphene.core.fields import Field, ListField, StringField
|
||||
from graphene import Interface, List, ObjectType, Schema, String
|
||||
from graphene.core.fields import Field
|
||||
from graphene.core.types.base import LazyType
|
||||
from graphql.core import graphql
|
||||
from graphql.core.type import (GraphQLInterfaceType, GraphQLObjectType,
|
||||
GraphQLSchema)
|
||||
from tests.utils import assert_equal_lists
|
||||
|
||||
schema = Schema(name='My own schema')
|
||||
|
||||
|
||||
class Character(Interface):
|
||||
name = StringField()
|
||||
name = String()
|
||||
|
||||
|
||||
class Pet(ObjectType):
|
||||
type = StringField(resolve=lambda *_: 'Dog')
|
||||
type = String(resolver=lambda *_: 'Dog')
|
||||
|
||||
|
||||
class Human(Character):
|
||||
friends = ListField(Character)
|
||||
friends = List(Character)
|
||||
pet = Field(Pet)
|
||||
|
||||
def resolve_name(self, *args):
|
||||
|
@ -95,9 +92,9 @@ def test_query_schema_execute():
|
|||
def test_schema_get_type_map():
|
||||
assert_equal_lists(
|
||||
schema.schema.get_type_map().keys(),
|
||||
['__Field', 'String', 'Pet', 'Character', '__InputValue', '__Directive',
|
||||
'__TypeKind', '__Schema', '__Type', 'Human', '__EnumValue', 'Boolean']
|
||||
)
|
||||
['__Field', 'String', 'Pet', 'Character', '__InputValue',
|
||||
'__Directive', '__TypeKind', '__Schema', '__Type', 'Human',
|
||||
'__EnumValue', 'Boolean'])
|
||||
|
||||
|
||||
def test_schema_no_query():
|
||||
|
@ -112,19 +109,19 @@ def test_schema_register():
|
|||
|
||||
@schema.register
|
||||
class MyType(ObjectType):
|
||||
type = StringField(resolve=lambda *_: 'Dog')
|
||||
type = String(resolver=lambda *_: 'Dog')
|
||||
|
||||
schema.query = MyType
|
||||
|
||||
assert schema.get_type('MyType') == MyType
|
||||
|
||||
|
||||
def test_schema_register():
|
||||
def test_schema_register_no_query_type():
|
||||
schema = Schema(name='My own schema')
|
||||
|
||||
@schema.register
|
||||
class MyType(ObjectType):
|
||||
type = StringField(resolve=lambda *_: 'Dog')
|
||||
type = String(resolver=lambda *_: 'Dog')
|
||||
|
||||
with raises(Exception) as excinfo:
|
||||
schema.get_type('MyType')
|
||||
|
@ -135,9 +132,23 @@ def test_schema_introspect():
|
|||
schema = Schema(name='My own schema')
|
||||
|
||||
class MyType(ObjectType):
|
||||
type = StringField(resolve=lambda *_: 'Dog')
|
||||
type = String(resolver=lambda *_: 'Dog')
|
||||
|
||||
schema.query = MyType
|
||||
|
||||
introspection = schema.introspect()
|
||||
assert '__schema' in introspection
|
||||
|
||||
|
||||
def test_lazytype():
|
||||
schema = Schema(name='My own schema')
|
||||
|
||||
t = LazyType('MyType')
|
||||
|
||||
@schema.register
|
||||
class MyType(ObjectType):
|
||||
type = String(resolver=lambda *_: 'Dog')
|
||||
|
||||
schema.query = MyType
|
||||
|
||||
assert schema.T(t) == schema.T(MyType)
|
30
graphene/core/types/__init__.py
Normal file
30
graphene/core/types/__init__.py
Normal 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']
|
71
graphene/core/types/argument.py
Normal file
71
graphene/core/types/argument.py
Normal 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
127
graphene/core/types/base.py
Normal 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
|
30
graphene/core/types/definitions.py
Normal file
30
graphene/core/types/definitions.py
Normal 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
|
151
graphene/core/types/field.py
Normal file
151
graphene/core/types/field.py
Normal 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)
|
|
@ -6,7 +6,11 @@ from functools import partial
|
|||
import six
|
||||
|
||||
from graphene import signals
|
||||
from graphene.core.exceptions import SkipField
|
||||
from graphene.core.options import Options
|
||||
from graphene.core.types.argument import ArgumentsGroup
|
||||
from graphene.core.types.base import BaseType
|
||||
from graphene.core.types.definitions import List, NonNull
|
||||
from graphql.core.type import (GraphQLArgument, GraphQLInputObjectType,
|
||||
GraphQLInterfaceType, GraphQLObjectType)
|
||||
|
||||
|
@ -50,10 +54,6 @@ class ObjectTypeMeta(type):
|
|||
assert not (
|
||||
new_class._meta.is_interface and new_class._meta.is_mutation)
|
||||
|
||||
input_class = None
|
||||
if new_class._meta.is_mutation:
|
||||
input_class = attrs.pop('Input', None)
|
||||
|
||||
# Add all attributes to the class.
|
||||
for obj_name, obj in attrs.items():
|
||||
new_class.add_to_class(obj_name, obj)
|
||||
|
@ -62,13 +62,6 @@ class ObjectTypeMeta(type):
|
|||
assert hasattr(
|
||||
new_class, 'mutate'), "All mutations must implement mutate method"
|
||||
|
||||
if input_class:
|
||||
items = dict(input_class.__dict__)
|
||||
items.pop('__dict__', None)
|
||||
input_type = type('{}Input'.format(
|
||||
new_class._meta.type_name), (ObjectType, ), items)
|
||||
new_class.add_to_class('input_type', input_type)
|
||||
|
||||
new_class.add_extra_fields()
|
||||
|
||||
new_fields = new_class._meta.local_fields
|
||||
|
@ -87,7 +80,8 @@ class ObjectTypeMeta(type):
|
|||
# on the base classes (we cannot handle shadowed fields at the
|
||||
# moment).
|
||||
for field in parent_fields:
|
||||
if field.name in field_names and field.__class__ != field_names[field.name].__class__:
|
||||
if field.name in field_names and field.type.__class__ != field_names[
|
||||
field.name].type.__class__:
|
||||
raise Exception(
|
||||
'Local field %r in class %r (%r) clashes '
|
||||
'with field with similar name from '
|
||||
|
@ -106,6 +100,9 @@ class ObjectTypeMeta(type):
|
|||
new_class._meta.interfaces.append(base)
|
||||
# new_class._meta.parents.extend(base._meta.parents)
|
||||
|
||||
setattr(new_class, 'NonNull', NonNull(new_class))
|
||||
setattr(new_class, 'List', List(new_class))
|
||||
|
||||
new_class._prepare()
|
||||
return new_class
|
||||
|
||||
|
@ -119,13 +116,14 @@ class ObjectTypeMeta(type):
|
|||
|
||||
def add_to_class(cls, name, value):
|
||||
# We should call the contribute_to_class method only if it's bound
|
||||
if not inspect.isclass(value) and hasattr(value, 'contribute_to_class'):
|
||||
if not inspect.isclass(value) and hasattr(
|
||||
value, 'contribute_to_class'):
|
||||
value.contribute_to_class(cls, name)
|
||||
else:
|
||||
setattr(cls, name, value)
|
||||
|
||||
|
||||
class BaseObjectType(object):
|
||||
class BaseObjectType(BaseType):
|
||||
|
||||
def __new__(cls, *args, **kwargs):
|
||||
if cls._meta.is_interface:
|
||||
|
@ -165,14 +163,16 @@ class BaseObjectType(object):
|
|||
pass
|
||||
if kwargs:
|
||||
raise TypeError(
|
||||
"'%s' is an invalid keyword argument for this function" % list(kwargs)[0])
|
||||
"'%s' is an invalid keyword argument for this function" %
|
||||
list(kwargs)[0])
|
||||
|
||||
signals.post_init.send(self.__class__, instance=self)
|
||||
|
||||
@classmethod
|
||||
def fields_as_arguments(cls, schema):
|
||||
return OrderedDict([(f.attname, GraphQLArgument(f.internal_type(schema)))
|
||||
for f in cls._meta.fields])
|
||||
return OrderedDict(
|
||||
[(f.attname, GraphQLArgument(f.internal_type(schema)))
|
||||
for f in cls._meta.fields])
|
||||
|
||||
@classmethod
|
||||
def resolve_objecttype(cls, schema, instance, *args):
|
||||
|
@ -185,23 +185,32 @@ class BaseObjectType(object):
|
|||
|
||||
@classmethod
|
||||
def internal_type(cls, schema):
|
||||
fields = lambda: OrderedDict([(f.name, f.internal_field(schema))
|
||||
for f in cls._meta.fields])
|
||||
if cls._meta.is_interface:
|
||||
return GraphQLInterfaceType(
|
||||
cls._meta.type_name,
|
||||
description=cls._meta.description,
|
||||
resolve_type=partial(cls.resolve_type, schema),
|
||||
fields=fields
|
||||
fields=partial(cls.get_fields, schema)
|
||||
)
|
||||
return GraphQLObjectType(
|
||||
cls._meta.type_name,
|
||||
description=cls._meta.description,
|
||||
interfaces=[schema.T(i) for i in cls._meta.interfaces],
|
||||
fields=fields,
|
||||
fields=partial(cls.get_fields, schema),
|
||||
is_type_of=getattr(cls, 'is_type_of', None)
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def get_fields(cls, schema):
|
||||
fields = []
|
||||
for field in cls._meta.fields:
|
||||
try:
|
||||
fields.append((field.name, schema.T(field)))
|
||||
except SkipField:
|
||||
continue
|
||||
|
||||
return OrderedDict(fields)
|
||||
|
||||
|
||||
class Interface(six.with_metaclass(ObjectTypeMeta, BaseObjectType)):
|
||||
pass
|
||||
|
@ -212,20 +221,33 @@ class ObjectType(six.with_metaclass(ObjectTypeMeta, BaseObjectType)):
|
|||
|
||||
|
||||
class Mutation(six.with_metaclass(ObjectTypeMeta, BaseObjectType)):
|
||||
@classmethod
|
||||
def _construct_arguments(cls, items):
|
||||
return ArgumentsGroup(**items)
|
||||
|
||||
@classmethod
|
||||
def get_input_type(cls):
|
||||
return getattr(cls, 'input_type', None)
|
||||
def _prepare_class(cls):
|
||||
input_class = getattr(cls, 'Input', None)
|
||||
if input_class:
|
||||
items = dict(vars(input_class))
|
||||
items.pop('__dict__', None)
|
||||
items.pop('__doc__', None)
|
||||
items.pop('__module__', None)
|
||||
items.pop('__weakref__', None)
|
||||
cls.add_to_class('arguments', cls._construct_arguments(items))
|
||||
delattr(cls, 'Input')
|
||||
|
||||
@classmethod
|
||||
def get_arguments(cls):
|
||||
return cls.arguments
|
||||
|
||||
|
||||
class InputObjectType(ObjectType):
|
||||
|
||||
@classmethod
|
||||
def internal_type(cls, schema):
|
||||
fields = lambda: OrderedDict([(f.name, f.internal_field(schema))
|
||||
for f in cls._meta.fields])
|
||||
return GraphQLInputObjectType(
|
||||
cls._meta.type_name,
|
||||
description=cls._meta.description,
|
||||
fields=fields,
|
||||
fields=partial(cls.get_fields, schema),
|
||||
)
|
41
graphene/core/types/scalars.py
Normal file
41
graphene/core/types/scalars.py
Normal 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
|
||||
)
|
0
graphene/core/types/tests/__init__.py
Normal file
0
graphene/core/types/tests/__init__.py
Normal file
47
graphene/core/types/tests/test_argument.py
Normal file
47
graphene/core/types/tests/test_argument.py
Normal 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)
|
94
graphene/core/types/tests/test_base.py
Normal file
94
graphene/core/types/tests/test_base.py
Normal 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'}
|
26
graphene/core/types/tests/test_definitions.py
Normal file
26
graphene/core/types/tests/test_definitions.py
Normal 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
|
114
graphene/core/types/tests/test_field.py
Normal file
114
graphene/core/types/tests/test_field.py
Normal 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'
|
|
@ -1,9 +1,7 @@
|
|||
from py.test import raises
|
||||
from pytest import raises
|
||||
|
||||
from graphene.core.fields import IntField, StringField
|
||||
from graphene.core.schema import Schema
|
||||
from graphene.core.types import Interface
|
||||
from graphene.core.types import Int, Interface, String
|
||||
from graphql.core.execution.middlewares.utils import (resolver_has_tag,
|
||||
tag_resolver)
|
||||
from graphql.core.type import GraphQLInterfaceType, GraphQLObjectType
|
||||
|
@ -11,7 +9,7 @@ from graphql.core.type import GraphQLInterfaceType, GraphQLObjectType
|
|||
|
||||
class Character(Interface):
|
||||
'''Character description'''
|
||||
name = StringField()
|
||||
name = String()
|
||||
|
||||
class Meta:
|
||||
type_name = 'core_Character'
|
||||
|
@ -19,7 +17,7 @@ class Character(Interface):
|
|||
|
||||
class Human(Character):
|
||||
'''Human description'''
|
||||
friends = StringField()
|
||||
friends = String()
|
||||
|
||||
class Meta:
|
||||
type_name = 'core_Human'
|
||||
|
@ -66,9 +64,6 @@ def test_object_type():
|
|||
assert isinstance(object_type, GraphQLObjectType)
|
||||
assert object_type.description == 'Human description'
|
||||
assert list(object_type.get_fields().keys()) == ['name', 'friends']
|
||||
# assert object_type.get_fields() == {'name': Human._meta.fields_map['name'].internal_field(
|
||||
# schema), 'friends':
|
||||
# Human._meta.fields_map['friends'].internal_field(schema)}
|
||||
assert object_type.get_interfaces() == [schema.T(Character)]
|
||||
assert Human._meta.fields_map['name'].object_type == Human
|
||||
|
||||
|
@ -97,7 +92,7 @@ def test_object_type_container_too_many_args():
|
|||
def test_field_clashes():
|
||||
with raises(Exception) as excinfo:
|
||||
class Droid(Character):
|
||||
name = IntField()
|
||||
name = Int()
|
||||
|
||||
assert 'clashes' in str(excinfo.value)
|
||||
|
||||
|
@ -108,12 +103,26 @@ def test_fields_inherited_should_be_different():
|
|||
|
||||
def test_field_mantain_resolver_tags():
|
||||
class Droid(Character):
|
||||
name = StringField()
|
||||
name = String()
|
||||
|
||||
def resolve_name(self, *args):
|
||||
return 'My Droid'
|
||||
|
||||
tag_resolver(resolve_name, 'test')
|
||||
|
||||
field = Droid._meta.fields_map['name'].internal_field(schema)
|
||||
field = schema.T(Droid._meta.fields_map['name'])
|
||||
assert resolver_has_tag(field.resolver, 'test')
|
||||
|
||||
|
||||
def test_type_has_nonnull():
|
||||
class Droid(Character):
|
||||
name = String()
|
||||
|
||||
assert Droid.NonNull.of_type == Droid
|
||||
|
||||
|
||||
def test_type_has_list():
|
||||
class Droid(Character):
|
||||
name = String()
|
||||
|
||||
assert Droid.List.of_type == Droid
|
53
graphene/core/types/tests/test_scalars.py
Normal file
53
graphene/core/types/tests/test_scalars.py
Normal 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'
|
|
@ -1,41 +1,50 @@
|
|||
from collections import Iterable
|
||||
|
||||
from graphene.core.fields import Field, IDField
|
||||
from graphql.core.type import GraphQLArgument, GraphQLID, GraphQLNonNull
|
||||
from graphene.core.fields import Field
|
||||
from graphene.core.types.scalars import ID, Int, String
|
||||
from graphql_relay.connection.arrayconnection import connection_from_list
|
||||
from graphql_relay.connection.connection import connection_args
|
||||
from graphql_relay.node.node import from_global_id
|
||||
|
||||
|
||||
class ConnectionField(Field):
|
||||
|
||||
def __init__(self, field_type, resolve=None, description='',
|
||||
def __init__(self, field_type, resolver=None, description='',
|
||||
connection_type=None, edge_type=None, **kwargs):
|
||||
super(ConnectionField, self).__init__(field_type, resolve=resolve,
|
||||
args=connection_args,
|
||||
description=description, **kwargs)
|
||||
super(
|
||||
ConnectionField,
|
||||
self).__init__(
|
||||
field_type,
|
||||
resolver=resolver,
|
||||
before=String(),
|
||||
after=String(),
|
||||
first=Int(),
|
||||
last=Int(),
|
||||
description=description,
|
||||
**kwargs)
|
||||
self.connection_type = connection_type
|
||||
self.edge_type = edge_type
|
||||
|
||||
def wrap_resolved(self, value, instance, args, info):
|
||||
return value
|
||||
|
||||
def resolve(self, instance, args, info):
|
||||
def resolver(self, instance, args, info):
|
||||
from graphene.relay.types import PageInfo
|
||||
schema = info.schema.graphene_schema
|
||||
|
||||
resolved = super(ConnectionField, self).resolve(instance, args, info)
|
||||
resolved = super(ConnectionField, self).resolver(instance, args, info)
|
||||
if resolved:
|
||||
resolved = self.wrap_resolved(resolved, instance, args, info)
|
||||
assert isinstance(
|
||||
resolved, Iterable), 'Resolved value from the connection field have to be iterable'
|
||||
|
||||
node = self.get_object_type(schema)
|
||||
type = schema.T(self.type)
|
||||
node = schema.objecttype(type)
|
||||
connection_type = self.get_connection_type(node)
|
||||
edge_type = self.get_edge_type(node)
|
||||
|
||||
connection = connection_from_list(resolved, args, connection_type=connection_type,
|
||||
edge_type=edge_type, pageinfo_type=PageInfo)
|
||||
connection = connection_from_list(
|
||||
resolved, args, connection_type=connection_type,
|
||||
edge_type=edge_type, pageinfo_type=PageInfo)
|
||||
connection.set_connection_data(resolved)
|
||||
return connection
|
||||
|
||||
|
@ -47,25 +56,24 @@ class ConnectionField(Field):
|
|||
def get_edge_type(self, node):
|
||||
return self.edge_type or node.get_edge_type()
|
||||
|
||||
def internal_type(self, schema):
|
||||
def get_type(self, schema):
|
||||
from graphene.relay.utils import is_node
|
||||
node = self.get_object_type(schema)
|
||||
type = schema.T(self.type)
|
||||
node = schema.objecttype(type)
|
||||
assert is_node(node), 'Only nodes have connections.'
|
||||
schema.register(node)
|
||||
connection_type = self.get_connection_type(node)
|
||||
return schema.T(connection_type)
|
||||
return connection_type
|
||||
|
||||
|
||||
class NodeField(Field):
|
||||
|
||||
def __init__(self, object_type=None, *args, **kwargs):
|
||||
from graphene.relay.types import Node
|
||||
super(NodeField, self).__init__(object_type or Node, *args, **kwargs)
|
||||
id = kwargs.pop('id', None) or ID(description='The ID of an object')
|
||||
super(NodeField, self).__init__(
|
||||
object_type or Node, id=id, *args, **kwargs)
|
||||
self.field_object_type = object_type
|
||||
self.args['id'] = GraphQLArgument(
|
||||
GraphQLNonNull(GraphQLID),
|
||||
description='The ID of an object'
|
||||
)
|
||||
|
||||
def id_fetcher(self, global_id, info):
|
||||
from graphene.relay.utils import is_node
|
||||
|
@ -79,20 +87,23 @@ class NodeField(Field):
|
|||
|
||||
return object_type.get_node(_id)
|
||||
|
||||
def resolve(self, instance, args, info):
|
||||
def resolver(self, instance, args, info):
|
||||
global_id = args.get('id')
|
||||
return self.id_fetcher(global_id, info)
|
||||
|
||||
|
||||
class GlobalIDField(IDField):
|
||||
class GlobalIDField(Field):
|
||||
'''The ID of an object'''
|
||||
required = True
|
||||
|
||||
def contribute_to_class(self, cls, name, add=True):
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(GlobalIDField, self).__init__(ID(), *args, **kwargs)
|
||||
self.required = True
|
||||
|
||||
def contribute_to_class(self, cls, name):
|
||||
from graphene.relay.utils import is_node, is_node_type
|
||||
in_node = is_node(cls) or is_node_type(cls)
|
||||
assert in_node, 'GlobalIDField could only be inside a Node, but got %r' % cls
|
||||
super(GlobalIDField, self).contribute_to_class(cls, name, add)
|
||||
super(GlobalIDField, self).contribute_to_class(cls, name)
|
||||
|
||||
def resolve(self, instance, args, info):
|
||||
return self.object_type.to_global_id(instance, args, info)
|
||||
def resolver(self, instance, args, info):
|
||||
return instance.to_global_id()
|
||||
|
|
0
graphene/relay/tests/__init__.py
Normal file
0
graphene/relay/tests/__init__.py
Normal file
|
@ -1,22 +1,21 @@
|
|||
import graphene
|
||||
from graphene import relay
|
||||
from graphene.core.schema import Schema
|
||||
from graphene.core.types import InputObjectType
|
||||
from graphql.core.type import GraphQLInputObjectField
|
||||
|
||||
my_id = 0
|
||||
|
||||
|
||||
class Query(graphene.ObjectType):
|
||||
base = graphene.StringField()
|
||||
base = graphene.String()
|
||||
|
||||
|
||||
class ChangeNumber(relay.ClientIDMutation):
|
||||
'''Result mutation'''
|
||||
class Input:
|
||||
to = graphene.IntField()
|
||||
to = graphene.Int()
|
||||
|
||||
result = graphene.StringField()
|
||||
result = graphene.String()
|
||||
|
||||
@classmethod
|
||||
def mutate_and_get_payload(cls, input, info):
|
||||
|
@ -32,19 +31,18 @@ class MyResultMutation(graphene.ObjectType):
|
|||
schema = Schema(query=Query, mutation=MyResultMutation)
|
||||
|
||||
|
||||
def test_mutation_input():
|
||||
assert ChangeNumber.input_type
|
||||
assert ChangeNumber.input_type._meta.type_name == 'ChangeNumberInput'
|
||||
assert list(ChangeNumber.input_type._meta.fields_map.keys()) == ['input']
|
||||
_input = ChangeNumber.input_type._meta.fields_map['input']
|
||||
inner_type = _input.get_object_type(schema)
|
||||
def test_mutation_arguments():
|
||||
assert ChangeNumber.arguments
|
||||
assert list(ChangeNumber.arguments) == ['input']
|
||||
assert 'input' in ChangeNumber.arguments
|
||||
inner_type = ChangeNumber.input_type
|
||||
client_mutation_id_field = inner_type._meta.fields_map[
|
||||
'client_mutation_id']
|
||||
assert issubclass(inner_type, InputObjectType)
|
||||
assert isinstance(client_mutation_id_field, graphene.StringField)
|
||||
assert issubclass(inner_type, graphene.InputObjectType)
|
||||
assert isinstance(client_mutation_id_field.type, graphene.NonNull)
|
||||
assert isinstance(client_mutation_id_field.type.of_type, graphene.String)
|
||||
assert client_mutation_id_field.object_type == inner_type
|
||||
assert isinstance(client_mutation_id_field.internal_field(
|
||||
schema), GraphQLInputObjectField)
|
||||
assert isinstance(schema.T(client_mutation_id_field), GraphQLInputObjectField)
|
||||
|
||||
|
||||
def test_execute_mutations():
|
|
@ -6,22 +6,22 @@ schema = graphene.Schema()
|
|||
|
||||
|
||||
class MyConnection(relay.Connection):
|
||||
my_custom_field = graphene.StringField(
|
||||
resolve=lambda instance, *_: 'Custom')
|
||||
my_custom_field = graphene.String(
|
||||
resolver=lambda instance, *_: 'Custom')
|
||||
|
||||
|
||||
class MyNode(relay.Node):
|
||||
name = graphene.StringField()
|
||||
name = graphene.String()
|
||||
|
||||
@classmethod
|
||||
def get_node(cls, id):
|
||||
return MyNode(name='mo')
|
||||
return MyNode(id=id, name='mo')
|
||||
|
||||
|
||||
class Query(graphene.ObjectType):
|
||||
my_node = relay.NodeField(MyNode)
|
||||
all_my_nodes = relay.ConnectionField(
|
||||
MyNode, connection_type=MyConnection, customArg=graphene.Argument(graphene.String))
|
||||
MyNode, connection_type=MyConnection, customArg=graphene.String())
|
||||
|
||||
def resolve_all_my_nodes(self, args, info):
|
||||
custom_arg = args.get('customArg')
|
||||
|
@ -35,6 +35,7 @@ def test_nodefield_query():
|
|||
query = '''
|
||||
query RebelsShipsQuery {
|
||||
myNode(id:"TXlOb2RlOjE=") {
|
||||
id
|
||||
name
|
||||
},
|
||||
allMyNodes (customArg:"1") {
|
||||
|
@ -52,6 +53,7 @@ def test_nodefield_query():
|
|||
'''
|
||||
expected = {
|
||||
'myNode': {
|
||||
'id': 'TXlOb2RlOjE=',
|
||||
'name': 'mo'
|
||||
},
|
||||
'allMyNodes': {
|
||||
|
@ -73,5 +75,6 @@ def test_nodefield_query():
|
|||
|
||||
def test_nodeidfield():
|
||||
id_field = MyNode._meta.fields_map['id']
|
||||
assert isinstance(id_field.internal_field(schema).type, GraphQLNonNull)
|
||||
assert id_field.internal_field(schema).type.of_type == GraphQLID
|
||||
id_field_type = schema.T(id_field)
|
||||
assert isinstance(id_field_type.type, GraphQLNonNull)
|
||||
assert id_field_type.type.of_type == GraphQLID
|
|
@ -7,7 +7,7 @@ schema = graphene.Schema()
|
|||
|
||||
|
||||
class OtherNode(relay.Node):
|
||||
name = graphene.StringField()
|
||||
name = graphene.String()
|
||||
|
||||
@classmethod
|
||||
def get_node(cls, id):
|
||||
|
@ -17,7 +17,7 @@ class OtherNode(relay.Node):
|
|||
def test_field_no_contributed_raises_error():
|
||||
with raises(Exception) as excinfo:
|
||||
class Part(relay.Node):
|
||||
x = graphene.StringField()
|
||||
x = graphene.String()
|
||||
|
||||
assert 'get_node' in str(excinfo.value)
|
||||
|
|
@ -1,19 +1,23 @@
|
|||
from graphene.core.fields import BooleanField, Field, ListField, StringField
|
||||
from graphene.core.types import (InputObjectType, Interface, Mutation,
|
||||
ObjectType)
|
||||
from graphene.core.types import (Boolean, Field, InputObjectType, Interface,
|
||||
List, Mutation, ObjectType, String)
|
||||
from graphene.core.types.argument import ArgumentsGroup
|
||||
from graphene.core.types.base import LazyType
|
||||
from graphene.core.types.definitions import NonNull
|
||||
from graphene.relay.fields import GlobalIDField
|
||||
from graphene.utils import memoize
|
||||
from graphql_relay.node.node import to_global_id
|
||||
|
||||
|
||||
class PageInfo(ObjectType):
|
||||
has_next_page = BooleanField(
|
||||
required=True, description='When paginating forwards, are there more items?')
|
||||
has_previous_page = BooleanField(
|
||||
required=True, description='When paginating backwards, are there more items?')
|
||||
start_cursor = StringField(
|
||||
has_next_page = Boolean(
|
||||
required=True,
|
||||
description='When paginating forwards, are there more items?')
|
||||
has_previous_page = Boolean(
|
||||
required=True,
|
||||
description='When paginating backwards, are there more items?')
|
||||
start_cursor = String(
|
||||
description='When paginating backwards, the cursor to continue.')
|
||||
end_cursor = StringField(
|
||||
end_cursor = String(
|
||||
description='When paginating forwards, the cursor to continue.')
|
||||
|
||||
|
||||
|
@ -22,9 +26,9 @@ class Edge(ObjectType):
|
|||
class Meta:
|
||||
type_name = 'DefaultEdge'
|
||||
|
||||
node = Field(lambda field: field.object_type.node_type,
|
||||
node = Field(LazyType(lambda object_type: object_type.node_type),
|
||||
description='The item at the end of the edge')
|
||||
cursor = StringField(
|
||||
cursor = String(
|
||||
required=True, description='A cursor for use in pagination')
|
||||
|
||||
@classmethod
|
||||
|
@ -32,7 +36,10 @@ class Edge(ObjectType):
|
|||
def for_node(cls, node):
|
||||
from graphene.relay.utils import is_node
|
||||
assert is_node(node), 'ObjectTypes in a edge have to be Nodes'
|
||||
return type('%s%s' % (node._meta.type_name, cls._meta.type_name), (cls, ), {'node_type': node})
|
||||
return type(
|
||||
'%s%s' % (node._meta.type_name, cls._meta.type_name),
|
||||
(cls,),
|
||||
{'node_type': node})
|
||||
|
||||
|
||||
class Connection(ObjectType):
|
||||
|
@ -42,8 +49,8 @@ class Connection(ObjectType):
|
|||
|
||||
page_info = Field(PageInfo, required=True,
|
||||
description='The Information to aid in pagination')
|
||||
edges = ListField(lambda field: field.object_type.edge_type,
|
||||
description='Information to aid in pagination.')
|
||||
edges = List(LazyType(lambda object_type: object_type.edge_type),
|
||||
description='Information to aid in pagination.')
|
||||
|
||||
_connection_data = None
|
||||
|
||||
|
@ -53,7 +60,10 @@ class Connection(ObjectType):
|
|||
from graphene.relay.utils import is_node
|
||||
edge_type = edge_type or Edge
|
||||
assert is_node(node), 'ObjectTypes in a connection have to be Nodes'
|
||||
return type('%s%s' % (node._meta.type_name, cls._meta.type_name), (cls, ), {'edge_type': edge_type.for_node(node)})
|
||||
return type(
|
||||
'%s%s' % (node._meta.type_name, cls._meta.type_name),
|
||||
(cls,),
|
||||
{'edge_type': edge_type.for_node(node)})
|
||||
|
||||
def set_connection_data(self, data):
|
||||
self._connection_data = data
|
||||
|
@ -71,10 +81,9 @@ class BaseNode(object):
|
|||
assert hasattr(
|
||||
cls, 'get_node'), 'get_node classmethod not found in %s Node' % cls
|
||||
|
||||
@classmethod
|
||||
def to_global_id(cls, instance, args, info):
|
||||
type_name = cls._meta.type_name
|
||||
return to_global_id(type_name, instance.id)
|
||||
def to_global_id(self):
|
||||
type_name = self._meta.type_name
|
||||
return to_global_id(type_name, self.id)
|
||||
|
||||
connection_type = Connection
|
||||
edge_type = Edge
|
||||
|
@ -90,31 +99,24 @@ class BaseNode(object):
|
|||
|
||||
class Node(BaseNode, Interface):
|
||||
'''An object with an ID'''
|
||||
id = GlobalIDField()
|
||||
id = GlobalIDField(required=True)
|
||||
|
||||
|
||||
class MutationInputType(InputObjectType):
|
||||
client_mutation_id = StringField(required=True)
|
||||
client_mutation_id = String(required=True)
|
||||
|
||||
|
||||
class ClientIDMutation(Mutation):
|
||||
client_mutation_id = StringField(required=True)
|
||||
client_mutation_id = String(required=True)
|
||||
|
||||
@classmethod
|
||||
def _prepare_class(cls):
|
||||
input_type = getattr(cls, 'input_type', None)
|
||||
if input_type:
|
||||
assert hasattr(
|
||||
cls, 'mutate_and_get_payload'), 'You have to implement mutate_and_get_payload'
|
||||
new_input_inner_type = type('{}InnerInput'.format(
|
||||
cls._meta.type_name), (MutationInputType, input_type, ), {})
|
||||
items = {
|
||||
'input': Field(new_input_inner_type)
|
||||
}
|
||||
assert issubclass(new_input_inner_type, InputObjectType)
|
||||
input_type = type('{}Input'.format(
|
||||
cls._meta.type_name), (ObjectType, ), items)
|
||||
setattr(cls, 'input_type', input_type)
|
||||
def _construct_arguments(cls, items):
|
||||
assert hasattr(
|
||||
cls, 'mutate_and_get_payload'), 'You have to implement mutate_and_get_payload'
|
||||
new_input_type = type('{}Input'.format(
|
||||
cls._meta.type_name), (MutationInputType, ), items)
|
||||
cls.add_to_class('input_type', new_input_type)
|
||||
return ArgumentsGroup(input=NonNull(new_input_type))
|
||||
|
||||
@classmethod
|
||||
def mutate(cls, instance, args, info):
|
||||
|
|
|
@ -2,7 +2,8 @@ from graphene.relay.types import BaseNode
|
|||
|
||||
|
||||
def is_node(object_type):
|
||||
return object_type and issubclass(object_type, BaseNode) and not is_node_type(object_type)
|
||||
return object_type and issubclass(
|
||||
object_type, BaseNode) and not is_node_type(object_type)
|
||||
|
||||
|
||||
def is_node_type(object_type):
|
||||
|
|
|
@ -19,11 +19,14 @@ def test_proxy_snake_dict():
|
|||
assert p.get('three_or_for') == 3
|
||||
assert 'inside' in p
|
||||
assert 'other_camel_case' in p['inside']
|
||||
assert sorted(p.items()) == sorted(list([('inside', ProxySnakeDict({'other_camel_case': 3})),
|
||||
('none', None),
|
||||
('three_or_for', 3),
|
||||
('two', 2),
|
||||
('one', 1)]))
|
||||
assert sorted(
|
||||
p.items()) == sorted(
|
||||
list(
|
||||
[('inside', ProxySnakeDict({'other_camel_case': 3})),
|
||||
('none', None),
|
||||
('three_or_for', 3),
|
||||
('two', 2),
|
||||
('one', 1)]))
|
||||
|
||||
|
||||
def test_proxy_snake_dict_as_kwargs():
|
|
@ -1,3 +1,6 @@
|
|||
[flake8]
|
||||
exclude = tests/*,setup.py
|
||||
max-line-length = 160
|
||||
exclude = setup.py
|
||||
max-line-length = 120
|
||||
|
||||
[coverage:run]
|
||||
omit = core/ntypes/tests/*
|
||||
|
|
1
setup.py
1
setup.py
|
@ -62,6 +62,7 @@ setup(
|
|||
tests_require=[
|
||||
'pytest>=2.7.2',
|
||||
'pytest-django',
|
||||
'mock',
|
||||
],
|
||||
extras_require={
|
||||
'django': [
|
||||
|
|
|
@ -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'
|
|
@ -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
|
|
@ -2,7 +2,6 @@ SECRET_KEY = 1
|
|||
|
||||
INSTALLED_APPS = [
|
||||
'examples.starwars_django',
|
||||
'tests.contrib_django',
|
||||
]
|
||||
|
||||
DATABASES = {
|
||||
|
|
Loading…
Reference in New Issue
Block a user