mirror of
https://github.com/graphql-python/graphene.git
synced 2024-11-29 13:03:56 +03:00
Refactored all graphene code moving to 1.0
This commit is contained in:
parent
1711e6a529
commit
33d4f44f04
|
@ -1,70 +0,0 @@
|
||||||
from graphene import signals
|
|
||||||
|
|
||||||
from .core import (
|
|
||||||
Schema,
|
|
||||||
ObjectType,
|
|
||||||
InputObjectType,
|
|
||||||
Interface,
|
|
||||||
Mutation,
|
|
||||||
Scalar,
|
|
||||||
Enum,
|
|
||||||
InstanceType,
|
|
||||||
LazyType,
|
|
||||||
Argument,
|
|
||||||
Field,
|
|
||||||
InputField,
|
|
||||||
String,
|
|
||||||
Int,
|
|
||||||
Boolean,
|
|
||||||
ID,
|
|
||||||
Float,
|
|
||||||
List,
|
|
||||||
NonNull
|
|
||||||
)
|
|
||||||
|
|
||||||
from graphene.core.fields import (
|
|
||||||
StringField,
|
|
||||||
IntField,
|
|
||||||
BooleanField,
|
|
||||||
IDField,
|
|
||||||
ListField,
|
|
||||||
NonNullField,
|
|
||||||
FloatField,
|
|
||||||
)
|
|
||||||
|
|
||||||
from graphene.utils import (
|
|
||||||
resolve_only_args,
|
|
||||||
with_context
|
|
||||||
)
|
|
||||||
|
|
||||||
__all__ = [
|
|
||||||
'Enum',
|
|
||||||
'Argument',
|
|
||||||
'String',
|
|
||||||
'Int',
|
|
||||||
'Boolean',
|
|
||||||
'Float',
|
|
||||||
'ID',
|
|
||||||
'List',
|
|
||||||
'NonNull',
|
|
||||||
'signals',
|
|
||||||
'Schema',
|
|
||||||
'InstanceType',
|
|
||||||
'LazyType',
|
|
||||||
'ObjectType',
|
|
||||||
'InputObjectType',
|
|
||||||
'Interface',
|
|
||||||
'Mutation',
|
|
||||||
'Scalar',
|
|
||||||
'Enum',
|
|
||||||
'Field',
|
|
||||||
'InputField',
|
|
||||||
'StringField',
|
|
||||||
'IntField',
|
|
||||||
'BooleanField',
|
|
||||||
'IDField',
|
|
||||||
'ListField',
|
|
||||||
'NonNullField',
|
|
||||||
'FloatField',
|
|
||||||
'resolve_only_args',
|
|
||||||
'with_context']
|
|
|
@ -1,12 +0,0 @@
|
||||||
from graphene.contrib.django.types import (
|
|
||||||
DjangoConnection,
|
|
||||||
DjangoObjectType,
|
|
||||||
DjangoNode
|
|
||||||
)
|
|
||||||
from graphene.contrib.django.fields import (
|
|
||||||
DjangoConnectionField,
|
|
||||||
DjangoModelField
|
|
||||||
)
|
|
||||||
|
|
||||||
__all__ = ['DjangoObjectType', 'DjangoNode', 'DjangoConnection',
|
|
||||||
'DjangoModelField', 'DjangoConnectionField']
|
|
|
@ -1,24 +0,0 @@
|
||||||
from django.db import models
|
|
||||||
|
|
||||||
|
|
||||||
class MissingType(object):
|
|
||||||
pass
|
|
||||||
|
|
||||||
try:
|
|
||||||
UUIDField = models.UUIDField
|
|
||||||
except AttributeError:
|
|
||||||
# Improved compatibility for Django 1.6
|
|
||||||
UUIDField = MissingType
|
|
||||||
|
|
||||||
try:
|
|
||||||
from django.db.models.related import RelatedObject
|
|
||||||
except:
|
|
||||||
# Improved compatibility for Django 1.6
|
|
||||||
RelatedObject = MissingType
|
|
||||||
|
|
||||||
|
|
||||||
try:
|
|
||||||
# Postgres fields are only available in Django 1.8+
|
|
||||||
from django.contrib.postgres.fields import ArrayField, HStoreField, JSONField, RangeField
|
|
||||||
except ImportError:
|
|
||||||
ArrayField, HStoreField, JSONField, RangeField = (MissingType, ) * 4
|
|
|
@ -1,136 +0,0 @@
|
||||||
from django.db import models
|
|
||||||
from django.utils.encoding import force_text
|
|
||||||
|
|
||||||
from ...core.classtypes.enum import Enum
|
|
||||||
from ...core.types.custom_scalars import DateTime, JSONString
|
|
||||||
from ...core.types.definitions import List
|
|
||||||
from ...core.types.scalars import ID, Boolean, Float, Int, String
|
|
||||||
from ...utils import to_const
|
|
||||||
from .compat import (ArrayField, HStoreField, JSONField, RangeField,
|
|
||||||
RelatedObject, UUIDField)
|
|
||||||
from .utils import get_related_model, import_single_dispatch
|
|
||||||
|
|
||||||
singledispatch = import_single_dispatch()
|
|
||||||
|
|
||||||
|
|
||||||
def convert_choices(choices):
|
|
||||||
for value, name in choices:
|
|
||||||
if isinstance(name, (tuple, list)):
|
|
||||||
for choice in convert_choices(name):
|
|
||||||
yield choice
|
|
||||||
else:
|
|
||||||
yield to_const(force_text(name)), value
|
|
||||||
|
|
||||||
|
|
||||||
def convert_django_field_with_choices(field):
|
|
||||||
choices = getattr(field, 'choices', None)
|
|
||||||
if choices:
|
|
||||||
meta = field.model._meta
|
|
||||||
name = '{}_{}_{}'.format(meta.app_label, meta.object_name, field.name)
|
|
||||||
graphql_choices = list(convert_choices(choices))
|
|
||||||
return Enum(name.upper(), graphql_choices, description=field.help_text)
|
|
||||||
return convert_django_field(field)
|
|
||||||
|
|
||||||
|
|
||||||
@singledispatch
|
|
||||||
def convert_django_field(field):
|
|
||||||
raise Exception(
|
|
||||||
"Don't know how to convert the Django field %s (%s)" %
|
|
||||||
(field, field.__class__))
|
|
||||||
|
|
||||||
|
|
||||||
@convert_django_field.register(models.CharField)
|
|
||||||
@convert_django_field.register(models.TextField)
|
|
||||||
@convert_django_field.register(models.EmailField)
|
|
||||||
@convert_django_field.register(models.SlugField)
|
|
||||||
@convert_django_field.register(models.URLField)
|
|
||||||
@convert_django_field.register(models.GenericIPAddressField)
|
|
||||||
@convert_django_field.register(models.FileField)
|
|
||||||
@convert_django_field.register(UUIDField)
|
|
||||||
def convert_field_to_string(field):
|
|
||||||
return String(description=field.help_text)
|
|
||||||
|
|
||||||
|
|
||||||
@convert_django_field.register(models.AutoField)
|
|
||||||
def convert_field_to_id(field):
|
|
||||||
return ID(description=field.help_text)
|
|
||||||
|
|
||||||
|
|
||||||
@convert_django_field.register(models.PositiveIntegerField)
|
|
||||||
@convert_django_field.register(models.PositiveSmallIntegerField)
|
|
||||||
@convert_django_field.register(models.SmallIntegerField)
|
|
||||||
@convert_django_field.register(models.BigIntegerField)
|
|
||||||
@convert_django_field.register(models.IntegerField)
|
|
||||||
def convert_field_to_int(field):
|
|
||||||
return Int(description=field.help_text)
|
|
||||||
|
|
||||||
|
|
||||||
@convert_django_field.register(models.BooleanField)
|
|
||||||
def convert_field_to_boolean(field):
|
|
||||||
return Boolean(description=field.help_text, required=True)
|
|
||||||
|
|
||||||
|
|
||||||
@convert_django_field.register(models.NullBooleanField)
|
|
||||||
def convert_field_to_nullboolean(field):
|
|
||||||
return Boolean(description=field.help_text)
|
|
||||||
|
|
||||||
|
|
||||||
@convert_django_field.register(models.DecimalField)
|
|
||||||
@convert_django_field.register(models.FloatField)
|
|
||||||
def convert_field_to_float(field):
|
|
||||||
return Float(description=field.help_text)
|
|
||||||
|
|
||||||
|
|
||||||
@convert_django_field.register(models.DateField)
|
|
||||||
def convert_date_to_string(field):
|
|
||||||
return DateTime(description=field.help_text)
|
|
||||||
|
|
||||||
|
|
||||||
@convert_django_field.register(models.OneToOneRel)
|
|
||||||
def convert_onetoone_field_to_djangomodel(field):
|
|
||||||
from .fields import DjangoModelField
|
|
||||||
return DjangoModelField(get_related_model(field))
|
|
||||||
|
|
||||||
|
|
||||||
@convert_django_field.register(models.ManyToManyField)
|
|
||||||
@convert_django_field.register(models.ManyToManyRel)
|
|
||||||
@convert_django_field.register(models.ManyToOneRel)
|
|
||||||
def convert_field_to_list_or_connection(field):
|
|
||||||
from .fields import DjangoModelField, ConnectionOrListField
|
|
||||||
model_field = DjangoModelField(get_related_model(field))
|
|
||||||
return ConnectionOrListField(model_field)
|
|
||||||
|
|
||||||
|
|
||||||
# For Django 1.6
|
|
||||||
@convert_django_field.register(RelatedObject)
|
|
||||||
def convert_relatedfield_to_djangomodel(field):
|
|
||||||
from .fields import DjangoModelField, ConnectionOrListField
|
|
||||||
model_field = DjangoModelField(field.model)
|
|
||||||
if isinstance(field.field, models.OneToOneField):
|
|
||||||
return model_field
|
|
||||||
return ConnectionOrListField(model_field)
|
|
||||||
|
|
||||||
|
|
||||||
@convert_django_field.register(models.OneToOneField)
|
|
||||||
@convert_django_field.register(models.ForeignKey)
|
|
||||||
def convert_field_to_djangomodel(field):
|
|
||||||
from .fields import DjangoModelField
|
|
||||||
return DjangoModelField(get_related_model(field), description=field.help_text)
|
|
||||||
|
|
||||||
|
|
||||||
@convert_django_field.register(ArrayField)
|
|
||||||
def convert_postgres_array_to_list(field):
|
|
||||||
base_type = convert_django_field(field.base_field)
|
|
||||||
return List(base_type, description=field.help_text)
|
|
||||||
|
|
||||||
|
|
||||||
@convert_django_field.register(HStoreField)
|
|
||||||
@convert_django_field.register(JSONField)
|
|
||||||
def convert_posgres_field_to_string(field):
|
|
||||||
return JSONString(description=field.help_text)
|
|
||||||
|
|
||||||
|
|
||||||
@convert_django_field.register(RangeField)
|
|
||||||
def convert_posgres_range_to_string(field):
|
|
||||||
inner_type = convert_django_field(field.base_field)
|
|
||||||
return List(inner_type, description=field.help_text)
|
|
|
@ -1,4 +0,0 @@
|
||||||
from .middleware import DjangoDebugMiddleware
|
|
||||||
from .types import DjangoDebug
|
|
||||||
|
|
||||||
__all__ = ['DjangoDebugMiddleware', 'DjangoDebug']
|
|
|
@ -1,56 +0,0 @@
|
||||||
from promise import Promise
|
|
||||||
from django.db import connections
|
|
||||||
|
|
||||||
from .sql.tracking import unwrap_cursor, wrap_cursor
|
|
||||||
from .types import DjangoDebug
|
|
||||||
|
|
||||||
|
|
||||||
class DjangoDebugContext(object):
|
|
||||||
|
|
||||||
def __init__(self):
|
|
||||||
self.debug_promise = None
|
|
||||||
self.promises = []
|
|
||||||
self.enable_instrumentation()
|
|
||||||
self.object = DjangoDebug(sql=[])
|
|
||||||
|
|
||||||
def get_debug_promise(self):
|
|
||||||
if not self.debug_promise:
|
|
||||||
self.debug_promise = Promise.all(self.promises)
|
|
||||||
return self.debug_promise.then(self.on_resolve_all_promises)
|
|
||||||
|
|
||||||
def on_resolve_all_promises(self, values):
|
|
||||||
self.disable_instrumentation()
|
|
||||||
return self.object
|
|
||||||
|
|
||||||
def add_promise(self, promise):
|
|
||||||
if self.debug_promise and not self.debug_promise.is_fulfilled:
|
|
||||||
self.promises.append(promise)
|
|
||||||
|
|
||||||
def enable_instrumentation(self):
|
|
||||||
# This is thread-safe because database connections are thread-local.
|
|
||||||
for connection in connections.all():
|
|
||||||
wrap_cursor(connection, self)
|
|
||||||
|
|
||||||
def disable_instrumentation(self):
|
|
||||||
for connection in connections.all():
|
|
||||||
unwrap_cursor(connection)
|
|
||||||
|
|
||||||
|
|
||||||
class DjangoDebugMiddleware(object):
|
|
||||||
|
|
||||||
def resolve(self, next, root, args, context, info):
|
|
||||||
django_debug = getattr(context, 'django_debug', None)
|
|
||||||
if not django_debug:
|
|
||||||
if context is None:
|
|
||||||
raise Exception('DjangoDebug cannot be executed in None contexts')
|
|
||||||
try:
|
|
||||||
context.django_debug = DjangoDebugContext()
|
|
||||||
except Exception:
|
|
||||||
raise Exception('DjangoDebug need the context to be writable, context received: {}.'.format(
|
|
||||||
context.__class__.__name__
|
|
||||||
))
|
|
||||||
if info.schema.graphene_schema.T(DjangoDebug) == info.return_type:
|
|
||||||
return context.django_debug.get_debug_promise()
|
|
||||||
promise = next(root, args, context, info)
|
|
||||||
context.django_debug.add_promise(promise)
|
|
||||||
return promise
|
|
|
@ -1,170 +0,0 @@
|
||||||
# Code obtained from django-debug-toolbar sql panel tracking
|
|
||||||
from __future__ import absolute_import, unicode_literals
|
|
||||||
|
|
||||||
import json
|
|
||||||
from threading import local
|
|
||||||
from time import time
|
|
||||||
|
|
||||||
from django.utils import six
|
|
||||||
from django.utils.encoding import force_text
|
|
||||||
|
|
||||||
from .types import DjangoDebugSQL, DjangoDebugPostgreSQL
|
|
||||||
|
|
||||||
|
|
||||||
class SQLQueryTriggered(Exception):
|
|
||||||
"""Thrown when template panel triggers a query"""
|
|
||||||
|
|
||||||
|
|
||||||
class ThreadLocalState(local):
|
|
||||||
|
|
||||||
def __init__(self):
|
|
||||||
self.enabled = True
|
|
||||||
|
|
||||||
@property
|
|
||||||
def Wrapper(self):
|
|
||||||
if self.enabled:
|
|
||||||
return NormalCursorWrapper
|
|
||||||
return ExceptionCursorWrapper
|
|
||||||
|
|
||||||
def recording(self, v):
|
|
||||||
self.enabled = v
|
|
||||||
|
|
||||||
|
|
||||||
state = ThreadLocalState()
|
|
||||||
recording = state.recording # export function
|
|
||||||
|
|
||||||
|
|
||||||
def wrap_cursor(connection, panel):
|
|
||||||
if not hasattr(connection, '_graphene_cursor'):
|
|
||||||
connection._graphene_cursor = connection.cursor
|
|
||||||
|
|
||||||
def cursor():
|
|
||||||
return state.Wrapper(connection._graphene_cursor(), connection, panel)
|
|
||||||
|
|
||||||
connection.cursor = cursor
|
|
||||||
return cursor
|
|
||||||
|
|
||||||
|
|
||||||
def unwrap_cursor(connection):
|
|
||||||
if hasattr(connection, '_graphene_cursor'):
|
|
||||||
previous_cursor = connection._graphene_cursor
|
|
||||||
connection.cursor = previous_cursor
|
|
||||||
del connection._graphene_cursor
|
|
||||||
|
|
||||||
|
|
||||||
class ExceptionCursorWrapper(object):
|
|
||||||
"""
|
|
||||||
Wraps a cursor and raises an exception on any operation.
|
|
||||||
Used in Templates panel.
|
|
||||||
"""
|
|
||||||
|
|
||||||
def __init__(self, cursor, db, logger):
|
|
||||||
pass
|
|
||||||
|
|
||||||
def __getattr__(self, attr):
|
|
||||||
raise SQLQueryTriggered()
|
|
||||||
|
|
||||||
|
|
||||||
class NormalCursorWrapper(object):
|
|
||||||
"""
|
|
||||||
Wraps a cursor and logs queries.
|
|
||||||
"""
|
|
||||||
|
|
||||||
def __init__(self, cursor, db, logger):
|
|
||||||
self.cursor = cursor
|
|
||||||
# Instance of a BaseDatabaseWrapper subclass
|
|
||||||
self.db = db
|
|
||||||
# logger must implement a ``record`` method
|
|
||||||
self.logger = logger
|
|
||||||
|
|
||||||
def _quote_expr(self, element):
|
|
||||||
if isinstance(element, six.string_types):
|
|
||||||
return "'%s'" % force_text(element).replace("'", "''")
|
|
||||||
else:
|
|
||||||
return repr(element)
|
|
||||||
|
|
||||||
def _quote_params(self, params):
|
|
||||||
if not params:
|
|
||||||
return params
|
|
||||||
if isinstance(params, dict):
|
|
||||||
return dict((key, self._quote_expr(value))
|
|
||||||
for key, value in params.items())
|
|
||||||
return list(map(self._quote_expr, params))
|
|
||||||
|
|
||||||
def _decode(self, param):
|
|
||||||
try:
|
|
||||||
return force_text(param, strings_only=True)
|
|
||||||
except UnicodeDecodeError:
|
|
||||||
return '(encoded string)'
|
|
||||||
|
|
||||||
def _record(self, method, sql, params):
|
|
||||||
start_time = time()
|
|
||||||
try:
|
|
||||||
return method(sql, params)
|
|
||||||
finally:
|
|
||||||
stop_time = time()
|
|
||||||
duration = (stop_time - start_time)
|
|
||||||
_params = ''
|
|
||||||
try:
|
|
||||||
_params = json.dumps(list(map(self._decode, params)))
|
|
||||||
except Exception:
|
|
||||||
pass # object not JSON serializable
|
|
||||||
|
|
||||||
alias = getattr(self.db, 'alias', 'default')
|
|
||||||
conn = self.db.connection
|
|
||||||
vendor = getattr(conn, 'vendor', 'unknown')
|
|
||||||
|
|
||||||
params = {
|
|
||||||
'vendor': vendor,
|
|
||||||
'alias': alias,
|
|
||||||
'sql': self.db.ops.last_executed_query(
|
|
||||||
self.cursor, sql, self._quote_params(params)),
|
|
||||||
'duration': duration,
|
|
||||||
'raw_sql': sql,
|
|
||||||
'params': _params,
|
|
||||||
'start_time': start_time,
|
|
||||||
'stop_time': stop_time,
|
|
||||||
'is_slow': duration > 10,
|
|
||||||
'is_select': sql.lower().strip().startswith('select'),
|
|
||||||
}
|
|
||||||
|
|
||||||
if vendor == 'postgresql':
|
|
||||||
# If an erroneous query was ran on the connection, it might
|
|
||||||
# be in a state where checking isolation_level raises an
|
|
||||||
# exception.
|
|
||||||
try:
|
|
||||||
iso_level = conn.isolation_level
|
|
||||||
except conn.InternalError:
|
|
||||||
iso_level = 'unknown'
|
|
||||||
params.update({
|
|
||||||
'trans_id': self.logger.get_transaction_id(alias),
|
|
||||||
'trans_status': conn.get_transaction_status(),
|
|
||||||
'iso_level': iso_level,
|
|
||||||
'encoding': conn.encoding,
|
|
||||||
})
|
|
||||||
_sql = DjangoDebugPostgreSQL(**params)
|
|
||||||
else:
|
|
||||||
_sql = DjangoDebugSQL(**params)
|
|
||||||
# We keep `sql` to maintain backwards compatibility
|
|
||||||
self.logger.object.sql.append(_sql)
|
|
||||||
|
|
||||||
def callproc(self, procname, params=()):
|
|
||||||
return self._record(self.cursor.callproc, procname, params)
|
|
||||||
|
|
||||||
def execute(self, sql, params=()):
|
|
||||||
return self._record(self.cursor.execute, sql, params)
|
|
||||||
|
|
||||||
def executemany(self, sql, param_list):
|
|
||||||
return self._record(self.cursor.executemany, sql, param_list)
|
|
||||||
|
|
||||||
def __getattr__(self, attr):
|
|
||||||
return getattr(self.cursor, attr)
|
|
||||||
|
|
||||||
def __iter__(self):
|
|
||||||
return iter(self.cursor)
|
|
||||||
|
|
||||||
def __enter__(self):
|
|
||||||
return self
|
|
||||||
|
|
||||||
def __exit__(self, type, value, traceback):
|
|
||||||
self.close()
|
|
|
@ -1,25 +0,0 @@
|
||||||
from .....core import Boolean, Float, ObjectType, String
|
|
||||||
|
|
||||||
|
|
||||||
class DjangoDebugBaseSQL(ObjectType):
|
|
||||||
vendor = String()
|
|
||||||
alias = String()
|
|
||||||
sql = String()
|
|
||||||
duration = Float()
|
|
||||||
raw_sql = String()
|
|
||||||
params = String()
|
|
||||||
start_time = Float()
|
|
||||||
stop_time = Float()
|
|
||||||
is_slow = Boolean()
|
|
||||||
is_select = Boolean()
|
|
||||||
|
|
||||||
|
|
||||||
class DjangoDebugSQL(DjangoDebugBaseSQL):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
class DjangoDebugPostgreSQL(DjangoDebugBaseSQL):
|
|
||||||
trans_id = String()
|
|
||||||
trans_status = String()
|
|
||||||
iso_level = String()
|
|
||||||
encoding = String()
|
|
|
@ -1,219 +0,0 @@
|
||||||
import pytest
|
|
||||||
|
|
||||||
import graphene
|
|
||||||
from graphene.contrib.django import DjangoConnectionField, DjangoNode
|
|
||||||
from graphene.contrib.django.utils import DJANGO_FILTER_INSTALLED
|
|
||||||
|
|
||||||
from ...tests.models import Reporter
|
|
||||||
from ..middleware import DjangoDebugMiddleware
|
|
||||||
from ..types import DjangoDebug
|
|
||||||
|
|
||||||
|
|
||||||
class context(object):
|
|
||||||
pass
|
|
||||||
|
|
||||||
# from examples.starwars_django.models import Character
|
|
||||||
|
|
||||||
pytestmark = pytest.mark.django_db
|
|
||||||
|
|
||||||
|
|
||||||
def test_should_query_field():
|
|
||||||
r1 = Reporter(last_name='ABA')
|
|
||||||
r1.save()
|
|
||||||
r2 = Reporter(last_name='Griffin')
|
|
||||||
r2.save()
|
|
||||||
|
|
||||||
class ReporterType(DjangoNode):
|
|
||||||
|
|
||||||
class Meta:
|
|
||||||
model = Reporter
|
|
||||||
|
|
||||||
class Query(graphene.ObjectType):
|
|
||||||
reporter = graphene.Field(ReporterType)
|
|
||||||
debug = graphene.Field(DjangoDebug, name='__debug')
|
|
||||||
|
|
||||||
def resolve_reporter(self, *args, **kwargs):
|
|
||||||
return Reporter.objects.first()
|
|
||||||
|
|
||||||
query = '''
|
|
||||||
query ReporterQuery {
|
|
||||||
reporter {
|
|
||||||
lastName
|
|
||||||
}
|
|
||||||
__debug {
|
|
||||||
sql {
|
|
||||||
rawSql
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
'''
|
|
||||||
expected = {
|
|
||||||
'reporter': {
|
|
||||||
'lastName': 'ABA',
|
|
||||||
},
|
|
||||||
'__debug': {
|
|
||||||
'sql': [{
|
|
||||||
'rawSql': str(Reporter.objects.order_by('pk')[:1].query)
|
|
||||||
}]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
schema = graphene.Schema(query=Query, middlewares=[DjangoDebugMiddleware()])
|
|
||||||
result = schema.execute(query, context_value=context())
|
|
||||||
assert not result.errors
|
|
||||||
assert result.data == expected
|
|
||||||
|
|
||||||
|
|
||||||
def test_should_query_list():
|
|
||||||
r1 = Reporter(last_name='ABA')
|
|
||||||
r1.save()
|
|
||||||
r2 = Reporter(last_name='Griffin')
|
|
||||||
r2.save()
|
|
||||||
|
|
||||||
class ReporterType(DjangoNode):
|
|
||||||
|
|
||||||
class Meta:
|
|
||||||
model = Reporter
|
|
||||||
|
|
||||||
class Query(graphene.ObjectType):
|
|
||||||
all_reporters = ReporterType.List()
|
|
||||||
debug = graphene.Field(DjangoDebug, name='__debug')
|
|
||||||
|
|
||||||
def resolve_all_reporters(self, *args, **kwargs):
|
|
||||||
return Reporter.objects.all()
|
|
||||||
|
|
||||||
query = '''
|
|
||||||
query ReporterQuery {
|
|
||||||
allReporters {
|
|
||||||
lastName
|
|
||||||
}
|
|
||||||
__debug {
|
|
||||||
sql {
|
|
||||||
rawSql
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
'''
|
|
||||||
expected = {
|
|
||||||
'allReporters': [{
|
|
||||||
'lastName': 'ABA',
|
|
||||||
}, {
|
|
||||||
'lastName': 'Griffin',
|
|
||||||
}],
|
|
||||||
'__debug': {
|
|
||||||
'sql': [{
|
|
||||||
'rawSql': str(Reporter.objects.all().query)
|
|
||||||
}]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
schema = graphene.Schema(query=Query, middlewares=[DjangoDebugMiddleware()])
|
|
||||||
result = schema.execute(query, context_value=context())
|
|
||||||
assert not result.errors
|
|
||||||
assert result.data == expected
|
|
||||||
|
|
||||||
|
|
||||||
def test_should_query_connection():
|
|
||||||
r1 = Reporter(last_name='ABA')
|
|
||||||
r1.save()
|
|
||||||
r2 = Reporter(last_name='Griffin')
|
|
||||||
r2.save()
|
|
||||||
|
|
||||||
class ReporterType(DjangoNode):
|
|
||||||
|
|
||||||
class Meta:
|
|
||||||
model = Reporter
|
|
||||||
|
|
||||||
class Query(graphene.ObjectType):
|
|
||||||
all_reporters = DjangoConnectionField(ReporterType)
|
|
||||||
debug = graphene.Field(DjangoDebug, name='__debug')
|
|
||||||
|
|
||||||
def resolve_all_reporters(self, *args, **kwargs):
|
|
||||||
return Reporter.objects.all()
|
|
||||||
|
|
||||||
query = '''
|
|
||||||
query ReporterQuery {
|
|
||||||
allReporters(first:1) {
|
|
||||||
edges {
|
|
||||||
node {
|
|
||||||
lastName
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
__debug {
|
|
||||||
sql {
|
|
||||||
rawSql
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
'''
|
|
||||||
expected = {
|
|
||||||
'allReporters': {
|
|
||||||
'edges': [{
|
|
||||||
'node': {
|
|
||||||
'lastName': 'ABA',
|
|
||||||
}
|
|
||||||
}]
|
|
||||||
},
|
|
||||||
}
|
|
||||||
schema = graphene.Schema(query=Query, middlewares=[DjangoDebugMiddleware()])
|
|
||||||
result = schema.execute(query, context_value=context())
|
|
||||||
assert not result.errors
|
|
||||||
assert result.data['allReporters'] == expected['allReporters']
|
|
||||||
assert 'COUNT' in result.data['__debug']['sql'][0]['rawSql']
|
|
||||||
query = str(Reporter.objects.all()[:1].query)
|
|
||||||
assert result.data['__debug']['sql'][1]['rawSql'] == query
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.skipif(not DJANGO_FILTER_INSTALLED,
|
|
||||||
reason="requires django-filter")
|
|
||||||
def test_should_query_connectionfilter():
|
|
||||||
from graphene.contrib.django.filter import DjangoFilterConnectionField
|
|
||||||
|
|
||||||
r1 = Reporter(last_name='ABA')
|
|
||||||
r1.save()
|
|
||||||
r2 = Reporter(last_name='Griffin')
|
|
||||||
r2.save()
|
|
||||||
|
|
||||||
class ReporterType(DjangoNode):
|
|
||||||
|
|
||||||
class Meta:
|
|
||||||
model = Reporter
|
|
||||||
|
|
||||||
class Query(graphene.ObjectType):
|
|
||||||
all_reporters = DjangoFilterConnectionField(ReporterType)
|
|
||||||
debug = graphene.Field(DjangoDebug, name='__debug')
|
|
||||||
|
|
||||||
def resolve_all_reporters(self, *args, **kwargs):
|
|
||||||
return Reporter.objects.all()
|
|
||||||
|
|
||||||
query = '''
|
|
||||||
query ReporterQuery {
|
|
||||||
allReporters(first:1) {
|
|
||||||
edges {
|
|
||||||
node {
|
|
||||||
lastName
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
__debug {
|
|
||||||
sql {
|
|
||||||
rawSql
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
'''
|
|
||||||
expected = {
|
|
||||||
'allReporters': {
|
|
||||||
'edges': [{
|
|
||||||
'node': {
|
|
||||||
'lastName': 'ABA',
|
|
||||||
}
|
|
||||||
}]
|
|
||||||
},
|
|
||||||
}
|
|
||||||
schema = graphene.Schema(query=Query, middlewares=[DjangoDebugMiddleware()])
|
|
||||||
result = schema.execute(query, context_value=context())
|
|
||||||
assert not result.errors
|
|
||||||
assert result.data['allReporters'] == expected['allReporters']
|
|
||||||
assert 'COUNT' in result.data['__debug']['sql'][0]['rawSql']
|
|
||||||
query = str(Reporter.objects.all()[:1].query)
|
|
||||||
assert result.data['__debug']['sql'][1]['rawSql'] == query
|
|
|
@ -1,7 +0,0 @@
|
||||||
from ....core.classtypes.objecttype import ObjectType
|
|
||||||
from ....core.types import Field
|
|
||||||
from .sql.types import DjangoDebugBaseSQL
|
|
||||||
|
|
||||||
|
|
||||||
class DjangoDebug(ObjectType):
|
|
||||||
sql = Field(DjangoDebugBaseSQL.List())
|
|
|
@ -1,80 +0,0 @@
|
||||||
from ...core.exceptions import SkipField
|
|
||||||
from ...core.fields import Field
|
|
||||||
from ...core.types.base import FieldType
|
|
||||||
from ...core.types.definitions import List
|
|
||||||
from ...relay import ConnectionField
|
|
||||||
from ...relay.utils import is_node
|
|
||||||
from .utils import DJANGO_FILTER_INSTALLED, get_type_for_model, maybe_queryset
|
|
||||||
|
|
||||||
|
|
||||||
class DjangoConnectionField(ConnectionField):
|
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
|
||||||
self.on = kwargs.pop('on', False)
|
|
||||||
kwargs['default'] = kwargs.pop('default', self.get_manager)
|
|
||||||
return super(DjangoConnectionField, self).__init__(*args, **kwargs)
|
|
||||||
|
|
||||||
@property
|
|
||||||
def model(self):
|
|
||||||
return self.type._meta.model
|
|
||||||
|
|
||||||
def get_manager(self):
|
|
||||||
if self.on:
|
|
||||||
return getattr(self.model, self.on)
|
|
||||||
else:
|
|
||||||
return self.model._default_manager
|
|
||||||
|
|
||||||
def get_queryset(self, resolved_qs, args, info):
|
|
||||||
return resolved_qs
|
|
||||||
|
|
||||||
def from_list(self, connection_type, resolved, args, context, info):
|
|
||||||
resolved_qs = maybe_queryset(resolved)
|
|
||||||
qs = self.get_queryset(resolved_qs, args, info)
|
|
||||||
return super(DjangoConnectionField, self).from_list(connection_type, qs, args, context, info)
|
|
||||||
|
|
||||||
|
|
||||||
class ConnectionOrListField(Field):
|
|
||||||
|
|
||||||
def internal_type(self, schema):
|
|
||||||
if DJANGO_FILTER_INSTALLED:
|
|
||||||
from .filter.fields import DjangoFilterConnectionField
|
|
||||||
|
|
||||||
model_field = self.type
|
|
||||||
field_object_type = model_field.get_object_type(schema)
|
|
||||||
if not field_object_type:
|
|
||||||
raise SkipField()
|
|
||||||
if is_node(field_object_type):
|
|
||||||
if field_object_type._meta.filter_fields:
|
|
||||||
field = DjangoFilterConnectionField(field_object_type)
|
|
||||||
else:
|
|
||||||
field = DjangoConnectionField(field_object_type)
|
|
||||||
else:
|
|
||||||
field = Field(List(field_object_type))
|
|
||||||
field.contribute_to_class(self.object_type, self.attname)
|
|
||||||
return schema.T(field)
|
|
||||||
|
|
||||||
|
|
||||||
class DjangoModelField(FieldType):
|
|
||||||
|
|
||||||
def __init__(self, model, *args, **kwargs):
|
|
||||||
self.model = model
|
|
||||||
super(DjangoModelField, self).__init__(*args, **kwargs)
|
|
||||||
|
|
||||||
def internal_type(self, schema):
|
|
||||||
_type = self.get_object_type(schema)
|
|
||||||
if not _type and self.parent._meta.only_fields:
|
|
||||||
raise Exception(
|
|
||||||
"Model %r is not accessible by the schema. "
|
|
||||||
"You can either register the type manually "
|
|
||||||
"using @schema.register. "
|
|
||||||
"Or disable the field in %s" % (
|
|
||||||
self.model,
|
|
||||||
self.parent,
|
|
||||||
)
|
|
||||||
)
|
|
||||||
if not _type:
|
|
||||||
raise SkipField()
|
|
||||||
return schema.T(_type)
|
|
||||||
|
|
||||||
def get_object_type(self, schema):
|
|
||||||
return get_type_for_model(schema, self.model)
|
|
|
@ -1,14 +0,0 @@
|
||||||
import warnings
|
|
||||||
from graphene.contrib.django.utils import DJANGO_FILTER_INSTALLED
|
|
||||||
|
|
||||||
if not DJANGO_FILTER_INSTALLED:
|
|
||||||
warnings.warn(
|
|
||||||
"Use of django filtering requires the django-filter package "
|
|
||||||
"be installed. You can do so using `pip install django-filter`", ImportWarning
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
from .fields import DjangoFilterConnectionField
|
|
||||||
from .filterset import GrapheneFilterSet, GlobalIDFilter, GlobalIDMultipleChoiceFilter
|
|
||||||
|
|
||||||
__all__ = ['DjangoFilterConnectionField', 'GrapheneFilterSet',
|
|
||||||
'GlobalIDFilter', 'GlobalIDMultipleChoiceFilter']
|
|
|
@ -1,36 +0,0 @@
|
||||||
from ..fields import DjangoConnectionField
|
|
||||||
from .utils import get_filtering_args_from_filterset, get_filterset_class
|
|
||||||
|
|
||||||
|
|
||||||
class DjangoFilterConnectionField(DjangoConnectionField):
|
|
||||||
|
|
||||||
def __init__(self, type, fields=None, order_by=None,
|
|
||||||
extra_filter_meta=None, filterset_class=None,
|
|
||||||
*args, **kwargs):
|
|
||||||
|
|
||||||
self.order_by = order_by or type._meta.filter_order_by
|
|
||||||
self.fields = fields or type._meta.filter_fields
|
|
||||||
meta = dict(model=type._meta.model,
|
|
||||||
fields=self.fields,
|
|
||||||
order_by=self.order_by)
|
|
||||||
if extra_filter_meta:
|
|
||||||
meta.update(extra_filter_meta)
|
|
||||||
self.filterset_class = get_filterset_class(filterset_class, **meta)
|
|
||||||
self.filtering_args = get_filtering_args_from_filterset(self.filterset_class, type)
|
|
||||||
kwargs.setdefault('args', {})
|
|
||||||
kwargs['args'].update(**self.filtering_args)
|
|
||||||
super(DjangoFilterConnectionField, self).__init__(type, *args, **kwargs)
|
|
||||||
|
|
||||||
def get_queryset(self, qs, args, info):
|
|
||||||
filterset_class = self.filterset_class
|
|
||||||
filter_kwargs = self.get_filter_kwargs(args)
|
|
||||||
order = self.get_order(args)
|
|
||||||
if order:
|
|
||||||
qs = qs.order_by(order)
|
|
||||||
return filterset_class(data=filter_kwargs, queryset=qs)
|
|
||||||
|
|
||||||
def get_filter_kwargs(self, args):
|
|
||||||
return {k: v for k, v in args.items() if k in self.filtering_args}
|
|
||||||
|
|
||||||
def get_order(self, args):
|
|
||||||
return args.get('order_by', None)
|
|
|
@ -1,116 +0,0 @@
|
||||||
import six
|
|
||||||
from django.conf import settings
|
|
||||||
from django.db import models
|
|
||||||
from django.utils.text import capfirst
|
|
||||||
from django_filters import Filter, MultipleChoiceFilter
|
|
||||||
from django_filters.filterset import FilterSet, FilterSetMetaclass
|
|
||||||
|
|
||||||
from graphene.contrib.django.forms import (GlobalIDFormField,
|
|
||||||
GlobalIDMultipleChoiceField)
|
|
||||||
from graphql_relay.node.node import from_global_id
|
|
||||||
|
|
||||||
|
|
||||||
class GlobalIDFilter(Filter):
|
|
||||||
field_class = GlobalIDFormField
|
|
||||||
|
|
||||||
def filter(self, qs, value):
|
|
||||||
_type, _id = from_global_id(value)
|
|
||||||
return super(GlobalIDFilter, self).filter(qs, _id)
|
|
||||||
|
|
||||||
|
|
||||||
class GlobalIDMultipleChoiceFilter(MultipleChoiceFilter):
|
|
||||||
field_class = GlobalIDMultipleChoiceField
|
|
||||||
|
|
||||||
def filter(self, qs, value):
|
|
||||||
gids = [from_global_id(v)[1] for v in value]
|
|
||||||
return super(GlobalIDMultipleChoiceFilter, self).filter(qs, gids)
|
|
||||||
|
|
||||||
|
|
||||||
ORDER_BY_FIELD = getattr(settings, 'GRAPHENE_ORDER_BY_FIELD', 'order_by')
|
|
||||||
|
|
||||||
|
|
||||||
GRAPHENE_FILTER_SET_OVERRIDES = {
|
|
||||||
models.AutoField: {
|
|
||||||
'filter_class': GlobalIDFilter,
|
|
||||||
},
|
|
||||||
models.OneToOneField: {
|
|
||||||
'filter_class': GlobalIDFilter,
|
|
||||||
},
|
|
||||||
models.ForeignKey: {
|
|
||||||
'filter_class': GlobalIDFilter,
|
|
||||||
},
|
|
||||||
models.ManyToManyField: {
|
|
||||||
'filter_class': GlobalIDMultipleChoiceFilter,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
class GrapheneFilterSetMetaclass(FilterSetMetaclass):
|
|
||||||
|
|
||||||
def __new__(cls, name, bases, attrs):
|
|
||||||
new_class = super(GrapheneFilterSetMetaclass, cls).__new__(cls, name, bases, attrs)
|
|
||||||
# Customise the filter_overrides for Graphene
|
|
||||||
for k, v in GRAPHENE_FILTER_SET_OVERRIDES.items():
|
|
||||||
new_class.filter_overrides.setdefault(k, v)
|
|
||||||
return new_class
|
|
||||||
|
|
||||||
|
|
||||||
class GrapheneFilterSetMixin(object):
|
|
||||||
order_by_field = ORDER_BY_FIELD
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def filter_for_reverse_field(cls, f, name):
|
|
||||||
"""Handles retrieving filters for reverse relationships
|
|
||||||
|
|
||||||
We override the default implementation so that we can handle
|
|
||||||
Global IDs (the default implementation expects database
|
|
||||||
primary keys)
|
|
||||||
"""
|
|
||||||
rel = f.field.rel
|
|
||||||
default = {
|
|
||||||
'name': name,
|
|
||||||
'label': capfirst(rel.related_name)
|
|
||||||
}
|
|
||||||
if rel.multiple:
|
|
||||||
# For to-many relationships
|
|
||||||
return GlobalIDMultipleChoiceFilter(**default)
|
|
||||||
else:
|
|
||||||
# For to-one relationships
|
|
||||||
return GlobalIDFilter(**default)
|
|
||||||
|
|
||||||
|
|
||||||
class GrapheneFilterSet(six.with_metaclass(GrapheneFilterSetMetaclass, GrapheneFilterSetMixin, FilterSet)):
|
|
||||||
""" Base class for FilterSets used by Graphene
|
|
||||||
|
|
||||||
You shouldn't usually need to use this class. The
|
|
||||||
DjangoFilterConnectionField will wrap FilterSets with this class as
|
|
||||||
necessary
|
|
||||||
"""
|
|
||||||
|
|
||||||
|
|
||||||
def setup_filterset(filterset_class):
|
|
||||||
""" Wrap a provided filterset in Graphene-specific functionality
|
|
||||||
"""
|
|
||||||
return type(
|
|
||||||
'Graphene{}'.format(filterset_class.__name__),
|
|
||||||
(six.with_metaclass(GrapheneFilterSetMetaclass, GrapheneFilterSetMixin, filterset_class),),
|
|
||||||
{},
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def custom_filterset_factory(model, filterset_base_class=GrapheneFilterSet,
|
|
||||||
**meta):
|
|
||||||
""" Create a filterset for the given model using the provided meta data
|
|
||||||
"""
|
|
||||||
meta.update({
|
|
||||||
'model': model,
|
|
||||||
})
|
|
||||||
meta_class = type(str('Meta'), (object,), meta)
|
|
||||||
filterset = type(
|
|
||||||
str('%sFilterSet' % model._meta.object_name),
|
|
||||||
(filterset_base_class,),
|
|
||||||
{
|
|
||||||
'Meta': meta_class
|
|
||||||
}
|
|
||||||
)
|
|
||||||
return filterset
|
|
|
@ -1,31 +0,0 @@
|
||||||
import django_filters
|
|
||||||
|
|
||||||
from graphene.contrib.django.tests.models import Article, Pet, Reporter
|
|
||||||
|
|
||||||
|
|
||||||
class ArticleFilter(django_filters.FilterSet):
|
|
||||||
|
|
||||||
class Meta:
|
|
||||||
model = Article
|
|
||||||
fields = {
|
|
||||||
'headline': ['exact', 'icontains'],
|
|
||||||
'pub_date': ['gt', 'lt', 'exact'],
|
|
||||||
'reporter': ['exact'],
|
|
||||||
}
|
|
||||||
order_by = True
|
|
||||||
|
|
||||||
|
|
||||||
class ReporterFilter(django_filters.FilterSet):
|
|
||||||
|
|
||||||
class Meta:
|
|
||||||
model = Reporter
|
|
||||||
fields = ['first_name', 'last_name', 'email', 'pets']
|
|
||||||
order_by = False
|
|
||||||
|
|
||||||
|
|
||||||
class PetFilter(django_filters.FilterSet):
|
|
||||||
|
|
||||||
class Meta:
|
|
||||||
model = Pet
|
|
||||||
fields = ['name']
|
|
||||||
order_by = False
|
|
|
@ -1,287 +0,0 @@
|
||||||
from datetime import datetime
|
|
||||||
|
|
||||||
import pytest
|
|
||||||
|
|
||||||
from graphene import ObjectType, Schema
|
|
||||||
from graphene.contrib.django import DjangoNode
|
|
||||||
from graphene.contrib.django.forms import (GlobalIDFormField,
|
|
||||||
GlobalIDMultipleChoiceField)
|
|
||||||
from graphene.contrib.django.tests.models import Article, Pet, Reporter
|
|
||||||
from graphene.contrib.django.utils import DJANGO_FILTER_INSTALLED
|
|
||||||
from graphene.relay import NodeField
|
|
||||||
|
|
||||||
pytestmark = []
|
|
||||||
if DJANGO_FILTER_INSTALLED:
|
|
||||||
import django_filters
|
|
||||||
from graphene.contrib.django.filter import (GlobalIDFilter, DjangoFilterConnectionField,
|
|
||||||
GlobalIDMultipleChoiceFilter)
|
|
||||||
from graphene.contrib.django.filter.tests.filters import ArticleFilter, PetFilter
|
|
||||||
else:
|
|
||||||
pytestmark.append(pytest.mark.skipif(True, reason='django_filters not installed'))
|
|
||||||
|
|
||||||
pytestmark.append(pytest.mark.django_db)
|
|
||||||
|
|
||||||
|
|
||||||
class ArticleNode(DjangoNode):
|
|
||||||
|
|
||||||
class Meta:
|
|
||||||
model = Article
|
|
||||||
|
|
||||||
|
|
||||||
class ReporterNode(DjangoNode):
|
|
||||||
|
|
||||||
class Meta:
|
|
||||||
model = Reporter
|
|
||||||
|
|
||||||
|
|
||||||
class PetNode(DjangoNode):
|
|
||||||
|
|
||||||
class Meta:
|
|
||||||
model = Pet
|
|
||||||
|
|
||||||
schema = Schema()
|
|
||||||
|
|
||||||
|
|
||||||
def assert_arguments(field, *arguments):
|
|
||||||
ignore = ('after', 'before', 'first', 'last', 'orderBy')
|
|
||||||
actual = [
|
|
||||||
name
|
|
||||||
for name in schema.T(field.arguments)
|
|
||||||
if name not in ignore and not name.startswith('_')
|
|
||||||
]
|
|
||||||
assert set(arguments) == set(actual), \
|
|
||||||
'Expected arguments ({}) did not match actual ({})'.format(
|
|
||||||
arguments,
|
|
||||||
actual
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def assert_orderable(field):
|
|
||||||
assert 'orderBy' in schema.T(field.arguments), \
|
|
||||||
'Field cannot be ordered'
|
|
||||||
|
|
||||||
|
|
||||||
def assert_not_orderable(field):
|
|
||||||
assert 'orderBy' not in schema.T(field.arguments), \
|
|
||||||
'Field can be ordered'
|
|
||||||
|
|
||||||
|
|
||||||
def test_filter_explicit_filterset_arguments():
|
|
||||||
field = DjangoFilterConnectionField(ArticleNode, filterset_class=ArticleFilter)
|
|
||||||
assert_arguments(field,
|
|
||||||
'headline', 'headline_Icontains',
|
|
||||||
'pubDate', 'pubDate_Gt', 'pubDate_Lt',
|
|
||||||
'reporter',
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def test_filter_shortcut_filterset_arguments_list():
|
|
||||||
field = DjangoFilterConnectionField(ArticleNode, fields=['pub_date', 'reporter'])
|
|
||||||
assert_arguments(field,
|
|
||||||
'pubDate',
|
|
||||||
'reporter',
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def test_filter_shortcut_filterset_arguments_dict():
|
|
||||||
field = DjangoFilterConnectionField(ArticleNode, fields={
|
|
||||||
'headline': ['exact', 'icontains'],
|
|
||||||
'reporter': ['exact'],
|
|
||||||
})
|
|
||||||
assert_arguments(field,
|
|
||||||
'headline', 'headline_Icontains',
|
|
||||||
'reporter',
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def test_filter_explicit_filterset_orderable():
|
|
||||||
field = DjangoFilterConnectionField(ArticleNode, filterset_class=ArticleFilter)
|
|
||||||
assert_orderable(field)
|
|
||||||
|
|
||||||
|
|
||||||
def test_filter_shortcut_filterset_orderable_true():
|
|
||||||
field = DjangoFilterConnectionField(ArticleNode, order_by=True)
|
|
||||||
assert_orderable(field)
|
|
||||||
|
|
||||||
|
|
||||||
def test_filter_shortcut_filterset_orderable_headline():
|
|
||||||
field = DjangoFilterConnectionField(ArticleNode, order_by=['headline'])
|
|
||||||
assert_orderable(field)
|
|
||||||
|
|
||||||
|
|
||||||
def test_filter_explicit_filterset_not_orderable():
|
|
||||||
field = DjangoFilterConnectionField(PetNode, filterset_class=PetFilter)
|
|
||||||
assert_not_orderable(field)
|
|
||||||
|
|
||||||
|
|
||||||
def test_filter_shortcut_filterset_extra_meta():
|
|
||||||
field = DjangoFilterConnectionField(ArticleNode, extra_filter_meta={
|
|
||||||
'order_by': True
|
|
||||||
})
|
|
||||||
assert_orderable(field)
|
|
||||||
|
|
||||||
|
|
||||||
def test_filter_filterset_information_on_meta():
|
|
||||||
class ReporterFilterNode(DjangoNode):
|
|
||||||
|
|
||||||
class Meta:
|
|
||||||
model = Reporter
|
|
||||||
filter_fields = ['first_name', 'articles']
|
|
||||||
filter_order_by = True
|
|
||||||
|
|
||||||
field = DjangoFilterConnectionField(ReporterFilterNode)
|
|
||||||
assert_arguments(field, 'firstName', 'articles')
|
|
||||||
assert_orderable(field)
|
|
||||||
|
|
||||||
|
|
||||||
def test_filter_filterset_information_on_meta_related():
|
|
||||||
class ReporterFilterNode(DjangoNode):
|
|
||||||
|
|
||||||
class Meta:
|
|
||||||
model = Reporter
|
|
||||||
filter_fields = ['first_name', 'articles']
|
|
||||||
filter_order_by = True
|
|
||||||
|
|
||||||
class ArticleFilterNode(DjangoNode):
|
|
||||||
|
|
||||||
class Meta:
|
|
||||||
model = Article
|
|
||||||
filter_fields = ['headline', 'reporter']
|
|
||||||
filter_order_by = True
|
|
||||||
|
|
||||||
class Query(ObjectType):
|
|
||||||
all_reporters = DjangoFilterConnectionField(ReporterFilterNode)
|
|
||||||
all_articles = DjangoFilterConnectionField(ArticleFilterNode)
|
|
||||||
reporter = NodeField(ReporterFilterNode)
|
|
||||||
article = NodeField(ArticleFilterNode)
|
|
||||||
|
|
||||||
schema = Schema(query=Query)
|
|
||||||
schema.schema # Trigger the schema loading
|
|
||||||
articles_field = schema.get_type('ReporterFilterNode')._meta.fields_map['articles']
|
|
||||||
assert_arguments(articles_field, 'headline', 'reporter')
|
|
||||||
assert_orderable(articles_field)
|
|
||||||
|
|
||||||
|
|
||||||
def test_filter_filterset_related_results():
|
|
||||||
class ReporterFilterNode(DjangoNode):
|
|
||||||
|
|
||||||
class Meta:
|
|
||||||
model = Reporter
|
|
||||||
filter_fields = ['first_name', 'articles']
|
|
||||||
filter_order_by = True
|
|
||||||
|
|
||||||
class ArticleFilterNode(DjangoNode):
|
|
||||||
|
|
||||||
class Meta:
|
|
||||||
model = Article
|
|
||||||
filter_fields = ['headline', 'reporter']
|
|
||||||
filter_order_by = True
|
|
||||||
|
|
||||||
class Query(ObjectType):
|
|
||||||
all_reporters = DjangoFilterConnectionField(ReporterFilterNode)
|
|
||||||
all_articles = DjangoFilterConnectionField(ArticleFilterNode)
|
|
||||||
reporter = NodeField(ReporterFilterNode)
|
|
||||||
article = NodeField(ArticleFilterNode)
|
|
||||||
|
|
||||||
r1 = Reporter.objects.create(first_name='r1', last_name='r1', email='r1@test.com')
|
|
||||||
r2 = Reporter.objects.create(first_name='r2', last_name='r2', email='r2@test.com')
|
|
||||||
Article.objects.create(headline='a1', pub_date=datetime.now(), reporter=r1)
|
|
||||||
Article.objects.create(headline='a2', pub_date=datetime.now(), reporter=r2)
|
|
||||||
|
|
||||||
query = '''
|
|
||||||
query {
|
|
||||||
allReporters {
|
|
||||||
edges {
|
|
||||||
node {
|
|
||||||
articles {
|
|
||||||
edges {
|
|
||||||
node {
|
|
||||||
headline
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
'''
|
|
||||||
schema = Schema(query=Query)
|
|
||||||
result = schema.execute(query)
|
|
||||||
assert not result.errors
|
|
||||||
# We should only get back a single article for each reporter
|
|
||||||
assert len(result.data['allReporters']['edges'][0]['node']['articles']['edges']) == 1
|
|
||||||
assert len(result.data['allReporters']['edges'][1]['node']['articles']['edges']) == 1
|
|
||||||
|
|
||||||
|
|
||||||
def test_global_id_field_implicit():
|
|
||||||
field = DjangoFilterConnectionField(ArticleNode, fields=['id'])
|
|
||||||
filterset_class = field.filterset_class
|
|
||||||
id_filter = filterset_class.base_filters['id']
|
|
||||||
assert isinstance(id_filter, GlobalIDFilter)
|
|
||||||
assert id_filter.field_class == GlobalIDFormField
|
|
||||||
|
|
||||||
|
|
||||||
def test_global_id_field_explicit():
|
|
||||||
class ArticleIdFilter(django_filters.FilterSet):
|
|
||||||
|
|
||||||
class Meta:
|
|
||||||
model = Article
|
|
||||||
fields = ['id']
|
|
||||||
|
|
||||||
field = DjangoFilterConnectionField(ArticleNode, filterset_class=ArticleIdFilter)
|
|
||||||
filterset_class = field.filterset_class
|
|
||||||
id_filter = filterset_class.base_filters['id']
|
|
||||||
assert isinstance(id_filter, GlobalIDFilter)
|
|
||||||
assert id_filter.field_class == GlobalIDFormField
|
|
||||||
|
|
||||||
|
|
||||||
def test_global_id_field_relation():
|
|
||||||
field = DjangoFilterConnectionField(ArticleNode, fields=['reporter'])
|
|
||||||
filterset_class = field.filterset_class
|
|
||||||
id_filter = filterset_class.base_filters['reporter']
|
|
||||||
assert isinstance(id_filter, GlobalIDFilter)
|
|
||||||
assert id_filter.field_class == GlobalIDFormField
|
|
||||||
|
|
||||||
|
|
||||||
def test_global_id_multiple_field_implicit():
|
|
||||||
field = DjangoFilterConnectionField(ReporterNode, fields=['pets'])
|
|
||||||
filterset_class = field.filterset_class
|
|
||||||
multiple_filter = filterset_class.base_filters['pets']
|
|
||||||
assert isinstance(multiple_filter, GlobalIDMultipleChoiceFilter)
|
|
||||||
assert multiple_filter.field_class == GlobalIDMultipleChoiceField
|
|
||||||
|
|
||||||
|
|
||||||
def test_global_id_multiple_field_explicit():
|
|
||||||
class ReporterPetsFilter(django_filters.FilterSet):
|
|
||||||
|
|
||||||
class Meta:
|
|
||||||
model = Reporter
|
|
||||||
fields = ['pets']
|
|
||||||
|
|
||||||
field = DjangoFilterConnectionField(ReporterNode, filterset_class=ReporterPetsFilter)
|
|
||||||
filterset_class = field.filterset_class
|
|
||||||
multiple_filter = filterset_class.base_filters['pets']
|
|
||||||
assert isinstance(multiple_filter, GlobalIDMultipleChoiceFilter)
|
|
||||||
assert multiple_filter.field_class == GlobalIDMultipleChoiceField
|
|
||||||
|
|
||||||
|
|
||||||
def test_global_id_multiple_field_implicit_reverse():
|
|
||||||
field = DjangoFilterConnectionField(ReporterNode, fields=['articles'])
|
|
||||||
filterset_class = field.filterset_class
|
|
||||||
multiple_filter = filterset_class.base_filters['articles']
|
|
||||||
assert isinstance(multiple_filter, GlobalIDMultipleChoiceFilter)
|
|
||||||
assert multiple_filter.field_class == GlobalIDMultipleChoiceField
|
|
||||||
|
|
||||||
|
|
||||||
def test_global_id_multiple_field_explicit_reverse():
|
|
||||||
class ReporterPetsFilter(django_filters.FilterSet):
|
|
||||||
|
|
||||||
class Meta:
|
|
||||||
model = Reporter
|
|
||||||
fields = ['articles']
|
|
||||||
|
|
||||||
field = DjangoFilterConnectionField(ReporterNode, filterset_class=ReporterPetsFilter)
|
|
||||||
filterset_class = field.filterset_class
|
|
||||||
multiple_filter = filterset_class.base_filters['articles']
|
|
||||||
assert isinstance(multiple_filter, GlobalIDMultipleChoiceFilter)
|
|
||||||
assert multiple_filter.field_class == GlobalIDMultipleChoiceField
|
|
|
@ -1,31 +0,0 @@
|
||||||
import six
|
|
||||||
|
|
||||||
from ....core.types import Argument, String
|
|
||||||
from .filterset import custom_filterset_factory, setup_filterset
|
|
||||||
|
|
||||||
|
|
||||||
def get_filtering_args_from_filterset(filterset_class, type):
|
|
||||||
""" Inspect a FilterSet and produce the arguments to pass to
|
|
||||||
a Graphene Field. These arguments will be available to
|
|
||||||
filter against in the GraphQL
|
|
||||||
"""
|
|
||||||
from graphene.contrib.django.form_converter import convert_form_field
|
|
||||||
|
|
||||||
args = {}
|
|
||||||
for name, filter_field in six.iteritems(filterset_class.base_filters):
|
|
||||||
field_type = Argument(convert_form_field(filter_field.field))
|
|
||||||
args[name] = field_type
|
|
||||||
|
|
||||||
# Also add the 'order_by' field
|
|
||||||
if filterset_class._meta.order_by:
|
|
||||||
args[filterset_class.order_by_field] = Argument(String())
|
|
||||||
return args
|
|
||||||
|
|
||||||
|
|
||||||
def get_filterset_class(filterset_class, **meta):
|
|
||||||
"""Get the class to be used as the FilterSet"""
|
|
||||||
if filterset_class:
|
|
||||||
# If were given a FilterSet class, then set it up and
|
|
||||||
# return it
|
|
||||||
return setup_filterset(filterset_class)
|
|
||||||
return custom_filterset_factory(**meta)
|
|
|
@ -1,73 +0,0 @@
|
||||||
from django import forms
|
|
||||||
from django.forms.fields import BaseTemporalField
|
|
||||||
|
|
||||||
from graphene import ID, Boolean, Float, Int, String
|
|
||||||
from graphene.contrib.django.forms import (GlobalIDFormField,
|
|
||||||
GlobalIDMultipleChoiceField)
|
|
||||||
from graphene.contrib.django.utils import import_single_dispatch
|
|
||||||
from graphene.core.types.definitions import List
|
|
||||||
|
|
||||||
singledispatch = import_single_dispatch()
|
|
||||||
|
|
||||||
try:
|
|
||||||
UUIDField = forms.UUIDField
|
|
||||||
except AttributeError:
|
|
||||||
class UUIDField(object):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
@singledispatch
|
|
||||||
def convert_form_field(field):
|
|
||||||
raise Exception(
|
|
||||||
"Don't know how to convert the Django form field %s (%s) "
|
|
||||||
"to Graphene type" %
|
|
||||||
(field, field.__class__)
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
@convert_form_field.register(BaseTemporalField)
|
|
||||||
@convert_form_field.register(forms.CharField)
|
|
||||||
@convert_form_field.register(forms.EmailField)
|
|
||||||
@convert_form_field.register(forms.SlugField)
|
|
||||||
@convert_form_field.register(forms.URLField)
|
|
||||||
@convert_form_field.register(forms.ChoiceField)
|
|
||||||
@convert_form_field.register(forms.RegexField)
|
|
||||||
@convert_form_field.register(forms.Field)
|
|
||||||
@convert_form_field.register(UUIDField)
|
|
||||||
def convert_form_field_to_string(field):
|
|
||||||
return String(description=field.help_text)
|
|
||||||
|
|
||||||
|
|
||||||
@convert_form_field.register(forms.IntegerField)
|
|
||||||
@convert_form_field.register(forms.NumberInput)
|
|
||||||
def convert_form_field_to_int(field):
|
|
||||||
return Int(description=field.help_text)
|
|
||||||
|
|
||||||
|
|
||||||
@convert_form_field.register(forms.BooleanField)
|
|
||||||
@convert_form_field.register(forms.NullBooleanField)
|
|
||||||
def convert_form_field_to_boolean(field):
|
|
||||||
return Boolean(description=field.help_text, required=True)
|
|
||||||
|
|
||||||
|
|
||||||
@convert_form_field.register(forms.NullBooleanField)
|
|
||||||
def convert_form_field_to_nullboolean(field):
|
|
||||||
return Boolean(description=field.help_text)
|
|
||||||
|
|
||||||
|
|
||||||
@convert_form_field.register(forms.DecimalField)
|
|
||||||
@convert_form_field.register(forms.FloatField)
|
|
||||||
def convert_form_field_to_float(field):
|
|
||||||
return Float(description=field.help_text)
|
|
||||||
|
|
||||||
|
|
||||||
@convert_form_field.register(forms.ModelMultipleChoiceField)
|
|
||||||
@convert_form_field.register(GlobalIDMultipleChoiceField)
|
|
||||||
def convert_form_field_to_list(field):
|
|
||||||
return List(ID())
|
|
||||||
|
|
||||||
|
|
||||||
@convert_form_field.register(forms.ModelChoiceField)
|
|
||||||
@convert_form_field.register(GlobalIDFormField)
|
|
||||||
def convert_form_field_to_id(field):
|
|
||||||
return ID()
|
|
|
@ -1,42 +0,0 @@
|
||||||
import binascii
|
|
||||||
|
|
||||||
from django.core.exceptions import ValidationError
|
|
||||||
from django.forms import CharField, Field, IntegerField, MultipleChoiceField
|
|
||||||
from django.utils.translation import ugettext_lazy as _
|
|
||||||
|
|
||||||
from graphql_relay import from_global_id
|
|
||||||
|
|
||||||
|
|
||||||
class GlobalIDFormField(Field):
|
|
||||||
default_error_messages = {
|
|
||||||
'invalid': _('Invalid ID specified.'),
|
|
||||||
}
|
|
||||||
|
|
||||||
def clean(self, value):
|
|
||||||
if not value and not self.required:
|
|
||||||
return None
|
|
||||||
|
|
||||||
try:
|
|
||||||
_type, _id = from_global_id(value)
|
|
||||||
except (TypeError, ValueError, UnicodeDecodeError, binascii.Error):
|
|
||||||
raise ValidationError(self.error_messages['invalid'])
|
|
||||||
|
|
||||||
try:
|
|
||||||
IntegerField().clean(_id)
|
|
||||||
CharField().clean(_type)
|
|
||||||
except ValidationError:
|
|
||||||
raise ValidationError(self.error_messages['invalid'])
|
|
||||||
|
|
||||||
return value
|
|
||||||
|
|
||||||
|
|
||||||
class GlobalIDMultipleChoiceField(MultipleChoiceField):
|
|
||||||
default_error_messages = {
|
|
||||||
'invalid_choice': _('One of the specified IDs was invalid (%(value)s).'),
|
|
||||||
'invalid_list': _('Enter a list of values.'),
|
|
||||||
}
|
|
||||||
|
|
||||||
def valid_value(self, value):
|
|
||||||
# Clean will raise a validation error if there is a problem
|
|
||||||
GlobalIDFormField().clean(value)
|
|
||||||
return True
|
|
|
@ -1,72 +0,0 @@
|
||||||
import importlib
|
|
||||||
import json
|
|
||||||
from distutils.version import StrictVersion
|
|
||||||
from optparse import make_option
|
|
||||||
|
|
||||||
from django import get_version as get_django_version
|
|
||||||
from django.core.management.base import BaseCommand, CommandError
|
|
||||||
|
|
||||||
LT_DJANGO_1_8 = StrictVersion(get_django_version()) < StrictVersion('1.8')
|
|
||||||
|
|
||||||
if LT_DJANGO_1_8:
|
|
||||||
class CommandArguments(BaseCommand):
|
|
||||||
option_list = BaseCommand.option_list + (
|
|
||||||
make_option(
|
|
||||||
'--schema',
|
|
||||||
type=str,
|
|
||||||
dest='schema',
|
|
||||||
default='',
|
|
||||||
help='Django app containing schema to dump, e.g. myproject.core.schema',
|
|
||||||
),
|
|
||||||
make_option(
|
|
||||||
'--out',
|
|
||||||
type=str,
|
|
||||||
dest='out',
|
|
||||||
default='',
|
|
||||||
help='Output file (default: schema.json)'
|
|
||||||
),
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
class CommandArguments(BaseCommand):
|
|
||||||
|
|
||||||
def add_arguments(self, parser):
|
|
||||||
from django.conf import settings
|
|
||||||
parser.add_argument(
|
|
||||||
'--schema',
|
|
||||||
type=str,
|
|
||||||
dest='schema',
|
|
||||||
default=getattr(settings, 'GRAPHENE_SCHEMA', ''),
|
|
||||||
help='Django app containing schema to dump, e.g. myproject.core.schema')
|
|
||||||
|
|
||||||
parser.add_argument(
|
|
||||||
'--out',
|
|
||||||
type=str,
|
|
||||||
dest='out',
|
|
||||||
default=getattr(settings, 'GRAPHENE_SCHEMA_OUTPUT', 'schema.json'),
|
|
||||||
help='Output file (default: schema.json)')
|
|
||||||
|
|
||||||
|
|
||||||
class Command(CommandArguments):
|
|
||||||
help = 'Dump Graphene schema JSON to file'
|
|
||||||
can_import_settings = True
|
|
||||||
|
|
||||||
def save_file(self, out, schema_dict):
|
|
||||||
with open(out, 'w') as outfile:
|
|
||||||
json.dump(schema_dict, outfile)
|
|
||||||
|
|
||||||
def handle(self, *args, **options):
|
|
||||||
from django.conf import settings
|
|
||||||
schema = options.get('schema') or getattr(settings, 'GRAPHENE_SCHEMA', '')
|
|
||||||
out = options.get('out') or getattr(settings, 'GRAPHENE_SCHEMA_OUTPUT', 'schema.json')
|
|
||||||
|
|
||||||
if schema == '':
|
|
||||||
raise CommandError('Specify schema on GRAPHENE_SCHEMA setting or by using --schema')
|
|
||||||
i = importlib.import_module(schema)
|
|
||||||
|
|
||||||
schema_dict = {'data': i.schema.introspect()}
|
|
||||||
self.save_file(out, schema_dict)
|
|
||||||
|
|
||||||
style = getattr(self, 'style', None)
|
|
||||||
SUCCESS = getattr(style, 'SUCCESS', lambda x: x)
|
|
||||||
|
|
||||||
self.stdout.write(SUCCESS('Successfully dumped GraphQL schema to %s' % out))
|
|
|
@ -1,27 +0,0 @@
|
||||||
from ...core.classtypes.objecttype import ObjectTypeOptions
|
|
||||||
from ...relay.types import Node
|
|
||||||
from ...relay.utils import is_node
|
|
||||||
from .utils import DJANGO_FILTER_INSTALLED
|
|
||||||
|
|
||||||
VALID_ATTRS = ('model', 'only_fields', 'exclude_fields')
|
|
||||||
|
|
||||||
if DJANGO_FILTER_INSTALLED:
|
|
||||||
VALID_ATTRS += ('filter_fields', 'filter_order_by')
|
|
||||||
|
|
||||||
|
|
||||||
class DjangoOptions(ObjectTypeOptions):
|
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
|
||||||
super(DjangoOptions, self).__init__(*args, **kwargs)
|
|
||||||
self.model = None
|
|
||||||
self.valid_attrs += VALID_ATTRS
|
|
||||||
self.only_fields = None
|
|
||||||
self.exclude_fields = []
|
|
||||||
self.filter_fields = None
|
|
||||||
self.filter_order_by = None
|
|
||||||
|
|
||||||
def contribute_to_class(self, cls, name):
|
|
||||||
super(DjangoOptions, self).contribute_to_class(cls, name)
|
|
||||||
if is_node(cls):
|
|
||||||
self.exclude_fields = list(self.exclude_fields) + ['id']
|
|
||||||
self.interfaces.append(Node)
|
|
|
@ -1,52 +0,0 @@
|
||||||
from __future__ import absolute_import
|
|
||||||
|
|
||||||
from django.db import models
|
|
||||||
from django.utils.translation import ugettext_lazy as _
|
|
||||||
|
|
||||||
CHOICES = (
|
|
||||||
(1, 'this'),
|
|
||||||
(2, _('that'))
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class Pet(models.Model):
|
|
||||||
name = models.CharField(max_length=30)
|
|
||||||
|
|
||||||
|
|
||||||
class FilmDetails(models.Model):
|
|
||||||
location = models.CharField(max_length=30)
|
|
||||||
film = models.OneToOneField('Film', related_name='details')
|
|
||||||
|
|
||||||
|
|
||||||
class Film(models.Model):
|
|
||||||
reporters = models.ManyToManyField('Reporter',
|
|
||||||
related_name='films')
|
|
||||||
|
|
||||||
|
|
||||||
class Reporter(models.Model):
|
|
||||||
first_name = models.CharField(max_length=30)
|
|
||||||
last_name = models.CharField(max_length=30)
|
|
||||||
email = models.EmailField()
|
|
||||||
pets = models.ManyToManyField('self')
|
|
||||||
a_choice = models.CharField(max_length=30, choices=CHOICES)
|
|
||||||
|
|
||||||
def __str__(self): # __unicode__ on Python 2
|
|
||||||
return "%s %s" % (self.first_name, self.last_name)
|
|
||||||
|
|
||||||
|
|
||||||
class Article(models.Model):
|
|
||||||
headline = models.CharField(max_length=100)
|
|
||||||
pub_date = models.DateField()
|
|
||||||
reporter = models.ForeignKey(Reporter, related_name='articles')
|
|
||||||
lang = models.CharField(max_length=2, help_text='Language', choices=[
|
|
||||||
('es', 'Spanish'),
|
|
||||||
('en', 'English')
|
|
||||||
], default='es')
|
|
||||||
importance = models.IntegerField('Importance', null=True, blank=True,
|
|
||||||
choices=[(1, u'Very important'), (2, u'Not as important')])
|
|
||||||
|
|
||||||
def __str__(self): # __unicode__ on Python 2
|
|
||||||
return self.headline
|
|
||||||
|
|
||||||
class Meta:
|
|
||||||
ordering = ('headline',)
|
|
|
@ -1,11 +0,0 @@
|
||||||
from django.core import management
|
|
||||||
from mock import patch
|
|
||||||
from six import StringIO
|
|
||||||
|
|
||||||
|
|
||||||
@patch('graphene.contrib.django.management.commands.graphql_schema.Command.save_file')
|
|
||||||
def test_generate_file_on_call_graphql_schema(savefile_mock, settings):
|
|
||||||
settings.GRAPHENE_SCHEMA = 'graphene.contrib.django.tests.test_urls'
|
|
||||||
out = StringIO()
|
|
||||||
management.call_command('graphql_schema', schema='', stdout=out)
|
|
||||||
assert "Successfully dumped GraphQL schema to schema.json" in out.getvalue()
|
|
|
@ -1,227 +0,0 @@
|
||||||
import pytest
|
|
||||||
from django.db import models
|
|
||||||
from django.utils.translation import ugettext_lazy as _
|
|
||||||
from py.test import raises
|
|
||||||
|
|
||||||
import graphene
|
|
||||||
from graphene.core.types.custom_scalars import DateTime, JSONString
|
|
||||||
|
|
||||||
from ..compat import (ArrayField, HStoreField, JSONField, MissingType,
|
|
||||||
RangeField)
|
|
||||||
from ..converter import convert_django_field, convert_django_field_with_choices
|
|
||||||
from ..fields import ConnectionOrListField, DjangoModelField
|
|
||||||
from .models import Article, Reporter, Film, FilmDetails
|
|
||||||
|
|
||||||
|
|
||||||
def assert_conversion(django_field, graphene_field, *args, **kwargs):
|
|
||||||
field = django_field(help_text='Custom Help Text', *args, **kwargs)
|
|
||||||
graphene_type = convert_django_field(field)
|
|
||||||
assert isinstance(graphene_type, graphene_field)
|
|
||||||
field = graphene_type.as_field()
|
|
||||||
assert field.description == 'Custom Help Text'
|
|
||||||
return field
|
|
||||||
|
|
||||||
|
|
||||||
def test_should_unknown_django_field_raise_exception():
|
|
||||||
with raises(Exception) as excinfo:
|
|
||||||
convert_django_field(None)
|
|
||||||
assert 'Don\'t know how to convert the Django field' in str(excinfo.value)
|
|
||||||
|
|
||||||
|
|
||||||
def test_should_date_convert_string():
|
|
||||||
assert_conversion(models.DateField, DateTime)
|
|
||||||
|
|
||||||
|
|
||||||
def test_should_char_convert_string():
|
|
||||||
assert_conversion(models.CharField, graphene.String)
|
|
||||||
|
|
||||||
|
|
||||||
def test_should_text_convert_string():
|
|
||||||
assert_conversion(models.TextField, graphene.String)
|
|
||||||
|
|
||||||
|
|
||||||
def test_should_email_convert_string():
|
|
||||||
assert_conversion(models.EmailField, graphene.String)
|
|
||||||
|
|
||||||
|
|
||||||
def test_should_slug_convert_string():
|
|
||||||
assert_conversion(models.SlugField, graphene.String)
|
|
||||||
|
|
||||||
|
|
||||||
def test_should_url_convert_string():
|
|
||||||
assert_conversion(models.URLField, graphene.String)
|
|
||||||
|
|
||||||
|
|
||||||
def test_should_ipaddress_convert_string():
|
|
||||||
assert_conversion(models.GenericIPAddressField, graphene.String)
|
|
||||||
|
|
||||||
|
|
||||||
def test_should_file_convert_string():
|
|
||||||
assert_conversion(models.FileField, graphene.String)
|
|
||||||
|
|
||||||
|
|
||||||
def test_should_image_convert_string():
|
|
||||||
assert_conversion(models.ImageField, graphene.String)
|
|
||||||
|
|
||||||
|
|
||||||
def test_should_auto_convert_id():
|
|
||||||
assert_conversion(models.AutoField, graphene.ID, primary_key=True)
|
|
||||||
|
|
||||||
|
|
||||||
def test_should_positive_integer_convert_int():
|
|
||||||
assert_conversion(models.PositiveIntegerField, graphene.Int)
|
|
||||||
|
|
||||||
|
|
||||||
def test_should_positive_small_convert_int():
|
|
||||||
assert_conversion(models.PositiveSmallIntegerField, graphene.Int)
|
|
||||||
|
|
||||||
|
|
||||||
def test_should_small_integer_convert_int():
|
|
||||||
assert_conversion(models.SmallIntegerField, graphene.Int)
|
|
||||||
|
|
||||||
|
|
||||||
def test_should_big_integer_convert_int():
|
|
||||||
assert_conversion(models.BigIntegerField, graphene.Int)
|
|
||||||
|
|
||||||
|
|
||||||
def test_should_integer_convert_int():
|
|
||||||
assert_conversion(models.IntegerField, graphene.Int)
|
|
||||||
|
|
||||||
|
|
||||||
def test_should_boolean_convert_boolean():
|
|
||||||
field = assert_conversion(models.BooleanField, graphene.Boolean)
|
|
||||||
assert field.required is True
|
|
||||||
|
|
||||||
|
|
||||||
def test_should_nullboolean_convert_boolean():
|
|
||||||
field = assert_conversion(models.NullBooleanField, graphene.Boolean)
|
|
||||||
assert field.required is False
|
|
||||||
|
|
||||||
|
|
||||||
def test_field_with_choices_convert_enum():
|
|
||||||
field = models.CharField(help_text='Language', choices=(
|
|
||||||
('es', 'Spanish'),
|
|
||||||
('en', 'English')
|
|
||||||
))
|
|
||||||
|
|
||||||
class TranslatedModel(models.Model):
|
|
||||||
language = field
|
|
||||||
|
|
||||||
class Meta:
|
|
||||||
app_label = 'test'
|
|
||||||
|
|
||||||
graphene_type = convert_django_field_with_choices(field)
|
|
||||||
assert issubclass(graphene_type, graphene.Enum)
|
|
||||||
assert graphene_type._meta.type_name == 'TEST_TRANSLATEDMODEL_LANGUAGE'
|
|
||||||
assert graphene_type._meta.description == 'Language'
|
|
||||||
assert graphene_type.__enum__.__members__['SPANISH'].value == 'es'
|
|
||||||
assert graphene_type.__enum__.__members__['ENGLISH'].value == 'en'
|
|
||||||
|
|
||||||
|
|
||||||
def test_field_with_grouped_choices():
|
|
||||||
field = models.CharField(help_text='Language', choices=(
|
|
||||||
('Europe', (
|
|
||||||
('es', 'Spanish'),
|
|
||||||
('en', 'English'),
|
|
||||||
)),
|
|
||||||
))
|
|
||||||
|
|
||||||
class GroupedChoicesModel(models.Model):
|
|
||||||
language = field
|
|
||||||
|
|
||||||
class Meta:
|
|
||||||
app_label = 'test'
|
|
||||||
|
|
||||||
convert_django_field_with_choices(field)
|
|
||||||
|
|
||||||
|
|
||||||
def test_field_with_choices_gettext():
|
|
||||||
field = models.CharField(help_text='Language', choices=(
|
|
||||||
('es', _('Spanish')),
|
|
||||||
('en', _('English'))
|
|
||||||
))
|
|
||||||
|
|
||||||
class TranslatedChoicesModel(models.Model):
|
|
||||||
language = field
|
|
||||||
|
|
||||||
class Meta:
|
|
||||||
app_label = 'test'
|
|
||||||
|
|
||||||
convert_django_field_with_choices(field)
|
|
||||||
|
|
||||||
|
|
||||||
def test_should_float_convert_float():
|
|
||||||
assert_conversion(models.FloatField, graphene.Float)
|
|
||||||
|
|
||||||
|
|
||||||
def test_should_manytomany_convert_connectionorlist():
|
|
||||||
graphene_type = convert_django_field(Reporter._meta.local_many_to_many[0])
|
|
||||||
assert isinstance(graphene_type, ConnectionOrListField)
|
|
||||||
assert isinstance(graphene_type.type, DjangoModelField)
|
|
||||||
assert graphene_type.type.model == Reporter
|
|
||||||
|
|
||||||
|
|
||||||
def test_should_manytoone_convert_connectionorlist():
|
|
||||||
# Django 1.9 uses 'rel', <1.9 uses 'related
|
|
||||||
related = getattr(Reporter.articles, 'rel', None) or \
|
|
||||||
getattr(Reporter.articles, 'related')
|
|
||||||
graphene_type = convert_django_field(related)
|
|
||||||
assert isinstance(graphene_type, ConnectionOrListField)
|
|
||||||
assert isinstance(graphene_type.type, DjangoModelField)
|
|
||||||
assert graphene_type.type.model == Article
|
|
||||||
|
|
||||||
|
|
||||||
def test_should_onetoone_reverse_convert_model():
|
|
||||||
# Django 1.9 uses 'rel', <1.9 uses 'related
|
|
||||||
related = getattr(Film.details, 'rel', None) or \
|
|
||||||
getattr(Film.details, 'related')
|
|
||||||
graphene_type = convert_django_field(related)
|
|
||||||
assert isinstance(graphene_type, DjangoModelField)
|
|
||||||
assert graphene_type.model == FilmDetails
|
|
||||||
|
|
||||||
|
|
||||||
def test_should_onetoone_convert_model():
|
|
||||||
field = assert_conversion(models.OneToOneField, DjangoModelField, Article)
|
|
||||||
assert field.type.model == Article
|
|
||||||
|
|
||||||
|
|
||||||
def test_should_foreignkey_convert_model():
|
|
||||||
field = assert_conversion(models.ForeignKey, DjangoModelField, Article)
|
|
||||||
assert field.type.model == Article
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.skipif(ArrayField is MissingType,
|
|
||||||
reason="ArrayField should exist")
|
|
||||||
def test_should_postgres_array_convert_list():
|
|
||||||
field = assert_conversion(ArrayField, graphene.List, models.CharField(max_length=100))
|
|
||||||
assert isinstance(field.type, graphene.List)
|
|
||||||
assert isinstance(field.type.of_type, graphene.String)
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.skipif(ArrayField is MissingType,
|
|
||||||
reason="ArrayField should exist")
|
|
||||||
def test_should_postgres_array_multiple_convert_list():
|
|
||||||
field = assert_conversion(ArrayField, graphene.List, ArrayField(models.CharField(max_length=100)))
|
|
||||||
assert isinstance(field.type, graphene.List)
|
|
||||||
assert isinstance(field.type.of_type, graphene.List)
|
|
||||||
assert isinstance(field.type.of_type.of_type, graphene.String)
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.skipif(HStoreField is MissingType,
|
|
||||||
reason="HStoreField should exist")
|
|
||||||
def test_should_postgres_hstore_convert_string():
|
|
||||||
assert_conversion(HStoreField, JSONString)
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.skipif(JSONField is MissingType,
|
|
||||||
reason="JSONField should exist")
|
|
||||||
def test_should_postgres_json_convert_string():
|
|
||||||
assert_conversion(JSONField, JSONString)
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.skipif(RangeField is MissingType,
|
|
||||||
reason="RangeField should exist")
|
|
||||||
def test_should_postgres_range_convert_list():
|
|
||||||
from django.contrib.postgres.fields import IntegerRangeField
|
|
||||||
field = assert_conversion(IntegerRangeField, graphene.List)
|
|
||||||
assert isinstance(field.type.of_type, graphene.Int)
|
|
|
@ -1,103 +0,0 @@
|
||||||
from django import forms
|
|
||||||
from py.test import raises
|
|
||||||
|
|
||||||
import graphene
|
|
||||||
from graphene.contrib.django.form_converter import convert_form_field
|
|
||||||
from graphene.core.types import ID, List
|
|
||||||
|
|
||||||
from .models import Reporter
|
|
||||||
|
|
||||||
|
|
||||||
def assert_conversion(django_field, graphene_field, *args):
|
|
||||||
field = django_field(*args, help_text='Custom Help Text')
|
|
||||||
graphene_type = convert_form_field(field)
|
|
||||||
assert isinstance(graphene_type, graphene_field)
|
|
||||||
field = graphene_type.as_field()
|
|
||||||
assert field.description == 'Custom Help Text'
|
|
||||||
return field
|
|
||||||
|
|
||||||
|
|
||||||
def test_should_unknown_django_field_raise_exception():
|
|
||||||
with raises(Exception) as excinfo:
|
|
||||||
convert_form_field(None)
|
|
||||||
assert 'Don\'t know how to convert the Django form field' in str(excinfo.value)
|
|
||||||
|
|
||||||
|
|
||||||
def test_should_date_convert_string():
|
|
||||||
assert_conversion(forms.DateField, graphene.String)
|
|
||||||
|
|
||||||
|
|
||||||
def test_should_time_convert_string():
|
|
||||||
assert_conversion(forms.TimeField, graphene.String)
|
|
||||||
|
|
||||||
|
|
||||||
def test_should_date_time_convert_string():
|
|
||||||
assert_conversion(forms.DateTimeField, graphene.String)
|
|
||||||
|
|
||||||
|
|
||||||
def test_should_char_convert_string():
|
|
||||||
assert_conversion(forms.CharField, graphene.String)
|
|
||||||
|
|
||||||
|
|
||||||
def test_should_email_convert_string():
|
|
||||||
assert_conversion(forms.EmailField, graphene.String)
|
|
||||||
|
|
||||||
|
|
||||||
def test_should_slug_convert_string():
|
|
||||||
assert_conversion(forms.SlugField, graphene.String)
|
|
||||||
|
|
||||||
|
|
||||||
def test_should_url_convert_string():
|
|
||||||
assert_conversion(forms.URLField, graphene.String)
|
|
||||||
|
|
||||||
|
|
||||||
def test_should_choice_convert_string():
|
|
||||||
assert_conversion(forms.ChoiceField, graphene.String)
|
|
||||||
|
|
||||||
|
|
||||||
def test_should_base_field_convert_string():
|
|
||||||
assert_conversion(forms.Field, graphene.String)
|
|
||||||
|
|
||||||
|
|
||||||
def test_should_regex_convert_string():
|
|
||||||
assert_conversion(forms.RegexField, graphene.String, '[0-9]+')
|
|
||||||
|
|
||||||
|
|
||||||
def test_should_uuid_convert_string():
|
|
||||||
if hasattr(forms, 'UUIDField'):
|
|
||||||
assert_conversion(forms.UUIDField, graphene.String)
|
|
||||||
|
|
||||||
|
|
||||||
def test_should_integer_convert_int():
|
|
||||||
assert_conversion(forms.IntegerField, graphene.Int)
|
|
||||||
|
|
||||||
|
|
||||||
def test_should_boolean_convert_boolean():
|
|
||||||
field = assert_conversion(forms.BooleanField, graphene.Boolean)
|
|
||||||
assert field.required is True
|
|
||||||
|
|
||||||
|
|
||||||
def test_should_nullboolean_convert_boolean():
|
|
||||||
field = assert_conversion(forms.NullBooleanField, graphene.Boolean)
|
|
||||||
assert field.required is False
|
|
||||||
|
|
||||||
|
|
||||||
def test_should_float_convert_float():
|
|
||||||
assert_conversion(forms.FloatField, graphene.Float)
|
|
||||||
|
|
||||||
|
|
||||||
def test_should_decimal_convert_float():
|
|
||||||
assert_conversion(forms.DecimalField, graphene.Float)
|
|
||||||
|
|
||||||
|
|
||||||
def test_should_multiple_choice_convert_connectionorlist():
|
|
||||||
field = forms.ModelMultipleChoiceField(Reporter.objects.all())
|
|
||||||
graphene_type = convert_form_field(field)
|
|
||||||
assert isinstance(graphene_type, List)
|
|
||||||
assert isinstance(graphene_type.of_type, ID)
|
|
||||||
|
|
||||||
|
|
||||||
def test_should_manytoone_convert_connectionorlist():
|
|
||||||
field = forms.ModelChoiceField(Reporter.objects.all())
|
|
||||||
graphene_type = convert_form_field(field)
|
|
||||||
assert isinstance(graphene_type, graphene.ID)
|
|
|
@ -1,36 +0,0 @@
|
||||||
from django.core.exceptions import ValidationError
|
|
||||||
from py.test import raises
|
|
||||||
|
|
||||||
from graphene.contrib.django.forms import GlobalIDFormField
|
|
||||||
|
|
||||||
|
|
||||||
# 'TXlUeXBlOjEwMA==' -> 'MyType', 100
|
|
||||||
# 'TXlUeXBlOmFiYw==' -> 'MyType', 'abc'
|
|
||||||
|
|
||||||
|
|
||||||
def test_global_id_valid():
|
|
||||||
field = GlobalIDFormField()
|
|
||||||
field.clean('TXlUeXBlOjEwMA==')
|
|
||||||
|
|
||||||
|
|
||||||
def test_global_id_invalid():
|
|
||||||
field = GlobalIDFormField()
|
|
||||||
with raises(ValidationError):
|
|
||||||
field.clean('badvalue')
|
|
||||||
|
|
||||||
|
|
||||||
def test_global_id_none():
|
|
||||||
field = GlobalIDFormField()
|
|
||||||
with raises(ValidationError):
|
|
||||||
field.clean(None)
|
|
||||||
|
|
||||||
|
|
||||||
def test_global_id_none_optional():
|
|
||||||
field = GlobalIDFormField(required=False)
|
|
||||||
field.clean(None)
|
|
||||||
|
|
||||||
|
|
||||||
def test_global_id_bad_int():
|
|
||||||
field = GlobalIDFormField()
|
|
||||||
with raises(ValidationError):
|
|
||||||
field.clean('TXlUeXBlOmFiYw==')
|
|
|
@ -1,201 +0,0 @@
|
||||||
import datetime
|
|
||||||
|
|
||||||
import pytest
|
|
||||||
from django.db import models
|
|
||||||
from py.test import raises
|
|
||||||
|
|
||||||
import graphene
|
|
||||||
from graphene import relay
|
|
||||||
|
|
||||||
from ..compat import MissingType, RangeField
|
|
||||||
from ..types import DjangoNode, DjangoObjectType
|
|
||||||
from .models import Article, Reporter
|
|
||||||
|
|
||||||
pytestmark = pytest.mark.django_db
|
|
||||||
|
|
||||||
|
|
||||||
def test_should_query_only_fields():
|
|
||||||
with raises(Exception):
|
|
||||||
class ReporterType(DjangoObjectType):
|
|
||||||
|
|
||||||
class Meta:
|
|
||||||
model = Reporter
|
|
||||||
only_fields = ('articles', )
|
|
||||||
|
|
||||||
schema = graphene.Schema(query=ReporterType)
|
|
||||||
query = '''
|
|
||||||
query ReporterQuery {
|
|
||||||
articles
|
|
||||||
}
|
|
||||||
'''
|
|
||||||
result = schema.execute(query)
|
|
||||||
assert not result.errors
|
|
||||||
|
|
||||||
|
|
||||||
def test_should_query_well():
|
|
||||||
class ReporterType(DjangoObjectType):
|
|
||||||
|
|
||||||
class Meta:
|
|
||||||
model = Reporter
|
|
||||||
|
|
||||||
class Query(graphene.ObjectType):
|
|
||||||
reporter = graphene.Field(ReporterType)
|
|
||||||
|
|
||||||
def resolve_reporter(self, *args, **kwargs):
|
|
||||||
return ReporterType(Reporter(first_name='ABA', last_name='X'))
|
|
||||||
|
|
||||||
query = '''
|
|
||||||
query ReporterQuery {
|
|
||||||
reporter {
|
|
||||||
firstName,
|
|
||||||
lastName,
|
|
||||||
email
|
|
||||||
}
|
|
||||||
}
|
|
||||||
'''
|
|
||||||
expected = {
|
|
||||||
'reporter': {
|
|
||||||
'firstName': 'ABA',
|
|
||||||
'lastName': 'X',
|
|
||||||
'email': ''
|
|
||||||
}
|
|
||||||
}
|
|
||||||
schema = graphene.Schema(query=Query)
|
|
||||||
result = schema.execute(query)
|
|
||||||
assert not result.errors
|
|
||||||
assert result.data == expected
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.skipif(RangeField is MissingType,
|
|
||||||
reason="RangeField should exist")
|
|
||||||
def test_should_query_postgres_fields():
|
|
||||||
from django.contrib.postgres.fields import IntegerRangeField, ArrayField, JSONField, HStoreField
|
|
||||||
|
|
||||||
class Event(models.Model):
|
|
||||||
ages = IntegerRangeField(help_text='The age ranges')
|
|
||||||
data = JSONField(help_text='Data')
|
|
||||||
store = HStoreField()
|
|
||||||
tags = ArrayField(models.CharField(max_length=50))
|
|
||||||
|
|
||||||
class EventType(DjangoObjectType):
|
|
||||||
|
|
||||||
class Meta:
|
|
||||||
model = Event
|
|
||||||
|
|
||||||
class Query(graphene.ObjectType):
|
|
||||||
event = graphene.Field(EventType)
|
|
||||||
|
|
||||||
def resolve_event(self, *args, **kwargs):
|
|
||||||
return Event(
|
|
||||||
ages=(0, 10),
|
|
||||||
data={'angry_babies': True},
|
|
||||||
store={'h': 'store'},
|
|
||||||
tags=['child', 'angry', 'babies']
|
|
||||||
)
|
|
||||||
|
|
||||||
schema = graphene.Schema(query=Query)
|
|
||||||
query = '''
|
|
||||||
query myQuery {
|
|
||||||
event {
|
|
||||||
ages
|
|
||||||
tags
|
|
||||||
data
|
|
||||||
store
|
|
||||||
}
|
|
||||||
}
|
|
||||||
'''
|
|
||||||
expected = {
|
|
||||||
'event': {
|
|
||||||
'ages': [0, 10],
|
|
||||||
'tags': ['child', 'angry', 'babies'],
|
|
||||||
'data': '{"angry_babies": true}',
|
|
||||||
'store': '{"h": "store"}',
|
|
||||||
},
|
|
||||||
}
|
|
||||||
result = schema.execute(query)
|
|
||||||
assert not result.errors
|
|
||||||
assert result.data == expected
|
|
||||||
|
|
||||||
|
|
||||||
def test_should_node():
|
|
||||||
class ReporterNode(DjangoNode):
|
|
||||||
|
|
||||||
class Meta:
|
|
||||||
model = Reporter
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def get_node(cls, id, info):
|
|
||||||
return ReporterNode(Reporter(id=2, first_name='Cookie Monster'))
|
|
||||||
|
|
||||||
def resolve_articles(self, *args, **kwargs):
|
|
||||||
return [ArticleNode(Article(headline='Hi!'))]
|
|
||||||
|
|
||||||
class ArticleNode(DjangoNode):
|
|
||||||
|
|
||||||
class Meta:
|
|
||||||
model = Article
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def get_node(cls, id, info):
|
|
||||||
return ArticleNode(Article(id=1, headline='Article node', pub_date=datetime.date(2002, 3, 11)))
|
|
||||||
|
|
||||||
class Query(graphene.ObjectType):
|
|
||||||
node = relay.NodeField()
|
|
||||||
reporter = graphene.Field(ReporterNode)
|
|
||||||
article = graphene.Field(ArticleNode)
|
|
||||||
|
|
||||||
def resolve_reporter(self, *args, **kwargs):
|
|
||||||
return ReporterNode(
|
|
||||||
Reporter(id=1, first_name='ABA', last_name='X'))
|
|
||||||
|
|
||||||
query = '''
|
|
||||||
query ReporterQuery {
|
|
||||||
reporter {
|
|
||||||
id,
|
|
||||||
firstName,
|
|
||||||
articles {
|
|
||||||
edges {
|
|
||||||
node {
|
|
||||||
headline
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
lastName,
|
|
||||||
email
|
|
||||||
}
|
|
||||||
myArticle: node(id:"QXJ0aWNsZU5vZGU6MQ==") {
|
|
||||||
id
|
|
||||||
... on ReporterNode {
|
|
||||||
firstName
|
|
||||||
}
|
|
||||||
... on ArticleNode {
|
|
||||||
headline
|
|
||||||
pubDate
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
'''
|
|
||||||
expected = {
|
|
||||||
'reporter': {
|
|
||||||
'id': 'UmVwb3J0ZXJOb2RlOjE=',
|
|
||||||
'firstName': 'ABA',
|
|
||||||
'lastName': 'X',
|
|
||||||
'email': '',
|
|
||||||
'articles': {
|
|
||||||
'edges': [{
|
|
||||||
'node': {
|
|
||||||
'headline': 'Hi!'
|
|
||||||
}
|
|
||||||
}]
|
|
||||||
},
|
|
||||||
},
|
|
||||||
'myArticle': {
|
|
||||||
'id': 'QXJ0aWNsZU5vZGU6MQ==',
|
|
||||||
'headline': 'Article node',
|
|
||||||
'pubDate': '2002-03-11',
|
|
||||||
}
|
|
||||||
}
|
|
||||||
schema = graphene.Schema(query=Query)
|
|
||||||
result = schema.execute(query)
|
|
||||||
assert not result.errors
|
|
||||||
assert result.data == expected
|
|
|
@ -1,45 +0,0 @@
|
||||||
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', 'films']
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
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']
|
|
||||||
)
|
|
|
@ -1,102 +0,0 @@
|
||||||
from graphql.type import GraphQLObjectType
|
|
||||||
from mock import patch
|
|
||||||
|
|
||||||
from graphene import Schema, Interface
|
|
||||||
from graphene.contrib.django.types import DjangoNode, DjangoObjectType
|
|
||||||
from graphene.core.fields import Field
|
|
||||||
from graphene.core.types.scalars import Int
|
|
||||||
from graphene.relay.fields import GlobalIDField
|
|
||||||
from tests.utils import assert_equal_lists
|
|
||||||
|
|
||||||
from .models import Article, Reporter
|
|
||||||
|
|
||||||
schema = Schema()
|
|
||||||
|
|
||||||
|
|
||||||
@schema.register
|
|
||||||
class Character(DjangoObjectType):
|
|
||||||
'''Character description'''
|
|
||||||
class Meta:
|
|
||||||
model = Reporter
|
|
||||||
|
|
||||||
|
|
||||||
@schema.register
|
|
||||||
class Human(DjangoNode):
|
|
||||||
'''Human description'''
|
|
||||||
|
|
||||||
pub_date = Int()
|
|
||||||
|
|
||||||
class Meta:
|
|
||||||
model = Article
|
|
||||||
|
|
||||||
|
|
||||||
def test_django_interface():
|
|
||||||
assert DjangoNode._meta.interface is True
|
|
||||||
|
|
||||||
|
|
||||||
@patch('graphene.contrib.django.tests.models.Article.objects.get', return_value=Article(id=1))
|
|
||||||
def test_django_get_node(get):
|
|
||||||
human = Human.get_node(1, None)
|
|
||||||
get.assert_called_with(id=1)
|
|
||||||
assert human.id == 1
|
|
||||||
|
|
||||||
|
|
||||||
def test_djangonode_idfield():
|
|
||||||
idfield = DjangoNode._meta.fields_map['id']
|
|
||||||
assert isinstance(idfield, GlobalIDField)
|
|
||||||
|
|
||||||
|
|
||||||
def test_node_idfield():
|
|
||||||
idfield = Human._meta.fields_map['id']
|
|
||||||
assert isinstance(idfield, GlobalIDField)
|
|
||||||
|
|
||||||
|
|
||||||
def test_node_replacedfield():
|
|
||||||
idfield = Human._meta.fields_map['pub_date']
|
|
||||||
assert isinstance(idfield, Field)
|
|
||||||
assert schema.T(idfield).type == schema.T(Int())
|
|
||||||
|
|
||||||
|
|
||||||
def test_objecttype_init_none():
|
|
||||||
h = Human()
|
|
||||||
assert h._root is None
|
|
||||||
|
|
||||||
|
|
||||||
def test_objecttype_init_good():
|
|
||||||
instance = Article()
|
|
||||||
h = Human(instance)
|
|
||||||
assert h._root == instance
|
|
||||||
|
|
||||||
|
|
||||||
def test_object_type():
|
|
||||||
object_type = schema.T(Human)
|
|
||||||
Human._meta.fields_map
|
|
||||||
assert Human._meta.interface is False
|
|
||||||
assert isinstance(object_type, GraphQLObjectType)
|
|
||||||
assert_equal_lists(
|
|
||||||
object_type.get_fields().keys(),
|
|
||||||
['headline', 'id', 'reporter', 'pubDate']
|
|
||||||
)
|
|
||||||
assert schema.T(DjangoNode) in object_type.get_interfaces()
|
|
||||||
|
|
||||||
|
|
||||||
def test_node_notinterface():
|
|
||||||
assert Human._meta.interface is False
|
|
||||||
assert DjangoNode in Human._meta.interfaces
|
|
||||||
|
|
||||||
|
|
||||||
def test_django_objecttype_could_extend_interface():
|
|
||||||
schema = Schema()
|
|
||||||
|
|
||||||
@schema.register
|
|
||||||
class Customer(Interface):
|
|
||||||
id = Int()
|
|
||||||
|
|
||||||
@schema.register
|
|
||||||
class UserType(DjangoObjectType):
|
|
||||||
class Meta:
|
|
||||||
model = Reporter
|
|
||||||
interfaces = [Customer]
|
|
||||||
|
|
||||||
object_type = schema.T(UserType)
|
|
||||||
assert schema.T(Customer) in object_type.get_interfaces()
|
|
|
@ -1,45 +0,0 @@
|
||||||
from django.conf.urls import url
|
|
||||||
|
|
||||||
import graphene
|
|
||||||
from graphene import Schema
|
|
||||||
from graphene.contrib.django.types import DjangoNode
|
|
||||||
from graphene.contrib.django.views import GraphQLView
|
|
||||||
|
|
||||||
from .models import Article, Reporter
|
|
||||||
|
|
||||||
|
|
||||||
class Character(DjangoNode):
|
|
||||||
|
|
||||||
class Meta:
|
|
||||||
model = Reporter
|
|
||||||
|
|
||||||
def get_node(self, id):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
class Human(DjangoNode):
|
|
||||||
raises = graphene.String()
|
|
||||||
|
|
||||||
class Meta:
|
|
||||||
model = Article
|
|
||||||
|
|
||||||
def resolve_raises(self, *args):
|
|
||||||
raise Exception("This field should raise exception")
|
|
||||||
|
|
||||||
def get_node(self, id):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
class Query(graphene.ObjectType):
|
|
||||||
human = graphene.Field(Human)
|
|
||||||
|
|
||||||
def resolve_human(self, args, info):
|
|
||||||
return Human()
|
|
||||||
|
|
||||||
|
|
||||||
schema = Schema(query=Query)
|
|
||||||
|
|
||||||
|
|
||||||
urlpatterns = [
|
|
||||||
url(r'^graphql', GraphQLView.as_view(schema=schema)),
|
|
||||||
]
|
|
|
@ -1,57 +0,0 @@
|
||||||
import json
|
|
||||||
|
|
||||||
|
|
||||||
def format_response(response):
|
|
||||||
return json.loads(response.content.decode())
|
|
||||||
|
|
||||||
|
|
||||||
def test_client_get_good_query(settings, client):
|
|
||||||
settings.ROOT_URLCONF = 'graphene.contrib.django.tests.test_urls'
|
|
||||||
response = client.get('/graphql', {'query': '{ human { headline } }'})
|
|
||||||
json_response = format_response(response)
|
|
||||||
expected_json = {
|
|
||||||
'data': {
|
|
||||||
'human': {
|
|
||||||
'headline': None
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
assert json_response == expected_json
|
|
||||||
|
|
||||||
|
|
||||||
def test_client_get_good_query_with_raise(settings, client):
|
|
||||||
settings.ROOT_URLCONF = 'graphene.contrib.django.tests.test_urls'
|
|
||||||
response = client.get('/graphql', {'query': '{ human { raises } }'})
|
|
||||||
json_response = format_response(response)
|
|
||||||
assert json_response['errors'][0]['message'] == 'This field should raise exception'
|
|
||||||
assert json_response['data']['human']['raises'] is None
|
|
||||||
|
|
||||||
|
|
||||||
def test_client_post_good_query_json(settings, client):
|
|
||||||
settings.ROOT_URLCONF = 'graphene.contrib.django.tests.test_urls'
|
|
||||||
response = client.post(
|
|
||||||
'/graphql', json.dumps({'query': '{ human { headline } }'}), 'application/json')
|
|
||||||
json_response = format_response(response)
|
|
||||||
expected_json = {
|
|
||||||
'data': {
|
|
||||||
'human': {
|
|
||||||
'headline': None
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
assert json_response == expected_json
|
|
||||||
|
|
||||||
|
|
||||||
def test_client_post_good_query_graphql(settings, client):
|
|
||||||
settings.ROOT_URLCONF = 'graphene.contrib.django.tests.test_urls'
|
|
||||||
response = client.post(
|
|
||||||
'/graphql', '{ human { headline } }', 'application/graphql')
|
|
||||||
json_response = format_response(response)
|
|
||||||
expected_json = {
|
|
||||||
'data': {
|
|
||||||
'human': {
|
|
||||||
'headline': None
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
assert json_response == expected_json
|
|
|
@ -1,106 +0,0 @@
|
||||||
import inspect
|
|
||||||
|
|
||||||
import six
|
|
||||||
from django.db import models
|
|
||||||
|
|
||||||
from ...core.classtypes.objecttype import ObjectType, ObjectTypeMeta
|
|
||||||
from ...relay.types import Connection, Node, NodeMeta
|
|
||||||
from .converter import convert_django_field_with_choices
|
|
||||||
from .options import DjangoOptions
|
|
||||||
from .utils import get_reverse_fields
|
|
||||||
|
|
||||||
|
|
||||||
class DjangoObjectTypeMeta(ObjectTypeMeta):
|
|
||||||
options_class = DjangoOptions
|
|
||||||
|
|
||||||
def construct_fields(cls):
|
|
||||||
only_fields = cls._meta.only_fields
|
|
||||||
reverse_fields = get_reverse_fields(cls._meta.model)
|
|
||||||
all_fields = sorted(list(cls._meta.model._meta.fields) +
|
|
||||||
list(cls._meta.model._meta.local_many_to_many))
|
|
||||||
all_fields += list(reverse_fields)
|
|
||||||
already_created_fields = {f.attname for f in cls._meta.local_fields}
|
|
||||||
|
|
||||||
for field in all_fields:
|
|
||||||
is_not_in_only = only_fields and field.name not in only_fields
|
|
||||||
is_already_created = field.name in already_created_fields
|
|
||||||
is_excluded = field.name in cls._meta.exclude_fields or is_already_created
|
|
||||||
if is_not_in_only or is_excluded:
|
|
||||||
# We skip this field if we specify only_fields and is not
|
|
||||||
# in there. Or when we exclude this field in exclude_fields
|
|
||||||
continue
|
|
||||||
converted_field = convert_django_field_with_choices(field)
|
|
||||||
cls.add_to_class(field.name, converted_field)
|
|
||||||
|
|
||||||
def construct(cls, *args, **kwargs):
|
|
||||||
cls = super(DjangoObjectTypeMeta, cls).construct(*args, **kwargs)
|
|
||||||
if not cls._meta.abstract:
|
|
||||||
if not cls._meta.model:
|
|
||||||
raise Exception(
|
|
||||||
'Django ObjectType %s must have a model in the Meta class attr' %
|
|
||||||
cls)
|
|
||||||
elif not inspect.isclass(cls._meta.model) or not issubclass(cls._meta.model, models.Model):
|
|
||||||
raise Exception('Provided model in %s is not a Django model' % cls)
|
|
||||||
|
|
||||||
cls.construct_fields()
|
|
||||||
return cls
|
|
||||||
|
|
||||||
|
|
||||||
class InstanceObjectType(ObjectType):
|
|
||||||
|
|
||||||
class Meta:
|
|
||||||
abstract = True
|
|
||||||
|
|
||||||
def __init__(self, _root=None):
|
|
||||||
super(InstanceObjectType, self).__init__(_root=_root)
|
|
||||||
assert not self._root or isinstance(self._root, self._meta.model), (
|
|
||||||
'{} received a non-compatible instance ({}) '
|
|
||||||
'when expecting {}'.format(
|
|
||||||
self.__class__.__name__,
|
|
||||||
self._root.__class__.__name__,
|
|
||||||
self._meta.model.__name__
|
|
||||||
))
|
|
||||||
|
|
||||||
@property
|
|
||||||
def instance(self):
|
|
||||||
return self._root
|
|
||||||
|
|
||||||
@instance.setter
|
|
||||||
def instance(self, value):
|
|
||||||
self._root = value
|
|
||||||
|
|
||||||
|
|
||||||
class DjangoObjectType(six.with_metaclass(
|
|
||||||
DjangoObjectTypeMeta, InstanceObjectType)):
|
|
||||||
|
|
||||||
class Meta:
|
|
||||||
abstract = True
|
|
||||||
|
|
||||||
|
|
||||||
class DjangoConnection(Connection):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
class DjangoNodeMeta(DjangoObjectTypeMeta, NodeMeta):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
class NodeInstance(Node, InstanceObjectType):
|
|
||||||
|
|
||||||
class Meta:
|
|
||||||
abstract = True
|
|
||||||
|
|
||||||
|
|
||||||
class DjangoNode(six.with_metaclass(
|
|
||||||
DjangoNodeMeta, NodeInstance)):
|
|
||||||
|
|
||||||
class Meta:
|
|
||||||
abstract = True
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def get_node(cls, id, info=None):
|
|
||||||
try:
|
|
||||||
instance = cls._meta.model.objects.get(id=id)
|
|
||||||
return cls(instance)
|
|
||||||
except cls._meta.model.DoesNotExist:
|
|
||||||
return None
|
|
|
@ -1,87 +0,0 @@
|
||||||
from django.db import models
|
|
||||||
from django.db.models.manager import Manager
|
|
||||||
from django.db.models.query import QuerySet
|
|
||||||
|
|
||||||
from graphene.utils import LazyList
|
|
||||||
|
|
||||||
from .compat import RelatedObject
|
|
||||||
|
|
||||||
try:
|
|
||||||
import django_filters # noqa
|
|
||||||
DJANGO_FILTER_INSTALLED = True
|
|
||||||
except (ImportError, AttributeError):
|
|
||||||
# AtributeError raised if DjangoFilters installed with a incompatible Django Version
|
|
||||||
DJANGO_FILTER_INSTALLED = False
|
|
||||||
|
|
||||||
|
|
||||||
def get_type_for_model(schema, model):
|
|
||||||
schema = schema
|
|
||||||
types = schema.types.values()
|
|
||||||
for _type in types:
|
|
||||||
type_model = hasattr(_type, '_meta') and getattr(
|
|
||||||
_type._meta, 'model', None)
|
|
||||||
if model == type_model:
|
|
||||||
return _type
|
|
||||||
|
|
||||||
|
|
||||||
def get_reverse_fields(model):
|
|
||||||
for name, attr in model.__dict__.items():
|
|
||||||
# Django =>1.9 uses 'rel', django <1.9 uses 'related'
|
|
||||||
related = getattr(attr, 'rel', None) or \
|
|
||||||
getattr(attr, 'related', None)
|
|
||||||
if isinstance(related, RelatedObject):
|
|
||||||
# Hack for making it compatible with Django 1.6
|
|
||||||
new_related = RelatedObject(related.parent_model, related.model, related.field)
|
|
||||||
new_related.name = name
|
|
||||||
yield new_related
|
|
||||||
elif isinstance(related, models.ManyToOneRel):
|
|
||||||
yield related
|
|
||||||
elif isinstance(related, models.ManyToManyRel) and not related.symmetrical:
|
|
||||||
yield related
|
|
||||||
|
|
||||||
|
|
||||||
class WrappedQueryset(LazyList):
|
|
||||||
|
|
||||||
def __len__(self):
|
|
||||||
# Dont calculate the length using len(queryset), as this will
|
|
||||||
# evaluate the whole queryset and return it's length.
|
|
||||||
# Use .count() instead
|
|
||||||
return self._origin.count()
|
|
||||||
|
|
||||||
|
|
||||||
def maybe_queryset(value):
|
|
||||||
if isinstance(value, Manager):
|
|
||||||
value = value.get_queryset()
|
|
||||||
if isinstance(value, QuerySet):
|
|
||||||
return WrappedQueryset(value)
|
|
||||||
return value
|
|
||||||
|
|
||||||
|
|
||||||
def get_related_model(field):
|
|
||||||
if hasattr(field, 'rel'):
|
|
||||||
# Django 1.6, 1.7
|
|
||||||
return field.rel.to
|
|
||||||
return field.related_model
|
|
||||||
|
|
||||||
|
|
||||||
def import_single_dispatch():
|
|
||||||
try:
|
|
||||||
from functools import singledispatch
|
|
||||||
except ImportError:
|
|
||||||
singledispatch = None
|
|
||||||
|
|
||||||
if not singledispatch:
|
|
||||||
try:
|
|
||||||
from singledispatch import singledispatch
|
|
||||||
except ImportError:
|
|
||||||
pass
|
|
||||||
|
|
||||||
if not singledispatch:
|
|
||||||
raise Exception(
|
|
||||||
"It seems your python version does not include "
|
|
||||||
"functools.singledispatch. Please install the 'singledispatch' "
|
|
||||||
"package. More information here: "
|
|
||||||
"https://pypi.python.org/pypi/singledispatch"
|
|
||||||
)
|
|
||||||
|
|
||||||
return singledispatch
|
|
|
@ -1,13 +0,0 @@
|
||||||
from graphql_django_view import GraphQLView as BaseGraphQLView
|
|
||||||
|
|
||||||
|
|
||||||
class GraphQLView(BaseGraphQLView):
|
|
||||||
graphene_schema = None
|
|
||||||
|
|
||||||
def __init__(self, schema, **kwargs):
|
|
||||||
super(GraphQLView, self).__init__(
|
|
||||||
graphene_schema=schema,
|
|
||||||
schema=schema.schema,
|
|
||||||
executor=schema.executor,
|
|
||||||
**kwargs
|
|
||||||
)
|
|
|
@ -1,11 +0,0 @@
|
||||||
from graphene.contrib.sqlalchemy.types import (
|
|
||||||
SQLAlchemyObjectType,
|
|
||||||
SQLAlchemyNode
|
|
||||||
)
|
|
||||||
from graphene.contrib.sqlalchemy.fields import (
|
|
||||||
SQLAlchemyConnectionField,
|
|
||||||
SQLAlchemyModelField
|
|
||||||
)
|
|
||||||
|
|
||||||
__all__ = ['SQLAlchemyObjectType', 'SQLAlchemyNode',
|
|
||||||
'SQLAlchemyConnectionField', 'SQLAlchemyModelField']
|
|
|
@ -1,73 +0,0 @@
|
||||||
from singledispatch import singledispatch
|
|
||||||
from sqlalchemy import types
|
|
||||||
from sqlalchemy.orm import interfaces
|
|
||||||
|
|
||||||
from ...core.classtypes.enum import Enum
|
|
||||||
from ...core.types.scalars import ID, Boolean, Float, Int, String
|
|
||||||
from .fields import ConnectionOrListField, SQLAlchemyModelField
|
|
||||||
|
|
||||||
try:
|
|
||||||
from sqlalchemy_utils.types.choice import ChoiceType
|
|
||||||
except ImportError:
|
|
||||||
class ChoiceType(object):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
def convert_sqlalchemy_relationship(relationship):
|
|
||||||
direction = relationship.direction
|
|
||||||
model = relationship.mapper.entity
|
|
||||||
model_field = SQLAlchemyModelField(model, description=relationship.doc)
|
|
||||||
if direction == interfaces.MANYTOONE:
|
|
||||||
return model_field
|
|
||||||
elif (direction == interfaces.ONETOMANY or
|
|
||||||
direction == interfaces.MANYTOMANY):
|
|
||||||
return ConnectionOrListField(model_field)
|
|
||||||
|
|
||||||
|
|
||||||
def convert_sqlalchemy_column(column):
|
|
||||||
return convert_sqlalchemy_type(getattr(column, 'type', None), column)
|
|
||||||
|
|
||||||
|
|
||||||
@singledispatch
|
|
||||||
def convert_sqlalchemy_type(type, column):
|
|
||||||
raise Exception(
|
|
||||||
"Don't know how to convert the SQLAlchemy field %s (%s)" % (column, column.__class__))
|
|
||||||
|
|
||||||
|
|
||||||
@convert_sqlalchemy_type.register(types.Date)
|
|
||||||
@convert_sqlalchemy_type.register(types.DateTime)
|
|
||||||
@convert_sqlalchemy_type.register(types.Time)
|
|
||||||
@convert_sqlalchemy_type.register(types.String)
|
|
||||||
@convert_sqlalchemy_type.register(types.Text)
|
|
||||||
@convert_sqlalchemy_type.register(types.Unicode)
|
|
||||||
@convert_sqlalchemy_type.register(types.UnicodeText)
|
|
||||||
@convert_sqlalchemy_type.register(types.Enum)
|
|
||||||
def convert_column_to_string(type, column):
|
|
||||||
return String(description=column.doc)
|
|
||||||
|
|
||||||
|
|
||||||
@convert_sqlalchemy_type.register(types.SmallInteger)
|
|
||||||
@convert_sqlalchemy_type.register(types.BigInteger)
|
|
||||||
@convert_sqlalchemy_type.register(types.Integer)
|
|
||||||
def convert_column_to_int_or_id(type, column):
|
|
||||||
if column.primary_key:
|
|
||||||
return ID(description=column.doc)
|
|
||||||
else:
|
|
||||||
return Int(description=column.doc)
|
|
||||||
|
|
||||||
|
|
||||||
@convert_sqlalchemy_type.register(types.Boolean)
|
|
||||||
def convert_column_to_boolean(type, column):
|
|
||||||
return Boolean(description=column.doc)
|
|
||||||
|
|
||||||
|
|
||||||
@convert_sqlalchemy_type.register(types.Float)
|
|
||||||
@convert_sqlalchemy_type.register(types.Numeric)
|
|
||||||
def convert_column_to_float(type, column):
|
|
||||||
return Float(description=column.doc)
|
|
||||||
|
|
||||||
|
|
||||||
@convert_sqlalchemy_type.register(ChoiceType)
|
|
||||||
def convert_column_to_enum(type, column):
|
|
||||||
name = '{}_{}'.format(column.table.name, column.name).upper()
|
|
||||||
return Enum(name, type.choices, description=column.doc)
|
|
|
@ -1,69 +0,0 @@
|
||||||
from ...core.exceptions import SkipField
|
|
||||||
from ...core.fields import Field
|
|
||||||
from ...core.types.base import FieldType
|
|
||||||
from ...core.types.definitions import List
|
|
||||||
from ...relay import ConnectionField
|
|
||||||
from ...relay.utils import is_node
|
|
||||||
from .utils import get_query, get_type_for_model, maybe_query
|
|
||||||
|
|
||||||
|
|
||||||
class DefaultQuery(object):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
class SQLAlchemyConnectionField(ConnectionField):
|
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
|
||||||
kwargs['default'] = kwargs.pop('default', lambda: DefaultQuery)
|
|
||||||
return super(SQLAlchemyConnectionField, self).__init__(*args, **kwargs)
|
|
||||||
|
|
||||||
@property
|
|
||||||
def model(self):
|
|
||||||
return self.type._meta.model
|
|
||||||
|
|
||||||
def from_list(self, connection_type, resolved, args, context, info):
|
|
||||||
if resolved is DefaultQuery:
|
|
||||||
resolved = get_query(self.model, info)
|
|
||||||
query = maybe_query(resolved)
|
|
||||||
return super(SQLAlchemyConnectionField, self).from_list(connection_type, query, args, context, info)
|
|
||||||
|
|
||||||
|
|
||||||
class ConnectionOrListField(Field):
|
|
||||||
|
|
||||||
def internal_type(self, schema):
|
|
||||||
model_field = self.type
|
|
||||||
field_object_type = model_field.get_object_type(schema)
|
|
||||||
if not field_object_type:
|
|
||||||
raise SkipField()
|
|
||||||
if is_node(field_object_type):
|
|
||||||
field = SQLAlchemyConnectionField(field_object_type)
|
|
||||||
else:
|
|
||||||
field = Field(List(field_object_type))
|
|
||||||
field.contribute_to_class(self.object_type, self.attname)
|
|
||||||
return schema.T(field)
|
|
||||||
|
|
||||||
|
|
||||||
class SQLAlchemyModelField(FieldType):
|
|
||||||
|
|
||||||
def __init__(self, model, *args, **kwargs):
|
|
||||||
self.model = model
|
|
||||||
super(SQLAlchemyModelField, self).__init__(*args, **kwargs)
|
|
||||||
|
|
||||||
def internal_type(self, schema):
|
|
||||||
_type = self.get_object_type(schema)
|
|
||||||
if not _type and self.parent._meta.only_fields:
|
|
||||||
raise Exception(
|
|
||||||
"Table %r is not accessible by the schema. "
|
|
||||||
"You can either register the type manually "
|
|
||||||
"using @schema.register. "
|
|
||||||
"Or disable the field in %s" % (
|
|
||||||
self.model,
|
|
||||||
self.parent,
|
|
||||||
)
|
|
||||||
)
|
|
||||||
if not _type:
|
|
||||||
raise SkipField()
|
|
||||||
return schema.T(_type)
|
|
||||||
|
|
||||||
def get_object_type(self, schema):
|
|
||||||
return get_type_for_model(schema, self.model)
|
|
|
@ -1,24 +0,0 @@
|
||||||
from ...core.classtypes.objecttype import ObjectTypeOptions
|
|
||||||
from ...relay.types import Node
|
|
||||||
from ...relay.utils import is_node
|
|
||||||
|
|
||||||
VALID_ATTRS = ('model', 'only_fields', 'exclude_fields', 'identifier')
|
|
||||||
|
|
||||||
|
|
||||||
class SQLAlchemyOptions(ObjectTypeOptions):
|
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
|
||||||
super(SQLAlchemyOptions, self).__init__(*args, **kwargs)
|
|
||||||
self.model = None
|
|
||||||
self.identifier = "id"
|
|
||||||
self.valid_attrs += VALID_ATTRS
|
|
||||||
self.only_fields = None
|
|
||||||
self.exclude_fields = []
|
|
||||||
self.filter_fields = None
|
|
||||||
self.filter_order_by = None
|
|
||||||
|
|
||||||
def contribute_to_class(self, cls, name):
|
|
||||||
super(SQLAlchemyOptions, self).contribute_to_class(cls, name)
|
|
||||||
if is_node(cls):
|
|
||||||
self.exclude_fields = list(self.exclude_fields) + ['id']
|
|
||||||
self.interfaces.append(Node)
|
|
|
@ -1,42 +0,0 @@
|
||||||
from __future__ import absolute_import
|
|
||||||
|
|
||||||
from sqlalchemy import Column, Date, ForeignKey, Integer, String, Table
|
|
||||||
from sqlalchemy.ext.declarative import declarative_base
|
|
||||||
from sqlalchemy.orm import relationship
|
|
||||||
|
|
||||||
Base = declarative_base()
|
|
||||||
|
|
||||||
association_table = Table('association', Base.metadata,
|
|
||||||
Column('pet_id', Integer, ForeignKey('pets.id')),
|
|
||||||
Column('reporter_id', Integer, ForeignKey('reporters.id')))
|
|
||||||
|
|
||||||
|
|
||||||
class Editor(Base):
|
|
||||||
__tablename__ = 'editors'
|
|
||||||
editor_id = Column(Integer(), primary_key=True)
|
|
||||||
name = Column(String(100))
|
|
||||||
|
|
||||||
|
|
||||||
class Pet(Base):
|
|
||||||
__tablename__ = 'pets'
|
|
||||||
id = Column(Integer(), primary_key=True)
|
|
||||||
name = Column(String(30))
|
|
||||||
reporter_id = Column(Integer(), ForeignKey('reporters.id'))
|
|
||||||
|
|
||||||
|
|
||||||
class Reporter(Base):
|
|
||||||
__tablename__ = 'reporters'
|
|
||||||
id = Column(Integer(), primary_key=True)
|
|
||||||
first_name = Column(String(30))
|
|
||||||
last_name = Column(String(30))
|
|
||||||
email = Column(String())
|
|
||||||
pets = relationship('Pet', secondary=association_table, backref='reporters')
|
|
||||||
articles = relationship('Article', backref='reporter')
|
|
||||||
|
|
||||||
|
|
||||||
class Article(Base):
|
|
||||||
__tablename__ = 'articles'
|
|
||||||
id = Column(Integer(), primary_key=True)
|
|
||||||
headline = Column(String(100))
|
|
||||||
pub_date = Column(Date())
|
|
||||||
reporter_id = Column(Integer(), ForeignKey('reporters.id'))
|
|
|
@ -1,124 +0,0 @@
|
||||||
from py.test import raises
|
|
||||||
from sqlalchemy import Column, Table, types
|
|
||||||
from sqlalchemy.ext.declarative import declarative_base
|
|
||||||
from sqlalchemy_utils.types.choice import ChoiceType
|
|
||||||
|
|
||||||
import graphene
|
|
||||||
from graphene.contrib.sqlalchemy.converter import (convert_sqlalchemy_column,
|
|
||||||
convert_sqlalchemy_relationship)
|
|
||||||
from graphene.contrib.sqlalchemy.fields import (ConnectionOrListField,
|
|
||||||
SQLAlchemyModelField)
|
|
||||||
|
|
||||||
from .models import Article, Pet, Reporter
|
|
||||||
|
|
||||||
|
|
||||||
def assert_column_conversion(sqlalchemy_type, graphene_field, **kwargs):
|
|
||||||
column = Column(sqlalchemy_type, doc='Custom Help Text', **kwargs)
|
|
||||||
graphene_type = convert_sqlalchemy_column(column)
|
|
||||||
assert isinstance(graphene_type, graphene_field)
|
|
||||||
field = graphene_type.as_field()
|
|
||||||
assert field.description == 'Custom Help Text'
|
|
||||||
return field
|
|
||||||
|
|
||||||
|
|
||||||
def test_should_unknown_sqlalchemy_field_raise_exception():
|
|
||||||
with raises(Exception) as excinfo:
|
|
||||||
convert_sqlalchemy_column(None)
|
|
||||||
assert 'Don\'t know how to convert the SQLAlchemy field' in str(excinfo.value)
|
|
||||||
|
|
||||||
|
|
||||||
def test_should_date_convert_string():
|
|
||||||
assert_column_conversion(types.Date(), graphene.String)
|
|
||||||
|
|
||||||
|
|
||||||
def test_should_datetime_convert_string():
|
|
||||||
assert_column_conversion(types.DateTime(), graphene.String)
|
|
||||||
|
|
||||||
|
|
||||||
def test_should_time_convert_string():
|
|
||||||
assert_column_conversion(types.Time(), graphene.String)
|
|
||||||
|
|
||||||
|
|
||||||
def test_should_string_convert_string():
|
|
||||||
assert_column_conversion(types.String(), graphene.String)
|
|
||||||
|
|
||||||
|
|
||||||
def test_should_text_convert_string():
|
|
||||||
assert_column_conversion(types.Text(), graphene.String)
|
|
||||||
|
|
||||||
|
|
||||||
def test_should_unicode_convert_string():
|
|
||||||
assert_column_conversion(types.Unicode(), graphene.String)
|
|
||||||
|
|
||||||
|
|
||||||
def test_should_unicodetext_convert_string():
|
|
||||||
assert_column_conversion(types.UnicodeText(), graphene.String)
|
|
||||||
|
|
||||||
|
|
||||||
def test_should_enum_convert_string():
|
|
||||||
assert_column_conversion(types.Enum(), graphene.String)
|
|
||||||
|
|
||||||
|
|
||||||
def test_should_small_integer_convert_int():
|
|
||||||
assert_column_conversion(types.SmallInteger(), graphene.Int)
|
|
||||||
|
|
||||||
|
|
||||||
def test_should_big_integer_convert_int():
|
|
||||||
assert_column_conversion(types.BigInteger(), graphene.Int)
|
|
||||||
|
|
||||||
|
|
||||||
def test_should_integer_convert_int():
|
|
||||||
assert_column_conversion(types.Integer(), graphene.Int)
|
|
||||||
|
|
||||||
|
|
||||||
def test_should_integer_convert_id():
|
|
||||||
assert_column_conversion(types.Integer(), graphene.ID, primary_key=True)
|
|
||||||
|
|
||||||
|
|
||||||
def test_should_boolean_convert_boolean():
|
|
||||||
assert_column_conversion(types.Boolean(), graphene.Boolean)
|
|
||||||
|
|
||||||
|
|
||||||
def test_should_float_convert_float():
|
|
||||||
assert_column_conversion(types.Float(), graphene.Float)
|
|
||||||
|
|
||||||
|
|
||||||
def test_should_numeric_convert_float():
|
|
||||||
assert_column_conversion(types.Numeric(), graphene.Float)
|
|
||||||
|
|
||||||
|
|
||||||
def test_should_choice_convert_enum():
|
|
||||||
TYPES = [
|
|
||||||
(u'es', u'Spanish'),
|
|
||||||
(u'en', u'English')
|
|
||||||
]
|
|
||||||
column = Column(ChoiceType(TYPES), doc='Language', name='language')
|
|
||||||
Base = declarative_base()
|
|
||||||
|
|
||||||
Table('translatedmodel', Base.metadata, column)
|
|
||||||
graphene_type = convert_sqlalchemy_column(column)
|
|
||||||
assert issubclass(graphene_type, graphene.Enum)
|
|
||||||
assert graphene_type._meta.type_name == 'TRANSLATEDMODEL_LANGUAGE'
|
|
||||||
assert graphene_type._meta.description == 'Language'
|
|
||||||
assert graphene_type.__enum__.__members__['es'].value == 'Spanish'
|
|
||||||
assert graphene_type.__enum__.__members__['en'].value == 'English'
|
|
||||||
|
|
||||||
|
|
||||||
def test_should_manytomany_convert_connectionorlist():
|
|
||||||
graphene_type = convert_sqlalchemy_relationship(Reporter.pets.property)
|
|
||||||
assert isinstance(graphene_type, ConnectionOrListField)
|
|
||||||
assert isinstance(graphene_type.type, SQLAlchemyModelField)
|
|
||||||
assert graphene_type.type.model == Pet
|
|
||||||
|
|
||||||
|
|
||||||
def test_should_manytoone_convert_connectionorlist():
|
|
||||||
field = convert_sqlalchemy_relationship(Article.reporter.property)
|
|
||||||
assert isinstance(field, SQLAlchemyModelField)
|
|
||||||
assert field.model == Reporter
|
|
||||||
|
|
||||||
|
|
||||||
def test_should_onetomany_convert_model():
|
|
||||||
graphene_type = convert_sqlalchemy_relationship(Reporter.articles.property)
|
|
||||||
assert isinstance(graphene_type, ConnectionOrListField)
|
|
||||||
assert isinstance(graphene_type.type, SQLAlchemyModelField)
|
|
||||||
assert graphene_type.type.model == Article
|
|
|
@ -1,239 +0,0 @@
|
||||||
import pytest
|
|
||||||
from sqlalchemy import create_engine
|
|
||||||
from sqlalchemy.orm import scoped_session, sessionmaker
|
|
||||||
|
|
||||||
import graphene
|
|
||||||
from graphene import relay
|
|
||||||
from graphene.contrib.sqlalchemy import (SQLAlchemyConnectionField,
|
|
||||||
SQLAlchemyNode, SQLAlchemyObjectType)
|
|
||||||
|
|
||||||
from .models import Article, Base, Editor, Reporter
|
|
||||||
|
|
||||||
db = create_engine('sqlite:///test_sqlalchemy.sqlite3')
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.yield_fixture(scope='function')
|
|
||||||
def session():
|
|
||||||
connection = db.engine.connect()
|
|
||||||
transaction = connection.begin()
|
|
||||||
Base.metadata.create_all(connection)
|
|
||||||
|
|
||||||
# options = dict(bind=connection, binds={})
|
|
||||||
session_factory = sessionmaker(bind=connection)
|
|
||||||
session = scoped_session(session_factory)
|
|
||||||
|
|
||||||
yield session
|
|
||||||
|
|
||||||
# Finalize test here
|
|
||||||
transaction.rollback()
|
|
||||||
connection.close()
|
|
||||||
session.remove()
|
|
||||||
|
|
||||||
|
|
||||||
def setup_fixtures(session):
|
|
||||||
reporter = Reporter(first_name='ABA', last_name='X')
|
|
||||||
session.add(reporter)
|
|
||||||
reporter2 = Reporter(first_name='ABO', last_name='Y')
|
|
||||||
session.add(reporter2)
|
|
||||||
article = Article(headline='Hi!')
|
|
||||||
session.add(article)
|
|
||||||
editor = Editor(name="John")
|
|
||||||
session.add(editor)
|
|
||||||
session.commit()
|
|
||||||
|
|
||||||
|
|
||||||
def test_should_query_well(session):
|
|
||||||
setup_fixtures(session)
|
|
||||||
|
|
||||||
class ReporterType(SQLAlchemyObjectType):
|
|
||||||
|
|
||||||
class Meta:
|
|
||||||
model = Reporter
|
|
||||||
|
|
||||||
class Query(graphene.ObjectType):
|
|
||||||
reporter = graphene.Field(ReporterType)
|
|
||||||
reporters = ReporterType.List()
|
|
||||||
|
|
||||||
def resolve_reporter(self, *args, **kwargs):
|
|
||||||
return session.query(Reporter).first()
|
|
||||||
|
|
||||||
def resolve_reporters(self, *args, **kwargs):
|
|
||||||
return session.query(Reporter)
|
|
||||||
|
|
||||||
query = '''
|
|
||||||
query ReporterQuery {
|
|
||||||
reporter {
|
|
||||||
firstName,
|
|
||||||
lastName,
|
|
||||||
email
|
|
||||||
}
|
|
||||||
reporters {
|
|
||||||
firstName
|
|
||||||
}
|
|
||||||
}
|
|
||||||
'''
|
|
||||||
expected = {
|
|
||||||
'reporter': {
|
|
||||||
'firstName': 'ABA',
|
|
||||||
'lastName': 'X',
|
|
||||||
'email': None
|
|
||||||
},
|
|
||||||
'reporters': [{
|
|
||||||
'firstName': 'ABA',
|
|
||||||
}, {
|
|
||||||
'firstName': 'ABO',
|
|
||||||
}]
|
|
||||||
}
|
|
||||||
schema = graphene.Schema(query=Query)
|
|
||||||
result = schema.execute(query)
|
|
||||||
assert not result.errors
|
|
||||||
assert result.data == expected
|
|
||||||
|
|
||||||
|
|
||||||
def test_should_node(session):
|
|
||||||
setup_fixtures(session)
|
|
||||||
|
|
||||||
class ReporterNode(SQLAlchemyNode):
|
|
||||||
|
|
||||||
class Meta:
|
|
||||||
model = Reporter
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def get_node(cls, id, info):
|
|
||||||
return Reporter(id=2, first_name='Cookie Monster')
|
|
||||||
|
|
||||||
def resolve_articles(self, *args, **kwargs):
|
|
||||||
return [Article(headline='Hi!')]
|
|
||||||
|
|
||||||
class ArticleNode(SQLAlchemyNode):
|
|
||||||
|
|
||||||
class Meta:
|
|
||||||
model = Article
|
|
||||||
|
|
||||||
# @classmethod
|
|
||||||
# def get_node(cls, id, info):
|
|
||||||
# return Article(id=1, headline='Article node')
|
|
||||||
|
|
||||||
class Query(graphene.ObjectType):
|
|
||||||
node = relay.NodeField()
|
|
||||||
reporter = graphene.Field(ReporterNode)
|
|
||||||
article = graphene.Field(ArticleNode)
|
|
||||||
all_articles = SQLAlchemyConnectionField(ArticleNode)
|
|
||||||
|
|
||||||
def resolve_reporter(self, *args, **kwargs):
|
|
||||||
return Reporter(id=1, first_name='ABA', last_name='X')
|
|
||||||
|
|
||||||
def resolve_article(self, *args, **kwargs):
|
|
||||||
return Article(id=1, headline='Article node')
|
|
||||||
|
|
||||||
query = '''
|
|
||||||
query ReporterQuery {
|
|
||||||
reporter {
|
|
||||||
id,
|
|
||||||
firstName,
|
|
||||||
articles {
|
|
||||||
edges {
|
|
||||||
node {
|
|
||||||
headline
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
lastName,
|
|
||||||
email
|
|
||||||
}
|
|
||||||
allArticles {
|
|
||||||
edges {
|
|
||||||
node {
|
|
||||||
headline
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
myArticle: node(id:"QXJ0aWNsZU5vZGU6MQ==") {
|
|
||||||
id
|
|
||||||
... on ReporterNode {
|
|
||||||
firstName
|
|
||||||
}
|
|
||||||
... on ArticleNode {
|
|
||||||
headline
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
'''
|
|
||||||
expected = {
|
|
||||||
'reporter': {
|
|
||||||
'id': 'UmVwb3J0ZXJOb2RlOjE=',
|
|
||||||
'firstName': 'ABA',
|
|
||||||
'lastName': 'X',
|
|
||||||
'email': None,
|
|
||||||
'articles': {
|
|
||||||
'edges': [{
|
|
||||||
'node': {
|
|
||||||
'headline': 'Hi!'
|
|
||||||
}
|
|
||||||
}]
|
|
||||||
},
|
|
||||||
},
|
|
||||||
'allArticles': {
|
|
||||||
'edges': [{
|
|
||||||
'node': {
|
|
||||||
'headline': 'Hi!'
|
|
||||||
}
|
|
||||||
}]
|
|
||||||
},
|
|
||||||
'myArticle': {
|
|
||||||
'id': 'QXJ0aWNsZU5vZGU6MQ==',
|
|
||||||
'headline': 'Hi!'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
schema = graphene.Schema(query=Query, session=session)
|
|
||||||
result = schema.execute(query)
|
|
||||||
assert not result.errors
|
|
||||||
assert result.data == expected
|
|
||||||
|
|
||||||
|
|
||||||
def test_should_custom_identifier(session):
|
|
||||||
setup_fixtures(session)
|
|
||||||
|
|
||||||
class EditorNode(SQLAlchemyNode):
|
|
||||||
|
|
||||||
class Meta:
|
|
||||||
model = Editor
|
|
||||||
identifier = "editor_id"
|
|
||||||
|
|
||||||
class Query(graphene.ObjectType):
|
|
||||||
node = relay.NodeField(EditorNode)
|
|
||||||
all_editors = SQLAlchemyConnectionField(EditorNode)
|
|
||||||
|
|
||||||
query = '''
|
|
||||||
query EditorQuery {
|
|
||||||
allEditors {
|
|
||||||
edges {
|
|
||||||
node {
|
|
||||||
id,
|
|
||||||
name
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
node(id: "RWRpdG9yTm9kZTox") {
|
|
||||||
name
|
|
||||||
}
|
|
||||||
}
|
|
||||||
'''
|
|
||||||
expected = {
|
|
||||||
'allEditors': {
|
|
||||||
'edges': [{
|
|
||||||
'node': {
|
|
||||||
'id': 'RWRpdG9yTm9kZTox',
|
|
||||||
'name': 'John'
|
|
||||||
}
|
|
||||||
}]
|
|
||||||
},
|
|
||||||
'node': {
|
|
||||||
'name': 'John'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
schema = graphene.Schema(query=Query, session=session)
|
|
||||||
result = schema.execute(query)
|
|
||||||
assert not result.errors
|
|
||||||
assert result.data == expected
|
|
|
@ -1,45 +0,0 @@
|
||||||
from py.test import raises
|
|
||||||
|
|
||||||
from graphene.contrib.sqlalchemy import SQLAlchemyObjectType
|
|
||||||
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(SQLAlchemyObjectType):
|
|
||||||
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(SQLAlchemyObjectType):
|
|
||||||
|
|
||||||
class Meta:
|
|
||||||
model = 1
|
|
||||||
assert 'not a SQLAlchemy model' in str(excinfo.value)
|
|
||||||
|
|
||||||
|
|
||||||
def test_should_map_fields_correctly():
|
|
||||||
class ReporterType2(SQLAlchemyObjectType):
|
|
||||||
|
|
||||||
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(SQLAlchemyObjectType):
|
|
||||||
|
|
||||||
class Meta:
|
|
||||||
model = Reporter
|
|
||||||
only_fields = ('id', 'email')
|
|
||||||
assert_equal_lists(
|
|
||||||
Reporter2._meta.fields_map.keys(),
|
|
||||||
['id', 'email']
|
|
||||||
)
|
|
|
@ -1,102 +0,0 @@
|
||||||
from graphql.type import GraphQLObjectType
|
|
||||||
from pytest import raises
|
|
||||||
|
|
||||||
from graphene import Schema
|
|
||||||
from graphene.contrib.sqlalchemy.types import (SQLAlchemyNode,
|
|
||||||
SQLAlchemyObjectType)
|
|
||||||
from graphene.core.fields import Field
|
|
||||||
from graphene.core.types.scalars import Int
|
|
||||||
from graphene.relay.fields import GlobalIDField
|
|
||||||
from tests.utils import assert_equal_lists
|
|
||||||
|
|
||||||
from .models import Article, Reporter
|
|
||||||
|
|
||||||
schema = Schema()
|
|
||||||
|
|
||||||
|
|
||||||
class Character(SQLAlchemyObjectType):
|
|
||||||
'''Character description'''
|
|
||||||
class Meta:
|
|
||||||
model = Reporter
|
|
||||||
|
|
||||||
|
|
||||||
@schema.register
|
|
||||||
class Human(SQLAlchemyNode):
|
|
||||||
'''Human description'''
|
|
||||||
|
|
||||||
pub_date = Int()
|
|
||||||
|
|
||||||
class Meta:
|
|
||||||
model = Article
|
|
||||||
exclude_fields = ('id', )
|
|
||||||
|
|
||||||
|
|
||||||
def test_sqlalchemy_interface():
|
|
||||||
assert SQLAlchemyNode._meta.interface is True
|
|
||||||
|
|
||||||
|
|
||||||
# @patch('graphene.contrib.sqlalchemy.tests.models.Article.filter', return_value=Article(id=1))
|
|
||||||
# def test_sqlalchemy_get_node(get):
|
|
||||||
# human = Human.get_node(1, None)
|
|
||||||
# get.assert_called_with(id=1)
|
|
||||||
# assert human.id == 1
|
|
||||||
|
|
||||||
|
|
||||||
def test_objecttype_registered():
|
|
||||||
object_type = schema.T(Character)
|
|
||||||
assert isinstance(object_type, GraphQLObjectType)
|
|
||||||
assert Character._meta.model == Reporter
|
|
||||||
assert_equal_lists(
|
|
||||||
object_type.get_fields().keys(),
|
|
||||||
['articles', 'firstName', 'lastName', 'email', 'id']
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def test_sqlalchemynode_idfield():
|
|
||||||
idfield = SQLAlchemyNode._meta.fields_map['id']
|
|
||||||
assert isinstance(idfield, GlobalIDField)
|
|
||||||
|
|
||||||
|
|
||||||
def test_node_idfield():
|
|
||||||
idfield = Human._meta.fields_map['id']
|
|
||||||
assert isinstance(idfield, GlobalIDField)
|
|
||||||
|
|
||||||
|
|
||||||
def test_node_replacedfield():
|
|
||||||
idfield = Human._meta.fields_map['pub_date']
|
|
||||||
assert isinstance(idfield, Field)
|
|
||||||
assert schema.T(idfield).type == schema.T(Int())
|
|
||||||
|
|
||||||
|
|
||||||
def test_interface_objecttype_init_none():
|
|
||||||
h = Human()
|
|
||||||
assert h._root is None
|
|
||||||
|
|
||||||
|
|
||||||
def test_interface_objecttype_init_good():
|
|
||||||
instance = Article()
|
|
||||||
h = Human(instance)
|
|
||||||
assert h._root == instance
|
|
||||||
|
|
||||||
|
|
||||||
def test_interface_objecttype_init_unexpected():
|
|
||||||
with raises(AssertionError) as excinfo:
|
|
||||||
Human(object())
|
|
||||||
assert str(excinfo.value) == "Human received a non-compatible instance (object) when expecting Article"
|
|
||||||
|
|
||||||
|
|
||||||
def test_object_type():
|
|
||||||
object_type = schema.T(Human)
|
|
||||||
Human._meta.fields_map
|
|
||||||
assert Human._meta.interface is False
|
|
||||||
assert isinstance(object_type, GraphQLObjectType)
|
|
||||||
assert_equal_lists(
|
|
||||||
object_type.get_fields().keys(),
|
|
||||||
['headline', 'id', 'reporter', 'reporterId', 'pubDate']
|
|
||||||
)
|
|
||||||
assert schema.T(SQLAlchemyNode) in object_type.get_interfaces()
|
|
||||||
|
|
||||||
|
|
||||||
def test_node_notinterface():
|
|
||||||
assert Human._meta.interface is False
|
|
||||||
assert SQLAlchemyNode in Human._meta.interfaces
|
|
|
@ -1,25 +0,0 @@
|
||||||
from graphene import ObjectType, Schema, String
|
|
||||||
|
|
||||||
from ..utils import get_session
|
|
||||||
|
|
||||||
|
|
||||||
def test_get_session():
|
|
||||||
session = 'My SQLAlchemy session'
|
|
||||||
schema = Schema(session=session)
|
|
||||||
|
|
||||||
class Query(ObjectType):
|
|
||||||
x = String()
|
|
||||||
|
|
||||||
def resolve_x(self, args, info):
|
|
||||||
return get_session(info)
|
|
||||||
|
|
||||||
query = '''
|
|
||||||
query ReporterQuery {
|
|
||||||
x
|
|
||||||
}
|
|
||||||
'''
|
|
||||||
|
|
||||||
schema = Schema(query=Query, session=session)
|
|
||||||
result = schema.execute(query)
|
|
||||||
assert not result.errors
|
|
||||||
assert result.data['x'] == session
|
|
|
@ -1,125 +0,0 @@
|
||||||
import inspect
|
|
||||||
|
|
||||||
import six
|
|
||||||
from sqlalchemy.inspection import inspect as sqlalchemyinspect
|
|
||||||
from sqlalchemy.orm.exc import NoResultFound
|
|
||||||
|
|
||||||
from ...core.classtypes.objecttype import ObjectType, ObjectTypeMeta
|
|
||||||
from ...relay.types import Connection, Node, NodeMeta
|
|
||||||
from .converter import (convert_sqlalchemy_column,
|
|
||||||
convert_sqlalchemy_relationship)
|
|
||||||
from .options import SQLAlchemyOptions
|
|
||||||
from .utils import get_query, is_mapped
|
|
||||||
|
|
||||||
|
|
||||||
class SQLAlchemyObjectTypeMeta(ObjectTypeMeta):
|
|
||||||
options_class = SQLAlchemyOptions
|
|
||||||
|
|
||||||
def construct_fields(cls):
|
|
||||||
only_fields = cls._meta.only_fields
|
|
||||||
exclude_fields = cls._meta.exclude_fields
|
|
||||||
already_created_fields = {f.attname for f in cls._meta.local_fields}
|
|
||||||
inspected_model = sqlalchemyinspect(cls._meta.model)
|
|
||||||
|
|
||||||
# Get all the columns for the relationships on the model
|
|
||||||
for relationship in inspected_model.relationships:
|
|
||||||
is_not_in_only = only_fields and relationship.key not in only_fields
|
|
||||||
is_already_created = relationship.key in already_created_fields
|
|
||||||
is_excluded = relationship.key in exclude_fields or is_already_created
|
|
||||||
if is_not_in_only or is_excluded:
|
|
||||||
# We skip this field if we specify only_fields and is not
|
|
||||||
# in there. Or when we excldue this field in exclude_fields
|
|
||||||
continue
|
|
||||||
converted_relationship = convert_sqlalchemy_relationship(relationship)
|
|
||||||
cls.add_to_class(relationship.key, converted_relationship)
|
|
||||||
|
|
||||||
for column in inspected_model.columns:
|
|
||||||
is_not_in_only = only_fields and column.name not in only_fields
|
|
||||||
is_already_created = column.name in already_created_fields
|
|
||||||
is_excluded = column.name in exclude_fields or is_already_created
|
|
||||||
if is_not_in_only or is_excluded:
|
|
||||||
# We skip this field if we specify only_fields and is not
|
|
||||||
# in there. Or when we excldue this field in exclude_fields
|
|
||||||
continue
|
|
||||||
converted_column = convert_sqlalchemy_column(column)
|
|
||||||
cls.add_to_class(column.name, converted_column)
|
|
||||||
|
|
||||||
def construct(cls, *args, **kwargs):
|
|
||||||
cls = super(SQLAlchemyObjectTypeMeta, cls).construct(*args, **kwargs)
|
|
||||||
if not cls._meta.abstract:
|
|
||||||
if not cls._meta.model:
|
|
||||||
raise Exception(
|
|
||||||
'SQLAlchemy ObjectType %s must have a model in the Meta class attr' %
|
|
||||||
cls)
|
|
||||||
elif not inspect.isclass(cls._meta.model) or not is_mapped(cls._meta.model):
|
|
||||||
raise Exception('Provided model in %s is not a SQLAlchemy model' % cls)
|
|
||||||
|
|
||||||
cls.construct_fields()
|
|
||||||
return cls
|
|
||||||
|
|
||||||
|
|
||||||
class InstanceObjectType(ObjectType):
|
|
||||||
|
|
||||||
class Meta:
|
|
||||||
abstract = True
|
|
||||||
|
|
||||||
def __init__(self, _root=None):
|
|
||||||
super(InstanceObjectType, self).__init__(_root=_root)
|
|
||||||
assert not self._root or isinstance(self._root, self._meta.model), (
|
|
||||||
'{} received a non-compatible instance ({}) '
|
|
||||||
'when expecting {}'.format(
|
|
||||||
self.__class__.__name__,
|
|
||||||
self._root.__class__.__name__,
|
|
||||||
self._meta.model.__name__
|
|
||||||
))
|
|
||||||
|
|
||||||
@property
|
|
||||||
def instance(self):
|
|
||||||
return self._root
|
|
||||||
|
|
||||||
@instance.setter
|
|
||||||
def instance(self, value):
|
|
||||||
self._root = value
|
|
||||||
|
|
||||||
|
|
||||||
class SQLAlchemyObjectType(six.with_metaclass(
|
|
||||||
SQLAlchemyObjectTypeMeta, InstanceObjectType)):
|
|
||||||
|
|
||||||
class Meta:
|
|
||||||
abstract = True
|
|
||||||
|
|
||||||
|
|
||||||
class SQLAlchemyConnection(Connection):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
class SQLAlchemyNodeMeta(SQLAlchemyObjectTypeMeta, NodeMeta):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
class NodeInstance(Node, InstanceObjectType):
|
|
||||||
|
|
||||||
class Meta:
|
|
||||||
abstract = True
|
|
||||||
|
|
||||||
|
|
||||||
class SQLAlchemyNode(six.with_metaclass(
|
|
||||||
SQLAlchemyNodeMeta, NodeInstance)):
|
|
||||||
|
|
||||||
class Meta:
|
|
||||||
abstract = True
|
|
||||||
|
|
||||||
def to_global_id(self):
|
|
||||||
id_ = getattr(self.instance, self._meta.identifier)
|
|
||||||
return self.global_id(id_)
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def get_node(cls, id, info=None):
|
|
||||||
try:
|
|
||||||
model = cls._meta.model
|
|
||||||
identifier = cls._meta.identifier
|
|
||||||
query = get_query(model, info)
|
|
||||||
instance = query.filter(getattr(model, identifier) == id).one()
|
|
||||||
return cls(instance)
|
|
||||||
except NoResultFound:
|
|
||||||
return None
|
|
|
@ -1,49 +0,0 @@
|
||||||
from sqlalchemy.ext.declarative.api import DeclarativeMeta
|
|
||||||
from sqlalchemy.orm.query import Query
|
|
||||||
|
|
||||||
from graphene.utils import LazyList
|
|
||||||
|
|
||||||
|
|
||||||
def get_type_for_model(schema, model):
|
|
||||||
schema = schema
|
|
||||||
types = schema.types.values()
|
|
||||||
for _type in types:
|
|
||||||
type_model = hasattr(_type, '_meta') and getattr(
|
|
||||||
_type._meta, 'model', None)
|
|
||||||
if model == type_model:
|
|
||||||
return _type
|
|
||||||
|
|
||||||
|
|
||||||
def get_session(info):
|
|
||||||
schema = info.schema.graphene_schema
|
|
||||||
return schema.options.get('session')
|
|
||||||
|
|
||||||
|
|
||||||
def get_query(model, info):
|
|
||||||
query = getattr(model, 'query', None)
|
|
||||||
if not query:
|
|
||||||
session = get_session(info)
|
|
||||||
if not session:
|
|
||||||
raise Exception('A query in the model Base or a session in the schema is required for querying.\n'
|
|
||||||
'Read more http://graphene-python.org/docs/sqlalchemy/tips/#querying')
|
|
||||||
query = session.query(model)
|
|
||||||
return query
|
|
||||||
|
|
||||||
|
|
||||||
class WrappedQuery(LazyList):
|
|
||||||
|
|
||||||
def __len__(self):
|
|
||||||
# Dont calculate the length using len(query), as this will
|
|
||||||
# evaluate the whole queryset and return it's length.
|
|
||||||
# Use .count() instead
|
|
||||||
return self._origin.count()
|
|
||||||
|
|
||||||
|
|
||||||
def maybe_query(value):
|
|
||||||
if isinstance(value, Query):
|
|
||||||
return WrappedQuery(value)
|
|
||||||
return value
|
|
||||||
|
|
||||||
|
|
||||||
def is_mapped(obj):
|
|
||||||
return isinstance(obj, DeclarativeMeta)
|
|
|
@ -1,48 +0,0 @@
|
||||||
from .schema import (
|
|
||||||
Schema
|
|
||||||
)
|
|
||||||
|
|
||||||
from .classtypes import (
|
|
||||||
ObjectType,
|
|
||||||
InputObjectType,
|
|
||||||
Interface,
|
|
||||||
Mutation,
|
|
||||||
Scalar,
|
|
||||||
Enum
|
|
||||||
)
|
|
||||||
|
|
||||||
from .types import (
|
|
||||||
InstanceType,
|
|
||||||
LazyType,
|
|
||||||
Argument,
|
|
||||||
Field,
|
|
||||||
InputField,
|
|
||||||
String,
|
|
||||||
Int,
|
|
||||||
Boolean,
|
|
||||||
ID,
|
|
||||||
Float,
|
|
||||||
List,
|
|
||||||
NonNull
|
|
||||||
)
|
|
||||||
|
|
||||||
__all__ = [
|
|
||||||
'Argument',
|
|
||||||
'String',
|
|
||||||
'Int',
|
|
||||||
'Boolean',
|
|
||||||
'Float',
|
|
||||||
'ID',
|
|
||||||
'List',
|
|
||||||
'NonNull',
|
|
||||||
'Schema',
|
|
||||||
'InstanceType',
|
|
||||||
'LazyType',
|
|
||||||
'ObjectType',
|
|
||||||
'InputObjectType',
|
|
||||||
'Interface',
|
|
||||||
'Mutation',
|
|
||||||
'Scalar',
|
|
||||||
'Enum',
|
|
||||||
'Field',
|
|
||||||
'InputField']
|
|
|
@ -1,18 +0,0 @@
|
||||||
from .inputobjecttype import InputObjectType
|
|
||||||
from .interface import Interface
|
|
||||||
from .mutation import Mutation
|
|
||||||
from .objecttype import ObjectType
|
|
||||||
from .options import Options
|
|
||||||
from .scalar import Scalar
|
|
||||||
from .enum import Enum
|
|
||||||
from .uniontype import UnionType
|
|
||||||
|
|
||||||
__all__ = [
|
|
||||||
'InputObjectType',
|
|
||||||
'Interface',
|
|
||||||
'Mutation',
|
|
||||||
'ObjectType',
|
|
||||||
'Options',
|
|
||||||
'Scalar',
|
|
||||||
'Enum',
|
|
||||||
'UnionType']
|
|
|
@ -1,131 +0,0 @@
|
||||||
import copy
|
|
||||||
import inspect
|
|
||||||
from collections import OrderedDict
|
|
||||||
from functools import partial
|
|
||||||
|
|
||||||
import six
|
|
||||||
|
|
||||||
from .options import Options
|
|
||||||
|
|
||||||
|
|
||||||
class ClassTypeMeta(type):
|
|
||||||
options_class = Options
|
|
||||||
|
|
||||||
def __new__(mcs, name, bases, attrs):
|
|
||||||
super_new = super(ClassTypeMeta, mcs).__new__
|
|
||||||
|
|
||||||
module = attrs.pop('__module__', None)
|
|
||||||
doc = attrs.pop('__doc__', None)
|
|
||||||
new_class = super_new(mcs, name, bases, {
|
|
||||||
'__module__': module,
|
|
||||||
'__doc__': doc
|
|
||||||
})
|
|
||||||
attr_meta = attrs.pop('Meta', None)
|
|
||||||
if not attr_meta:
|
|
||||||
meta = getattr(new_class, 'Meta', None)
|
|
||||||
else:
|
|
||||||
meta = attr_meta
|
|
||||||
|
|
||||||
new_class.add_to_class('_meta', new_class.get_options(meta))
|
|
||||||
|
|
||||||
return mcs.construct(new_class, bases, attrs)
|
|
||||||
|
|
||||||
def get_options(cls, meta):
|
|
||||||
return cls.options_class(meta)
|
|
||||||
|
|
||||||
def add_to_class(cls, name, value):
|
|
||||||
# We should call the contribute_to_class method only if it's bound
|
|
||||||
if not inspect.isclass(value) and hasattr(
|
|
||||||
value, 'contribute_to_class'):
|
|
||||||
value.contribute_to_class(cls, name)
|
|
||||||
else:
|
|
||||||
setattr(cls, name, value)
|
|
||||||
|
|
||||||
def construct(cls, bases, attrs):
|
|
||||||
# Add all attributes to the class.
|
|
||||||
for obj_name, obj in attrs.items():
|
|
||||||
cls.add_to_class(obj_name, obj)
|
|
||||||
|
|
||||||
if not cls._meta.abstract:
|
|
||||||
from ..types import List, NonNull
|
|
||||||
setattr(cls, 'NonNull', partial(NonNull, cls))
|
|
||||||
setattr(cls, 'List', partial(List, cls))
|
|
||||||
|
|
||||||
return cls
|
|
||||||
|
|
||||||
|
|
||||||
class ClassType(six.with_metaclass(ClassTypeMeta)):
|
|
||||||
|
|
||||||
class Meta:
|
|
||||||
abstract = True
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def internal_type(cls, schema):
|
|
||||||
raise NotImplementedError("Function internal_type not implemented in type {}".format(cls))
|
|
||||||
|
|
||||||
|
|
||||||
class FieldsOptions(Options):
|
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
|
||||||
super(FieldsOptions, self).__init__(*args, **kwargs)
|
|
||||||
self.local_fields = []
|
|
||||||
|
|
||||||
def add_field(self, field):
|
|
||||||
self.local_fields.append(field)
|
|
||||||
|
|
||||||
@property
|
|
||||||
def fields(self):
|
|
||||||
return sorted(self.local_fields)
|
|
||||||
|
|
||||||
@property
|
|
||||||
def fields_map(self):
|
|
||||||
return OrderedDict([(f.attname, f) for f in self.fields])
|
|
||||||
|
|
||||||
@property
|
|
||||||
def fields_group_type(self):
|
|
||||||
from ..types.field import FieldsGroupType
|
|
||||||
return FieldsGroupType(*self.local_fields)
|
|
||||||
|
|
||||||
|
|
||||||
class FieldsClassTypeMeta(ClassTypeMeta):
|
|
||||||
options_class = FieldsOptions
|
|
||||||
|
|
||||||
def extend_fields(cls, bases):
|
|
||||||
new_fields = cls._meta.local_fields
|
|
||||||
field_names = {f.attname: f for f in new_fields}
|
|
||||||
|
|
||||||
for base in bases:
|
|
||||||
if not isinstance(base, FieldsClassTypeMeta):
|
|
||||||
continue
|
|
||||||
|
|
||||||
parent_fields = base._meta.local_fields
|
|
||||||
for field in parent_fields:
|
|
||||||
if field.attname in field_names and field.type.__class__ != field_names[
|
|
||||||
field.attname].type.__class__:
|
|
||||||
raise Exception(
|
|
||||||
'Local field %r in class %r (%r) clashes '
|
|
||||||
'with field with similar name from '
|
|
||||||
'Interface %s (%r)' % (
|
|
||||||
field.attname,
|
|
||||||
cls.__name__,
|
|
||||||
field.__class__,
|
|
||||||
base.__name__,
|
|
||||||
field_names[field.attname].__class__)
|
|
||||||
)
|
|
||||||
new_field = copy.copy(field)
|
|
||||||
cls.add_to_class(field.attname, new_field)
|
|
||||||
|
|
||||||
def construct(cls, bases, attrs):
|
|
||||||
cls = super(FieldsClassTypeMeta, cls).construct(bases, attrs)
|
|
||||||
cls.extend_fields(bases)
|
|
||||||
return cls
|
|
||||||
|
|
||||||
|
|
||||||
class FieldsClassType(six.with_metaclass(FieldsClassTypeMeta, ClassType)):
|
|
||||||
|
|
||||||
class Meta:
|
|
||||||
abstract = True
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def fields_internal_types(cls, schema):
|
|
||||||
return schema.T(cls._meta.fields_group_type)
|
|
|
@ -1,51 +0,0 @@
|
||||||
import six
|
|
||||||
from graphql.type import GraphQLEnumType, GraphQLEnumValue
|
|
||||||
|
|
||||||
from ...utils.enum import Enum as PyEnum
|
|
||||||
from ..types.base import MountedType
|
|
||||||
from .base import ClassType, ClassTypeMeta
|
|
||||||
|
|
||||||
|
|
||||||
class EnumMeta(ClassTypeMeta):
|
|
||||||
|
|
||||||
def construct(cls, bases, attrs):
|
|
||||||
__enum__ = attrs.get('__enum__', None)
|
|
||||||
if not cls._meta.abstract and not __enum__:
|
|
||||||
__enum__ = PyEnum(cls._meta.type_name, attrs)
|
|
||||||
setattr(cls, '__enum__', __enum__)
|
|
||||||
if __enum__:
|
|
||||||
for k, v in __enum__.__members__.items():
|
|
||||||
attrs[k] = v.value
|
|
||||||
return super(EnumMeta, cls).construct(bases, attrs)
|
|
||||||
|
|
||||||
def __call__(cls, *args, **kwargs):
|
|
||||||
if cls is Enum:
|
|
||||||
return cls.create_enum(*args, **kwargs)
|
|
||||||
return super(EnumMeta, cls).__call__(*args, **kwargs)
|
|
||||||
|
|
||||||
def create_enum(cls, name, names=None, description=None):
|
|
||||||
attrs = {
|
|
||||||
'__enum__': PyEnum(name, names)
|
|
||||||
}
|
|
||||||
if description:
|
|
||||||
attrs['__doc__'] = description
|
|
||||||
return type(name, (Enum,), attrs)
|
|
||||||
|
|
||||||
|
|
||||||
class Enum(six.with_metaclass(EnumMeta, ClassType, MountedType)):
|
|
||||||
|
|
||||||
class Meta:
|
|
||||||
abstract = True
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def internal_type(cls, schema):
|
|
||||||
if cls._meta.abstract:
|
|
||||||
raise Exception("Abstract Enum don't have a specific type.")
|
|
||||||
|
|
||||||
values = {k: GraphQLEnumValue(v.value) for k, v in cls.__enum__.__members__.items()}
|
|
||||||
# GraphQLEnumValue
|
|
||||||
return GraphQLEnumType(
|
|
||||||
cls._meta.type_name,
|
|
||||||
values=values,
|
|
||||||
description=cls._meta.description,
|
|
||||||
)
|
|
|
@ -1,25 +0,0 @@
|
||||||
from functools import partial
|
|
||||||
|
|
||||||
from graphql.type import GraphQLInputObjectType
|
|
||||||
|
|
||||||
from .base import FieldsClassType
|
|
||||||
|
|
||||||
|
|
||||||
class InputObjectType(FieldsClassType):
|
|
||||||
|
|
||||||
class Meta:
|
|
||||||
abstract = True
|
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
|
||||||
raise Exception("An InputObjectType cannot be initialized")
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def internal_type(cls, schema):
|
|
||||||
if cls._meta.abstract:
|
|
||||||
raise Exception("Abstract InputObjectTypes don't have a specific type.")
|
|
||||||
|
|
||||||
return GraphQLInputObjectType(
|
|
||||||
cls._meta.type_name,
|
|
||||||
description=cls._meta.description,
|
|
||||||
fields=partial(cls.fields_internal_types, schema),
|
|
||||||
)
|
|
|
@ -1,53 +0,0 @@
|
||||||
from functools import partial
|
|
||||||
|
|
||||||
import six
|
|
||||||
from graphql.type import GraphQLInterfaceType
|
|
||||||
|
|
||||||
from .base import FieldsClassTypeMeta
|
|
||||||
from .objecttype import ObjectType, ObjectTypeMeta
|
|
||||||
|
|
||||||
|
|
||||||
class InterfaceMeta(ObjectTypeMeta):
|
|
||||||
|
|
||||||
def construct(cls, bases, attrs):
|
|
||||||
if cls._meta.abstract or Interface in bases:
|
|
||||||
# Return Interface type
|
|
||||||
cls = FieldsClassTypeMeta.construct(cls, bases, attrs)
|
|
||||||
setattr(cls._meta, 'interface', True)
|
|
||||||
return cls
|
|
||||||
else:
|
|
||||||
# Return ObjectType class with all the inherited interfaces
|
|
||||||
cls = super(InterfaceMeta, cls).construct(bases, attrs)
|
|
||||||
for interface in bases:
|
|
||||||
is_interface = issubclass(interface, Interface) and getattr(interface._meta, 'interface', False)
|
|
||||||
if not is_interface:
|
|
||||||
continue
|
|
||||||
cls._meta.interfaces.append(interface)
|
|
||||||
return cls
|
|
||||||
|
|
||||||
|
|
||||||
class Interface(six.with_metaclass(InterfaceMeta, ObjectType)):
|
|
||||||
|
|
||||||
class Meta:
|
|
||||||
abstract = True
|
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
|
||||||
if self._meta.interface:
|
|
||||||
raise Exception("An interface cannot be initialized")
|
|
||||||
return super(Interface, self).__init__(*args, **kwargs)
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def _resolve_type(cls, schema, instance, *args):
|
|
||||||
return schema.T(instance.__class__)
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def internal_type(cls, schema):
|
|
||||||
if not cls._meta.interface:
|
|
||||||
return super(Interface, cls).internal_type(schema)
|
|
||||||
|
|
||||||
return GraphQLInterfaceType(
|
|
||||||
cls._meta.type_name,
|
|
||||||
description=cls._meta.description,
|
|
||||||
resolve_type=partial(cls._resolve_type, schema),
|
|
||||||
fields=partial(cls.fields_internal_types, schema)
|
|
||||||
)
|
|
|
@ -1,32 +0,0 @@
|
||||||
import six
|
|
||||||
|
|
||||||
from .objecttype import ObjectType, ObjectTypeMeta
|
|
||||||
|
|
||||||
|
|
||||||
class MutationMeta(ObjectTypeMeta):
|
|
||||||
|
|
||||||
def construct(cls, bases, attrs):
|
|
||||||
input_class = attrs.pop('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))
|
|
||||||
cls = super(MutationMeta, cls).construct(bases, attrs)
|
|
||||||
return cls
|
|
||||||
|
|
||||||
def construct_arguments(cls, items):
|
|
||||||
from ..types.argument import ArgumentsGroup
|
|
||||||
return ArgumentsGroup(**items)
|
|
||||||
|
|
||||||
|
|
||||||
class Mutation(six.with_metaclass(MutationMeta, ObjectType)):
|
|
||||||
|
|
||||||
class Meta:
|
|
||||||
abstract = True
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def get_arguments(cls):
|
|
||||||
return cls.arguments
|
|
|
@ -1,109 +0,0 @@
|
||||||
from functools import partial
|
|
||||||
|
|
||||||
import six
|
|
||||||
from graphql.type import GraphQLObjectType
|
|
||||||
|
|
||||||
from graphene import signals
|
|
||||||
|
|
||||||
from .base import FieldsClassType, FieldsClassTypeMeta, FieldsOptions
|
|
||||||
from .uniontype import UnionType
|
|
||||||
|
|
||||||
|
|
||||||
def is_objecttype(cls):
|
|
||||||
if not issubclass(cls, ObjectType):
|
|
||||||
return False
|
|
||||||
return not(cls._meta.abstract or cls._meta.interface)
|
|
||||||
|
|
||||||
|
|
||||||
class ObjectTypeOptions(FieldsOptions):
|
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
|
||||||
super(ObjectTypeOptions, self).__init__(*args, **kwargs)
|
|
||||||
self.interface = False
|
|
||||||
self.valid_attrs += ['interfaces']
|
|
||||||
self.interfaces = []
|
|
||||||
|
|
||||||
|
|
||||||
class ObjectTypeMeta(FieldsClassTypeMeta):
|
|
||||||
|
|
||||||
def construct(cls, bases, attrs):
|
|
||||||
cls = super(ObjectTypeMeta, cls).construct(bases, attrs)
|
|
||||||
if not cls._meta.abstract:
|
|
||||||
union_types = list(filter(is_objecttype, bases))
|
|
||||||
if len(union_types) > 1:
|
|
||||||
meta_attrs = dict(cls._meta.original_attrs, types=union_types)
|
|
||||||
Meta = type('Meta', (object, ), meta_attrs)
|
|
||||||
attrs['Meta'] = Meta
|
|
||||||
attrs['__module__'] = cls.__module__
|
|
||||||
attrs['__doc__'] = cls.__doc__
|
|
||||||
return type(cls.__name__, (UnionType, ), attrs)
|
|
||||||
return cls
|
|
||||||
|
|
||||||
options_class = ObjectTypeOptions
|
|
||||||
|
|
||||||
|
|
||||||
class ObjectType(six.with_metaclass(ObjectTypeMeta, FieldsClassType)):
|
|
||||||
|
|
||||||
class Meta:
|
|
||||||
abstract = True
|
|
||||||
|
|
||||||
def __getattr__(self, name):
|
|
||||||
if name == '_root':
|
|
||||||
return
|
|
||||||
return getattr(self._root, name)
|
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
|
||||||
signals.pre_init.send(self.__class__, args=args, kwargs=kwargs)
|
|
||||||
self._root = kwargs.pop('_root', None)
|
|
||||||
args_len = len(args)
|
|
||||||
fields = self._meta.fields
|
|
||||||
if args_len > len(fields):
|
|
||||||
# Daft, but matches old exception sans the err msg.
|
|
||||||
raise IndexError("Number of args exceeds number of fields")
|
|
||||||
fields_iter = iter(fields)
|
|
||||||
|
|
||||||
if not kwargs:
|
|
||||||
for val, field in zip(args, fields_iter):
|
|
||||||
setattr(self, field.attname, val)
|
|
||||||
else:
|
|
||||||
for val, field in zip(args, fields_iter):
|
|
||||||
setattr(self, field.attname, val)
|
|
||||||
kwargs.pop(field.attname, None)
|
|
||||||
|
|
||||||
for field in fields_iter:
|
|
||||||
try:
|
|
||||||
val = kwargs.pop(field.attname)
|
|
||||||
setattr(self, field.attname, val)
|
|
||||||
except KeyError:
|
|
||||||
pass
|
|
||||||
|
|
||||||
if kwargs:
|
|
||||||
for prop in list(kwargs):
|
|
||||||
try:
|
|
||||||
if isinstance(getattr(self.__class__, prop), property):
|
|
||||||
setattr(self, prop, kwargs.pop(prop))
|
|
||||||
except AttributeError:
|
|
||||||
pass
|
|
||||||
if kwargs:
|
|
||||||
raise TypeError(
|
|
||||||
"'%s' is an invalid keyword argument for this function" %
|
|
||||||
list(kwargs)[0])
|
|
||||||
|
|
||||||
signals.post_init.send(self.__class__, instance=self)
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def internal_type(cls, schema):
|
|
||||||
if cls._meta.abstract:
|
|
||||||
raise Exception("Abstract ObjectTypes don't have a specific type.")
|
|
||||||
|
|
||||||
return GraphQLObjectType(
|
|
||||||
cls._meta.type_name,
|
|
||||||
description=cls._meta.description,
|
|
||||||
interfaces=list(map(schema.T, cls._meta.interfaces)),
|
|
||||||
fields=partial(cls.fields_internal_types, schema),
|
|
||||||
is_type_of=getattr(cls, 'is_type_of', None)
|
|
||||||
)
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def wrap(cls, instance, args, info):
|
|
||||||
return cls(_root=instance)
|
|
|
@ -1,21 +0,0 @@
|
||||||
from graphql.type import GraphQLScalarType
|
|
||||||
|
|
||||||
from ..types.base import MountedType
|
|
||||||
from .base import ClassType
|
|
||||||
|
|
||||||
|
|
||||||
class Scalar(ClassType, 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._meta.type_name,
|
|
||||||
description=cls._meta.description,
|
|
||||||
serialize=serialize,
|
|
||||||
parse_value=parse_value,
|
|
||||||
parse_literal=parse_literal
|
|
||||||
)
|
|
|
@ -1,78 +0,0 @@
|
||||||
from ...schema import Schema
|
|
||||||
from ...types import Field, List, NonNull, String
|
|
||||||
from ..base import ClassType, FieldsClassType
|
|
||||||
|
|
||||||
|
|
||||||
def test_classtype_basic():
|
|
||||||
class Character(ClassType):
|
|
||||||
'''Character description'''
|
|
||||||
assert Character._meta.type_name == 'Character'
|
|
||||||
assert Character._meta.description == 'Character description'
|
|
||||||
|
|
||||||
|
|
||||||
def test_classtype_advanced():
|
|
||||||
class Character(ClassType):
|
|
||||||
|
|
||||||
class Meta:
|
|
||||||
type_name = 'OtherCharacter'
|
|
||||||
description = 'OtherCharacter description'
|
|
||||||
assert Character._meta.type_name == 'OtherCharacter'
|
|
||||||
assert Character._meta.description == 'OtherCharacter description'
|
|
||||||
|
|
||||||
|
|
||||||
def test_classtype_definition_list():
|
|
||||||
class Character(ClassType):
|
|
||||||
'''Character description'''
|
|
||||||
assert isinstance(Character.List(), List)
|
|
||||||
assert Character.List().of_type == Character
|
|
||||||
|
|
||||||
|
|
||||||
def test_classtype_definition_nonnull():
|
|
||||||
class Character(ClassType):
|
|
||||||
'''Character description'''
|
|
||||||
assert isinstance(Character.NonNull(), NonNull)
|
|
||||||
assert Character.NonNull().of_type == Character
|
|
||||||
|
|
||||||
|
|
||||||
def test_fieldsclasstype_definition_order():
|
|
||||||
class Character(ClassType):
|
|
||||||
'''Character description'''
|
|
||||||
|
|
||||||
class Query(FieldsClassType):
|
|
||||||
name = String()
|
|
||||||
char = Character.NonNull()
|
|
||||||
|
|
||||||
assert list(Query._meta.fields_map.keys()) == ['name', 'char']
|
|
||||||
|
|
||||||
|
|
||||||
def test_fieldsclasstype():
|
|
||||||
f = Field(String())
|
|
||||||
|
|
||||||
class Character(FieldsClassType):
|
|
||||||
field_name = f
|
|
||||||
|
|
||||||
assert Character._meta.fields == [f]
|
|
||||||
|
|
||||||
|
|
||||||
def test_fieldsclasstype_fieldtype():
|
|
||||||
f = Field(String())
|
|
||||||
|
|
||||||
class Character(FieldsClassType):
|
|
||||||
field_name = f
|
|
||||||
|
|
||||||
schema = Schema(query=Character)
|
|
||||||
assert Character.fields_internal_types(schema)['fieldName'] == schema.T(f)
|
|
||||||
assert Character._meta.fields_map['field_name'] == f
|
|
||||||
|
|
||||||
|
|
||||||
def test_fieldsclasstype_inheritfields():
|
|
||||||
name_field = Field(String())
|
|
||||||
last_name_field = Field(String())
|
|
||||||
|
|
||||||
class Fields1(FieldsClassType):
|
|
||||||
name = name_field
|
|
||||||
|
|
||||||
class Fields2(Fields1):
|
|
||||||
last_name = last_name_field
|
|
||||||
|
|
||||||
assert list(Fields2._meta.fields_map.keys()) == ['name', 'last_name']
|
|
|
@ -1,49 +0,0 @@
|
||||||
from graphql.type import GraphQLEnumType
|
|
||||||
|
|
||||||
from graphene.core.schema import Schema
|
|
||||||
|
|
||||||
from ..enum import Enum
|
|
||||||
from ..objecttype import ObjectType
|
|
||||||
|
|
||||||
|
|
||||||
def test_enum():
|
|
||||||
class RGB(Enum):
|
|
||||||
'''RGB enum description'''
|
|
||||||
RED = 0
|
|
||||||
GREEN = 1
|
|
||||||
BLUE = 2
|
|
||||||
|
|
||||||
schema = Schema()
|
|
||||||
|
|
||||||
object_type = schema.T(RGB)
|
|
||||||
assert isinstance(object_type, GraphQLEnumType)
|
|
||||||
assert RGB._meta.type_name == 'RGB'
|
|
||||||
assert RGB._meta.description == 'RGB enum description'
|
|
||||||
assert RGB.RED == 0
|
|
||||||
assert RGB.GREEN == 1
|
|
||||||
assert RGB.BLUE == 2
|
|
||||||
|
|
||||||
|
|
||||||
def test_enum_values():
|
|
||||||
RGB = Enum('RGB', dict(RED=0, GREEN=1, BLUE=2), description='RGB enum description')
|
|
||||||
|
|
||||||
schema = Schema()
|
|
||||||
|
|
||||||
object_type = schema.T(RGB)
|
|
||||||
assert isinstance(object_type, GraphQLEnumType)
|
|
||||||
assert RGB._meta.type_name == 'RGB'
|
|
||||||
assert RGB._meta.description == 'RGB enum description'
|
|
||||||
assert RGB.RED == 0
|
|
||||||
assert RGB.GREEN == 1
|
|
||||||
assert RGB.BLUE == 2
|
|
||||||
|
|
||||||
|
|
||||||
def test_enum_instance():
|
|
||||||
RGB = Enum('RGB', dict(RED=0, GREEN=1, BLUE=2))
|
|
||||||
RGB_field = RGB(description='RGB enum description')
|
|
||||||
|
|
||||||
class ObjectWithColor(ObjectType):
|
|
||||||
color = RGB_field
|
|
||||||
|
|
||||||
object_field = ObjectWithColor._meta.fields_map['color']
|
|
||||||
assert object_field.description == 'RGB enum description'
|
|
|
@ -1,21 +0,0 @@
|
||||||
|
|
||||||
from graphql.type import GraphQLInputObjectType
|
|
||||||
|
|
||||||
from graphene.core.schema import Schema
|
|
||||||
from graphene.core.types import String
|
|
||||||
|
|
||||||
from ..inputobjecttype import InputObjectType
|
|
||||||
|
|
||||||
|
|
||||||
def test_inputobjecttype():
|
|
||||||
class InputCharacter(InputObjectType):
|
|
||||||
'''InputCharacter description'''
|
|
||||||
name = String()
|
|
||||||
|
|
||||||
schema = Schema()
|
|
||||||
|
|
||||||
object_type = schema.T(InputCharacter)
|
|
||||||
assert isinstance(object_type, GraphQLInputObjectType)
|
|
||||||
assert InputCharacter._meta.type_name == 'InputCharacter'
|
|
||||||
assert object_type.description == 'InputCharacter description'
|
|
||||||
assert list(object_type.get_fields().keys()) == ['name']
|
|
|
@ -1,86 +0,0 @@
|
||||||
from graphql.type import GraphQLInterfaceType, GraphQLObjectType
|
|
||||||
from py.test import raises
|
|
||||||
|
|
||||||
from graphene.core.schema import Schema
|
|
||||||
from graphene.core.types import String
|
|
||||||
|
|
||||||
from ..interface import Interface
|
|
||||||
from ..objecttype import ObjectType
|
|
||||||
|
|
||||||
|
|
||||||
def test_interface():
|
|
||||||
class Character(Interface):
|
|
||||||
'''Character description'''
|
|
||||||
name = String()
|
|
||||||
|
|
||||||
schema = Schema()
|
|
||||||
|
|
||||||
object_type = schema.T(Character)
|
|
||||||
assert issubclass(Character, Interface)
|
|
||||||
assert isinstance(object_type, GraphQLInterfaceType)
|
|
||||||
assert Character._meta.interface
|
|
||||||
assert Character._meta.type_name == 'Character'
|
|
||||||
assert object_type.description == 'Character description'
|
|
||||||
assert list(object_type.get_fields().keys()) == ['name']
|
|
||||||
|
|
||||||
|
|
||||||
def test_interface_cannot_initialize():
|
|
||||||
class Character(Interface):
|
|
||||||
pass
|
|
||||||
|
|
||||||
with raises(Exception) as excinfo:
|
|
||||||
Character()
|
|
||||||
assert 'An interface cannot be initialized' == str(excinfo.value)
|
|
||||||
|
|
||||||
|
|
||||||
def test_interface_inheritance_abstract():
|
|
||||||
class Character(Interface):
|
|
||||||
pass
|
|
||||||
|
|
||||||
class ShouldBeInterface(Character):
|
|
||||||
|
|
||||||
class Meta:
|
|
||||||
abstract = True
|
|
||||||
|
|
||||||
class ShouldBeObjectType(ShouldBeInterface):
|
|
||||||
pass
|
|
||||||
|
|
||||||
assert ShouldBeInterface._meta.interface
|
|
||||||
assert not ShouldBeObjectType._meta.interface
|
|
||||||
assert issubclass(ShouldBeObjectType, ObjectType)
|
|
||||||
|
|
||||||
|
|
||||||
def test_interface_inheritance():
|
|
||||||
class Character(Interface):
|
|
||||||
pass
|
|
||||||
|
|
||||||
class GeneralInterface(Interface):
|
|
||||||
pass
|
|
||||||
|
|
||||||
class ShouldBeObjectType(GeneralInterface, Character):
|
|
||||||
pass
|
|
||||||
|
|
||||||
schema = Schema()
|
|
||||||
|
|
||||||
assert Character._meta.interface
|
|
||||||
assert not ShouldBeObjectType._meta.interface
|
|
||||||
assert issubclass(ShouldBeObjectType, ObjectType)
|
|
||||||
assert Character in ShouldBeObjectType._meta.interfaces
|
|
||||||
assert GeneralInterface in ShouldBeObjectType._meta.interfaces
|
|
||||||
assert isinstance(schema.T(Character), GraphQLInterfaceType)
|
|
||||||
assert isinstance(schema.T(ShouldBeObjectType), GraphQLObjectType)
|
|
||||||
|
|
||||||
|
|
||||||
def test_interface_inheritance_non_objects():
|
|
||||||
class CommonClass(object):
|
|
||||||
common_attr = True
|
|
||||||
|
|
||||||
class Character(CommonClass, Interface):
|
|
||||||
pass
|
|
||||||
|
|
||||||
class ShouldBeObjectType(Character):
|
|
||||||
pass
|
|
||||||
|
|
||||||
assert Character._meta.interface
|
|
||||||
assert Character.common_attr
|
|
||||||
assert ShouldBeObjectType.common_attr
|
|
|
@ -1,27 +0,0 @@
|
||||||
|
|
||||||
from graphql.type import GraphQLObjectType
|
|
||||||
|
|
||||||
from graphene.core.schema import Schema
|
|
||||||
from graphene.core.types import String
|
|
||||||
|
|
||||||
from ...types.argument import ArgumentsGroup
|
|
||||||
from ..mutation import Mutation
|
|
||||||
|
|
||||||
|
|
||||||
def test_mutation():
|
|
||||||
class MyMutation(Mutation):
|
|
||||||
'''MyMutation description'''
|
|
||||||
class Input:
|
|
||||||
arg_name = String()
|
|
||||||
name = String()
|
|
||||||
|
|
||||||
schema = Schema()
|
|
||||||
|
|
||||||
object_type = schema.T(MyMutation)
|
|
||||||
assert MyMutation._meta.type_name == 'MyMutation'
|
|
||||||
assert isinstance(object_type, GraphQLObjectType)
|
|
||||||
assert object_type.description == 'MyMutation description'
|
|
||||||
assert list(object_type.get_fields().keys()) == ['name']
|
|
||||||
assert MyMutation._meta.fields_map['name'].object_type == MyMutation
|
|
||||||
assert isinstance(MyMutation.arguments, ArgumentsGroup)
|
|
||||||
assert 'argName' in schema.T(MyMutation.arguments)
|
|
|
@ -1,116 +0,0 @@
|
||||||
from graphql.type import GraphQLObjectType
|
|
||||||
from py.test import raises
|
|
||||||
|
|
||||||
from graphene.core.schema import Schema
|
|
||||||
from graphene.core.types import String
|
|
||||||
|
|
||||||
from ..objecttype import ObjectType
|
|
||||||
from ..uniontype import UnionType
|
|
||||||
|
|
||||||
|
|
||||||
def test_object_type():
|
|
||||||
class Human(ObjectType):
|
|
||||||
'''Human description'''
|
|
||||||
name = String()
|
|
||||||
friends = String()
|
|
||||||
|
|
||||||
schema = Schema()
|
|
||||||
|
|
||||||
object_type = schema.T(Human)
|
|
||||||
assert Human._meta.type_name == 'Human'
|
|
||||||
assert isinstance(object_type, GraphQLObjectType)
|
|
||||||
assert object_type.description == 'Human description'
|
|
||||||
assert list(object_type.get_fields().keys()) == ['name', 'friends']
|
|
||||||
assert Human._meta.fields_map['name'].object_type == Human
|
|
||||||
|
|
||||||
|
|
||||||
def test_object_type_container():
|
|
||||||
class Human(ObjectType):
|
|
||||||
name = String()
|
|
||||||
friends = String()
|
|
||||||
|
|
||||||
h = Human(name='My name')
|
|
||||||
assert h.name == 'My name'
|
|
||||||
|
|
||||||
|
|
||||||
def test_object_type_set_properties():
|
|
||||||
class Human(ObjectType):
|
|
||||||
name = String()
|
|
||||||
friends = String()
|
|
||||||
|
|
||||||
@property
|
|
||||||
def readonly_prop(self):
|
|
||||||
return 'readonly'
|
|
||||||
|
|
||||||
@property
|
|
||||||
def write_prop(self):
|
|
||||||
return self._write_prop
|
|
||||||
|
|
||||||
@write_prop.setter
|
|
||||||
def write_prop(self, value):
|
|
||||||
self._write_prop = value
|
|
||||||
|
|
||||||
h = Human(readonly_prop='custom', write_prop='custom')
|
|
||||||
assert h.readonly_prop == 'readonly'
|
|
||||||
assert h.write_prop == 'custom'
|
|
||||||
|
|
||||||
|
|
||||||
def test_object_type_container_invalid_kwarg():
|
|
||||||
class Human(ObjectType):
|
|
||||||
name = String()
|
|
||||||
|
|
||||||
with raises(TypeError):
|
|
||||||
Human(invalid='My name')
|
|
||||||
|
|
||||||
|
|
||||||
def test_object_type_container_too_many_args():
|
|
||||||
class Human(ObjectType):
|
|
||||||
name = String()
|
|
||||||
|
|
||||||
with raises(IndexError):
|
|
||||||
Human('Peter', 'No friends :(', None)
|
|
||||||
|
|
||||||
|
|
||||||
def test_object_type_union():
|
|
||||||
class Human(ObjectType):
|
|
||||||
name = String()
|
|
||||||
|
|
||||||
class Pet(ObjectType):
|
|
||||||
name = String()
|
|
||||||
|
|
||||||
class Thing(Human, Pet):
|
|
||||||
'''Thing union description'''
|
|
||||||
my_attr = True
|
|
||||||
|
|
||||||
assert issubclass(Thing, UnionType)
|
|
||||||
assert Thing._meta.types == [Human, Pet]
|
|
||||||
assert Thing._meta.type_name == 'Thing'
|
|
||||||
assert Thing._meta.description == 'Thing union description'
|
|
||||||
assert Thing.my_attr
|
|
||||||
|
|
||||||
|
|
||||||
def test_object_type_not_union_if_abstract():
|
|
||||||
schema = Schema()
|
|
||||||
|
|
||||||
class Query1(ObjectType):
|
|
||||||
field1 = String()
|
|
||||||
|
|
||||||
class Meta:
|
|
||||||
abstract = True
|
|
||||||
|
|
||||||
class Query2(ObjectType):
|
|
||||||
field2 = String()
|
|
||||||
|
|
||||||
class Meta:
|
|
||||||
abstract = True
|
|
||||||
|
|
||||||
class Query(Query1, Query2):
|
|
||||||
'''Query description'''
|
|
||||||
my_attr = True
|
|
||||||
|
|
||||||
object_type = schema.T(Query)
|
|
||||||
assert issubclass(Query, ObjectType)
|
|
||||||
assert Query._meta.type_name == 'Query'
|
|
||||||
assert Query._meta.description == 'Query description'
|
|
||||||
assert isinstance(object_type, GraphQLObjectType)
|
|
||||||
assert list(Query._meta.fields_map.keys()) == ['field1', 'field2']
|
|
|
@ -1,54 +0,0 @@
|
||||||
from py.test import raises
|
|
||||||
|
|
||||||
from graphene.core.classtypes import Options
|
|
||||||
|
|
||||||
|
|
||||||
class Meta:
|
|
||||||
type_name = 'Character'
|
|
||||||
|
|
||||||
|
|
||||||
class InvalidMeta:
|
|
||||||
other_value = True
|
|
||||||
|
|
||||||
|
|
||||||
def test_options_contribute():
|
|
||||||
opt = Options(Meta)
|
|
||||||
|
|
||||||
class ObjectType(object):
|
|
||||||
pass
|
|
||||||
|
|
||||||
opt.contribute_to_class(ObjectType, '_meta')
|
|
||||||
assert ObjectType._meta == opt
|
|
||||||
|
|
||||||
|
|
||||||
def test_options_typename():
|
|
||||||
opt = Options(Meta)
|
|
||||||
|
|
||||||
class ObjectType(object):
|
|
||||||
pass
|
|
||||||
|
|
||||||
opt.contribute_to_class(ObjectType, '_meta')
|
|
||||||
assert opt.type_name == 'Character'
|
|
||||||
|
|
||||||
|
|
||||||
def test_options_description():
|
|
||||||
opt = Options(Meta)
|
|
||||||
|
|
||||||
class ObjectType(object):
|
|
||||||
|
|
||||||
'''False description'''
|
|
||||||
|
|
||||||
opt.contribute_to_class(ObjectType, '_meta')
|
|
||||||
assert opt.description == 'False description'
|
|
||||||
|
|
||||||
|
|
||||||
def test_field_no_contributed_raises_error():
|
|
||||||
opt = Options(InvalidMeta)
|
|
||||||
|
|
||||||
class ObjectType(object):
|
|
||||||
pass
|
|
||||||
|
|
||||||
with raises(Exception) as excinfo:
|
|
||||||
opt.contribute_to_class(ObjectType, '_meta')
|
|
||||||
|
|
||||||
assert 'invalid attribute' in str(excinfo.value)
|
|
|
@ -1,32 +0,0 @@
|
||||||
from graphql.type import GraphQLScalarType
|
|
||||||
|
|
||||||
from ...schema import Schema
|
|
||||||
from ..scalar import Scalar
|
|
||||||
|
|
||||||
|
|
||||||
def test_custom_scalar():
|
|
||||||
import datetime
|
|
||||||
from graphql.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")
|
|
||||||
|
|
||||||
schema = Schema()
|
|
||||||
|
|
||||||
scalar_type = schema.T(DateTimeScalar)
|
|
||||||
assert isinstance(scalar_type, GraphQLScalarType)
|
|
||||||
assert scalar_type.name == 'DateTimeScalar'
|
|
||||||
assert scalar_type.description == 'DateTimeScalar Documentation'
|
|
|
@ -1,28 +0,0 @@
|
||||||
from graphql.type import GraphQLUnionType
|
|
||||||
|
|
||||||
from graphene.core.schema import Schema
|
|
||||||
from graphene.core.types import String
|
|
||||||
|
|
||||||
from ..objecttype import ObjectType
|
|
||||||
from ..uniontype import UnionType
|
|
||||||
|
|
||||||
|
|
||||||
def test_uniontype():
|
|
||||||
class Human(ObjectType):
|
|
||||||
name = String()
|
|
||||||
|
|
||||||
class Pet(ObjectType):
|
|
||||||
name = String()
|
|
||||||
|
|
||||||
class Thing(UnionType):
|
|
||||||
'''Thing union description'''
|
|
||||||
class Meta:
|
|
||||||
types = [Human, Pet]
|
|
||||||
|
|
||||||
schema = Schema()
|
|
||||||
|
|
||||||
object_type = schema.T(Thing)
|
|
||||||
assert isinstance(object_type, GraphQLUnionType)
|
|
||||||
assert Thing._meta.type_name == 'Thing'
|
|
||||||
assert object_type.description == 'Thing union description'
|
|
||||||
assert object_type.get_types() == [schema.T(Human), schema.T(Pet)]
|
|
|
@ -1,42 +0,0 @@
|
||||||
from functools import partial
|
|
||||||
|
|
||||||
import six
|
|
||||||
from graphql.type import GraphQLUnionType
|
|
||||||
|
|
||||||
from .base import FieldsClassType, FieldsClassTypeMeta, FieldsOptions
|
|
||||||
|
|
||||||
|
|
||||||
class UnionTypeOptions(FieldsOptions):
|
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
|
||||||
super(UnionTypeOptions, self).__init__(*args, **kwargs)
|
|
||||||
self.types = []
|
|
||||||
|
|
||||||
|
|
||||||
class UnionTypeMeta(FieldsClassTypeMeta):
|
|
||||||
options_class = UnionTypeOptions
|
|
||||||
|
|
||||||
def get_options(cls, meta):
|
|
||||||
return cls.options_class(meta, types=[])
|
|
||||||
|
|
||||||
|
|
||||||
class UnionType(six.with_metaclass(UnionTypeMeta, FieldsClassType)):
|
|
||||||
|
|
||||||
class Meta:
|
|
||||||
abstract = True
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def _resolve_type(cls, schema, instance, *args):
|
|
||||||
return schema.T(instance.__class__)
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def internal_type(cls, schema):
|
|
||||||
if cls._meta.abstract:
|
|
||||||
raise Exception("Abstract ObjectTypes don't have a specific type.")
|
|
||||||
|
|
||||||
return GraphQLUnionType(
|
|
||||||
cls._meta.type_name,
|
|
||||||
types=list(map(schema.T, cls._meta.types)),
|
|
||||||
resolve_type=partial(cls._resolve_type, schema),
|
|
||||||
description=cls._meta.description,
|
|
||||||
)
|
|
|
@ -1,2 +0,0 @@
|
||||||
class SkipField(Exception):
|
|
||||||
pass
|
|
|
@ -1,49 +0,0 @@
|
||||||
import warnings
|
|
||||||
|
|
||||||
from .types.base import FieldType
|
|
||||||
from .types.definitions import List, NonNull
|
|
||||||
from .types.field import Field
|
|
||||||
from .types.scalars import ID, Boolean, Float, Int, String
|
|
||||||
|
|
||||||
|
|
||||||
class 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
|
|
||||||
|
|
||||||
|
|
||||||
class IntField(DeprecatedField, Int):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
class BooleanField(DeprecatedField, Boolean):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
class IDField(DeprecatedField, ID):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
class FloatField(DeprecatedField, Float):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
class ListField(DeprecatedField, List):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
class NonNullField(DeprecatedField, NonNull):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
__all__ = ['Field', 'StringField', 'IntField', 'BooleanField',
|
|
||||||
'IDField', 'FloatField', 'ListField', 'NonNullField']
|
|
|
@ -1,133 +0,0 @@
|
||||||
import inspect
|
|
||||||
|
|
||||||
from graphql import graphql
|
|
||||||
from graphql.type import GraphQLSchema as _GraphQLSchema
|
|
||||||
from graphql.utils.introspection_query import introspection_query
|
|
||||||
from graphql.utils.schema_printer import print_schema
|
|
||||||
|
|
||||||
from graphene import signals
|
|
||||||
|
|
||||||
from ..middlewares import MiddlewareManager, CamelCaseArgsMiddleware
|
|
||||||
from .classtypes.base import ClassType
|
|
||||||
from .types.base import InstanceType
|
|
||||||
|
|
||||||
|
|
||||||
class GraphQLSchema(_GraphQLSchema):
|
|
||||||
|
|
||||||
def __init__(self, schema, *args, **kwargs):
|
|
||||||
self.graphene_schema = schema
|
|
||||||
super(GraphQLSchema, self).__init__(*args, **kwargs)
|
|
||||||
|
|
||||||
|
|
||||||
class Schema(object):
|
|
||||||
_executor = None
|
|
||||||
|
|
||||||
def __init__(self, query=None, mutation=None, subscription=None,
|
|
||||||
name='Schema', executor=None, middlewares=None, auto_camelcase=True, **options):
|
|
||||||
self._types_names = {}
|
|
||||||
self._types = {}
|
|
||||||
self.mutation = mutation
|
|
||||||
self.query = query
|
|
||||||
self.subscription = subscription
|
|
||||||
self.name = name
|
|
||||||
self.executor = executor
|
|
||||||
if 'plugins' in options:
|
|
||||||
raise Exception('Plugins are deprecated, please use middlewares.')
|
|
||||||
middlewares = middlewares or []
|
|
||||||
if auto_camelcase:
|
|
||||||
middlewares.append(CamelCaseArgsMiddleware())
|
|
||||||
self.auto_camelcase = auto_camelcase
|
|
||||||
self.middleware_manager = MiddlewareManager(self, middlewares)
|
|
||||||
self.options = options
|
|
||||||
signals.init_schema.send(self)
|
|
||||||
|
|
||||||
def __repr__(self):
|
|
||||||
return '<Schema: %s (%s)>' % (str(self.name), hash(self))
|
|
||||||
|
|
||||||
def T(self, _type):
|
|
||||||
if not _type:
|
|
||||||
return
|
|
||||||
if isinstance(_type, ClassType):
|
|
||||||
_type = type(_type)
|
|
||||||
is_classtype = inspect.isclass(_type) and issubclass(_type, ClassType)
|
|
||||||
is_instancetype = isinstance(_type, InstanceType)
|
|
||||||
if is_classtype or is_instancetype:
|
|
||||||
if _type not in self._types:
|
|
||||||
internal_type = _type.internal_type(self)
|
|
||||||
self._types[_type] = internal_type
|
|
||||||
if is_classtype:
|
|
||||||
self.register(_type)
|
|
||||||
return self._types[_type]
|
|
||||||
else:
|
|
||||||
return _type
|
|
||||||
|
|
||||||
@property
|
|
||||||
def executor(self):
|
|
||||||
return self._executor
|
|
||||||
|
|
||||||
@executor.setter
|
|
||||||
def executor(self, value):
|
|
||||||
self._executor = value
|
|
||||||
|
|
||||||
@property
|
|
||||||
def schema(self):
|
|
||||||
if not self.query:
|
|
||||||
raise Exception('You have to define a base query type')
|
|
||||||
return GraphQLSchema(
|
|
||||||
self,
|
|
||||||
query=self.T(self.query),
|
|
||||||
mutation=self.T(self.mutation),
|
|
||||||
types=[self.T(_type) for _type in list(self._types_names.values())],
|
|
||||||
subscription=self.T(self.subscription))
|
|
||||||
|
|
||||||
def register(self, object_type, force=False):
|
|
||||||
type_name = object_type._meta.type_name
|
|
||||||
registered_object_type = not force and self._types_names.get(type_name, None)
|
|
||||||
if registered_object_type:
|
|
||||||
assert registered_object_type == object_type, 'Type {} already registered with other object type'.format(
|
|
||||||
type_name)
|
|
||||||
self._types_names[object_type._meta.type_name] = object_type
|
|
||||||
return object_type
|
|
||||||
|
|
||||||
def objecttype(self, type):
|
|
||||||
name = getattr(type, 'name', None)
|
|
||||||
if name:
|
|
||||||
objecttype = self._types_names.get(name, None)
|
|
||||||
if objecttype and inspect.isclass(
|
|
||||||
objecttype) and issubclass(objecttype, ClassType):
|
|
||||||
return objecttype
|
|
||||||
|
|
||||||
def __str__(self):
|
|
||||||
return print_schema(self.schema)
|
|
||||||
|
|
||||||
def setup(self):
|
|
||||||
assert self.query, 'The base query type is not set'
|
|
||||||
self.T(self.query)
|
|
||||||
|
|
||||||
def get_type(self, type_name):
|
|
||||||
self.setup()
|
|
||||||
if type_name not in self._types_names:
|
|
||||||
raise KeyError('Type %r not found in %r' % (type_name, self))
|
|
||||||
return self._types_names[type_name]
|
|
||||||
|
|
||||||
def resolver_with_middleware(self, resolver):
|
|
||||||
return self.middleware_manager.wrap(resolver)
|
|
||||||
|
|
||||||
@property
|
|
||||||
def types(self):
|
|
||||||
return self._types_names
|
|
||||||
|
|
||||||
def execute(self, request_string='', root_value=None, variable_values=None,
|
|
||||||
context_value=None, operation_name=None, executor=None):
|
|
||||||
return graphql(
|
|
||||||
schema=self.schema,
|
|
||||||
request_string=request_string,
|
|
||||||
root_value=root_value,
|
|
||||||
context_value=context_value,
|
|
||||||
variable_values=variable_values,
|
|
||||||
operation_name=operation_name,
|
|
||||||
executor=executor or self._executor
|
|
||||||
)
|
|
||||||
|
|
||||||
def introspect(self):
|
|
||||||
return graphql(self.schema, introspection_query).data
|
|
|
@ -1,63 +0,0 @@
|
||||||
import graphene
|
|
||||||
from graphene.core.schema import Schema
|
|
||||||
|
|
||||||
my_id = 0
|
|
||||||
|
|
||||||
|
|
||||||
class Query(graphene.ObjectType):
|
|
||||||
base = graphene.String()
|
|
||||||
|
|
||||||
|
|
||||||
class ChangeNumber(graphene.Mutation):
|
|
||||||
'''Result mutation'''
|
|
||||||
class Input:
|
|
||||||
to = graphene.Int()
|
|
||||||
|
|
||||||
result = graphene.String()
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def mutate(cls, instance, args, info):
|
|
||||||
global my_id
|
|
||||||
my_id = args.get('to', my_id + 1)
|
|
||||||
return ChangeNumber(result=my_id)
|
|
||||||
|
|
||||||
|
|
||||||
class MyResultMutation(graphene.ObjectType):
|
|
||||||
change_number = graphene.Field(ChangeNumber)
|
|
||||||
|
|
||||||
|
|
||||||
schema = Schema(query=Query, mutation=MyResultMutation)
|
|
||||||
|
|
||||||
|
|
||||||
def test_mutation_input():
|
|
||||||
assert list(schema.T(ChangeNumber.arguments).keys()) == ['to']
|
|
||||||
|
|
||||||
|
|
||||||
def test_execute_mutations():
|
|
||||||
query = '''
|
|
||||||
mutation M{
|
|
||||||
first: changeNumber {
|
|
||||||
result
|
|
||||||
},
|
|
||||||
second: changeNumber {
|
|
||||||
result
|
|
||||||
}
|
|
||||||
third: changeNumber(to: 5) {
|
|
||||||
result
|
|
||||||
}
|
|
||||||
}
|
|
||||||
'''
|
|
||||||
expected = {
|
|
||||||
'first': {
|
|
||||||
'result': '1',
|
|
||||||
},
|
|
||||||
'second': {
|
|
||||||
'result': '2',
|
|
||||||
},
|
|
||||||
'third': {
|
|
||||||
'result': '5',
|
|
||||||
}
|
|
||||||
}
|
|
||||||
result = schema.execute(query, root_value=object())
|
|
||||||
assert not result.errors
|
|
||||||
assert result.data == expected
|
|
|
@ -1,178 +0,0 @@
|
||||||
from graphql.type import (GraphQLBoolean, GraphQLField, GraphQLFloat,
|
|
||||||
GraphQLID, GraphQLInt, GraphQLNonNull, GraphQLString)
|
|
||||||
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
|
|
||||||
|
|
||||||
|
|
||||||
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():
|
|
||||||
f = Field(GraphQLString)
|
|
||||||
f.contribute_to_class(MyOt, 'field_name')
|
|
||||||
assert f.name is None
|
|
||||||
assert f.attname == 'field_name'
|
|
||||||
|
|
||||||
|
|
||||||
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, None).value
|
|
||||||
|
|
||||||
|
|
||||||
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,64 +0,0 @@
|
||||||
|
|
||||||
|
|
||||||
from graphql import graphql
|
|
||||||
from graphql.type import GraphQLSchema
|
|
||||||
|
|
||||||
from graphene.core.fields import Field
|
|
||||||
from graphene.core.schema import Schema
|
|
||||||
from graphene.core.types import Interface, List, ObjectType, String
|
|
||||||
|
|
||||||
|
|
||||||
class Character(Interface):
|
|
||||||
name = String()
|
|
||||||
|
|
||||||
|
|
||||||
class Pet(ObjectType):
|
|
||||||
type = String()
|
|
||||||
|
|
||||||
def resolve_type(self, args, info):
|
|
||||||
return 'Dog'
|
|
||||||
|
|
||||||
|
|
||||||
class Human(Character):
|
|
||||||
friends = List(Character)
|
|
||||||
pet = Field(Pet)
|
|
||||||
|
|
||||||
def resolve_name(self, *args):
|
|
||||||
return 'Peter'
|
|
||||||
|
|
||||||
def resolve_friend(self, *args):
|
|
||||||
return Human(object())
|
|
||||||
|
|
||||||
def resolve_pet(self, *args):
|
|
||||||
return Pet(object())
|
|
||||||
|
|
||||||
|
|
||||||
schema = Schema()
|
|
||||||
|
|
||||||
Human_type = schema.T(Human)
|
|
||||||
|
|
||||||
|
|
||||||
def test_type():
|
|
||||||
assert Human._meta.fields_map['name'].resolver(
|
|
||||||
Human(object()), {}, None, None) == 'Peter'
|
|
||||||
|
|
||||||
|
|
||||||
def test_query():
|
|
||||||
schema = GraphQLSchema(query=Human_type)
|
|
||||||
query = '''
|
|
||||||
{
|
|
||||||
name
|
|
||||||
pet {
|
|
||||||
type
|
|
||||||
}
|
|
||||||
}
|
|
||||||
'''
|
|
||||||
expected = {
|
|
||||||
'name': 'Peter',
|
|
||||||
'pet': {
|
|
||||||
'type': 'Dog'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
result = graphql(schema, query, root_value=Human(object()))
|
|
||||||
assert not result.errors
|
|
||||||
assert result.data == expected
|
|
|
@ -1,214 +0,0 @@
|
||||||
from graphql import graphql
|
|
||||||
from py.test import raises
|
|
||||||
|
|
||||||
from graphene import Interface, List, ObjectType, Schema, String
|
|
||||||
from graphene.core.fields import Field
|
|
||||||
from graphene.core.types.base import LazyType
|
|
||||||
from tests.utils import assert_equal_lists
|
|
||||||
|
|
||||||
schema = Schema(name='My own schema')
|
|
||||||
|
|
||||||
|
|
||||||
class Character(Interface):
|
|
||||||
name = String()
|
|
||||||
|
|
||||||
|
|
||||||
class Pet(ObjectType):
|
|
||||||
type = String(resolver=lambda *_: 'Dog')
|
|
||||||
|
|
||||||
|
|
||||||
class Human(Character):
|
|
||||||
friends = List(Character)
|
|
||||||
pet = Field(Pet)
|
|
||||||
|
|
||||||
def resolve_name(self, *args):
|
|
||||||
return 'Peter'
|
|
||||||
|
|
||||||
def resolve_friend(self, *args):
|
|
||||||
return Human(object())
|
|
||||||
|
|
||||||
def resolve_pet(self, *args):
|
|
||||||
return Pet(object())
|
|
||||||
|
|
||||||
schema.query = Human
|
|
||||||
|
|
||||||
|
|
||||||
def test_get_registered_type():
|
|
||||||
assert schema.get_type('Character') == Character
|
|
||||||
|
|
||||||
|
|
||||||
def test_get_unregistered_type():
|
|
||||||
with raises(Exception) as excinfo:
|
|
||||||
schema.get_type('NON_EXISTENT_MODEL')
|
|
||||||
assert 'not found' in str(excinfo.value)
|
|
||||||
|
|
||||||
|
|
||||||
def test_schema_query():
|
|
||||||
assert schema.query == Human
|
|
||||||
|
|
||||||
|
|
||||||
def test_query_schema_graphql():
|
|
||||||
object()
|
|
||||||
query = '''
|
|
||||||
{
|
|
||||||
name
|
|
||||||
pet {
|
|
||||||
type
|
|
||||||
}
|
|
||||||
}
|
|
||||||
'''
|
|
||||||
expected = {
|
|
||||||
'name': 'Peter',
|
|
||||||
'pet': {
|
|
||||||
'type': 'Dog'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
result = graphql(schema.schema, query, root_value=Human(object()))
|
|
||||||
assert not result.errors
|
|
||||||
assert result.data == expected
|
|
||||||
|
|
||||||
|
|
||||||
def test_query_schema_execute():
|
|
||||||
object()
|
|
||||||
query = '''
|
|
||||||
{
|
|
||||||
name
|
|
||||||
pet {
|
|
||||||
type
|
|
||||||
}
|
|
||||||
}
|
|
||||||
'''
|
|
||||||
expected = {
|
|
||||||
'name': 'Peter',
|
|
||||||
'pet': {
|
|
||||||
'type': 'Dog'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
result = schema.execute(query, root_value=object())
|
|
||||||
assert not result.errors
|
|
||||||
assert result.data == expected
|
|
||||||
|
|
||||||
|
|
||||||
def test_schema_get_type_map():
|
|
||||||
assert_equal_lists(
|
|
||||||
schema.schema.get_type_map().keys(),
|
|
||||||
['__Field', 'String', 'Pet', 'Character', '__InputValue',
|
|
||||||
'__Directive', '__DirectiveLocation', '__TypeKind', '__Schema',
|
|
||||||
'__Type', 'Human', '__EnumValue', 'Boolean'])
|
|
||||||
|
|
||||||
|
|
||||||
def test_schema_no_query():
|
|
||||||
schema = Schema(name='My own schema')
|
|
||||||
with raises(Exception) as excinfo:
|
|
||||||
schema.schema
|
|
||||||
assert 'define a base query type' in str(excinfo)
|
|
||||||
|
|
||||||
|
|
||||||
def test_auto_camelcase_off():
|
|
||||||
schema = Schema(name='My own schema', auto_camelcase=False)
|
|
||||||
|
|
||||||
class Query(ObjectType):
|
|
||||||
test_field = String(resolver=lambda *_: 'Dog')
|
|
||||||
|
|
||||||
schema.query = Query
|
|
||||||
|
|
||||||
query = "query {test_field}"
|
|
||||||
expected = {"test_field": "Dog"}
|
|
||||||
|
|
||||||
result = graphql(schema.schema, query, root_value=Query(object()))
|
|
||||||
assert not result.errors
|
|
||||||
assert result.data == expected
|
|
||||||
|
|
||||||
|
|
||||||
def test_schema_register():
|
|
||||||
schema = Schema(name='My own schema')
|
|
||||||
|
|
||||||
@schema.register
|
|
||||||
class MyType(ObjectType):
|
|
||||||
type = String(resolver=lambda *_: 'Dog')
|
|
||||||
|
|
||||||
schema.query = MyType
|
|
||||||
|
|
||||||
assert schema.get_type('MyType') == MyType
|
|
||||||
|
|
||||||
|
|
||||||
def test_schema_register_interfaces():
|
|
||||||
class Query(ObjectType):
|
|
||||||
f = Field(Character)
|
|
||||||
|
|
||||||
def resolve_f(self, args, info):
|
|
||||||
return Human()
|
|
||||||
|
|
||||||
schema = Schema(query=Query)
|
|
||||||
|
|
||||||
schema.register(Human)
|
|
||||||
|
|
||||||
result = schema.execute('{ f { name } }')
|
|
||||||
assert not result.errors
|
|
||||||
|
|
||||||
|
|
||||||
def test_schema_register_no_query_type():
|
|
||||||
schema = Schema(name='My own schema')
|
|
||||||
|
|
||||||
@schema.register
|
|
||||||
class MyType(ObjectType):
|
|
||||||
type = String(resolver=lambda *_: 'Dog')
|
|
||||||
|
|
||||||
with raises(Exception) as excinfo:
|
|
||||||
schema.get_type('MyType')
|
|
||||||
assert 'base query type' in str(excinfo.value)
|
|
||||||
|
|
||||||
|
|
||||||
def test_schema_introspect():
|
|
||||||
schema = Schema(name='My own schema')
|
|
||||||
|
|
||||||
class MyType(ObjectType):
|
|
||||||
type = String(resolver=lambda *_: 'Dog')
|
|
||||||
|
|
||||||
schema.query = MyType
|
|
||||||
|
|
||||||
introspection = schema.introspect()
|
|
||||||
assert '__schema' in introspection
|
|
||||||
|
|
||||||
|
|
||||||
def test_lazytype():
|
|
||||||
schema = Schema(name='My own schema')
|
|
||||||
|
|
||||||
t = LazyType('MyType')
|
|
||||||
|
|
||||||
@schema.register
|
|
||||||
class MyType(ObjectType):
|
|
||||||
type = String(resolver=lambda *_: 'Dog')
|
|
||||||
|
|
||||||
schema.query = MyType
|
|
||||||
|
|
||||||
assert schema.T(t) == schema.T(MyType)
|
|
||||||
|
|
||||||
|
|
||||||
def test_deprecated_plugins_throws_exception():
|
|
||||||
with raises(Exception) as excinfo:
|
|
||||||
Schema(plugins=[])
|
|
||||||
assert 'Plugins are deprecated, please use middlewares' in str(excinfo.value)
|
|
||||||
|
|
||||||
|
|
||||||
def test_schema_str():
|
|
||||||
expected = """
|
|
||||||
schema {
|
|
||||||
query: Human
|
|
||||||
}
|
|
||||||
|
|
||||||
interface Character {
|
|
||||||
name: String
|
|
||||||
}
|
|
||||||
|
|
||||||
type Human implements Character {
|
|
||||||
name: String
|
|
||||||
friends: [Character]
|
|
||||||
pet: Pet
|
|
||||||
}
|
|
||||||
|
|
||||||
type Pet {
|
|
||||||
type: String
|
|
||||||
}
|
|
||||||
""".lstrip()
|
|
||||||
assert str(schema) == expected
|
|
|
@ -1,29 +0,0 @@
|
||||||
import graphene
|
|
||||||
|
|
||||||
|
|
||||||
class Query(graphene.ObjectType):
|
|
||||||
base = graphene.String()
|
|
||||||
|
|
||||||
|
|
||||||
class Subscription(graphene.ObjectType):
|
|
||||||
subscribe_to_foo = graphene.Boolean(id=graphene.Int())
|
|
||||||
|
|
||||||
def resolve_subscribe_to_foo(self, args, info):
|
|
||||||
return args.get('id') == 1
|
|
||||||
|
|
||||||
|
|
||||||
schema = graphene.Schema(query=Query, subscription=Subscription)
|
|
||||||
|
|
||||||
|
|
||||||
def test_execute_subscription():
|
|
||||||
query = '''
|
|
||||||
subscription {
|
|
||||||
subscribeToFoo(id: 1)
|
|
||||||
}
|
|
||||||
'''
|
|
||||||
expected = {
|
|
||||||
'subscribeToFoo': True
|
|
||||||
}
|
|
||||||
result = schema.execute(query)
|
|
||||||
assert not result.errors
|
|
||||||
assert result.data == expected
|
|
|
@ -1,29 +0,0 @@
|
||||||
from .base import InstanceType, LazyType, OrderedType
|
|
||||||
from .argument import Argument, ArgumentsGroup, to_arguments
|
|
||||||
from .definitions import List, NonNull
|
|
||||||
# Compatibility import
|
|
||||||
from .objecttype import Interface, ObjectType, Mutation, InputObjectType
|
|
||||||
|
|
||||||
from .scalars import String, ID, Boolean, Int, Float
|
|
||||||
from .field import Field, InputField
|
|
||||||
|
|
||||||
__all__ = [
|
|
||||||
'InstanceType',
|
|
||||||
'LazyType',
|
|
||||||
'OrderedType',
|
|
||||||
'Argument',
|
|
||||||
'ArgumentsGroup',
|
|
||||||
'to_arguments',
|
|
||||||
'List',
|
|
||||||
'NonNull',
|
|
||||||
'Field',
|
|
||||||
'InputField',
|
|
||||||
'Interface',
|
|
||||||
'ObjectType',
|
|
||||||
'Mutation',
|
|
||||||
'InputObjectType',
|
|
||||||
'String',
|
|
||||||
'ID',
|
|
||||||
'Boolean',
|
|
||||||
'Int',
|
|
||||||
'Float']
|
|
|
@ -1,53 +0,0 @@
|
||||||
from itertools import chain
|
|
||||||
|
|
||||||
from graphql.type import GraphQLArgument
|
|
||||||
|
|
||||||
from .base import ArgumentType, GroupNamedType, NamedType, OrderedType
|
|
||||||
|
|
||||||
|
|
||||||
class Argument(NamedType, OrderedType):
|
|
||||||
|
|
||||||
def __init__(self, type, description=None, default=None,
|
|
||||||
name=None, _creation_counter=None):
|
|
||||||
super(Argument, self).__init__(name=name, _creation_counter=_creation_counter)
|
|
||||||
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(GroupNamedType):
|
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
|
||||||
arguments = to_arguments(*args, **kwargs)
|
|
||||||
super(ArgumentsGroup, self).__init__(*arguments)
|
|
||||||
|
|
||||||
|
|
||||||
def to_arguments(*args, **kwargs):
|
|
||||||
arguments = {}
|
|
||||||
iter_arguments = chain(kwargs.items(), [(None, a) for a in args])
|
|
||||||
|
|
||||||
for default_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' % (default_name, arg))
|
|
||||||
|
|
||||||
if default_name:
|
|
||||||
argument.default_name = default_name
|
|
||||||
|
|
||||||
name = argument.name or argument.default_name
|
|
||||||
assert name, 'Argument in field must have a name'
|
|
||||||
assert name not in arguments, 'Found more than one Argument with same name {}'.format(name)
|
|
||||||
arguments[name] = argument
|
|
||||||
|
|
||||||
return sorted(arguments.values())
|
|
|
@ -1,170 +0,0 @@
|
||||||
from collections import OrderedDict
|
|
||||||
from functools import partial, total_ordering
|
|
||||||
|
|
||||||
import six
|
|
||||||
|
|
||||||
from ...utils import to_camel_case
|
|
||||||
|
|
||||||
|
|
||||||
class InstanceType(object):
|
|
||||||
|
|
||||||
def internal_type(self, schema):
|
|
||||||
raise NotImplementedError("internal_type for type {} is not implemented".format(self.__class__.__name__))
|
|
||||||
|
|
||||||
|
|
||||||
class MountType(InstanceType):
|
|
||||||
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 ..classtypes.base import FieldsClassType
|
|
||||||
from ..classtypes.inputobjecttype import InputObjectType
|
|
||||||
if issubclass(cls, (InputObjectType)):
|
|
||||||
inputfield = self.as_inputfield()
|
|
||||||
return inputfield.contribute_to_class(cls, name)
|
|
||||||
elif issubclass(cls, (FieldsClassType)):
|
|
||||||
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
|
|
||||||
|
|
||||||
|
|
||||||
class NamedType(InstanceType):
|
|
||||||
|
|
||||||
def __init__(self, name=None, default_name=None, *args, **kwargs):
|
|
||||||
self.name = name
|
|
||||||
self.default_name = None
|
|
||||||
super(NamedType, self).__init__(*args, **kwargs)
|
|
||||||
|
|
||||||
|
|
||||||
class GroupNamedType(InstanceType):
|
|
||||||
|
|
||||||
def __init__(self, *types):
|
|
||||||
self.types = types
|
|
||||||
|
|
||||||
def get_named_type(self, schema, type):
|
|
||||||
name = type.name
|
|
||||||
if not name and schema.auto_camelcase:
|
|
||||||
name = to_camel_case(type.default_name)
|
|
||||||
elif not name:
|
|
||||||
name = type.default_name
|
|
||||||
return name, schema.T(type)
|
|
||||||
|
|
||||||
def iter_types(self, schema):
|
|
||||||
return map(partial(self.get_named_type, schema), self.types)
|
|
||||||
|
|
||||||
def internal_type(self, schema):
|
|
||||||
return OrderedDict(self.iter_types(schema))
|
|
||||||
|
|
||||||
def __len__(self):
|
|
||||||
return len(self.types)
|
|
||||||
|
|
||||||
def __iter__(self):
|
|
||||||
return iter(self.types)
|
|
||||||
|
|
||||||
def __contains__(self, *args):
|
|
||||||
return self.types.__contains__(*args)
|
|
||||||
|
|
||||||
def __getitem__(self, *args):
|
|
||||||
return self.types.__getitem__(*args)
|
|
|
@ -1,40 +0,0 @@
|
||||||
import json
|
|
||||||
|
|
||||||
import iso8601
|
|
||||||
from graphql.language import ast
|
|
||||||
|
|
||||||
from ...core.classtypes.scalar import Scalar
|
|
||||||
|
|
||||||
|
|
||||||
class JSONString(Scalar):
|
|
||||||
'''JSON String'''
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def serialize(dt):
|
|
||||||
return json.dumps(dt)
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def parse_literal(node):
|
|
||||||
if isinstance(node, ast.StringValue):
|
|
||||||
return json.dumps(node.value)
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def parse_value(value):
|
|
||||||
return json.dumps(value)
|
|
||||||
|
|
||||||
|
|
||||||
class DateTime(Scalar):
|
|
||||||
'''DateTime in ISO 8601 format'''
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def serialize(dt):
|
|
||||||
return dt.isoformat()
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def parse_literal(node):
|
|
||||||
if isinstance(node, ast.StringValue):
|
|
||||||
return iso8601.parse_date(node.value)
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def parse_value(value):
|
|
||||||
return iso8601.parse_date(value)
|
|
|
@ -1,29 +0,0 @@
|
||||||
import six
|
|
||||||
from graphql.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
|
|
|
@ -1,189 +0,0 @@
|
||||||
from collections import OrderedDict
|
|
||||||
from functools import wraps
|
|
||||||
|
|
||||||
import six
|
|
||||||
from graphql.type import GraphQLField, GraphQLInputObjectField
|
|
||||||
|
|
||||||
from ...utils import maybe_func
|
|
||||||
from ...utils.wrap_resolver_function import wrap_resolver_function
|
|
||||||
from ..classtypes.base import FieldsClassType
|
|
||||||
from ..classtypes.inputobjecttype import InputObjectType
|
|
||||||
from ..classtypes.mutation import Mutation
|
|
||||||
from ..exceptions import SkipField
|
|
||||||
from .argument import Argument, ArgumentsGroup
|
|
||||||
from .base import (ArgumentType, GroupNamedType, LazyType, MountType,
|
|
||||||
NamedType, OrderedType)
|
|
||||||
from .definitions import NonNull
|
|
||||||
|
|
||||||
|
|
||||||
class Field(NamedType, OrderedType):
|
|
||||||
|
|
||||||
def __init__(
|
|
||||||
self, type, description=None, args=None, name=None, resolver=None,
|
|
||||||
source=None, required=False, default=None, deprecation_reason=None,
|
|
||||||
*args_list, **kwargs):
|
|
||||||
_creation_counter = kwargs.pop('_creation_counter', None)
|
|
||||||
if isinstance(name, (Argument, ArgumentType)):
|
|
||||||
kwargs['name'] = name
|
|
||||||
name = None
|
|
||||||
super(Field, self).__init__(name=name, _creation_counter=_creation_counter)
|
|
||||||
if isinstance(type, six.string_types):
|
|
||||||
type = LazyType(type)
|
|
||||||
self.required = required
|
|
||||||
self.type = type
|
|
||||||
self.description = description
|
|
||||||
self.deprecation_reason = deprecation_reason
|
|
||||||
args = OrderedDict(args or {}, **kwargs)
|
|
||||||
self.arguments = ArgumentsGroup(*args_list, **args)
|
|
||||||
self.object_type = None
|
|
||||||
self.attname = None
|
|
||||||
self.default_name = None
|
|
||||||
self.resolver_fn = resolver
|
|
||||||
self.source = source
|
|
||||||
assert not (self.source and self.resolver_fn), ('You cannot have a source'
|
|
||||||
' and a resolver at the same time')
|
|
||||||
self.default = default
|
|
||||||
|
|
||||||
def contribute_to_class(self, cls, attname):
|
|
||||||
assert issubclass(
|
|
||||||
cls, (FieldsClassType)), 'Field {} cannot be mounted in {}'.format(
|
|
||||||
self, cls)
|
|
||||||
self.attname = attname
|
|
||||||
self.default_name = 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):
|
|
||||||
resolver = self.get_resolver_fn()
|
|
||||||
return resolver
|
|
||||||
|
|
||||||
@property
|
|
||||||
def default(self):
|
|
||||||
if callable(self._default):
|
|
||||||
return self._default()
|
|
||||||
return self._default
|
|
||||||
|
|
||||||
@default.setter
|
|
||||||
def default(self, value):
|
|
||||||
self._default = value
|
|
||||||
|
|
||||||
def get_resolver_fn(self):
|
|
||||||
if self.resolver_fn:
|
|
||||||
return self.resolver_fn
|
|
||||||
|
|
||||||
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):
|
|
||||||
value = getattr(instance, self.source or self.attname, self.default)
|
|
||||||
return maybe_func(value)
|
|
||||||
return default_getter
|
|
||||||
|
|
||||||
def get_type(self, schema):
|
|
||||||
if self.required:
|
|
||||||
return NonNull(self.type)
|
|
||||||
return self.type
|
|
||||||
|
|
||||||
def internal_type(self, schema):
|
|
||||||
if not self.object_type:
|
|
||||||
raise Exception('The field is not mounted in any ClassType')
|
|
||||||
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 issubclass(type_objecttype, Mutation):
|
|
||||||
assert len(arguments) == 0
|
|
||||||
arguments = type_objecttype.get_arguments()
|
|
||||||
resolver = getattr(type_objecttype, 'mutate')
|
|
||||||
resolver = wrap_resolver_function(resolver)
|
|
||||||
else:
|
|
||||||
my_resolver = wrap_resolver_function(resolver)
|
|
||||||
|
|
||||||
@wraps(my_resolver)
|
|
||||||
def wrapped_func(instance, args, context, info):
|
|
||||||
if not isinstance(instance, self.object_type):
|
|
||||||
instance = self.object_type(_root=instance)
|
|
||||||
return my_resolver(instance, args, context, info)
|
|
||||||
resolver = wrapped_func
|
|
||||||
|
|
||||||
assert type, 'Internal type for field %s is None' % str(self)
|
|
||||||
return GraphQLField(
|
|
||||||
type,
|
|
||||||
args=schema.T(arguments),
|
|
||||||
resolver=schema.resolver_with_middleware(resolver),
|
|
||||||
deprecation_reason=self.deprecation_reason,
|
|
||||||
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(NamedType, OrderedType):
|
|
||||||
|
|
||||||
def __init__(self, type, description=None, default=None,
|
|
||||||
name=None, _creation_counter=None, required=False):
|
|
||||||
super(InputField, self).__init__(_creation_counter=_creation_counter)
|
|
||||||
if isinstance(type, six.string_types):
|
|
||||||
type = LazyType(type)
|
|
||||||
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)
|
|
||||||
self.attname = attname
|
|
||||||
self.default_name = 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
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class FieldsGroupType(GroupNamedType):
|
|
||||||
|
|
||||||
def iter_types(self, schema):
|
|
||||||
for field in sorted(self.types):
|
|
||||||
try:
|
|
||||||
yield self.get_named_type(schema, field)
|
|
||||||
except SkipField:
|
|
||||||
continue
|
|
|
@ -1,3 +0,0 @@
|
||||||
from ..classtypes import InputObjectType, Interface, Mutation, ObjectType
|
|
||||||
|
|
||||||
__all__ = ['ObjectType', 'Interface', 'Mutation', 'InputObjectType']
|
|
|
@ -1,30 +0,0 @@
|
||||||
from graphql.type import (GraphQLBoolean, GraphQLFloat, GraphQLID, GraphQLInt,
|
|
||||||
GraphQLString)
|
|
||||||
|
|
||||||
from .base import MountedType
|
|
||||||
|
|
||||||
|
|
||||||
class ScalarType(MountedType):
|
|
||||||
|
|
||||||
def internal_type(self, schema):
|
|
||||||
return self._internal_type
|
|
||||||
|
|
||||||
|
|
||||||
class String(ScalarType):
|
|
||||||
_internal_type = GraphQLString
|
|
||||||
|
|
||||||
|
|
||||||
class Int(ScalarType):
|
|
||||||
_internal_type = GraphQLInt
|
|
||||||
|
|
||||||
|
|
||||||
class Boolean(ScalarType):
|
|
||||||
_internal_type = GraphQLBoolean
|
|
||||||
|
|
||||||
|
|
||||||
class ID(ScalarType):
|
|
||||||
_internal_type = GraphQLID
|
|
||||||
|
|
||||||
|
|
||||||
class Float(ScalarType):
|
|
||||||
_internal_type = GraphQLFloat
|
|
|
@ -1,47 +0,0 @@
|
||||||
from graphql.type import GraphQLArgument
|
|
||||||
from pytest import raises
|
|
||||||
|
|
||||||
from graphene.core.schema import Schema
|
|
||||||
from graphene.core.types import ObjectType
|
|
||||||
|
|
||||||
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 or a.default_name for a in arguments] == [
|
|
||||||
'myArg', 'otherArg', 'my_kwarg', 'other_kwarg']
|
|
||||||
|
|
||||||
|
|
||||||
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)
|
|
|
@ -1,97 +0,0 @@
|
||||||
from mock import patch
|
|
||||||
|
|
||||||
from graphene.core.types import InputObjectType, ObjectType
|
|
||||||
|
|
||||||
from ..argument import Argument
|
|
||||||
from ..base import MountedType, OrderedType
|
|
||||||
from ..definitions import List, NonNull
|
|
||||||
from ..field import Field, InputField
|
|
||||||
|
|
||||||
|
|
||||||
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):
|
|
||||||
def resolver(x):
|
|
||||||
return 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():
|
|
||||||
def resolver(x):
|
|
||||||
return 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'}
|
|
|
@ -1,26 +0,0 @@
|
||||||
import iso8601
|
|
||||||
from graphql.language.ast import StringValue
|
|
||||||
|
|
||||||
from ..custom_scalars import DateTime
|
|
||||||
|
|
||||||
|
|
||||||
def test_date_time():
|
|
||||||
test_iso_string = "2016-04-29T18:34:12.502Z"
|
|
||||||
|
|
||||||
def check_datetime(test_dt):
|
|
||||||
assert test_dt.tzinfo == iso8601.UTC
|
|
||||||
assert test_dt.year == 2016
|
|
||||||
assert test_dt.month == 4
|
|
||||||
assert test_dt.day == 29
|
|
||||||
assert test_dt.hour == 18
|
|
||||||
assert test_dt.minute == 34
|
|
||||||
assert test_dt.second == 12
|
|
||||||
|
|
||||||
test_dt = DateTime().parse_value(test_iso_string)
|
|
||||||
check_datetime(test_dt)
|
|
||||||
|
|
||||||
assert DateTime.serialize(test_dt) == "2016-04-29T18:34:12.502000+00:00"
|
|
||||||
|
|
||||||
node = StringValue(test_iso_string)
|
|
||||||
test_dt = DateTime.parse_literal(node)
|
|
||||||
check_datetime(test_dt)
|
|
|
@ -1,27 +0,0 @@
|
||||||
from graphql.type import GraphQLList, GraphQLNonNull, GraphQLString
|
|
||||||
|
|
||||||
from graphene.core.schema import Schema
|
|
||||||
|
|
||||||
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
|
|
|
@ -1,236 +0,0 @@
|
||||||
from graphql.type import GraphQLField, GraphQLInputObjectField, GraphQLString
|
|
||||||
|
|
||||||
from graphene.core.schema import Schema
|
|
||||||
from graphene.core.types import InputObjectType, ObjectType
|
|
||||||
|
|
||||||
from ..base import LazyType
|
|
||||||
from ..definitions import List
|
|
||||||
from ..field import Field, InputField
|
|
||||||
from ..scalars import String
|
|
||||||
|
|
||||||
|
|
||||||
def test_field_internal_type():
|
|
||||||
def resolver(*args):
|
|
||||||
return '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 is None
|
|
||||||
assert field.attname == 'my_field'
|
|
||||||
assert isinstance(type, GraphQLField)
|
|
||||||
assert type.description == 'My argument'
|
|
||||||
assert type.resolver(None, {}, None, None).value == '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, None).value == '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_eq():
|
|
||||||
field = Field('self', name='my_customName')
|
|
||||||
field2 = Field('self', name='my_customName')
|
|
||||||
assert field == field
|
|
||||||
assert field2 != field
|
|
||||||
|
|
||||||
|
|
||||||
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())
|
|
||||||
schema = Schema()
|
|
||||||
|
|
||||||
args = field.arguments
|
|
||||||
assert 'p' in schema.T(args)
|
|
||||||
|
|
||||||
|
|
||||||
def test_field_name_as_argument():
|
|
||||||
field = Field(None, name=String())
|
|
||||||
schema = Schema()
|
|
||||||
|
|
||||||
args = field.arguments
|
|
||||||
assert 'name' in schema.T(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 is None
|
|
||||||
assert field.attname == 'my_field'
|
|
||||||
assert isinstance(type, GraphQLInputObjectField)
|
|
||||||
assert type.description == 'My input field'
|
|
||||||
assert type.default_value == '3'
|
|
||||||
|
|
||||||
|
|
||||||
def test_inputfield_string_reference():
|
|
||||||
class MyInput(InputObjectType):
|
|
||||||
my_field = InputField(String, description='My input field', default='3')
|
|
||||||
|
|
||||||
my_input_field = InputField('MyInput')
|
|
||||||
|
|
||||||
class OtherInput(InputObjectType):
|
|
||||||
my_input = my_input_field
|
|
||||||
|
|
||||||
class Query(ObjectType):
|
|
||||||
a = String()
|
|
||||||
|
|
||||||
schema = Schema(query=Query)
|
|
||||||
|
|
||||||
my_input_type = schema.T(MyInput)
|
|
||||||
my_input_field_type = schema.T(my_input_field)
|
|
||||||
assert my_input_field_type.type == my_input_type
|
|
||||||
|
|
||||||
|
|
||||||
def test_field_resolve_argument():
|
|
||||||
def resolver(instance, args, info):
|
|
||||||
return args.get('first_name')
|
|
||||||
|
|
||||||
field = Field(String(), first_name=String(), description='My argument', resolver=resolver)
|
|
||||||
|
|
||||||
class Query(ObjectType):
|
|
||||||
my_field = field
|
|
||||||
schema = Schema(query=Query)
|
|
||||||
|
|
||||||
type = schema.T(field)
|
|
||||||
assert type.resolver(None, {'firstName': 'Peter'}, None, None).value == 'Peter'
|
|
||||||
|
|
||||||
|
|
||||||
def test_field_resolve_vars():
|
|
||||||
class Query(ObjectType):
|
|
||||||
hello = String(first_name=String())
|
|
||||||
|
|
||||||
def resolve_hello(self, args, info):
|
|
||||||
return 'Hello ' + args.get('first_name')
|
|
||||||
|
|
||||||
schema = Schema(query=Query)
|
|
||||||
|
|
||||||
result = schema.execute("""
|
|
||||||
query foo($firstName:String)
|
|
||||||
{
|
|
||||||
hello(firstName:$firstName)
|
|
||||||
}
|
|
||||||
""", variable_values={"firstName": "Serkan"})
|
|
||||||
|
|
||||||
expected = {
|
|
||||||
'hello': 'Hello Serkan'
|
|
||||||
}
|
|
||||||
assert result.data == expected
|
|
||||||
|
|
||||||
|
|
||||||
def test_field_internal_type_deprecated():
|
|
||||||
deprecation_reason = 'No more used'
|
|
||||||
field = Field(String(), description='My argument',
|
|
||||||
deprecation_reason=deprecation_reason)
|
|
||||||
|
|
||||||
class Query(ObjectType):
|
|
||||||
my_field = field
|
|
||||||
schema = Schema(query=Query)
|
|
||||||
|
|
||||||
type = schema.T(field)
|
|
||||||
assert isinstance(type, GraphQLField)
|
|
||||||
assert type.deprecation_reason == deprecation_reason
|
|
||||||
|
|
||||||
|
|
||||||
def test_field_resolve_object():
|
|
||||||
class Root(object):
|
|
||||||
att = True
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def att_func():
|
|
||||||
return True
|
|
||||||
|
|
||||||
field = Field(String(), description='My argument')
|
|
||||||
field_func = Field(String(), description='My argument')
|
|
||||||
|
|
||||||
class Query(ObjectType):
|
|
||||||
att = field
|
|
||||||
att_func = field_func
|
|
||||||
|
|
||||||
assert field.resolver(Root, {}, None) is True
|
|
||||||
|
|
||||||
|
|
||||||
def test_field_resolve_source_object():
|
|
||||||
class Root(object):
|
|
||||||
att_source = True
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def att_func_source():
|
|
||||||
return True
|
|
||||||
|
|
||||||
field = Field(String(), source='att_source', description='My argument')
|
|
||||||
field_func = Field(String(), source='att_func_source', description='My argument')
|
|
||||||
|
|
||||||
class Query(ObjectType):
|
|
||||||
att = field
|
|
||||||
att_func = field_func
|
|
||||||
|
|
||||||
assert field.resolver(Root, {}, None) is True
|
|
|
@ -1,28 +0,0 @@
|
||||||
from graphql.type import (GraphQLBoolean, GraphQLFloat, GraphQLID, GraphQLInt,
|
|
||||||
GraphQLString)
|
|
||||||
|
|
||||||
from graphene.core.schema import Schema
|
|
||||||
|
|
||||||
from ..scalars import ID, Boolean, Float, Int, 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
|
|
|
@ -1,6 +0,0 @@
|
||||||
from .base import MiddlewareManager
|
|
||||||
from .camel_case import CamelCaseArgsMiddleware
|
|
||||||
|
|
||||||
__all__ = [
|
|
||||||
'MiddlewareManager', 'CamelCaseArgsMiddleware'
|
|
||||||
]
|
|
|
@ -1,23 +0,0 @@
|
||||||
from ..utils import promise_middleware
|
|
||||||
|
|
||||||
MIDDLEWARE_RESOLVER_FUNCTION = 'resolve'
|
|
||||||
|
|
||||||
|
|
||||||
class MiddlewareManager(object):
|
|
||||||
|
|
||||||
def __init__(self, schema, middlewares=None):
|
|
||||||
self.schema = schema
|
|
||||||
self.middlewares = middlewares or []
|
|
||||||
|
|
||||||
def add_middleware(self, middleware):
|
|
||||||
self.middlewares.append(middleware)
|
|
||||||
|
|
||||||
def get_middleware_resolvers(self):
|
|
||||||
for middleware in self.middlewares:
|
|
||||||
if not hasattr(middleware, MIDDLEWARE_RESOLVER_FUNCTION):
|
|
||||||
continue
|
|
||||||
yield getattr(middleware, MIDDLEWARE_RESOLVER_FUNCTION)
|
|
||||||
|
|
||||||
def wrap(self, resolver):
|
|
||||||
middleware_resolvers = self.get_middleware_resolvers()
|
|
||||||
return promise_middleware(resolver, middleware_resolvers)
|
|
|
@ -1,8 +0,0 @@
|
||||||
from ..utils import ProxySnakeDict
|
|
||||||
|
|
||||||
|
|
||||||
class CamelCaseArgsMiddleware(object):
|
|
||||||
|
|
||||||
def resolve(self, next, root, args, context, info):
|
|
||||||
args = ProxySnakeDict(args)
|
|
||||||
return next(root, args, context, info)
|
|
|
@ -1,18 +0,0 @@
|
||||||
from .fields import (
|
|
||||||
ConnectionField,
|
|
||||||
NodeField,
|
|
||||||
GlobalIDField,
|
|
||||||
)
|
|
||||||
|
|
||||||
from .types import (
|
|
||||||
Node,
|
|
||||||
PageInfo,
|
|
||||||
Edge,
|
|
||||||
Connection,
|
|
||||||
ClientIDMutation
|
|
||||||
)
|
|
||||||
|
|
||||||
from .utils import is_node
|
|
||||||
|
|
||||||
__all__ = ['ConnectionField', 'NodeField', 'GlobalIDField', 'Node',
|
|
||||||
'PageInfo', 'Edge', 'Connection', 'ClientIDMutation', 'is_node']
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user