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