mirror of
https://github.com/graphql-python/graphene.git
synced 2024-11-25 19:13:57 +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