diff --git a/graphene/__init__.py b/graphene/__init__.py index a4eb6299..e69de29b 100644 --- a/graphene/__init__.py +++ b/graphene/__init__.py @@ -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'] diff --git a/graphene/contrib/django/__init__.py b/graphene/contrib/django/__init__.py deleted file mode 100644 index 047fe0a3..00000000 --- a/graphene/contrib/django/__init__.py +++ /dev/null @@ -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'] diff --git a/graphene/contrib/django/compat.py b/graphene/contrib/django/compat.py deleted file mode 100644 index 4b1f55a6..00000000 --- a/graphene/contrib/django/compat.py +++ /dev/null @@ -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 diff --git a/graphene/contrib/django/converter.py b/graphene/contrib/django/converter.py deleted file mode 100644 index 69521b61..00000000 --- a/graphene/contrib/django/converter.py +++ /dev/null @@ -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) diff --git a/graphene/contrib/django/debug/__init__.py b/graphene/contrib/django/debug/__init__.py deleted file mode 100644 index cd5015e1..00000000 --- a/graphene/contrib/django/debug/__init__.py +++ /dev/null @@ -1,4 +0,0 @@ -from .middleware import DjangoDebugMiddleware -from .types import DjangoDebug - -__all__ = ['DjangoDebugMiddleware', 'DjangoDebug'] diff --git a/graphene/contrib/django/debug/middleware.py b/graphene/contrib/django/debug/middleware.py deleted file mode 100644 index 01b09e7b..00000000 --- a/graphene/contrib/django/debug/middleware.py +++ /dev/null @@ -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 diff --git a/graphene/contrib/django/debug/sql/__init__.py b/graphene/contrib/django/debug/sql/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/graphene/contrib/django/debug/sql/tracking.py b/graphene/contrib/django/debug/sql/tracking.py deleted file mode 100644 index 3d86401a..00000000 --- a/graphene/contrib/django/debug/sql/tracking.py +++ /dev/null @@ -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() diff --git a/graphene/contrib/django/debug/sql/types.py b/graphene/contrib/django/debug/sql/types.py deleted file mode 100644 index 43d2c73a..00000000 --- a/graphene/contrib/django/debug/sql/types.py +++ /dev/null @@ -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() diff --git a/graphene/contrib/django/debug/tests/__init__.py b/graphene/contrib/django/debug/tests/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/graphene/contrib/django/debug/tests/test_query.py b/graphene/contrib/django/debug/tests/test_query.py deleted file mode 100644 index 50976ad5..00000000 --- a/graphene/contrib/django/debug/tests/test_query.py +++ /dev/null @@ -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 diff --git a/graphene/contrib/django/debug/types.py b/graphene/contrib/django/debug/types.py deleted file mode 100644 index c6a498f4..00000000 --- a/graphene/contrib/django/debug/types.py +++ /dev/null @@ -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()) diff --git a/graphene/contrib/django/fields.py b/graphene/contrib/django/fields.py deleted file mode 100644 index 3f4681a3..00000000 --- a/graphene/contrib/django/fields.py +++ /dev/null @@ -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) diff --git a/graphene/contrib/django/filter/__init__.py b/graphene/contrib/django/filter/__init__.py deleted file mode 100644 index 4f8b0579..00000000 --- a/graphene/contrib/django/filter/__init__.py +++ /dev/null @@ -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'] diff --git a/graphene/contrib/django/filter/fields.py b/graphene/contrib/django/filter/fields.py deleted file mode 100644 index d8457fa8..00000000 --- a/graphene/contrib/django/filter/fields.py +++ /dev/null @@ -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) diff --git a/graphene/contrib/django/filter/filterset.py b/graphene/contrib/django/filter/filterset.py deleted file mode 100644 index 6b9c8ac9..00000000 --- a/graphene/contrib/django/filter/filterset.py +++ /dev/null @@ -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 diff --git a/graphene/contrib/django/filter/tests/__init__.py b/graphene/contrib/django/filter/tests/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/graphene/contrib/django/filter/tests/filters.py b/graphene/contrib/django/filter/tests/filters.py deleted file mode 100644 index bccd72d5..00000000 --- a/graphene/contrib/django/filter/tests/filters.py +++ /dev/null @@ -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 diff --git a/graphene/contrib/django/filter/tests/test_fields.py b/graphene/contrib/django/filter/tests/test_fields.py deleted file mode 100644 index 5b2875b2..00000000 --- a/graphene/contrib/django/filter/tests/test_fields.py +++ /dev/null @@ -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 diff --git a/graphene/contrib/django/filter/utils.py b/graphene/contrib/django/filter/utils.py deleted file mode 100644 index 5071ddc4..00000000 --- a/graphene/contrib/django/filter/utils.py +++ /dev/null @@ -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) diff --git a/graphene/contrib/django/form_converter.py b/graphene/contrib/django/form_converter.py deleted file mode 100644 index de2a40d8..00000000 --- a/graphene/contrib/django/form_converter.py +++ /dev/null @@ -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() diff --git a/graphene/contrib/django/forms.py b/graphene/contrib/django/forms.py deleted file mode 100644 index 8f8d0305..00000000 --- a/graphene/contrib/django/forms.py +++ /dev/null @@ -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 diff --git a/graphene/contrib/django/management/__init__.py b/graphene/contrib/django/management/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/graphene/contrib/django/management/commands/__init__.py b/graphene/contrib/django/management/commands/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/graphene/contrib/django/management/commands/graphql_schema.py b/graphene/contrib/django/management/commands/graphql_schema.py deleted file mode 100644 index 07b802d4..00000000 --- a/graphene/contrib/django/management/commands/graphql_schema.py +++ /dev/null @@ -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)) diff --git a/graphene/contrib/django/options.py b/graphene/contrib/django/options.py deleted file mode 100644 index dbd88aca..00000000 --- a/graphene/contrib/django/options.py +++ /dev/null @@ -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) diff --git a/graphene/contrib/django/tests/__init__.py b/graphene/contrib/django/tests/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/graphene/contrib/django/tests/models.py b/graphene/contrib/django/tests/models.py deleted file mode 100644 index a0559126..00000000 --- a/graphene/contrib/django/tests/models.py +++ /dev/null @@ -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',) diff --git a/graphene/contrib/django/tests/test_command.py b/graphene/contrib/django/tests/test_command.py deleted file mode 100644 index e8033f6d..00000000 --- a/graphene/contrib/django/tests/test_command.py +++ /dev/null @@ -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() diff --git a/graphene/contrib/django/tests/test_converter.py b/graphene/contrib/django/tests/test_converter.py deleted file mode 100644 index 3d5ee115..00000000 --- a/graphene/contrib/django/tests/test_converter.py +++ /dev/null @@ -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) diff --git a/graphene/contrib/django/tests/test_form_converter.py b/graphene/contrib/django/tests/test_form_converter.py deleted file mode 100644 index 44d9bec3..00000000 --- a/graphene/contrib/django/tests/test_form_converter.py +++ /dev/null @@ -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) diff --git a/graphene/contrib/django/tests/test_forms.py b/graphene/contrib/django/tests/test_forms.py deleted file mode 100644 index c499728a..00000000 --- a/graphene/contrib/django/tests/test_forms.py +++ /dev/null @@ -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==') diff --git a/graphene/contrib/django/tests/test_query.py b/graphene/contrib/django/tests/test_query.py deleted file mode 100644 index 6d6b7540..00000000 --- a/graphene/contrib/django/tests/test_query.py +++ /dev/null @@ -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 diff --git a/graphene/contrib/django/tests/test_schema.py b/graphene/contrib/django/tests/test_schema.py deleted file mode 100644 index 8296b3c9..00000000 --- a/graphene/contrib/django/tests/test_schema.py +++ /dev/null @@ -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'] - ) diff --git a/graphene/contrib/django/tests/test_types.py b/graphene/contrib/django/tests/test_types.py deleted file mode 100644 index 30a0f442..00000000 --- a/graphene/contrib/django/tests/test_types.py +++ /dev/null @@ -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() diff --git a/graphene/contrib/django/tests/test_urls.py b/graphene/contrib/django/tests/test_urls.py deleted file mode 100644 index 9d38980e..00000000 --- a/graphene/contrib/django/tests/test_urls.py +++ /dev/null @@ -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)), -] diff --git a/graphene/contrib/django/tests/test_views.py b/graphene/contrib/django/tests/test_views.py deleted file mode 100644 index b4e1b367..00000000 --- a/graphene/contrib/django/tests/test_views.py +++ /dev/null @@ -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 diff --git a/graphene/contrib/django/types.py b/graphene/contrib/django/types.py deleted file mode 100644 index f8f9cb2b..00000000 --- a/graphene/contrib/django/types.py +++ /dev/null @@ -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 diff --git a/graphene/contrib/django/utils.py b/graphene/contrib/django/utils.py deleted file mode 100644 index 4a16702b..00000000 --- a/graphene/contrib/django/utils.py +++ /dev/null @@ -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 diff --git a/graphene/contrib/django/views.py b/graphene/contrib/django/views.py deleted file mode 100644 index 81f3ceba..00000000 --- a/graphene/contrib/django/views.py +++ /dev/null @@ -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 - ) diff --git a/graphene/contrib/sqlalchemy/__init__.py b/graphene/contrib/sqlalchemy/__init__.py deleted file mode 100644 index 88509ba3..00000000 --- a/graphene/contrib/sqlalchemy/__init__.py +++ /dev/null @@ -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'] diff --git a/graphene/contrib/sqlalchemy/converter.py b/graphene/contrib/sqlalchemy/converter.py deleted file mode 100644 index 540fcdd0..00000000 --- a/graphene/contrib/sqlalchemy/converter.py +++ /dev/null @@ -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) diff --git a/graphene/contrib/sqlalchemy/fields.py b/graphene/contrib/sqlalchemy/fields.py deleted file mode 100644 index 598cd341..00000000 --- a/graphene/contrib/sqlalchemy/fields.py +++ /dev/null @@ -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) diff --git a/graphene/contrib/sqlalchemy/options.py b/graphene/contrib/sqlalchemy/options.py deleted file mode 100644 index 44886287..00000000 --- a/graphene/contrib/sqlalchemy/options.py +++ /dev/null @@ -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) diff --git a/graphene/contrib/sqlalchemy/tests/__init__.py b/graphene/contrib/sqlalchemy/tests/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/graphene/contrib/sqlalchemy/tests/models.py b/graphene/contrib/sqlalchemy/tests/models.py deleted file mode 100644 index 40f95e59..00000000 --- a/graphene/contrib/sqlalchemy/tests/models.py +++ /dev/null @@ -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')) diff --git a/graphene/contrib/sqlalchemy/tests/test_converter.py b/graphene/contrib/sqlalchemy/tests/test_converter.py deleted file mode 100644 index 7658ed79..00000000 --- a/graphene/contrib/sqlalchemy/tests/test_converter.py +++ /dev/null @@ -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 diff --git a/graphene/contrib/sqlalchemy/tests/test_query.py b/graphene/contrib/sqlalchemy/tests/test_query.py deleted file mode 100644 index da8f8e11..00000000 --- a/graphene/contrib/sqlalchemy/tests/test_query.py +++ /dev/null @@ -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 diff --git a/graphene/contrib/sqlalchemy/tests/test_schema.py b/graphene/contrib/sqlalchemy/tests/test_schema.py deleted file mode 100644 index 090b2e18..00000000 --- a/graphene/contrib/sqlalchemy/tests/test_schema.py +++ /dev/null @@ -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'] - ) diff --git a/graphene/contrib/sqlalchemy/tests/test_types.py b/graphene/contrib/sqlalchemy/tests/test_types.py deleted file mode 100644 index 378411ae..00000000 --- a/graphene/contrib/sqlalchemy/tests/test_types.py +++ /dev/null @@ -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 diff --git a/graphene/contrib/sqlalchemy/tests/test_utils.py b/graphene/contrib/sqlalchemy/tests/test_utils.py deleted file mode 100644 index 2925f016..00000000 --- a/graphene/contrib/sqlalchemy/tests/test_utils.py +++ /dev/null @@ -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 diff --git a/graphene/contrib/sqlalchemy/types.py b/graphene/contrib/sqlalchemy/types.py deleted file mode 100644 index f466a1af..00000000 --- a/graphene/contrib/sqlalchemy/types.py +++ /dev/null @@ -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 diff --git a/graphene/contrib/sqlalchemy/utils.py b/graphene/contrib/sqlalchemy/utils.py deleted file mode 100644 index 246a9d86..00000000 --- a/graphene/contrib/sqlalchemy/utils.py +++ /dev/null @@ -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) diff --git a/graphene/core/__init__.py b/graphene/core/__init__.py deleted file mode 100644 index 7b1acdb7..00000000 --- a/graphene/core/__init__.py +++ /dev/null @@ -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'] diff --git a/graphene/core/classtypes/__init__.py b/graphene/core/classtypes/__init__.py deleted file mode 100644 index b7994d9a..00000000 --- a/graphene/core/classtypes/__init__.py +++ /dev/null @@ -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'] diff --git a/graphene/core/classtypes/base.py b/graphene/core/classtypes/base.py deleted file mode 100644 index cabb909a..00000000 --- a/graphene/core/classtypes/base.py +++ /dev/null @@ -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) diff --git a/graphene/core/classtypes/enum.py b/graphene/core/classtypes/enum.py deleted file mode 100644 index 97c8fe9f..00000000 --- a/graphene/core/classtypes/enum.py +++ /dev/null @@ -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, - ) diff --git a/graphene/core/classtypes/inputobjecttype.py b/graphene/core/classtypes/inputobjecttype.py deleted file mode 100644 index 9bff031e..00000000 --- a/graphene/core/classtypes/inputobjecttype.py +++ /dev/null @@ -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), - ) diff --git a/graphene/core/classtypes/interface.py b/graphene/core/classtypes/interface.py deleted file mode 100644 index e7dd1870..00000000 --- a/graphene/core/classtypes/interface.py +++ /dev/null @@ -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) - ) diff --git a/graphene/core/classtypes/mutation.py b/graphene/core/classtypes/mutation.py deleted file mode 100644 index ab443607..00000000 --- a/graphene/core/classtypes/mutation.py +++ /dev/null @@ -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 diff --git a/graphene/core/classtypes/objecttype.py b/graphene/core/classtypes/objecttype.py deleted file mode 100644 index 2bcb5e68..00000000 --- a/graphene/core/classtypes/objecttype.py +++ /dev/null @@ -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) diff --git a/graphene/core/classtypes/scalar.py b/graphene/core/classtypes/scalar.py deleted file mode 100644 index 79dbd3df..00000000 --- a/graphene/core/classtypes/scalar.py +++ /dev/null @@ -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 - ) diff --git a/graphene/core/classtypes/tests/__init__.py b/graphene/core/classtypes/tests/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/graphene/core/classtypes/tests/test_base.py b/graphene/core/classtypes/tests/test_base.py deleted file mode 100644 index 4666fdc2..00000000 --- a/graphene/core/classtypes/tests/test_base.py +++ /dev/null @@ -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'] diff --git a/graphene/core/classtypes/tests/test_enum.py b/graphene/core/classtypes/tests/test_enum.py deleted file mode 100644 index e7ea9f83..00000000 --- a/graphene/core/classtypes/tests/test_enum.py +++ /dev/null @@ -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' diff --git a/graphene/core/classtypes/tests/test_inputobjecttype.py b/graphene/core/classtypes/tests/test_inputobjecttype.py deleted file mode 100644 index 98c6ba80..00000000 --- a/graphene/core/classtypes/tests/test_inputobjecttype.py +++ /dev/null @@ -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'] diff --git a/graphene/core/classtypes/tests/test_interface.py b/graphene/core/classtypes/tests/test_interface.py deleted file mode 100644 index d47df1e0..00000000 --- a/graphene/core/classtypes/tests/test_interface.py +++ /dev/null @@ -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 diff --git a/graphene/core/classtypes/tests/test_mutation.py b/graphene/core/classtypes/tests/test_mutation.py deleted file mode 100644 index 2d3cfab4..00000000 --- a/graphene/core/classtypes/tests/test_mutation.py +++ /dev/null @@ -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) diff --git a/graphene/core/classtypes/tests/test_objecttype.py b/graphene/core/classtypes/tests/test_objecttype.py deleted file mode 100644 index c554dbbf..00000000 --- a/graphene/core/classtypes/tests/test_objecttype.py +++ /dev/null @@ -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'] diff --git a/graphene/core/classtypes/tests/test_options.py b/graphene/core/classtypes/tests/test_options.py deleted file mode 100644 index 512c3e6a..00000000 --- a/graphene/core/classtypes/tests/test_options.py +++ /dev/null @@ -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) diff --git a/graphene/core/classtypes/tests/test_scalar.py b/graphene/core/classtypes/tests/test_scalar.py deleted file mode 100644 index 8c2e17c8..00000000 --- a/graphene/core/classtypes/tests/test_scalar.py +++ /dev/null @@ -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' diff --git a/graphene/core/classtypes/tests/test_uniontype.py b/graphene/core/classtypes/tests/test_uniontype.py deleted file mode 100644 index 0b456776..00000000 --- a/graphene/core/classtypes/tests/test_uniontype.py +++ /dev/null @@ -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)] diff --git a/graphene/core/classtypes/uniontype.py b/graphene/core/classtypes/uniontype.py deleted file mode 100644 index 2d545b59..00000000 --- a/graphene/core/classtypes/uniontype.py +++ /dev/null @@ -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, - ) diff --git a/graphene/core/exceptions.py b/graphene/core/exceptions.py deleted file mode 100644 index ce89b521..00000000 --- a/graphene/core/exceptions.py +++ /dev/null @@ -1,2 +0,0 @@ -class SkipField(Exception): - pass diff --git a/graphene/core/fields.py b/graphene/core/fields.py deleted file mode 100644 index 463e1983..00000000 --- a/graphene/core/fields.py +++ /dev/null @@ -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'] diff --git a/graphene/core/schema.py b/graphene/core/schema.py deleted file mode 100644 index e284ab1f..00000000 --- a/graphene/core/schema.py +++ /dev/null @@ -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 '' % (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 diff --git a/graphene/core/tests/test_mutations.py b/graphene/core/tests/test_mutations.py deleted file mode 100644 index 428340a6..00000000 --- a/graphene/core/tests/test_mutations.py +++ /dev/null @@ -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 diff --git a/graphene/core/tests/test_old_fields.py b/graphene/core/tests/test_old_fields.py deleted file mode 100644 index 696115d0..00000000 --- a/graphene/core/tests/test_old_fields.py +++ /dev/null @@ -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) == "" - - -def test_field_repr_contributed(): - f = StringField().as_field() - f.contribute_to_class(MyOt, 'field_name') - assert repr(f) == "" - - -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' diff --git a/graphene/core/tests/test_query.py b/graphene/core/tests/test_query.py deleted file mode 100644 index 57ab91c2..00000000 --- a/graphene/core/tests/test_query.py +++ /dev/null @@ -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 diff --git a/graphene/core/tests/test_schema.py b/graphene/core/tests/test_schema.py deleted file mode 100644 index b55b295c..00000000 --- a/graphene/core/tests/test_schema.py +++ /dev/null @@ -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 diff --git a/graphene/core/tests/test_subscription.py b/graphene/core/tests/test_subscription.py deleted file mode 100644 index 8f46b04d..00000000 --- a/graphene/core/tests/test_subscription.py +++ /dev/null @@ -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 diff --git a/graphene/core/types/__init__.py b/graphene/core/types/__init__.py deleted file mode 100644 index 51512ec4..00000000 --- a/graphene/core/types/__init__.py +++ /dev/null @@ -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'] diff --git a/graphene/core/types/argument.py b/graphene/core/types/argument.py deleted file mode 100644 index 88a8c92f..00000000 --- a/graphene/core/types/argument.py +++ /dev/null @@ -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()) diff --git a/graphene/core/types/base.py b/graphene/core/types/base.py deleted file mode 100644 index 6dd14aaf..00000000 --- a/graphene/core/types/base.py +++ /dev/null @@ -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) diff --git a/graphene/core/types/custom_scalars.py b/graphene/core/types/custom_scalars.py deleted file mode 100644 index bd33df76..00000000 --- a/graphene/core/types/custom_scalars.py +++ /dev/null @@ -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) diff --git a/graphene/core/types/definitions.py b/graphene/core/types/definitions.py deleted file mode 100644 index 396ed0dd..00000000 --- a/graphene/core/types/definitions.py +++ /dev/null @@ -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 diff --git a/graphene/core/types/field.py b/graphene/core/types/field.py deleted file mode 100644 index 7203b3c5..00000000 --- a/graphene/core/types/field.py +++ /dev/null @@ -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 diff --git a/graphene/core/types/objecttype.py b/graphene/core/types/objecttype.py deleted file mode 100644 index d7cf42ab..00000000 --- a/graphene/core/types/objecttype.py +++ /dev/null @@ -1,3 +0,0 @@ -from ..classtypes import InputObjectType, Interface, Mutation, ObjectType - -__all__ = ['ObjectType', 'Interface', 'Mutation', 'InputObjectType'] diff --git a/graphene/core/types/scalars.py b/graphene/core/types/scalars.py deleted file mode 100644 index 4f178cb9..00000000 --- a/graphene/core/types/scalars.py +++ /dev/null @@ -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 diff --git a/graphene/core/types/tests/__init__.py b/graphene/core/types/tests/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/graphene/core/types/tests/test_argument.py b/graphene/core/types/tests/test_argument.py deleted file mode 100644 index bd9cda10..00000000 --- a/graphene/core/types/tests/test_argument.py +++ /dev/null @@ -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) diff --git a/graphene/core/types/tests/test_base.py b/graphene/core/types/tests/test_base.py deleted file mode 100644 index be68e167..00000000 --- a/graphene/core/types/tests/test_base.py +++ /dev/null @@ -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'} diff --git a/graphene/core/types/tests/test_custom_scalars.py b/graphene/core/types/tests/test_custom_scalars.py deleted file mode 100644 index 28012b36..00000000 --- a/graphene/core/types/tests/test_custom_scalars.py +++ /dev/null @@ -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) diff --git a/graphene/core/types/tests/test_definitions.py b/graphene/core/types/tests/test_definitions.py deleted file mode 100644 index a87e89ee..00000000 --- a/graphene/core/types/tests/test_definitions.py +++ /dev/null @@ -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 diff --git a/graphene/core/types/tests/test_field.py b/graphene/core/types/tests/test_field.py deleted file mode 100644 index 38792705..00000000 --- a/graphene/core/types/tests/test_field.py +++ /dev/null @@ -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 diff --git a/graphene/core/types/tests/test_scalars.py b/graphene/core/types/tests/test_scalars.py deleted file mode 100644 index 124bb8c4..00000000 --- a/graphene/core/types/tests/test_scalars.py +++ /dev/null @@ -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 diff --git a/graphene/middlewares/__init__.py b/graphene/middlewares/__init__.py deleted file mode 100644 index 4cee0bcc..00000000 --- a/graphene/middlewares/__init__.py +++ /dev/null @@ -1,6 +0,0 @@ -from .base import MiddlewareManager -from .camel_case import CamelCaseArgsMiddleware - -__all__ = [ - 'MiddlewareManager', 'CamelCaseArgsMiddleware' -] diff --git a/graphene/middlewares/base.py b/graphene/middlewares/base.py deleted file mode 100644 index 33a26ac5..00000000 --- a/graphene/middlewares/base.py +++ /dev/null @@ -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) diff --git a/graphene/middlewares/camel_case.py b/graphene/middlewares/camel_case.py deleted file mode 100644 index b5266f02..00000000 --- a/graphene/middlewares/camel_case.py +++ /dev/null @@ -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) diff --git a/graphene/relay/__init__.py b/graphene/relay/__init__.py index 9e169237..e69de29b 100644 --- a/graphene/relay/__init__.py +++ b/graphene/relay/__init__.py @@ -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'] diff --git a/graphene/relay/fields.py b/graphene/relay/fields.py deleted file mode 100644 index e79b9592..00000000 --- a/graphene/relay/fields.py +++ /dev/null @@ -1,107 +0,0 @@ -import six - -from graphql_relay.node.node import from_global_id - -from ..core.fields import Field -from ..core.types.definitions import NonNull -from ..core.types.scalars import ID, Int, String -from ..utils.wrap_resolver_function import has_context, with_context - - -class ConnectionField(Field): - - def __init__(self, type, resolver=None, description='', - connection_type=None, edge_type=None, **kwargs): - super( - ConnectionField, - self).__init__( - type, - resolver=resolver, - before=String(), - after=String(), - first=Int(), - last=Int(), - description=description, - **kwargs) - self.connection_type = connection_type - self.edge_type = edge_type - - @with_context - def resolver(self, instance, args, context, info): - schema = info.schema.graphene_schema - connection_type = self.get_type(schema) - - resolver = super(ConnectionField, self).resolver - if has_context(resolver): - resolved = super(ConnectionField, self).resolver(instance, args, context, info) - else: - resolved = super(ConnectionField, self).resolver(instance, args, info) - - if isinstance(resolved, connection_type): - return resolved - return self.from_list(connection_type, resolved, args, context, info) - - def from_list(self, connection_type, resolved, args, context, info): - return connection_type.from_list(resolved, args, context, info) - - def get_connection_type(self, node): - connection_type = self.connection_type or node.get_connection_type() - edge_type = self.get_edge_type(node) - return connection_type.for_node(node, edge_type=edge_type) - - def get_edge_type(self, node): - edge_type = self.edge_type or node.get_edge_type() - return edge_type.for_node(node) - - def get_type(self, schema): - from graphene.relay.utils import is_node - type = schema.T(self.type) - node = schema.objecttype(type) - assert is_node(node), 'Only nodes have connections.' - schema.register(node) - connection_type = self.get_connection_type(node) - - return connection_type - - -class NodeField(Field): - '''Fetches an object given its ID''' - - def __init__(self, object_type=None, *args, **kwargs): - from graphene.relay.types import Node - id = kwargs.pop('id', None) or ID(description='The ID of an object') - super(NodeField, self).__init__( - object_type or Node, id=id, *args, **kwargs) - self.field_object_type = object_type - - def id_fetcher(self, global_id, context, info): - from graphene.relay.utils import is_node - schema = info.schema.graphene_schema - try: - _type, _id = from_global_id(global_id) - except: - return None - object_type = schema.get_type(_type) - if isinstance(self.field_object_type, six.string_types): - field_object_type = schema.get_type(self.field_object_type) - else: - field_object_type = self.field_object_type - if not is_node(object_type) or (self.field_object_type and object_type != field_object_type): - return - - return object_type.get_node(_id, context, info) - - @with_context - def resolver(self, instance, args, context, info): - global_id = args.get('id') - return self.id_fetcher(global_id, context, info) - - -class GlobalIDField(Field): - '''The ID of an object''' - - def __init__(self, *args, **kwargs): - super(GlobalIDField, self).__init__(NonNull(ID()), *args, **kwargs) - - def resolver(self, instance, args, info): - return instance.to_global_id() diff --git a/graphene/relay/node.py b/graphene/relay/node.py new file mode 100644 index 00000000..d65baa8d --- /dev/null +++ b/graphene/relay/node.py @@ -0,0 +1,57 @@ +from functools import partial +import six +from graphql_relay import node_definitions, from_global_id + +from ..types.definitions import GrapheneInterfaceType +from ..types.field import Field +from ..types.interface import Interface, InterfaceTypeMeta + + +class NodeMeta(InterfaceTypeMeta): + + def construct_graphql_type(cls, bases): + pass + + def construct(cls, *args, **kwargs): + constructed = super(NodeMeta, cls).construct(*args, **kwargs) + if not cls._meta.graphql_type: + node_interface, node_field = node_definitions( + cls.get_node, + interface_class=partial(GrapheneInterfaceType, graphene_type=cls), + field_class=Field + ) + cls._meta.graphql_type = node_interface + cls.Field = node_field + return constructed + + +class Node(six.with_metaclass(NodeMeta, Interface)): + + @classmethod + def require_get_node(cls): + return cls == Node + + @classmethod + def from_global_id(cls, global_id): + return from_global_id(global_id) + + @classmethod + def get_node(cls, global_id, context, info): + try: + _type, _id = cls.from_global_id(global_id) + except: + return None + graphql_type = info.schema.get_type(_type) + if cls._meta.graphql_type not in graphql_type.get_interfaces(): + return + return graphql_type.graphene_type.get_node(_id, context, info) + + @classmethod + def implements(cls, object_type): + ''' + We check here that the object_type have the required get_node method + in it + ''' + if cls.require_get_node(): + assert hasattr(object_type, 'get_node'), '{}.get_node method is required by the Node interface.'.format(object_type._meta.graphql_type.name) + return super(Node, cls).implements(object_type) diff --git a/graphene/relay/tests/test_mutations.py b/graphene/relay/tests/test_mutations.py deleted file mode 100644 index 0874be65..00000000 --- a/graphene/relay/tests/test_mutations.py +++ /dev/null @@ -1,134 +0,0 @@ -from graphql.type import GraphQLInputObjectField - -import graphene -from graphene import relay, with_context -from graphene.core.schema import Schema - -my_id = 0 -my_id_context = 0 - - -class Query(graphene.ObjectType): - base = graphene.String() - - -class ChangeNumber(relay.ClientIDMutation): - '''Result mutation''' - class Input: - to = graphene.Int() - - result = graphene.String() - - @classmethod - def mutate_and_get_payload(cls, input, info): - global my_id - my_id = input.get('to', my_id + 1) - return ChangeNumber(result=my_id) - - -class ChangeNumberContext(relay.ClientIDMutation): - '''Result mutation''' - class Input: - to = graphene.Int() - - result = graphene.String() - - @classmethod - @with_context - def mutate_and_get_payload(cls, input, context, info): - global my_id_context - my_id_context = input.get('to', my_id_context + context) - return ChangeNumber(result=my_id_context) - - -class MyResultMutation(graphene.ObjectType): - change_number = graphene.Field(ChangeNumber) - change_number_context = graphene.Field(ChangeNumberContext) - - -schema = Schema(query=Query, mutation=MyResultMutation) - - -def test_mutation_arguments(): - assert ChangeNumber.arguments - assert 'input' in schema.T(ChangeNumber.arguments) - inner_type = ChangeNumber.input_type - client_mutation_id_field = inner_type._meta.fields_map[ - 'clientMutationId'] - assert issubclass(inner_type, graphene.InputObjectType) - assert isinstance(client_mutation_id_field.type, graphene.NonNull) - assert isinstance(client_mutation_id_field.type.of_type, graphene.String) - assert client_mutation_id_field.object_type == inner_type - assert isinstance(schema.T(client_mutation_id_field), GraphQLInputObjectField) - - -def test_execute_mutations(): - query = ''' - mutation M{ - first: changeNumber(input: {clientMutationId: "mutation1"}) { - clientMutationId - result - }, - second: changeNumber(input: {clientMutationId: "mutation2"}) { - clientMutationId - result - } - third: changeNumber(input: {clientMutationId: "mutation3", to: 5}) { - result - clientMutationId - } - } - ''' - expected = { - 'first': { - 'clientMutationId': 'mutation1', - 'result': '1', - }, - 'second': { - 'clientMutationId': 'mutation2', - 'result': '2', - }, - 'third': { - 'clientMutationId': 'mutation3', - 'result': '5', - } - } - result = schema.execute(query, root_value=object()) - assert not result.errors - assert result.data == expected - - -def test_context_mutations(): - query = ''' - mutation M{ - first: changeNumberContext(input: {clientMutationId: "mutation1"}) { - clientMutationId - result - }, - second: changeNumberContext(input: {clientMutationId: "mutation2"}) { - clientMutationId - result - } - third: changeNumberContext(input: {clientMutationId: "mutation3", to: 5}) { - result - clientMutationId - } - } - ''' - expected = { - 'first': { - 'clientMutationId': 'mutation1', - 'result': '-1', - }, - 'second': { - 'clientMutationId': 'mutation2', - 'result': '-2', - }, - 'third': { - 'clientMutationId': 'mutation3', - 'result': '5', - } - } - result = schema.execute(query, root_value=object(), context_value=-1) - assert not result.errors - assert result.data == expected diff --git a/graphene/relay/tests/test_node.py b/graphene/relay/tests/test_node.py new file mode 100644 index 00000000..e5b4b961 --- /dev/null +++ b/graphene/relay/tests/test_node.py @@ -0,0 +1,61 @@ +import pytest + +from graphql_relay import to_global_id + +from ..node import Node +from ...types import ObjectType, Schema, implements +from ...types.scalars import String + + +@implements(Node) +class MyNode(ObjectType): + name = String() + + @staticmethod + def get_node(id, *_): + return MyNode(name=str(id)) + + +class RootQuery(ObjectType): + node = Node.Field + +schema = Schema(query=RootQuery, types=[MyNode]) + + +def test_node_no_get_node(): + with pytest.raises(AssertionError) as excinfo: + @implements(Node) + class MyNode(ObjectType): + pass + + assert "MyNode.get_node method is required by the Node interface." == str(excinfo.value) + + +def test_node_no_get_node(): + with pytest.raises(AssertionError) as excinfo: + class MyNode(ObjectType): + class Meta: + interfaces = [Node] + + assert "MyNode.get_node method is required by the Node interface." == str(excinfo.value) + + +def test_node_good(): + graphql_type = MyNode._meta.graphql_type + assert 'id' in graphql_type.get_fields() + + +def test_node_query(): + executed = schema.execute( + '{ node(id:"%s") { ... on MyNode { name } } }' % to_global_id("MyNode", 1) + ) + assert not executed.errors + assert executed.data == {'node': {'name': '1'}} + + +def test_node_query_incorrect_id(): + executed = schema.execute( + '{ node(id:"%s") { ... on MyNode { name } } }' % "something:2" + ) + assert not executed.errors + assert executed.data == {'node': None} diff --git a/graphene/relay/tests/test_node_custom.py b/graphene/relay/tests/test_node_custom.py new file mode 100644 index 00000000..7b24855b --- /dev/null +++ b/graphene/relay/tests/test_node_custom.py @@ -0,0 +1,299 @@ +from graphql import graphql + +from ..node import Node +from ...types import ObjectType, Schema, implements +from ...types.scalars import String, Int + + +class CustomNode(Node): + @staticmethod + def get_node(id, context, info): + assert info.schema == schema + if id in user_data: + return user_data.get(id) + else: + return photo_data.get(id) + + +@implements(CustomNode) +class User(ObjectType): + name = String() + + +@implements(CustomNode) +class Photo(ObjectType): + width = Int() + + +user_data = { + '1': User(id='1', name='John Doe'), + '2': User(id='2', name='Jane Smith'), +} + +photo_data = { + '3': Photo(id='3', width=300), + '4': Photo(id='4', width=400), +} + + +class RootQuery(ObjectType): + node = CustomNode.Field + +schema = Schema(query=RootQuery, types=[User, Photo]) + + +def test_gets_the_correct_id_for_users(): + query = ''' + { + node(id: "1") { + id + } + } + ''' + expected = { + 'node': { + 'id': '1', + } + } + result = graphql(schema, query) + assert not result.errors + assert result.data == expected + + +def test_gets_the_correct_id_for_photos(): + query = ''' + { + node(id: "4") { + id + } + } + ''' + expected = { + 'node': { + 'id': '4', + } + } + result = graphql(schema, query) + assert not result.errors + assert result.data == expected + + +def test_gets_the_correct_name_for_users(): + query = ''' + { + node(id: "1") { + id + ... on User { + name + } + } + } + ''' + expected = { + 'node': { + 'id': '1', + 'name': 'John Doe' + } + } + result = graphql(schema, query) + assert not result.errors + assert result.data == expected + + +def test_gets_the_correct_width_for_photos(): + query = ''' + { + node(id: "4") { + id + ... on Photo { + width + } + } + } + ''' + expected = { + 'node': { + 'id': '4', + 'width': 400 + } + } + result = graphql(schema, query) + assert not result.errors + assert result.data == expected + + +def test_gets_the_correct_typename_for_users(): + query = ''' + { + node(id: "1") { + id + __typename + } + } + ''' + expected = { + 'node': { + 'id': '1', + '__typename': 'User' + } + } + result = graphql(schema, query) + assert not result.errors + assert result.data == expected + + +def test_gets_the_correct_typename_for_photos(): + query = ''' + { + node(id: "4") { + id + __typename + } + } + ''' + expected = { + 'node': { + 'id': '4', + '__typename': 'Photo' + } + } + result = graphql(schema, query) + assert not result.errors + assert result.data == expected + + +def test_ignores_photo_fragments_on_user(): + query = ''' + { + node(id: "1") { + id + ... on Photo { + width + } + } + } + ''' + expected = { + 'node': { + 'id': '1', + } + } + result = graphql(schema, query) + assert not result.errors + assert result.data == expected + + +def test_returns_null_for_bad_ids(): + query = ''' + { + node(id: "5") { + id + } + } + ''' + expected = { + 'node': None + } + result = graphql(schema, query) + assert not result.errors + assert result.data == expected + + +def test_have_correct_node_interface(): + query = ''' + { + __type(name: "Node") { + name + kind + fields { + name + type { + kind + ofType { + name + kind + } + } + } + } + } + ''' + expected = { + '__type': { + 'name': 'Node', + 'kind': 'INTERFACE', + 'fields': [ + { + 'name': 'id', + 'type': { + 'kind': 'NON_NULL', + 'ofType': { + 'name': 'ID', + 'kind': 'SCALAR' + } + } + } + ] + } + } + result = graphql(schema, query) + assert not result.errors + assert result.data == expected + + +def test_has_correct_node_root_field(): + query = ''' + { + __schema { + queryType { + fields { + name + type { + name + kind + } + args { + name + type { + kind + ofType { + name + kind + } + } + } + } + } + } + } + ''' + expected = { + '__schema': { + 'queryType': { + 'fields': [ + { + 'name': 'node', + 'type': { + 'name': 'Node', + 'kind': 'INTERFACE' + }, + 'args': [ + { + 'name': 'id', + 'type': { + 'kind': 'NON_NULL', + 'ofType': { + 'name': 'ID', + 'kind': 'SCALAR' + } + } + } + ] + } + ] + } + } + } + result = graphql(schema, query) + assert not result.errors + assert result.data == expected diff --git a/graphene/relay/tests/test_query.py b/graphene/relay/tests/test_query.py deleted file mode 100644 index 08e57226..00000000 --- a/graphene/relay/tests/test_query.py +++ /dev/null @@ -1,184 +0,0 @@ -import pytest -from graphql.type import GraphQLID, GraphQLNonNull - -import graphene -from graphene import relay, with_context - -schema = graphene.Schema() - - -class MyConnection(relay.Connection): - my_custom_field = graphene.String( - resolver=lambda instance, *_: 'Custom') - - -class MyNode(relay.Node): - name = graphene.String() - - @classmethod - def get_node(cls, id, info): - return MyNode(id=id, name='mo') - - -class SpecialNode(relay.Node): - value = graphene.String() - - @classmethod - @with_context - def get_node(cls, id, context, info): - value = "!!!" if context.get('is_special') else "???" - return SpecialNode(id=id, value=value) - - -class Query(graphene.ObjectType): - my_node = relay.NodeField(MyNode) - my_node_lazy = relay.NodeField('MyNode') - special_node = relay.NodeField(SpecialNode) - all_my_nodes = relay.ConnectionField( - MyNode, connection_type=MyConnection, customArg=graphene.String()) - - context_nodes = relay.ConnectionField( - MyNode, connection_type=MyConnection, customArg=graphene.String()) - - def resolve_all_my_nodes(self, args, info): - custom_arg = args.get('customArg') - assert custom_arg == "1" - return [MyNode(name='my')] - - @with_context - def resolve_context_nodes(self, args, context, info): - custom_arg = args.get('customArg') - assert custom_arg == "1" - return [MyNode(name='my')] - -schema.query = Query - - -def test_nodefield_query(): - query = ''' - query RebelsShipsQuery { - contextNodes (customArg:"1") { - edges { - node { - name - } - }, - myCustomField - pageInfo { - hasNextPage - } - } - } - ''' - expected = { - 'contextNodes': { - 'edges': [{ - 'node': { - 'name': 'my' - } - }], - 'myCustomField': 'Custom', - 'pageInfo': { - 'hasNextPage': False, - } - } - } - result = schema.execute(query) - assert not result.errors - assert result.data == expected - - -def test_connectionfield_context_query(): - query = ''' - query RebelsShipsQuery { - myNode(id:"TXlOb2RlOjE=") { - id - name - }, - false: myNode(id:"WrongNodeId") { - id - name - }, - allMyNodes (customArg:"1") { - edges { - node { - name - } - }, - myCustomField - pageInfo { - hasNextPage - } - } - } - ''' - expected = { - 'myNode': { - 'id': 'TXlOb2RlOjE=', - 'name': 'mo' - }, - 'false': None, - 'allMyNodes': { - 'edges': [{ - 'node': { - 'name': 'my' - } - }], - 'myCustomField': 'Custom', - 'pageInfo': { - 'hasNextPage': False, - } - } - } - result = schema.execute(query) - assert not result.errors - assert result.data == expected - - -@pytest.mark.parametrize('specialness,value', [(True, '!!!'), (False, '???')]) -def test_get_node_info(specialness, value): - query = ''' - query ValueQuery { - specialNode(id:"U3BlY2lhbE5vZGU6Mg==") { - id - value - } - } - ''' - - expected = { - 'specialNode': { - 'id': 'U3BlY2lhbE5vZGU6Mg==', - 'value': value, - }, - } - result = schema.execute(query, context_value={'is_special': specialness}) - assert not result.errors - assert result.data == expected - - -def test_nodeidfield(): - id_field = MyNode._meta.fields_map['id'] - id_field_type = schema.T(id_field) - assert isinstance(id_field_type.type, GraphQLNonNull) - assert id_field_type.type.of_type == GraphQLID - - -def test_nodefield_lazy_query(): - query = ''' - query RebelsShipsQuery { - myNode(id:"TXlOb2RlOjE=") { - id - name - }, - myNodeLazy(id:"TXlOb2RlOjE=") { - id - name - }, - - } - ''' - result = schema.execute(query) - assert not result.errors - assert result.data['myNode'] == result.data['myNodeLazy'], \ - "NodeField with object_type direct reference and with object_type string name should not differ." diff --git a/graphene/relay/tests/test_types.py b/graphene/relay/tests/test_types.py deleted file mode 100644 index 5b1dc6f7..00000000 --- a/graphene/relay/tests/test_types.py +++ /dev/null @@ -1,102 +0,0 @@ -from graphql.type import GraphQLList -from pytest import raises - -import graphene -from graphene import relay - -schema = graphene.Schema() - - -class OtherNode(relay.Node): - name = graphene.String() - - @classmethod - def get_node(cls, id, info): - pass - - -def test_works_old_get_node(): - class Part(relay.Node): - x = graphene.String() - - @classmethod - def get_node(cls, id): - return id - - assert Part.get_node(1) == 1 - - -def test_works_old_static_get_node(): - class Part(relay.Node): - x = graphene.String() - - @staticmethod - def get_node(id): - return id - - assert Part.get_node(1) == 1 - - -def test_field_no_contributed_raises_error(): - with raises(Exception) as excinfo: - class Part(relay.Node): - x = graphene.String() - - assert 'get_node' in str(excinfo.value) - - -def test_node_should_have_same_connection_always(): - connection1 = relay.Connection.for_node(OtherNode) - connection2 = relay.Connection.for_node(OtherNode) - - assert connection1 == connection2 - - -def test_node_should_have_id_field(): - assert 'id' in OtherNode._meta.fields_map - - -def test_node_connection_should_have_edge(): - connection = relay.Connection.for_node(OtherNode) - edge = relay.Edge.for_node(OtherNode) - connection_type = schema.T(connection) - connection_fields = connection_type.get_fields() - assert 'edges' in connection_fields - assert 'pageInfo' in connection_fields - edges_type = connection_fields['edges'].type - assert isinstance(edges_type, GraphQLList) - assert edges_type.of_type == schema.T(edge) - - -def test_client_mutation_id(): - class RemoveWidget(relay.ClientIDMutation): - - class Input: - id = graphene.String(required=True) - - deletedWidgetID = graphene.String() - - @classmethod - def mutate_and_get_payload(cls, input, info): - pass - - graphql_type = schema.T(RemoveWidget) - assert graphql_type.name == 'RemoveWidgetPayload' - - -def test_client_mutation_id_with_name(): - class RemoveWidget(relay.ClientIDMutation): - class Meta: - type_name = 'RemoveWidgetCustomPayload' - - class Input: - id = graphene.String(required=True) - - deletedWidgetID = graphene.String() - - @classmethod - def mutate_and_get_payload(cls, input, info): - pass - - graphql_type = schema.T(RemoveWidget) - assert graphql_type.name == 'RemoveWidgetCustomPayload' diff --git a/graphene/relay/types.py b/graphene/relay/types.py deleted file mode 100644 index 3ab55770..00000000 --- a/graphene/relay/types.py +++ /dev/null @@ -1,206 +0,0 @@ -import inspect -import warnings -from collections import Iterable -from functools import wraps - -import six - -from graphql_relay.connection.arrayconnection import connection_from_list -from graphql_relay.node.node import to_global_id - -from ..core.classtypes import InputObjectType, Interface, Mutation, ObjectType -from ..core.classtypes.interface import InterfaceMeta -from ..core.classtypes.mutation import MutationMeta -from ..core.types import Boolean, Field, List, String -from ..core.types.argument import ArgumentsGroup -from ..core.types.definitions import NonNull -from ..utils import memoize -from ..utils.wrap_resolver_function import has_context, with_context -from .fields import GlobalIDField - - -class PageInfo(ObjectType): - - def __init__(self, start_cursor="", end_cursor="", - has_previous_page=False, has_next_page=False, **kwargs): - super(PageInfo, self).__init__(**kwargs) - self.startCursor = start_cursor - self.endCursor = end_cursor - self.hasPreviousPage = has_previous_page - self.hasNextPage = has_next_page - - hasNextPage = Boolean( - required=True, - description='When paginating forwards, are there more items?') - hasPreviousPage = Boolean( - required=True, - description='When paginating backwards, are there more items?') - startCursor = String( - description='When paginating backwards, the cursor to continue.') - endCursor = String( - description='When paginating forwards, the cursor to continue.') - - -class Edge(ObjectType): - '''An edge in a connection.''' - cursor = String( - required=True, description='A cursor for use in pagination') - - @classmethod - @memoize - def for_node(cls, node): - from graphene.relay.utils import is_node - assert is_node(node), 'ObjectTypes in a edge have to be Nodes' - node_field = Field(node, description='The item at the end of the edge') - return type( - '%s%s' % (node._meta.type_name, cls._meta.type_name), - (cls,), - {'node_type': node, 'node': node_field}) - - -class Connection(ObjectType): - '''A connection to a list of items.''' - - def __init__(self, edges, page_info, **kwargs): - super(Connection, self).__init__(**kwargs) - self.edges = edges - self.pageInfo = page_info - - class Meta: - type_name = 'DefaultConnection' - - pageInfo = Field(PageInfo, required=True, - description='The Information to aid in pagination') - - _connection_data = None - - @classmethod - @memoize - def for_node(cls, node, edge_type=None): - from graphene.relay.utils import is_node - edge_type = edge_type or Edge.for_node(node) - assert is_node(node), 'ObjectTypes in a connection have to be Nodes' - edges = List(edge_type, description='Information to aid in pagination.') - return type( - '%s%s' % (node._meta.type_name, cls._meta.type_name), - (cls,), - {'edge_type': edge_type, 'edges': edges}) - - @classmethod - def from_list(cls, iterable, args, context, info): - assert isinstance( - iterable, Iterable), 'Resolved value from the connection field have to be iterable' - connection = connection_from_list( - iterable, args, connection_type=cls, - edge_type=cls.edge_type, pageinfo_type=PageInfo) - connection.set_connection_data(iterable) - return connection - - def set_connection_data(self, data): - self._connection_data = data - - def get_connection_data(self): - return self._connection_data - - -class NodeMeta(InterfaceMeta): - - def construct_get_node(cls): - get_node = getattr(cls, 'get_node', None) - assert get_node, 'get_node classmethod not found in %s Node' % cls - assert callable(get_node), 'get_node have to be callable' - args = 4 - if isinstance(get_node, staticmethod): - args -= 1 - - get_node_num_args = len(inspect.getargspec(get_node).args) - if get_node_num_args < args: - warnings.warn("get_node will receive also the info arg" - " in future versions of graphene".format(cls.__name__), - FutureWarning) - - @staticmethod - @wraps(get_node) - def wrapped_node(id, context=None, info=None): - node_args = [id, info, context] - if has_context(get_node): - return get_node(*node_args[:get_node_num_args - 1], context=context) - if get_node_num_args - 1 == 0: - return get_node(id) - return get_node(*node_args[:get_node_num_args - 1]) - node_func = wrapped_node - setattr(cls, 'get_node', node_func) - - def construct(cls, *args, **kwargs): - cls = super(NodeMeta, cls).construct(*args, **kwargs) - if not cls._meta.abstract: - cls.construct_get_node() - return cls - - -class Node(six.with_metaclass(NodeMeta, Interface)): - '''An object with an ID''' - id = GlobalIDField() - - class Meta: - abstract = True - - @classmethod - def global_id(cls, id): - type_name = cls._meta.type_name - return to_global_id(type_name, id) - - def to_global_id(self): - return self.global_id(self.id) - - connection_type = Connection - edge_type = Edge - - @classmethod - def get_connection_type(cls): - return cls.connection_type - - @classmethod - def get_edge_type(cls): - return cls.edge_type - - -class MutationInputType(InputObjectType): - clientMutationId = String(required=True) - - -class RelayMutationMeta(MutationMeta): - - def construct(cls, *args, **kwargs): - cls = super(RelayMutationMeta, cls).construct(*args, **kwargs) - if not cls._meta.abstract: - assert hasattr( - cls, 'mutate_and_get_payload'), 'You have to implement mutate_and_get_payload' - if 'type_name' not in cls._meta.original_attrs: - cls._meta.type_name = '{}Payload'.format(cls.__name__) - return cls - - def construct_arguments(cls, items): - new_input_type = type('{}Input'.format( - cls._meta.type_name), (MutationInputType, ), items) - cls.add_to_class('input_type', new_input_type) - return ArgumentsGroup(input=NonNull(new_input_type)) - - -class ClientIDMutation(six.with_metaclass(RelayMutationMeta, Mutation)): - clientMutationId = String(required=True) - - class Meta: - abstract = True - - @classmethod - @with_context - def mutate(cls, instance, args, context, info): - input = args.get('input') - if has_context(cls.mutate_and_get_payload): - payload = cls.mutate_and_get_payload(input, context, info) - else: - payload = cls.mutate_and_get_payload(input, info) - client_mutation_id = input.get('clientMutationId') or input.get('client_mutation_id') - setattr(payload, 'clientMutationId', client_mutation_id) - return payload diff --git a/graphene/relay/utils.py b/graphene/relay/utils.py deleted file mode 100644 index 98cb81dd..00000000 --- a/graphene/relay/utils.py +++ /dev/null @@ -1,11 +0,0 @@ -from .types import Node - - -def is_node(object_type): - return object_type and issubclass( - object_type, Node) and not object_type._meta.abstract - - -def is_node_type(object_type): - return object_type and issubclass( - object_type, Node) and object_type._meta.abstract diff --git a/graphene/signals.py b/graphene/signals.py deleted file mode 100644 index 3183eeec..00000000 --- a/graphene/signals.py +++ /dev/null @@ -1,12 +0,0 @@ -try: - from blinker import Signal -except ImportError: - class Signal(object): - - def send(self, *args, **kwargs): - pass - -init_schema = Signal() -class_prepared = Signal() -pre_init = Signal() -post_init = Signal() diff --git a/graphene/types/__init__.py b/graphene/types/__init__.py new file mode 100644 index 00000000..7357ace4 --- /dev/null +++ b/graphene/types/__init__.py @@ -0,0 +1,7 @@ +from .objecttype import ObjectType, implements +from .interface import Interface +from .scalars import Scalar +from .schema import Schema +from .field import Field + +__all__ = ['ObjectType', 'Interface', 'implements', 'Field', 'Schema', 'Scalar'] diff --git a/graphene/types/definitions.py b/graphene/types/definitions.py new file mode 100644 index 00000000..a5ce29f7 --- /dev/null +++ b/graphene/types/definitions.py @@ -0,0 +1,212 @@ +from collections import OrderedDict +import inspect +import copy + +from graphql.utils.assert_valid_name import assert_valid_name +from graphql.type.definition import GraphQLObjectType, GraphQLInterfaceType, GraphQLScalarType + +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)) + if new_class._meta.name: + assert_valid_name(new_class._meta.name) + new_class.construct_graphql_type(bases) + + return mcs.construct(new_class, bases, attrs) + + def get_options(cls, meta): + raise NotImplementedError("get_options is not implemented") + + def construct_graphql_type(cls, bases): + raise NotImplementedError("construct_graphql_type is not implemented") + + 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 + + return cls + + +class GrapheneType(object): + def __init__(self, *args, **kwargs): + self.graphene_type = kwargs.pop('graphene_type') + self._name = None + self._description = None + super(GrapheneType, self).__init__(*args, **kwargs) + + @property + def name(self): + return self._name or self.graphene_type.__name__ + + @name.setter + def name(self, name): + self._name = name + + @property + def description(self): + return self._description or self.graphene_type.__doc__ + + @description.setter + def description(self, description): + self._description = description + + +class GrapheneFieldsType(GrapheneType): + def __init__(self, *args, **kwargs): + self._fields = None + self._field_map = None + super(GrapheneFieldsType, self).__init__(*args, **kwargs) + + def add_field(self, field): + # We clear the cached fields + self._field_map = None + self._fields.add(field) + + +class FieldMap(object): + def __init__(self, parent, bases=None, fields=None): + self.parent = parent + self.fields = fields or [] + self.bases = bases or [] + + def add(self, field): + self.fields.append(field) + + def __call__(self): + # It's in a call function for assuring that if a field is added + # in runtime then it will be reflected in the Class type fields + # If we add the field in the class type creation, then we + # would not be able to change it later. + from .field import Field + prev_fields = [] + graphql_type = self.parent._meta.graphql_type + + # We collect the fields from the interfaces + if isinstance(graphql_type, GraphQLObjectType): + interfaces = graphql_type.get_interfaces() + for interface in interfaces: + prev_fields += interface.get_fields().items() + + # We collect the fields from the bases + for base in self.bases: + prev_fields += base.get_fields().items() + + fields = prev_fields + [ + (field.name, field) for field in sorted(self.fields) + ] + + # Then we copy all the fields and assign the parent + new_fields = [] + for field_name, field in fields: + field = copy.copy(field) + if isinstance(field, Field): + field.parent = self.parent + new_fields.append((field_name, field)) + + return OrderedDict(new_fields) + + +class GrapheneObjectType(GrapheneFieldsType, GraphQLObjectType): + __slots__ = ('graphene_type', '_name', '_description', '_fields', '_field_map', '_is_type_of', '_provided_interfaces', '_interfaces') + + @property + def is_type_of(self): + return self._is_type_of or self.default_is_type_of + + @is_type_of.setter + def is_type_of(self, is_type_of): + self._is_type_of = is_type_of + + def default_is_type_of(self, interface, context, info): + from ..utils.get_graphql_type import get_graphql_type + try: + graphql_type = get_graphql_type(type(interface)) + return graphql_type == self or graphql_type in self._provided_interfaces + except: + return False + + def add_interface(self, interface): + from ..utils.get_graphql_type import get_graphql_type + # We clear the cached interfaces + self._interfaces = None + # We clear the cached fields as could be inherited from interfaces + self._field_map = None + self._provided_interfaces.append(get_graphql_type(interface)) + + +class GrapheneInterfaceType(GrapheneFieldsType, GraphQLInterfaceType): + __slots__ = ('graphene_type', '_name', '_description', '_fields', '_field_map', 'resolve_type') + + +class GrapheneScalarType(GrapheneType, GraphQLScalarType): + __slots__ = ('graphene_type', '_name', '_description', '_serialize', '_parse_value', '_parse_literal') + + def __init__(self, *args, **kwargs): + GrapheneType.__init__(self, *args, **kwargs) + + @staticmethod + def default_parse(value): + return None + + def setup(self): + serialize = getattr(self.graphene_type, 'serialize', None) + parse_value = getattr(self.graphene_type, 'parse_value', None) + parse_literal = getattr(self.graphene_type, 'parse_literal', None) + + assert callable(serialize), ( + '{} must provide "serialize" function. If this custom Scalar is ' + 'also used as an input type, ensure "parse_value" and "parse_literal" ' + 'functions are also provided.' + ).format(self) + + if parse_value is not None or parse_literal is not None: + assert callable(parse_value) and callable(parse_literal), ( + '{} must provide both "parse_value" and "parse_literal" functions.'.format(self) + ) + + self._serialize = serialize + self._parse_value = parse_value + self._parse_literal = parse_literal + + @property + def serialize(self): + return self.graphene_type.serialize + + @property + def parse_value(self): + return getattr(self.graphene_type, 'parse_value', self.default_parse) + + @property + def parse_literal(self): + return getattr(self.graphene_type, 'parse_literal', self.default_parse) diff --git a/graphene/types/field.py b/graphene/types/field.py new file mode 100644 index 00000000..9ce3fb86 --- /dev/null +++ b/graphene/types/field.py @@ -0,0 +1,76 @@ +import inspect + +from graphql import GraphQLField +from graphql.utils.assert_valid_name import assert_valid_name + +from .objecttype import ObjectType +from .interface import Interface +from ..utils.orderedtype import OrderedType + + +class Field(GraphQLField, OrderedType): + __slots__ = ('_name', '_type', '_args', '_resolver', 'deprecation_reason', 'description', 'source', '_extra_args', 'attname', 'parent', 'creation_counter') + + def __init__(self, type, args=None, resolver=None, source=None, deprecation_reason=None, name=None, description=None, **extra_args): + self.name = name + self.attname = None + self.parent = None + self.type = type + self.args = args + assert not (source and resolver), ('You cannot have a source ' + 'and a resolver at the same time') + + self.resolver = resolver + self.source = source + self.deprecation_reason = deprecation_reason + self.description = description + self._extra_args = extra_args + OrderedType.__init__(self) + + def contribute_to_class(self, cls, attname): + assert issubclass(cls, (ObjectType, Interface)), 'Field {} cannot be mounted in {}'.format( + self, + cls + ) + self.attname = attname + self.parent = cls + add_field = getattr(cls._meta.graphql_type, "add_field", None) + assert add_field, "Field {} cannot be mounted in {}".format(self, cls) + add_field(self) + + @property + def name(self): + return self._name or self.attname + + @name.setter + def name(self, name): + if name is not None: + assert_valid_name(name) + self._name = name + + @property + def type(self): + from ..utils.get_graphql_type import get_graphql_type + if inspect.isfunction(self._type): + return get_graphql_type(self._type()) + return get_graphql_type(self._type) + + @type.setter + def type(self, type): + self._type = type + + @property + def args(self): + return self._args + + @args.setter + def args(self, args): + self._args = args + + @property + def resolver(self): + return self._resolver or getattr(self.parent(), 'resolve_{}'.format(self.attname), None) + + @resolver.setter + def resolver(self, resolver): + self._resolver = resolver diff --git a/graphene/types/interface.py b/graphene/types/interface.py new file mode 100644 index 00000000..3ebf90b2 --- /dev/null +++ b/graphene/types/interface.py @@ -0,0 +1,48 @@ +import six + +from .definitions import ClassTypeMeta, GrapheneInterfaceType, FieldMap + + +class InterfaceTypeMeta(ClassTypeMeta): + + def get_options(cls, meta): + options = cls.options_class( + meta, + name=None, + description=None, + graphql_type=None, + ) + options.valid_attrs = ['graphql_type', 'name', 'description', 'abstract'] + return options + + def construct_graphql_type(cls, bases): + if not cls._meta.graphql_type and not cls._meta.abstract: + from ..utils.is_graphene_type import is_graphene_type + inherited_types = [ + base._meta.graphql_type for base in bases if is_graphene_type(base) + ] + + cls._meta.graphql_type = GrapheneInterfaceType( + graphene_type=cls, + name=cls._meta.name or cls.__name__, + description=cls._meta.description, + fields=FieldMap(cls, bases=filter(None, inherited_types)), + ) + + +class Interface(six.with_metaclass(InterfaceTypeMeta)): + class Meta: + abstract = True + + def __init__(self, *args, **kwargs): + raise Exception("An interface cannot be intitialized") + + @classmethod + def implements(cls, object_type): + ''' + We use this function for customizing what the @implements function do + for each implementation. + For example, if we want to check that the class have some required things + in it like Node.get_node + ''' + object_type._meta.graphql_type.add_interface(cls) diff --git a/graphene/types/objecttype.py b/graphene/types/objecttype.py new file mode 100644 index 00000000..afcba8f7 --- /dev/null +++ b/graphene/types/objecttype.py @@ -0,0 +1,88 @@ +import six + +from .definitions import ClassTypeMeta, GrapheneObjectType, GrapheneInterfaceType, FieldMap + + +class ObjectTypeMeta(ClassTypeMeta): + + def get_options(cls, meta): + options = cls.options_class( + meta, + name=None, + description=None, + graphql_type=None, + interfaces=[], + ) + options.valid_attrs = ['graphql_type', 'name', 'description', 'interfaces', 'abstract'] + return options + + def construct_graphql_type(cls, bases): + if not cls._meta.graphql_type and not cls._meta.abstract: + from ..utils.get_graphql_type import get_graphql_type + from ..utils.is_graphene_type import is_graphene_type + inherited_types = [ + base._meta.graphql_type for base in bases if is_graphene_type(base) + ] + + cls._meta.graphql_type = GrapheneObjectType( + graphene_type=cls, + name=cls._meta.name or cls.__name__, + description=cls._meta.description, + fields=FieldMap(cls, bases=filter(None, inherited_types)), + interfaces=map(get_graphql_type, cls._meta.interfaces), + ) + + +def implements(*interfaces): + from ..utils.get_graphql_type import get_graphql_type + + def wrap_class(cls): + for i in interfaces: + graphql_type = get_graphql_type(i) + if isinstance(graphql_type, GrapheneInterfaceType): + graphql_type.graphene_type.implements(cls) + else: + # We add the interface in the regular way + cls._meta.graphql_type.add_interface(graphql_type) + return cls + return wrap_class + + +class ObjectType(six.with_metaclass(ObjectTypeMeta)): + class Meta: + abstract = True + + def __init__(self, *args, **kwargs): + args_len = len(args) + fields = self._meta.graphql_type.get_fields().values() + 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.name, val) + else: + for val, field in zip(args, fields_iter): + setattr(self, field.name, val) + kwargs.pop(field.name, None) + + for field in fields_iter: + try: + val = kwargs.pop(field.name) + setattr(self, field.name, 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]) diff --git a/graphene/core/classtypes/options.py b/graphene/types/options.py similarity index 79% rename from graphene/core/classtypes/options.py rename to graphene/types/options.py index 52e4536e..be179d71 100644 --- a/graphene/core/classtypes/options.py +++ b/graphene/types/options.py @@ -3,18 +3,17 @@ class Options(object): def __init__(self, meta=None, **defaults): self.meta = meta self.abstract = False + self.parent = None for name, value in defaults.items(): setattr(self, name, value) - self.valid_attrs = list(defaults.keys()) + ['type_name', 'description', 'abstract'] + self.valid_attrs = [] def contribute_to_class(self, cls, name): cls._meta = self self.parent = cls - # First, construct the default values for these options. - self.object_name = cls.__name__ - self.type_name = self.object_name + self.validate_attrs() - self.description = cls.__doc__ + def validate_attrs(self): # Store the original user-defined values for each option, # for use when serializing the model definition self.original_attrs = {} @@ -41,8 +40,9 @@ class Options(object): # Any leftover attributes must be invalid. if meta_attrs != {}: raise TypeError( - "'class Meta' got invalid attribute(s): %s" % - ','.join( - meta_attrs.keys())) + "{}.Meta got invalid attributes: {}".format( + self.parent.__name__, + ','.join(meta_attrs.keys())) + ) del self.meta diff --git a/graphene/types/scalars.py b/graphene/types/scalars.py new file mode 100644 index 00000000..918abd74 --- /dev/null +++ b/graphene/types/scalars.py @@ -0,0 +1,69 @@ +import six +from graphql import GraphQLScalarType, GraphQLString, GraphQLInt, GraphQLFloat, GraphQLBoolean + +from .definitions import ClassTypeMeta, GrapheneScalarType +from .field import Field + + +class ScalarTypeMeta(ClassTypeMeta): + + def get_options(cls, meta): + options = cls.options_class( + meta, + name=None, + description=None, + graphql_type=None, + ) + options.valid_attrs = ['graphql_type', 'name', 'description', 'abstract'] + return options + + def construct_graphql_type(cls, bases): + if not cls._meta.graphql_type and not cls._meta.abstract: + cls._meta.graphql_type = GrapheneScalarType( + graphene_type=cls, + name=cls._meta.name or cls.__name__, + description=cls._meta.description, + # For passing the assertion in GraphQLScalarType + serialize=lambda: None + ) + + def construct(cls, *args, **kwargs): + constructed = super(ScalarTypeMeta, cls).construct(*args, **kwargs) + if isinstance(cls._meta.graphql_type, GrapheneScalarType): + cls._meta.graphql_type.setup() + return constructed + + +class Scalar(six.with_metaclass(ScalarTypeMeta)): + class Meta: + abstract = True + + def __init__(self, *args, **kwargs): + self.args = args + self.kwargs = kwargs + + def as_field(self): + return Field( + lambda: self._meta.graphql_type, + *self.args, + **self.kwargs + ) + + def contribute_to_class(self, cls, attname): + field = self.as_field() + return field.contribute_to_class(cls, attname) + + +def construct_scalar_class(graphql_type): + # This is equivalent to + # class String(Scalar): + # class Meta: + # graphql_type = graphql_type + Meta = type('Meta', (object,), {'graphql_type':graphql_type}) + return type(graphql_type.name, (Scalar, ), {'Meta': Meta}) + + +String = construct_scalar_class(GraphQLString) +Int = construct_scalar_class(GraphQLInt) +Float = construct_scalar_class(GraphQLFloat) +Boolean = construct_scalar_class(GraphQLBoolean) diff --git a/graphene/types/schema.py b/graphene/types/schema.py new file mode 100644 index 00000000..f1c04a28 --- /dev/null +++ b/graphene/types/schema.py @@ -0,0 +1,54 @@ +from graphql import graphql, GraphQLSchema +from graphql.utils.introspection_query import introspection_query +from graphql.utils.schema_printer import print_schema + +from ..utils.get_graphql_type import get_graphql_type + + +class Schema(GraphQLSchema): + __slots__ = '_query', '_mutation', '_subscription', '_type_map', '_directives', '_implementations', '_possible_type_map', '_types', '_executor' + + def __init__(self, query=None, mutation=None, subscription=None, directives=None, types=None, executor=None): + if query: + query = get_graphql_type(query) + if mutation: + mutation = get_graphql_type(mutation) + if subscription: + subscription = get_graphql_type(subscription) + if types: + types = map(get_graphql_type, types) + self._executor = executor + super(Schema, self).__init__( + query=query, + mutation=mutation, + subscription=subscription, + directives=directives, + types=types + ) + + def execute(self, request_string='', root_value=None, variable_values=None, + context_value=None, operation_name=None, executor=None): + return graphql( + schema=self, + 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 register(self, object_type): + self._types.append(object_type) + + def introspect(self): + return self.execute(introspection_query).data + + def __str__(self): + return print_schema(self) + + def get_type(self, _type): + return self._type_map[_type] + + def lazy(self, _type): + return lambda: self.get_type(_type) diff --git a/graphene/contrib/__init__.py b/graphene/types/tests/__init__.py similarity index 100% rename from graphene/contrib/__init__.py rename to graphene/types/tests/__init__.py diff --git a/graphene/types/tests/test_field.py b/graphene/types/tests/test_field.py new file mode 100644 index 00000000..791af548 --- /dev/null +++ b/graphene/types/tests/test_field.py @@ -0,0 +1,57 @@ +import pytest +import copy + +from graphql import GraphQLString, GraphQLField + +from ..field import Field +from ..objecttype import ObjectType + + +def test_field(): + field = Field(GraphQLString, name="name", description="description") + assert isinstance(field, GraphQLField) + assert field.name == "name" + assert field.description == "description" + + +def test_field_wrong_name(): + with pytest.raises(AssertionError) as excinfo: + Field(GraphQLString, name="a field") + + assert """Names must match /^[_a-zA-Z][_a-zA-Z0-9]*$/ but "a field" does not.""" == str(excinfo.value) + + +def test_not_source_and_resolver(): + with pytest.raises(AssertionError) as excinfo: + Field(GraphQLString, source="a", resolver=lambda *_:None) + + assert "You cannot have a source and a resolver at the same time" == str(excinfo.value) + + +def test_contributed_field_objecttype(): + class MyObject(ObjectType): + pass + + field = Field(GraphQLString) + field.contribute_to_class(MyObject, 'field_name') + + assert field.name == 'field_name' + + +def test_contributed_field_non_objecttype(): + class MyObject(object): + pass + + field = Field(GraphQLString) + with pytest.raises(AssertionError): + field.contribute_to_class(MyObject, 'field_name') + + +def test_copy_field_works(): + field = Field(GraphQLString) + copy.copy(field) + + +def test_field_callable_type(): + field = Field(lambda: GraphQLString) + assert field.type == GraphQLString diff --git a/graphene/types/tests/test_interface.py b/graphene/types/tests/test_interface.py new file mode 100644 index 00000000..a1f42660 --- /dev/null +++ b/graphene/types/tests/test_interface.py @@ -0,0 +1,84 @@ +import pytest + +from graphql import GraphQLInterfaceType, GraphQLField, GraphQLString, GraphQLInterfaceType + +from ..interface import Interface +from ..field import Field + + +def test_generate_interface(): + class MyInterface(Interface): + '''Documentation''' + pass + + graphql_type = MyInterface._meta.graphql_type + assert isinstance(graphql_type, GraphQLInterfaceType) + assert graphql_type.name == "MyInterface" + assert graphql_type.description == "Documentation" + + +def test_generate_interface_with_meta(): + class MyInterface(Interface): + class Meta: + name = 'MyOtherInterface' + description = 'Documentation' + + graphql_type = MyInterface._meta.graphql_type + assert isinstance(graphql_type, GraphQLInterfaceType) + assert graphql_type.name == "MyOtherInterface" + assert graphql_type.description == "Documentation" + + +def test_empty_interface_has_meta(): + class MyInterface(Interface): + pass + + assert MyInterface._meta + + +def test_generate_interface_with_fields(): + class MyInterface(Interface): + field = Field(GraphQLString) + + graphql_type = MyInterface._meta.graphql_type + fields = graphql_type.get_fields() + assert 'field' in fields + + +def test_interface_inheritance(): + class MyInheritedInterface(Interface): + inherited = Field(GraphQLString) + + class MyInterface(MyInheritedInterface): + field = Field(GraphQLString) + + graphql_type = MyInterface._meta.graphql_type + fields = graphql_type.get_fields() + assert 'field' in fields + assert 'inherited' in fields + assert fields['field'] > fields['inherited'] + + +def test_interface_instance(): + class MyInterface(Interface): + inherited = Field(GraphQLString) + + with pytest.raises(Exception) as excinfo: + MyInterface() + + assert "An interface cannot be intitialized" in str(excinfo.value) + + +def test_interface_add_fields_in_reused_graphql_type(): + MyGraphQLType = GraphQLInterfaceType('MyGraphQLType', fields={ + 'field': GraphQLField(GraphQLString) + }) + + with pytest.raises(AssertionError) as excinfo: + class GrapheneInterface(Interface): + field = Field(GraphQLString) + + class Meta: + graphql_type = MyGraphQLType + + assert "cannot be mounted in" in str(excinfo.value) diff --git a/graphene/types/tests/test_objecttype.py b/graphene/types/tests/test_objecttype.py new file mode 100644 index 00000000..8eb218ba --- /dev/null +++ b/graphene/types/tests/test_objecttype.py @@ -0,0 +1,163 @@ +import pytest + +from graphql import GraphQLObjectType, GraphQLField, GraphQLString, GraphQLInterfaceType + +from ..objecttype import ObjectType +from ..interface import Interface +from ..field import Field + + +class Container(ObjectType): + field1 = Field(GraphQLString) + field2 = Field(GraphQLString) + + +def test_generate_objecttype(): + class MyObjectType(ObjectType): + '''Documentation''' + pass + + graphql_type = MyObjectType._meta.graphql_type + assert isinstance(graphql_type, GraphQLObjectType) + assert graphql_type.name == "MyObjectType" + assert graphql_type.description == "Documentation" + + +def test_generate_objecttype_with_meta(): + class MyObjectType(ObjectType): + class Meta: + name = 'MyOtherObjectType' + description = 'Documentation' + + graphql_type = MyObjectType._meta.graphql_type + assert isinstance(graphql_type, GraphQLObjectType) + assert graphql_type.name == "MyOtherObjectType" + assert graphql_type.description == "Documentation" + + +def test_empty_objecttype_has_meta(): + class MyObjectType(ObjectType): + pass + + assert MyObjectType._meta + + +def test_generate_objecttype_with_fields(): + class MyObjectType(ObjectType): + field = Field(GraphQLString) + + graphql_type = MyObjectType._meta.graphql_type + fields = graphql_type.get_fields() + assert 'field' in fields + + +def test_objecttype_inheritance(): + class MyInheritedObjectType(ObjectType): + inherited = Field(GraphQLString) + + class MyObjectType(MyInheritedObjectType): + field = Field(GraphQLString) + + graphql_type = MyObjectType._meta.graphql_type + fields = graphql_type.get_fields() + assert 'field' in fields + assert 'inherited' in fields + assert fields['field'] > fields['inherited'] + + +def test_objecttype_as_container_only_args(): + container = Container("1", "2") + assert container.field1 == "1" + assert container.field2 == "2" + + +def test_objecttype_as_container_args_kwargs(): + container = Container("1", field2="2") + assert container.field1 == "1" + assert container.field2 == "2" + + +def test_objecttype_as_container_few_kwargs(): + container = Container(field2="2") + assert container.field2 == "2" + + +def test_objecttype_as_container_all_kwargs(): + container = Container(field1="1", field2="2") + assert container.field1 == "1" + assert container.field2 == "2" + + +def test_objecttype_as_container_extra_args(): + with pytest.raises(IndexError) as excinfo: + Container("1", "2", "3") + + assert "Number of args exceeds number of fields" == str(excinfo.value) + + +def test_objecttype_as_container_invalid_kwargs(): + with pytest.raises(TypeError) as excinfo: + Container(unexisting_field="3") + + assert "'unexisting_field' is an invalid keyword argument for this function" == str(excinfo.value) + + +def test_objecttype_reuse_graphql_type(): + MyGraphQLType = GraphQLObjectType('MyGraphQLType', fields={ + 'field': GraphQLField(GraphQLString) + }) + + class GrapheneObjectType(ObjectType): + class Meta: + graphql_type = MyGraphQLType + + graphql_type = GrapheneObjectType._meta.graphql_type + assert graphql_type == MyGraphQLType + instance = GrapheneObjectType(field="A") + assert instance.field == "A" + + +def test_objecttype_add_fields_in_reused_graphql_type(): + MyGraphQLType = GraphQLObjectType('MyGraphQLType', fields={ + 'field': GraphQLField(GraphQLString) + }) + + with pytest.raises(AssertionError) as excinfo: + class GrapheneObjectType(ObjectType): + field = Field(GraphQLString) + + class Meta: + graphql_type = MyGraphQLType + + assert "cannot be mounted in" in str(excinfo.value) + + +def test_objecttype_graphql_interface(): + MyInterface = GraphQLInterfaceType('MyInterface', fields={ + 'field': GraphQLField(GraphQLString) + }) + + class GrapheneObjectType(ObjectType): + class Meta: + interfaces = [MyInterface] + + graphql_type = GrapheneObjectType._meta.graphql_type + assert graphql_type.get_interfaces() == [MyInterface] + # assert graphql_type.is_type_of(MyInterface, None, None) + fields = graphql_type.get_fields() + assert 'field' in fields + + +def test_objecttype_graphene_interface(): + class GrapheneInterface(Interface): + field = Field(GraphQLString) + + class GrapheneObjectType(ObjectType): + class Meta: + interfaces = [GrapheneInterface] + + graphql_type = GrapheneObjectType._meta.graphql_type + assert graphql_type.get_interfaces() == [GrapheneInterface._meta.graphql_type] + assert graphql_type.is_type_of(GrapheneObjectType(), None, None) + fields = graphql_type.get_fields() + assert 'field' in fields diff --git a/graphene/types/tests/test_options.py b/graphene/types/tests/test_options.py new file mode 100644 index 00000000..5d4c334d --- /dev/null +++ b/graphene/types/tests/test_options.py @@ -0,0 +1,49 @@ +import pytest + +from ..options import Options + + +def test_options_defaults(): + class Meta: + overwritten = True + accepted = True + + options = Options(Meta, attr=True, overwritten=False) + options.valid_attrs = ['accepted', 'overwritten'] + options.validate_attrs() + + assert options.attr + assert options.overwritten + + +def test_options_contribute_to_class(): + class Meta: + overwritten = True + accepted = True + + class MyObject(object): + pass + + options = Options(Meta, attr=True, overwritten=False) + options.valid_attrs = ['accepted', 'overwritten'] + assert options.attr + assert not options.overwritten + options.contribute_to_class(MyObject, '_meta') + assert MyObject._meta == options + assert options.parent == MyObject + + +def test_options_invalid_attrs(): + class Meta: + invalid = True + + class MyObject(object): + pass + + options = Options(Meta, valid=True) + options.valid_attrs = ['valid'] + assert options.valid + with pytest.raises(TypeError) as excinfo: + options.contribute_to_class(MyObject, '_meta') + + assert "MyObject.Meta got invalid attributes: invalid" == str(excinfo.value) diff --git a/graphene/types/tests/test_scalars.py b/graphene/types/tests/test_scalars.py new file mode 100644 index 00000000..4c1fa14a --- /dev/null +++ b/graphene/types/tests/test_scalars.py @@ -0,0 +1,55 @@ +import pytest +from ..scalars import Scalar, String, Int, Float, Boolean +from ..field import Field +from ..objecttype import ObjectType + +from graphql import GraphQLString, GraphQLInt, GraphQLFloat, GraphQLBoolean +from graphene.utils.get_graphql_type import get_graphql_type + + +class DatetimeScalar(Scalar): + def serialize(value): + return value.isoformat() + + +scalar_classes = { + DatetimeScalar: DatetimeScalar._meta.graphql_type, + String: GraphQLString, + Int: GraphQLInt, + Float: GraphQLFloat, + Boolean: GraphQLBoolean, +} + + +@pytest.mark.parametrize("scalar_class,expected_graphql_type", scalar_classes.items()) +def test_scalar_as_field(scalar_class, expected_graphql_type): + scalar = scalar_class() + field = scalar.as_field() + graphql_type = get_graphql_type(scalar_class) + assert isinstance(field, Field) + assert field.type == graphql_type + assert graphql_type == expected_graphql_type + + +@pytest.mark.parametrize("scalar_class,graphql_type", scalar_classes.items()) +def test_scalar_in_objecttype(scalar_class, graphql_type): + class MyObjectType(ObjectType): + field = scalar_class() + + graphql_type = get_graphql_type(MyObjectType) + fields = graphql_type.get_fields() + assert 'field' in fields + assert isinstance(fields['field'], Field) + + +def test_custom_scalar_empty(): + with pytest.raises(AssertionError) as excinfo: + class DatetimeScalar(Scalar): + pass + + assert """DatetimeScalar must provide "serialize" function.""" in str(excinfo.value) + + +def test_custom_scalar(): + graphql_type = DatetimeScalar._meta.graphql_type + assert graphql_type.serialize == DatetimeScalar.serialize diff --git a/graphene/types/tests/test_schema.py b/graphene/types/tests/test_schema.py new file mode 100644 index 00000000..868d1c0f --- /dev/null +++ b/graphene/types/tests/test_schema.py @@ -0,0 +1,78 @@ +import pytest +from ..scalars import Scalar, String, Int, Float, Boolean +from ..field import Field +from ..objecttype import ObjectType, implements +from ..interface import Interface +from ..schema import Schema + + +class Character(Interface): + name = String() + + +class Pet(ObjectType): + type = String() + + +@implements(Character) +class Human(ObjectType): + pet = Field(Pet) + + def resolve_pet(self, *args): + return Pet(type='Dog') + + +class RootQuery(ObjectType): + character = Field(Character) + + def resolve_character(self, *_): + return Human(name='Hey!') + + +schema = Schema(query=RootQuery, types=[Human]) + + +def test_schema(): + executed = schema.execute('{ character {name, ...on Human {pet { type } } } }') + assert executed.data == {'character': {'name': 'Hey!', 'pet': {'type': 'Dog'}}} + + +def test_schema_introspect(): + introspection = schema.introspect() + assert '__schema' in introspection + + +def test_schema_str(): + expected = """ +schema { + query: RootQuery +} + +interface Character { + name: String +} + +type Human implements Character { + name: String + pet: Pet +} + +type Pet { + type: String +} + +type RootQuery { + character: Character +} +""".lstrip() + assert str(schema) == expected + + +def test_schema_get_type(): + pet = schema.get_type('Pet') + assert pet == Pet._meta.graphql_type + + +def test_schema_lazy_type(): + pet = schema.lazy('Pet') + assert pet() == Pet._meta.graphql_type diff --git a/graphene/utils/__init__.py b/graphene/utils/__init__.py index 261f33e3..e69de29b 100644 --- a/graphene/utils/__init__.py +++ b/graphene/utils/__init__.py @@ -1,15 +0,0 @@ -from .str_converters import to_camel_case, to_snake_case, to_const -from .proxy_snake_dict import ProxySnakeDict -from .caching import cached_property, memoize -from .maybe_func import maybe_func -from .misc import enum_to_graphql_enum -from .promise_middleware import promise_middleware -from .resolve_only_args import resolve_only_args -from .lazylist import LazyList -from .wrap_resolver_function import with_context, wrap_resolver_function - - -__all__ = ['to_camel_case', 'to_snake_case', 'to_const', 'ProxySnakeDict', - 'cached_property', 'memoize', 'maybe_func', 'enum_to_graphql_enum', - 'promise_middleware', 'resolve_only_args', 'LazyList', 'with_context', - 'wrap_resolver_function'] diff --git a/graphene/utils/caching.py b/graphene/utils/caching.py deleted file mode 100644 index b0a77df5..00000000 --- a/graphene/utils/caching.py +++ /dev/null @@ -1,35 +0,0 @@ -from functools import wraps - - -class CachedPropery(object): - """ - A property that is only computed once per instance and then replaces itself - with an ordinary attribute. Deleting the attribute resets the property. - Source: https://github.com/bottlepy/bottle/commit/fa7733e075da0d790d809aa3d2f53071897e6f76 - """ # noqa - - def __init__(self, func): - self.__doc__ = getattr(func, '__doc__') - self.func = func - - def __get__(self, obj, cls): - if obj is None: - return self - value = obj.__dict__[self.func.__name__] = self.func(obj) - return value - -cached_property = CachedPropery - - -def memoize(fun): - """A simple memoize decorator for functions supporting positional args.""" - @wraps(fun) - def wrapper(*args, **kwargs): - key = (args, frozenset(sorted(kwargs.items()))) - try: - return cache[key] - except KeyError: - ret = cache[key] = fun(*args, **kwargs) - return ret - cache = {} - return wrapper diff --git a/graphene/utils/enum.py b/graphene/utils/enum.py deleted file mode 100644 index 27a3043c..00000000 --- a/graphene/utils/enum.py +++ /dev/null @@ -1,830 +0,0 @@ -# flake8: noqa -"""Python Enumerations""" -from __future__ import absolute_import - -__all__ = ['Enum', 'IntEnum', 'unique'] - -try: - from enum import Enum, IntEnum, unique -except ImportError: - import sys as _sys - - version = 1, 1, 2 - - pyver = float('%s.%s' % _sys.version_info[:2]) - - try: - any - except NameError: - def any(iterable): - for element in iterable: - if element: - return True - return False - - try: - from collections import OrderedDict - except ImportError: - OrderedDict = None - - try: - basestring - except NameError: - # In Python 2 basestring is the ancestor of both str and unicode - # in Python 3 it's just str, but was missing in 3.1 - basestring = str - - try: - unicode - except NameError: - # In Python 3 unicode no longer exists (it's just str) - unicode = str - - class _RouteClassAttributeToGetattr(object): - """Route attribute access on a class to __getattr__. - - This is a descriptor, used to define attributes that act differently when - accessed through an instance and through a class. Instance access remains - normal, but access to an attribute through a class will be routed to the - class's __getattr__ method; this is done by raising AttributeError. - - """ - - def __init__(self, fget=None): - self.fget = fget - - def __get__(self, instance, ownerclass=None): - if instance is None: - raise AttributeError() - return self.fget(instance) - - def __set__(self, instance, value): - raise AttributeError("can't set attribute") - - def __delete__(self, instance): - raise AttributeError("can't delete attribute") - - def _is_descriptor(obj): - """Returns True if obj is a descriptor, False otherwise.""" - return ( - hasattr(obj, '__get__') or - hasattr(obj, '__set__') or - hasattr(obj, '__delete__')) - - def _is_dunder(name): - """Returns True if a __dunder__ name, False otherwise.""" - return (name[:2] == name[-2:] == '__' and - name[2:3] != '_' and - name[-3:-2] != '_' and - len(name) > 4) - - def _is_sunder(name): - """Returns True if a _sunder_ name, False otherwise.""" - return (name[0] == name[-1] == '_' and - name[1:2] != '_' and - name[-2:-1] != '_' and - len(name) > 2) - - def _make_class_unpicklable(cls): - """Make the given class un-picklable.""" - - def _break_on_call_reduce(self, protocol=None): - raise TypeError('%r cannot be pickled' % self) - cls.__reduce_ex__ = _break_on_call_reduce - cls.__module__ = '' - - class _EnumDict(dict): - """Track enum member order and ensure member names are not reused. - - EnumMeta will use the names found in self._member_names as the - enumeration member names. - - """ - - def __init__(self): - super(_EnumDict, self).__init__() - self._member_names = [] - - def __setitem__(self, key, value): - """Changes anything not dundered or not a descriptor. - - If a descriptor is added with the same name as an enum member, the name - is removed from _member_names (this may leave a hole in the numerical - sequence of values). - - If an enum member name is used twice, an error is raised; duplicate - values are not checked for. - - Single underscore (sunder) names are reserved. - - Note: in 3.x __order__ is simply discarded as a not necessary piece - leftover from 2.x - - """ - if pyver >= 3.0 and key == '__order__': - return - if _is_sunder(key): - raise ValueError('_names_ are reserved for future Enum use') - elif _is_dunder(key): - pass - elif key in self._member_names: - # descriptor overwriting an enum? - raise TypeError('Attempted to reuse key: %r' % key) - elif not _is_descriptor(value): - if key in self: - # enum overwriting a descriptor? - raise TypeError('Key already defined as: %r' % self[key]) - self._member_names.append(key) - super(_EnumDict, self).__setitem__(key, value) - - # Dummy value for Enum as EnumMeta explicity checks for it, but of course until - # EnumMeta finishes running the first time the Enum class doesn't exist. This - # is also why there are checks in EnumMeta like `if Enum is not None` - Enum = None - - class EnumMeta(type): - """Metaclass for Enum""" - @classmethod - def __prepare__(metacls, cls, bases): - return _EnumDict() - - def __new__(metacls, cls, bases, classdict): - # an Enum class is final once enumeration items have been defined; it - # cannot be mixed with other types (int, float, etc.) if it has an - # inherited __new__ unless a new __new__ is defined (or the resulting - # class will fail). - if isinstance(classdict, dict): - original_dict = classdict - classdict = _EnumDict() - for k, v in original_dict.items(): - classdict[k] = v - - member_type, first_enum = metacls._get_mixins_(bases) - __new__, save_new, use_args = metacls._find_new_(classdict, member_type, - first_enum) - # save enum items into separate mapping so they don't get baked into - # the new class - members = dict((k, classdict[k]) for k in classdict._member_names) - for name in classdict._member_names: - del classdict[name] - - # py2 support for definition order - __order__ = classdict.get('__order__') - if __order__ is None: - if pyver < 3.0: - try: - __order__ = [name for (name, value) in sorted(members.items(), key=lambda item: item[1])] - except TypeError: - __order__ = [name for name in sorted(members.keys())] - else: - __order__ = classdict._member_names - else: - del classdict['__order__'] - if pyver < 3.0: - __order__ = __order__.replace(',', ' ').split() - aliases = [name for name in members if name not in __order__] - __order__ += aliases - - # check for illegal enum names (any others?) - invalid_names = set(members) & set(['mro']) - if invalid_names: - raise ValueError('Invalid enum member name(s): %s' % ( - ', '.join(invalid_names), )) - - # save attributes from super classes so we know if we can take - # the shortcut of storing members in the class dict - base_attributes = set([a for b in bases for a in b.__dict__]) - # create our new Enum type - enum_class = super(EnumMeta, metacls).__new__(metacls, cls, bases, classdict) - enum_class._member_names_ = [] # names in random order - if OrderedDict is not None: - enum_class._member_map_ = OrderedDict() - else: - enum_class._member_map_ = {} # name->value map - enum_class._member_type_ = member_type - - # Reverse value->name map for hashable values. - enum_class._value2member_map_ = {} - - # instantiate them, checking for duplicates as we go - # we instantiate first instead of checking for duplicates first in case - # a custom __new__ is doing something funky with the values -- such as - # auto-numbering ;) - if __new__ is None: - __new__ = enum_class.__new__ - for member_name in __order__: - value = members[member_name] - if not isinstance(value, tuple): - args = (value, ) - else: - args = value - if member_type is tuple: # special case for tuple enums - args = (args, ) # wrap it one more time - if not use_args or not args: - enum_member = __new__(enum_class) - if not hasattr(enum_member, '_value_'): - enum_member._value_ = value - else: - enum_member = __new__(enum_class, *args) - if not hasattr(enum_member, '_value_'): - enum_member._value_ = member_type(*args) - value = enum_member._value_ - enum_member._name_ = member_name - enum_member.__objclass__ = enum_class - enum_member.__init__(*args) - # If another member with the same value was already defined, the - # new member becomes an alias to the existing one. - for name, canonical_member in enum_class._member_map_.items(): - if canonical_member.value == enum_member._value_: - enum_member = canonical_member - break - else: - # Aliases don't appear in member names (only in __members__). - enum_class._member_names_.append(member_name) - # performance boost for any member that would not shadow - # a DynamicClassAttribute (aka _RouteClassAttributeToGetattr) - if member_name not in base_attributes: - setattr(enum_class, member_name, enum_member) - # now add to _member_map_ - enum_class._member_map_[member_name] = enum_member - try: - # This may fail if value is not hashable. We can't add the value - # to the map, and by-value lookups for this value will be - # linear. - enum_class._value2member_map_[value] = enum_member - except TypeError: - pass - - # If a custom type is mixed into the Enum, and it does not know how - # to pickle itself, pickle.dumps will succeed but pickle.loads will - # fail. Rather than have the error show up later and possibly far - # from the source, sabotage the pickle protocol for this class so - # that pickle.dumps also fails. - # - # However, if the new class implements its own __reduce_ex__, do not - # sabotage -- it's on them to make sure it works correctly. We use - # __reduce_ex__ instead of any of the others as it is preferred by - # pickle over __reduce__, and it handles all pickle protocols. - unpicklable = False - if '__reduce_ex__' not in classdict: - if member_type is not object: - methods = ('__getnewargs_ex__', '__getnewargs__', - '__reduce_ex__', '__reduce__') - if not any(m in member_type.__dict__ for m in methods): - _make_class_unpicklable(enum_class) - unpicklable = True - - # double check that repr and friends are not the mixin's or various - # things break (such as pickle) - for name in ('__repr__', '__str__', '__format__', '__reduce_ex__'): - class_method = getattr(enum_class, name) - getattr(member_type, name, None) - enum_method = getattr(first_enum, name, None) - if name not in classdict and class_method is not enum_method: - if name == '__reduce_ex__' and unpicklable: - continue - setattr(enum_class, name, enum_method) - - # method resolution and int's are not playing nice - # Python's less than 2.6 use __cmp__ - - if pyver < 2.6: - - if issubclass(enum_class, int): - setattr(enum_class, '__cmp__', getattr(int, '__cmp__')) - - elif pyver < 3.0: - - if issubclass(enum_class, int): - for method in ( - '__le__', - '__lt__', - '__gt__', - '__ge__', - '__eq__', - '__ne__', - '__hash__', - ): - setattr(enum_class, method, getattr(int, method)) - - # replace any other __new__ with our own (as long as Enum is not None, - # anyway) -- again, this is to support pickle - if Enum is not None: - # if the user defined their own __new__, save it before it gets - # clobbered in case they subclass later - if save_new: - setattr(enum_class, '__member_new__', enum_class.__dict__['__new__']) - setattr(enum_class, '__new__', Enum.__dict__['__new__']) - return enum_class - - def __call__(cls, value, names=None, module=None, type=None, start=1): - """Either returns an existing member, or creates a new enum class. - - This method is used both when an enum class is given a value to match - to an enumeration member (i.e. Color(3)) and for the functional API - (i.e. Color = Enum('Color', names='red green blue')). - - When used for the functional API: `module`, if set, will be stored in - the new class' __module__ attribute; `type`, if set, will be mixed in - as the first base class. - - Note: if `module` is not set this routine will attempt to discover the - calling module by walking the frame stack; if this is unsuccessful - the resulting class will not be pickleable. - - """ - if names is None: # simple value lookup - return cls.__new__(cls, value) - # otherwise, functional API: we're creating a new Enum type - return cls._create_(value, names, module=module, type=type, start=start) - - def __contains__(cls, member): - return isinstance(member, cls) and member.name in cls._member_map_ - - def __delattr__(cls, attr): - # nicer error message when someone tries to delete an attribute - # (see issue19025). - if attr in cls._member_map_: - raise AttributeError( - "%s: cannot delete Enum member." % cls.__name__) - super(EnumMeta, cls).__delattr__(attr) - - def __dir__(self): - return (['__class__', '__doc__', '__members__', '__module__'] + - self._member_names_) - - @property - def __members__(cls): - """Returns a mapping of member name->value. - - This mapping lists all enum members, including aliases. Note that this - is a copy of the internal mapping. - - """ - return cls._member_map_.copy() - - def __getattr__(cls, name): - """Return the enum member matching `name` - - We use __getattr__ instead of descriptors or inserting into the enum - class' __dict__ in order to support `name` and `value` being both - properties for enum members (which live in the class' __dict__) and - enum members themselves. - - """ - if _is_dunder(name): - raise AttributeError(name) - try: - return cls._member_map_[name] - except KeyError: - raise AttributeError(name) - - def __getitem__(cls, name): - return cls._member_map_[name] - - def __iter__(cls): - return (cls._member_map_[name] for name in cls._member_names_) - - def __reversed__(cls): - return (cls._member_map_[name] for name in reversed(cls._member_names_)) - - def __len__(cls): - return len(cls._member_names_) - - def __repr__(cls): - return "" % cls.__name__ - - def __setattr__(cls, name, value): - """Block attempts to reassign Enum members. - - A simple assignment to the class namespace only changes one of the - several possible ways to get an Enum member from the Enum class, - resulting in an inconsistent Enumeration. - - """ - member_map = cls.__dict__.get('_member_map_', {}) - if name in member_map: - raise AttributeError('Cannot reassign members.') - super(EnumMeta, cls).__setattr__(name, value) - - def _create_(cls, class_name, names=None, module=None, type=None, start=1): - """Convenience method to create a new Enum class. - - `names` can be: - - * A string containing member names, separated either with spaces or - commas. Values are auto-numbered from 1. - * An iterable of member names. Values are auto-numbered from 1. - * An iterable of (member name, value) pairs. - * A mapping of member name -> value. - - """ - if pyver < 3.0: - # if class_name is unicode, attempt a conversion to ASCII - if isinstance(class_name, unicode): - try: - class_name = class_name.encode('ascii') - except UnicodeEncodeError: - raise TypeError('%r is not representable in ASCII' % class_name) - metacls = cls.__class__ - if type is None: - bases = (cls, ) - else: - bases = (type, cls) - classdict = metacls.__prepare__(class_name, bases) - __order__ = [] - - # special processing needed for names? - if isinstance(names, basestring): - names = names.replace(',', ' ').split() - if isinstance(names, (tuple, list)) and isinstance(names[0], basestring): - names = [(e, i + start) for (i, e) in enumerate(names)] - - # Here, names is either an iterable of (name, value) or a mapping. - item = None # in case names is empty - for item in names: - if isinstance(item, basestring): - member_name, member_value = item, names[item] - else: - member_name, member_value = item - classdict[member_name] = member_value - __order__.append(member_name) - # only set __order__ in classdict if name/value was not from a mapping - if not isinstance(item, basestring): - classdict['__order__'] = ' '.join(__order__) - enum_class = metacls.__new__(metacls, class_name, bases, classdict) - - # TODO: replace the frame hack if a blessed way to know the calling - # module is ever developed - if module is None: - try: - module = _sys._getframe(2).f_globals['__name__'] - except (AttributeError, ValueError): - pass - if module is None: - _make_class_unpicklable(enum_class) - else: - enum_class.__module__ = module - - return enum_class - - @staticmethod - def _get_mixins_(bases): - """Returns the type for creating enum members, and the first inherited - enum class. - - bases: the tuple of bases that was given to __new__ - - """ - if not bases or Enum is None: - return object, Enum - - # double check that we are not subclassing a class with existing - # enumeration members; while we're at it, see if any other data - # type has been mixed in so we can use the correct __new__ - member_type = first_enum = None - for base in bases: - if (base is not Enum and - issubclass(base, Enum) and - base._member_names_): - raise TypeError("Cannot extend enumerations") - # base is now the last base in bases - if not issubclass(base, Enum): - raise TypeError("new enumerations must be created as " - "`ClassName([mixin_type,] enum_type)`") - - # get correct mix-in type (either mix-in type of Enum subclass, or - # first base if last base is Enum) - if not issubclass(bases[0], Enum): - member_type = bases[0] # first data type - first_enum = bases[-1] # enum type - else: - for base in bases[0].__mro__: - # most common: (IntEnum, int, Enum, object) - # possible: (, , - # , , - # ) - if issubclass(base, Enum): - if first_enum is None: - first_enum = base - else: - if member_type is None: - member_type = base - - return member_type, first_enum - - if pyver < 3.0: - @staticmethod - def _find_new_(classdict, member_type, first_enum): - """Returns the __new__ to be used for creating the enum members. - - classdict: the class dictionary given to __new__ - member_type: the data type whose __new__ will be used by default - first_enum: enumeration to check for an overriding __new__ - - """ - # now find the correct __new__, checking to see of one was defined - # by the user; also check earlier enum classes in case a __new__ was - # saved as __member_new__ - __new__ = classdict.get('__new__', None) - if __new__: - return None, True, True # __new__, save_new, use_args - - N__new__ = getattr(None, '__new__') - O__new__ = getattr(object, '__new__') - if Enum is None: - E__new__ = N__new__ - else: - E__new__ = Enum.__dict__['__new__'] - # check all possibles for __member_new__ before falling back to - # __new__ - for method in ('__member_new__', '__new__'): - for possible in (member_type, first_enum): - try: - target = possible.__dict__[method] - except (AttributeError, KeyError): - target = getattr(possible, method, None) - if target not in [ - None, - N__new__, - O__new__, - E__new__, - ]: - if method == '__member_new__': - classdict['__new__'] = target - return None, False, True - if isinstance(target, staticmethod): - target = target.__get__(member_type) - __new__ = target - break - if __new__ is not None: - break - else: - __new__ = object.__new__ - - # if a non-object.__new__ is used then whatever value/tuple was - # assigned to the enum member name will be passed to __new__ and to the - # new enum member's __init__ - if __new__ is object.__new__: - use_args = False - else: - use_args = True - - return __new__, False, use_args - else: - @staticmethod - def _find_new_(classdict, member_type, first_enum): - """Returns the __new__ to be used for creating the enum members. - - classdict: the class dictionary given to __new__ - member_type: the data type whose __new__ will be used by default - first_enum: enumeration to check for an overriding __new__ - - """ - # now find the correct __new__, checking to see of one was defined - # by the user; also check earlier enum classes in case a __new__ was - # saved as __member_new__ - __new__ = classdict.get('__new__', None) - - # should __new__ be saved as __member_new__ later? - save_new = __new__ is not None - - if __new__ is None: - # check all possibles for __member_new__ before falling back to - # __new__ - for method in ('__member_new__', '__new__'): - for possible in (member_type, first_enum): - target = getattr(possible, method, None) - if target not in ( - None, - None.__new__, - object.__new__, - Enum.__new__, - ): - __new__ = target - break - if __new__ is not None: - break - else: - __new__ = object.__new__ - - # if a non-object.__new__ is used then whatever value/tuple was - # assigned to the enum member name will be passed to __new__ and to the - # new enum member's __init__ - if __new__ is object.__new__: - use_args = False - else: - use_args = True - - return __new__, save_new, use_args - - ######################################################## - # In order to support Python 2 and 3 with a single - # codebase we have to create the Enum methods separately - # and then use the `type(name, bases, dict)` method to - # create the class. - ######################################################## - temp_enum_dict = {} - temp_enum_dict['__doc__'] = "Generic enumeration.\n\n Derive from this class to define new enumerations.\n\n" - - def __new__(cls, value): - # all enum instances are actually created during class construction - # without calling this method; this method is called by the metaclass' - # __call__ (i.e. Color(3) ), and by pickle - if isinstance(value, cls): - # For lookups like Color(Color.red) - value = value.value - # return value - # by-value search for a matching enum member - # see if it's in the reverse mapping (for hashable values) - try: - if value in cls._value2member_map_: - return cls._value2member_map_[value] - except TypeError: - # not there, now do long search -- O(n) behavior - for member in cls._member_map_.values(): - if member.value == value: - return member - raise ValueError("%s is not a valid %s" % (value, cls.__name__)) - temp_enum_dict['__new__'] = __new__ - del __new__ - - def __repr__(self): - return "<%s.%s: %r>" % ( - self.__class__.__name__, self._name_, self._value_) - temp_enum_dict['__repr__'] = __repr__ - del __repr__ - - def __str__(self): - return "%s.%s" % (self.__class__.__name__, self._name_) - temp_enum_dict['__str__'] = __str__ - del __str__ - - if pyver >= 3.0: - def __dir__(self): - added_behavior = [ - m - for cls in self.__class__.mro() - for m in cls.__dict__ - if m[0] != '_' and m not in self._member_map_ - ] - return (['__class__', '__doc__', '__module__', ] + added_behavior) - temp_enum_dict['__dir__'] = __dir__ - del __dir__ - - def __format__(self, format_spec): - # mixed-in Enums should use the mixed-in type's __format__, otherwise - # we can get strange results with the Enum name showing up instead of - # the value - - # pure Enum branch - if self._member_type_ is object: - cls = str - val = str(self) - # mix-in branch - else: - cls = self._member_type_ - val = self.value - return cls.__format__(val, format_spec) - temp_enum_dict['__format__'] = __format__ - del __format__ - - #################################### - # Python's less than 2.6 use __cmp__ - - if pyver < 2.6: - - def __cmp__(self, other): - if isinstance(other, self.__class__): - if self is other: - return 0 - return -1 - return NotImplemented - raise TypeError("unorderable types: %s() and %s()" % (self.__class__.__name__, other.__class__.__name__)) - temp_enum_dict['__cmp__'] = __cmp__ - del __cmp__ - - else: - - def __le__(self, other): - raise TypeError("unorderable types: %s() <= %s()" % (self.__class__.__name__, other.__class__.__name__)) - temp_enum_dict['__le__'] = __le__ - del __le__ - - def __lt__(self, other): - raise TypeError("unorderable types: %s() < %s()" % (self.__class__.__name__, other.__class__.__name__)) - temp_enum_dict['__lt__'] = __lt__ - del __lt__ - - def __ge__(self, other): - raise TypeError("unorderable types: %s() >= %s()" % (self.__class__.__name__, other.__class__.__name__)) - temp_enum_dict['__ge__'] = __ge__ - del __ge__ - - def __gt__(self, other): - raise TypeError("unorderable types: %s() > %s()" % (self.__class__.__name__, other.__class__.__name__)) - temp_enum_dict['__gt__'] = __gt__ - del __gt__ - - def __eq__(self, other): - if isinstance(other, self.__class__): - return self is other - return NotImplemented - temp_enum_dict['__eq__'] = __eq__ - del __eq__ - - def __ne__(self, other): - if isinstance(other, self.__class__): - return self is not other - return NotImplemented - temp_enum_dict['__ne__'] = __ne__ - del __ne__ - - def __hash__(self): - return hash(self._name_) - temp_enum_dict['__hash__'] = __hash__ - del __hash__ - - # TODO: enable once Python 3.6 is released - # def __bool__(self): - # return bool(self._value_) - # if pyver < 3.0: - # temp_enum_dict['__nonzero__'] = __bool__ - # else: - # temp_enum_dict['__bool__'] = __bool__ - # del __bool__ - - def __reduce_ex__(self, proto): - return self.__class__, (self._value_, ) - temp_enum_dict['__reduce_ex__'] = __reduce_ex__ - del __reduce_ex__ - - # _RouteClassAttributeToGetattr is used to provide access to the `name` - # and `value` properties of enum members while keeping some measure of - # protection from modification, while still allowing for an enumeration - # to have members named `name` and `value`. This works because enumeration - # members are not set directly on the enum class -- __getattr__ is - # used to look them up. - - @_RouteClassAttributeToGetattr - def name(self): - return self._name_ - temp_enum_dict['name'] = name - del name - - @_RouteClassAttributeToGetattr - def value(self): - return self._value_ - temp_enum_dict['value'] = value - del value - - @classmethod - def _convert(cls, name, module, filter, source=None): - """ - Create a new Enum subclass that replaces a collection of global constants - """ - # convert all constants from source (or module) that pass filter() to - # a new Enum called name, and export the enum and its members back to - # module; - # also, replace the __reduce_ex__ method so unpickling works in - # previous Python versions - module_globals = vars(_sys.modules[module]) - if source: - source = vars(source) - else: - source = module_globals - members = dict((name, value) for name, value in source.items() if filter(name)) - cls = cls(name, members, module=module) - cls.__reduce_ex__ = _reduce_ex_by_name - module_globals.update(cls.__members__) - module_globals[name] = cls - return cls - temp_enum_dict['_convert'] = _convert - del _convert - - Enum = EnumMeta('Enum', (object, ), temp_enum_dict) - del temp_enum_dict - - # Enum has now been created - ########################### - - class IntEnum(int, Enum): - """Enum where members are also (and must be) ints""" - - def _reduce_ex_by_name(self, proto): - return self.name - - def unique(enumeration): - """Class decorator that ensures only unique members exist in an enumeration.""" - duplicates = [] - for name, member in enumeration.__members__.items(): - if name != member.name: - duplicates.append((name, member.name)) - if duplicates: - duplicate_names = ', '.join( - ["%s -> %s" % (alias, name) for (alias, name) in duplicates] - ) - raise ValueError('duplicate names found in %r: %s' % - (enumeration, duplicate_names) - ) - return enumeration diff --git a/graphene/utils/get_graphql_type.py b/graphene/utils/get_graphql_type.py new file mode 100644 index 00000000..fe182c4c --- /dev/null +++ b/graphene/utils/get_graphql_type.py @@ -0,0 +1,14 @@ +from graphql.type.definition import is_type + +from .is_graphene_type import is_graphene_type + + +def get_graphql_type(_type): + if is_type(_type): + return _type + elif is_graphene_type(_type): + if _type._meta.abstract: + raise Exception("{} has no type. Only non abstract types have GraphQL type.".format(_type.__name__)) + return _type._meta.graphql_type + + raise Exception("Cannot get GraphQL type of {}.".format(_type)) diff --git a/graphene/utils/is_graphene_type.py b/graphene/utils/is_graphene_type.py new file mode 100644 index 00000000..fa4a57d5 --- /dev/null +++ b/graphene/utils/is_graphene_type.py @@ -0,0 +1,12 @@ +import inspect +from ..types.objecttype import ObjectType +from ..types.interface import Interface +from ..types.scalars import Scalar + + +def is_graphene_type(_type): + return inspect.isclass(_type) and issubclass(_type, ( + Interface, + ObjectType, + Scalar + )) diff --git a/graphene/utils/lazylist.py b/graphene/utils/lazylist.py deleted file mode 100644 index 434dcfd4..00000000 --- a/graphene/utils/lazylist.py +++ /dev/null @@ -1,43 +0,0 @@ -class LazyList(object): - - def __init__(self, origin, state=None): - self._origin = origin - self._state = state or [] - self._origin_iter = None - self._finished = False - - def __iter__(self): - return self if not self._finished else iter(self._state) - - def iter(self): - return self.__iter__() - - def __len__(self): - return self._origin.__len__() - - def __next__(self): - try: - if not self._origin_iter: - self._origin_iter = self._origin.__iter__() - n = next(self._origin_iter) - except StopIteration as e: - self._finished = True - raise e - else: - self._state.append(n) - return n - - def next(self): - return self.__next__() - - def __getitem__(self, key): - item = self._origin[key] - if isinstance(key, slice): - return self.__class__(item) - return item - - def __getattr__(self, name): - return getattr(self._origin, name) - - def __repr__(self): - return "<{} {}>".format(self.__class__.__name__, repr(self._origin)) diff --git a/graphene/utils/maybe_func.py b/graphene/utils/maybe_func.py deleted file mode 100644 index 26c6a509..00000000 --- a/graphene/utils/maybe_func.py +++ /dev/null @@ -1,7 +0,0 @@ -import inspect - - -def maybe_func(f): - if inspect.isfunction(f): - return f() - return f diff --git a/graphene/utils/misc.py b/graphene/utils/misc.py deleted file mode 100644 index 9fc9d493..00000000 --- a/graphene/utils/misc.py +++ /dev/null @@ -1,12 +0,0 @@ -from collections import OrderedDict - -from graphql.type import GraphQLEnumType, GraphQLEnumValue - - -def enum_to_graphql_enum(enumeration): - return GraphQLEnumType( - name=enumeration.__name__, - values=OrderedDict([(it.name, GraphQLEnumValue(it.value)) - for it in enumeration]), - description=enumeration.__doc__ - ) diff --git a/graphene/utils/orderedtype.py b/graphene/utils/orderedtype.py new file mode 100644 index 00000000..d13b5528 --- /dev/null +++ b/graphene/utils/orderedtype.py @@ -0,0 +1,36 @@ +from functools import total_ordering + + +@total_ordering +class OrderedType(object): + 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)) diff --git a/graphene/utils/promise_middleware.py b/graphene/utils/promise_middleware.py deleted file mode 100644 index 5190ed02..00000000 --- a/graphene/utils/promise_middleware.py +++ /dev/null @@ -1,17 +0,0 @@ -from functools import partial -from itertools import chain - -from promise import Promise - - -def promise_middleware(func, middlewares): - middlewares = chain((func, make_it_promise), middlewares) - past = None - for m in middlewares: - past = partial(m, past) if past else m - - return past - - -def make_it_promise(next, *a, **b): - return Promise.resolve(next(*a, **b)) diff --git a/graphene/utils/proxy_snake_dict.py b/graphene/utils/proxy_snake_dict.py deleted file mode 100644 index 9defd30f..00000000 --- a/graphene/utils/proxy_snake_dict.py +++ /dev/null @@ -1,70 +0,0 @@ -import collections - -from .str_converters import to_camel_case, to_snake_case - - -class ProxySnakeDict(collections.MutableMapping): - __slots__ = ('data') - - def __init__(self, data): - self.data = data - - def __contains__(self, key): - return key in self.data or to_camel_case(key) in self.data - - def get(self, key, default=None): - try: - return self.__getitem__(key) - except KeyError: - return default - - def __iter__(self): - return self.iterkeys() - - def __len__(self): - return len(self.data) - - def __delitem__(self, item): - raise TypeError('ProxySnakeDict does not support item deletion') - - def __setitem__(self, item, value): - raise TypeError('ProxySnakeDict does not support item assignment') - - def __getitem__(self, key): - if key in self.data: - item = self.data[key] - else: - camel_key = to_camel_case(key) - if camel_key in self.data: - item = self.data[camel_key] - else: - raise KeyError(key, camel_key) - - if isinstance(item, dict): - return ProxySnakeDict(item) - return item - - def keys(self): - return list(self.iterkeys()) - - def items(self): - return list(self.iteritems()) - - def iterkeys(self): - for k in self.data.keys(): - yield to_snake_case(k) - return - - def iteritems(self): - for k in self.iterkeys(): - yield k, self[k] - - def to_data_dict(self): - return self.data.__class__(self.iteritems()) - - def __eq__(self, other): - return self.to_data_dict() == other.to_data_dict() - - def __repr__(self): - data_repr = self.to_data_dict().__repr__() - return ''.format(data_repr) diff --git a/graphene/utils/resolve_only_args.py b/graphene/utils/resolve_only_args.py deleted file mode 100644 index bf71aef1..00000000 --- a/graphene/utils/resolve_only_args.py +++ /dev/null @@ -1,8 +0,0 @@ -from functools import wraps - - -def resolve_only_args(func): - @wraps(func) - def inner(self, args, info): - return func(self, **args) - return inner diff --git a/graphene/utils/str_converters.py b/graphene/utils/str_converters.py deleted file mode 100644 index ae8ceffe..00000000 --- a/graphene/utils/str_converters.py +++ /dev/null @@ -1,21 +0,0 @@ -import re - - -# From this response in Stackoverflow -# http://stackoverflow.com/a/19053800/1072990 -def to_camel_case(snake_str): - components = snake_str.split('_') - # We capitalize the first letter of each component except the first one - # with the 'title' method and join them together. - return components[0] + "".join(x.title() if x else '_' for x in components[1:]) - - -# From this response in Stackoverflow -# http://stackoverflow.com/a/1176023/1072990 -def to_snake_case(name): - s1 = re.sub('(.)([A-Z][a-z]+)', r'\1_\2', name) - return re.sub('([a-z0-9])([A-Z])', r'\1_\2', s1).lower() - - -def to_const(string): - return re.sub('[\W|^]+', '_', string).upper() diff --git a/graphene/utils/tests/test_get_graphql_type.py b/graphene/utils/tests/test_get_graphql_type.py new file mode 100644 index 00000000..ba936151 --- /dev/null +++ b/graphene/utils/tests/test_get_graphql_type.py @@ -0,0 +1,49 @@ +import pytest + +from graphql.type.definition import is_type +from graphql import GraphQLObjectType, GraphQLField, GraphQLString + +from graphene.types import ObjectType +from ..get_graphql_type import get_graphql_type + + +MyGraphQLType = GraphQLObjectType('MyGraphQLType', fields={ + 'field': GraphQLField(GraphQLString) +}) + + +def test_get_graphql_type_graphene(): + class MyGrapheneType(ObjectType): + pass + + assert is_type(get_graphql_type(MyGrapheneType)) + + +def test_get_graphql_type_graphene_abstract(): + class MyGrapheneType(ObjectType): + class Meta: + abstract = True + + with pytest.raises(Exception) as excinfo: + get_graphql_type(MyGrapheneType) + + assert "MyGrapheneType has no type. Only non abstract types have GraphQL type." == str(excinfo.value) + + +def test_get_graphql_type_custom_graphene_type(): + class MyGrapheneType(ObjectType): + class Meta: + graphql_type = MyGraphQLType + + assert get_graphql_type(MyGrapheneType) == MyGraphQLType + + +def test_get_graphql_type_graphql_type(): + assert get_graphql_type(MyGraphQLType) == MyGraphQLType + + +def test_get_graphql_type_unknown_type(): + with pytest.raises(Exception) as excinfo: + get_graphql_type(object) + + assert "Cannot get GraphQL type of ." == str(excinfo.value) diff --git a/graphene/utils/tests/test_is_graphene_type.py b/graphene/utils/tests/test_is_graphene_type.py new file mode 100644 index 00000000..37cc6ac7 --- /dev/null +++ b/graphene/utils/tests/test_is_graphene_type.py @@ -0,0 +1,21 @@ +from graphql import GraphQLObjectType, GraphQLField, GraphQLString + +from ..is_graphene_type import is_graphene_type + +from graphene.types import ObjectType + + +def test_is_graphene_type_objecttype(): + assert is_graphene_type(ObjectType) + + +def test_is_graphene_type_graphqltype(): + MyGraphQLType = GraphQLObjectType('MyGraphQLType', fields={ + 'field': GraphQLField(GraphQLString) + }) + assert not is_graphene_type(MyGraphQLType) + + +def test_is_graphene_type_other(): + MyObject = object() + assert not is_graphene_type(MyObject) diff --git a/graphene/utils/tests/test_lazylist.py b/graphene/utils/tests/test_lazylist.py deleted file mode 100644 index 972e2942..00000000 --- a/graphene/utils/tests/test_lazylist.py +++ /dev/null @@ -1,23 +0,0 @@ -from py.test import raises - -from ..lazylist import LazyList - - -def test_lazymap(): - data = list(range(10)) - lm = LazyList(data) - assert len(lm) == 10 - assert lm[1] == 1 - assert isinstance(lm[1:4], LazyList) - assert lm.append == data.append - assert repr(lm) == '' - - -def test_lazymap_iter(): - data = list(range(2)) - lm = LazyList(data) - iter_lm = iter(lm) - assert iter_lm.next() == 0 - assert iter_lm.next() == 1 - with raises(StopIteration): - iter_lm.next() diff --git a/graphene/utils/tests/test_maybe_func.py b/graphene/utils/tests/test_maybe_func.py deleted file mode 100644 index 5a447fb0..00000000 --- a/graphene/utils/tests/test_maybe_func.py +++ /dev/null @@ -1,9 +0,0 @@ -from ..maybe_func import maybe_func - - -def maybe_func_function(): - assert maybe_func(lambda: True) is True - - -def maybe_func_value(): - assert maybe_func(True) is True diff --git a/graphene/utils/tests/test_misc.py b/graphene/utils/tests/test_misc.py deleted file mode 100644 index 7f5f596d..00000000 --- a/graphene/utils/tests/test_misc.py +++ /dev/null @@ -1,16 +0,0 @@ -import collections - -from graphql.type import GraphQLEnumType - -from ..misc import enum_to_graphql_enum - -item = collections.namedtuple('type', 'name value') - - -class MyCustomEnum(list): - __name__ = 'MyName' - - -def test_enum_to_graphql_enum(): - assert isinstance(enum_to_graphql_enum( - MyCustomEnum([item('k', 'v')])), GraphQLEnumType) diff --git a/graphene/utils/tests/test_orderedtype.py b/graphene/utils/tests/test_orderedtype.py new file mode 100644 index 00000000..2845b876 --- /dev/null +++ b/graphene/utils/tests/test_orderedtype.py @@ -0,0 +1,25 @@ +from ..orderedtype import OrderedType + + +def test_orderedtype(): + one = OrderedType() + two = OrderedType() + three = OrderedType() + + assert one < two < three + + +def test_orderedtype_eq(): + one = OrderedType() + two = OrderedType() + + assert one == one + assert one != two + + +def test_orderedtype_hash(): + one = OrderedType() + two = OrderedType() + + assert hash(one) == hash(one) + assert hash(one) != hash(two) diff --git a/graphene/utils/tests/test_proxy_snake_dict.py b/graphene/utils/tests/test_proxy_snake_dict.py deleted file mode 100644 index 39dccac6..00000000 --- a/graphene/utils/tests/test_proxy_snake_dict.py +++ /dev/null @@ -1,57 +0,0 @@ -from py.test import raises - -from ..proxy_snake_dict import ProxySnakeDict - - -def test_proxy_snake_dict(): - my_data = {'one': 1, 'two': 2, 'none': None, - 'threeOrFor': 3, 'inside': {'otherCamelCase': 3}} - p = ProxySnakeDict(my_data) - assert 'one' in p - assert 'two' in p - assert 'threeOrFor' in p - assert 'none' in p - assert len(p) == len(my_data) - assert p['none'] is None - assert p.get('none') is None - assert p.get('none_existent') is None - assert 'three_or_for' in p - assert p.get('three_or_for') == 3 - assert 'inside' in p - assert 'other_camel_case' in p['inside'] - assert sorted( - p.items()) == sorted( - list( - [('inside', ProxySnakeDict({'other_camel_case': 3})), - ('none', None), - ('three_or_for', 3), - ('two', 2), - ('one', 1)])) - - -def test_proxy_snake_dict_as_kwargs(): - my_data = {'myData': 1} - p = ProxySnakeDict(my_data) - - def func(**kwargs): - return kwargs.get('my_data') - assert func(**p) == 1 - - -def test_proxy_snake_dict_repr(): - my_data = {'myData': 1} - p = ProxySnakeDict(my_data) - - assert repr(p) == "" - - -def test_proxy_snake_dict_set(): - p = ProxySnakeDict({}) - with raises(TypeError): - p['a'] = 2 - - -def test_proxy_snake_dict_delete(): - p = ProxySnakeDict({}) - with raises(TypeError): - del p['a'] diff --git a/graphene/utils/tests/test_resolve_only_args.py b/graphene/utils/tests/test_resolve_only_args.py deleted file mode 100644 index 7866f32f..00000000 --- a/graphene/utils/tests/test_resolve_only_args.py +++ /dev/null @@ -1,12 +0,0 @@ -from ..resolve_only_args import resolve_only_args - - -def test_resolve_only_args(): - - def resolver(*args, **kwargs): - return kwargs - - my_data = {'one': 1, 'two': 2} - - wrapped = resolve_only_args(resolver) - assert wrapped(None, my_data, None) == my_data diff --git a/graphene/utils/tests/test_str_converter.py b/graphene/utils/tests/test_str_converter.py deleted file mode 100644 index 2ee7d7a5..00000000 --- a/graphene/utils/tests/test_str_converter.py +++ /dev/null @@ -1,22 +0,0 @@ -# coding: utf-8 -from ..str_converters import to_camel_case, to_const, to_snake_case - - -def test_snake_case(): - assert to_snake_case('snakesOnAPlane') == 'snakes_on_a_plane' - assert to_snake_case('SnakesOnAPlane') == 'snakes_on_a_plane' - assert to_snake_case('SnakesOnA_Plane') == 'snakes_on_a__plane' - assert to_snake_case('snakes_on_a_plane') == 'snakes_on_a_plane' - assert to_snake_case('snakes_on_a__plane') == 'snakes_on_a__plane' - assert to_snake_case('IPhoneHysteria') == 'i_phone_hysteria' - assert to_snake_case('iPhoneHysteria') == 'i_phone_hysteria' - - -def test_camel_case(): - assert to_camel_case('snakes_on_a_plane') == 'snakesOnAPlane' - assert to_camel_case('snakes_on_a__plane') == 'snakesOnA_Plane' - assert to_camel_case('i_phone_hysteria') == 'iPhoneHysteria' - - -def test_to_const(): - assert to_const('snakes $1. on a "#plane') == 'SNAKES_1_ON_A_PLANE' diff --git a/graphene/utils/wrap_resolver_function.py b/graphene/utils/wrap_resolver_function.py deleted file mode 100644 index c8bc9ec9..00000000 --- a/graphene/utils/wrap_resolver_function.py +++ /dev/null @@ -1,20 +0,0 @@ -from functools import wraps - - -def with_context(func): - setattr(func, 'with_context', 'context') - return func - - -def has_context(func): - return getattr(func, 'with_context', None) - - -def wrap_resolver_function(func): - @wraps(func) - def inner(self, args, context, info): - if has_context(func): - return func(self, args, context, info) - # For old compatibility - return func(self, args, info) - return inner diff --git a/tests/__init__.py b/tests/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/tests/django_settings.py b/tests/django_settings.py deleted file mode 100644 index 1cf5dd38..00000000 --- a/tests/django_settings.py +++ /dev/null @@ -1,14 +0,0 @@ -SECRET_KEY = 1 - -INSTALLED_APPS = [ - 'graphene.contrib.django', - 'graphene.contrib.django.tests', - 'examples.starwars_django', -] - -DATABASES = { - 'default': { - 'ENGINE': 'django.db.backends.sqlite3', - 'NAME': 'tests/django.sqlite', - } -} diff --git a/tests/utils.py b/tests/utils.py deleted file mode 100644 index 8101f15c..00000000 --- a/tests/utils.py +++ /dev/null @@ -1,3 +0,0 @@ - -def assert_equal_lists(l1, l2): - assert sorted(l1) == sorted(l2) diff --git a/tox.ini b/tox.ini index 05b37dfd..c8e24453 100644 --- a/tox.ini +++ b/tox.ini @@ -26,4 +26,3 @@ commands = flake8 graphene [pytest] -DJANGO_SETTINGS_MODULE = tests.django_settings