diff --git a/bin/autolinter b/bin/autolinter index 47d2201c..77af4388 100755 --- a/bin/autolinter +++ b/bin/autolinter @@ -1,4 +1,5 @@ #!/bin/bash -autoflake ./ -r --remove-unused-variables --remove-all-unused-imports --in-place +autoflake ./ -r --remove-unused-variables --remove-all-unused-imports --in-place +autopep8 ./ -r --in-place isort -rc . diff --git a/examples/starwars_django/schema.py b/examples/starwars_django/schema.py index 21b29a1f..22e30e6a 100644 --- a/examples/starwars_django/schema.py +++ b/examples/starwars_django/schema.py @@ -12,6 +12,7 @@ schema = graphene.Schema(name='Starwars Django Relay Schema') class Ship(DjangoNode): + class Meta: model = ShipModel @@ -21,11 +22,13 @@ class Ship(DjangoNode): class Character(DjangoObjectType): + class Meta: model = CharacterModel class Faction(DjangoNode): + class Meta: model = FactionModel @@ -35,6 +38,7 @@ class Faction(DjangoNode): class IntroduceShip(relay.ClientIDMutation): + class Input: ship_name = graphene.StringField(required=True) faction_id = graphene.StringField(required=True) @@ -48,7 +52,7 @@ class IntroduceShip(relay.ClientIDMutation): faction_id = input.get('faction_id') ship = create_ship(ship_name, faction_id) faction = get_faction(faction_id) - return IntroduceShip(ship=ship, faction=faction) + return IntroduceShip(ship=Ship(ship), faction=Faction(faction)) class Query(graphene.ObjectType): diff --git a/graphene/__init__.py b/graphene/__init__.py index c8c1d03d..71791c4d 100644 --- a/graphene/__init__.py +++ b/graphene/__init__.py @@ -14,7 +14,6 @@ from graphene.core.types import ( Mutation, BaseType, LazyType, - OrderedType, Argument, Field, InputField, @@ -28,7 +27,6 @@ from graphene.core.types import ( ) from graphene.core.fields import ( - Field, StringField, IntField, BooleanField, @@ -42,7 +40,7 @@ from graphene.decorators import ( resolve_only_args ) -__all__ = ['Enum', 'Argument', 'String', 'Int', 'ID', 'signals', 'Schema', - 'ObjectType', 'Interface', 'Mutation', 'Field', 'StringField', +__all__ = ['Enum', 'Argument', 'String', 'Int', 'Boolean', 'Float', 'ID', 'List', 'NonNull', 'signals', 'Schema', + 'BaseType', 'LazyType', 'ObjectType', 'Interface', 'Mutation', 'Field', 'InputField', 'StringField', 'IntField', 'BooleanField', 'IDField', 'ListField', 'NonNullField', 'FloatField', 'resolve_only_args'] diff --git a/graphene/contrib/django/converter.py b/graphene/contrib/django/converter.py index d5799a76..0a11d364 100644 --- a/graphene/contrib/django/converter.py +++ b/graphene/contrib/django/converter.py @@ -3,9 +3,7 @@ from singledispatch import singledispatch from graphene.contrib.django.fields import (ConnectionOrListField, DjangoModelField) -from graphene.core.fields import (BooleanField, FloatField, IDField, IntField, - StringField) -from graphene.core.types.scalars import Boolean, Float, ID, Int, String +from graphene.core.types.scalars import ID, Boolean, Float, Int, String try: UUIDField = models.UUIDField diff --git a/graphene/contrib/django/fields.py b/graphene/contrib/django/fields.py index b95a6d79..d76e984e 100644 --- a/graphene/contrib/django/fields.py +++ b/graphene/contrib/django/fields.py @@ -1,7 +1,9 @@ from graphene import relay from graphene.contrib.django.utils import get_type_for_model, lazy_map -from graphene.core.fields import Field, ListField +from graphene.core.exceptions import SkipField +from graphene.core.fields import Field from graphene.core.types.base import FieldType +from graphene.core.types.definitions import List from graphene.relay.utils import is_node @@ -12,7 +14,10 @@ class DjangoConnectionField(relay.ConnectionField): return lazy_map(value, self.type.get_object_type(schema)) -class LazyListField(ListField): +class LazyListField(Field): + + def get_type(self, schema): + return List(self.type) def resolver(self, instance, args, info): schema = info.schema.graphene_schema @@ -51,6 +56,8 @@ class DjangoModelField(FieldType): self.parent, ) ) + if not _type: + raise SkipField() return schema.T(_type) def get_object_type(self, schema): diff --git a/tests/contrib_django/__init__.py b/graphene/contrib/django/tests/__init__.py similarity index 100% rename from tests/contrib_django/__init__.py rename to graphene/contrib/django/tests/__init__.py diff --git a/tests/contrib_django/data.py b/graphene/contrib/django/tests/data.py similarity index 100% rename from tests/contrib_django/data.py rename to graphene/contrib/django/tests/data.py diff --git a/tests/contrib_django/models.py b/graphene/contrib/django/tests/models.py similarity index 100% rename from tests/contrib_django/models.py rename to graphene/contrib/django/tests/models.py diff --git a/tests/contrib_django/test_converter.py b/graphene/contrib/django/tests/test_converter.py similarity index 99% rename from tests/contrib_django/test_converter.py rename to graphene/contrib/django/tests/test_converter.py index 699b6ebd..3f4c3894 100644 --- a/tests/contrib_django/test_converter.py +++ b/graphene/contrib/django/tests/test_converter.py @@ -1,7 +1,6 @@ from django.db import models from py.test import raises -from pytest import raises import graphene from graphene.contrib.django.converter import convert_django_field diff --git a/tests/contrib_django/test_schema.py b/graphene/contrib/django/tests/test_query.py similarity index 51% rename from tests/contrib_django/test_schema.py rename to graphene/contrib/django/tests/test_query.py index a02cf81c..8d9366cd 100644 --- a/tests/contrib_django/test_schema.py +++ b/graphene/contrib/django/tests/test_query.py @@ -1,40 +1,21 @@ - from py.test import raises -from pytest import raises import graphene from graphene import relay from graphene.contrib.django import DjangoNode, DjangoObjectType -from tests.utils import assert_equal_lists from .models import Article, Reporter -def test_should_raise_if_no_model(): - with raises(Exception) as excinfo: - class Character1(DjangoObjectType): - pass - assert 'model in the Meta' in str(excinfo.value) - - -def test_should_raise_if_model_is_invalid(): - with raises(Exception) as excinfo: - class Character2(DjangoObjectType): - - class Meta: - model = 1 - assert 'not a Django model' in str(excinfo.value) - - -def test_should_raise_if_model_is_invalid(): - with raises(Exception) as excinfo: - class ReporterTypeError(DjangoObjectType): +def test_should_query_only_fields(): + with raises(Exception): + class ReporterType(DjangoObjectType): class Meta: model = Reporter only_fields = ('articles', ) - schema = graphene.Schema(query=ReporterTypeError) + schema = graphene.Schema(query=ReporterType) query = ''' query ReporterQuery { articles @@ -44,24 +25,13 @@ def test_should_raise_if_model_is_invalid(): assert not result.errors -def test_should_map_fields_correctly(): - class ReporterType2(DjangoObjectType): - - class Meta: - model = Reporter - assert_equal_lists( - ReporterType2._meta.fields_map.keys(), - ['articles', 'first_name', 'last_name', 'email', 'pets', 'id'] - ) - - -def test_should_map_fields(): +def test_should_query_well(): class ReporterType(DjangoObjectType): class Meta: model = Reporter - class Query2(graphene.ObjectType): + class Query(graphene.ObjectType): reporter = graphene.Field(ReporterType) def resolve_reporter(self, *args, **kwargs): @@ -83,53 +53,41 @@ def test_should_map_fields(): 'email': '' } } - Schema = graphene.Schema(query=Query2) - result = Schema.execute(query) + schema = graphene.Schema(query=Query) + result = schema.execute(query) assert not result.errors assert result.data == expected -def test_should_map_only_few_fields(): - class Reporter2(DjangoObjectType): - - class Meta: - model = Reporter - only_fields = ('id', 'email') - assert_equal_lists( - Reporter2._meta.fields_map.keys(), - ['id', 'email'] - ) - - def test_should_node(): - class ReporterNodeType(DjangoNode): + class ReporterNode(DjangoNode): class Meta: model = Reporter @classmethod def get_node(cls, id): - return ReporterNodeType(Reporter(id=2, first_name='Cookie Monster')) + return ReporterNode(Reporter(id=2, first_name='Cookie Monster')) def resolve_articles(self, *args, **kwargs): - return [ArticleNodeType(Article(headline='Hi!'))] + return [ArticleNode(Article(headline='Hi!'))] - class ArticleNodeType(DjangoNode): + class ArticleNode(DjangoNode): class Meta: model = Article @classmethod def get_node(cls, id): - return ArticleNodeType(Article(id=1, headline='Article node')) + return ArticleNode(Article(id=1, headline='Article node')) - class Query1(graphene.ObjectType): + class Query(graphene.ObjectType): node = relay.NodeField() - reporter = graphene.Field(ReporterNodeType) - article = graphene.Field(ArticleNodeType) + reporter = graphene.Field(ReporterNode) + article = graphene.Field(ArticleNode) def resolve_reporter(self, *args, **kwargs): - return ReporterNodeType(Reporter(id=1, first_name='ABA', last_name='X')) + return ReporterNode(Reporter(id=1, first_name='ABA', last_name='X')) query = ''' query ReporterQuery { @@ -146,12 +104,12 @@ def test_should_node(): lastName, email } - myArticle: node(id:"QXJ0aWNsZU5vZGVUeXBlOjE=") { + myArticle: node(id:"QXJ0aWNsZU5vZGU6MQ==") { id - ... on ReporterNodeType { + ... on ReporterNode { firstName } - ... on ArticleNodeType { + ... on ArticleNode { headline } } @@ -159,7 +117,7 @@ def test_should_node(): ''' expected = { 'reporter': { - 'id': 'UmVwb3J0ZXJOb2RlVHlwZTox', + 'id': 'UmVwb3J0ZXJOb2RlOjE=', 'firstName': 'ABA', 'lastName': 'X', 'email': '', @@ -172,11 +130,11 @@ def test_should_node(): }, }, 'myArticle': { - 'id': 'QXJ0aWNsZU5vZGVUeXBlOjE=', + 'id': 'QXJ0aWNsZU5vZGU6MQ==', 'headline': 'Article node' } } - Schema = graphene.Schema(query=Query1) - result = Schema.execute(query) + schema = graphene.Schema(query=Query) + result = schema.execute(query) assert not result.errors assert result.data == expected diff --git a/graphene/contrib/django/tests/test_schema.py b/graphene/contrib/django/tests/test_schema.py new file mode 100644 index 00000000..e474121f --- /dev/null +++ b/graphene/contrib/django/tests/test_schema.py @@ -0,0 +1,45 @@ +from py.test import raises + +from graphene.contrib.django import DjangoObjectType +from tests.utils import assert_equal_lists + +from .models import Reporter + + +def test_should_raise_if_no_model(): + with raises(Exception) as excinfo: + class Character1(DjangoObjectType): + pass + assert 'model in the Meta' in str(excinfo.value) + + +def test_should_raise_if_model_is_invalid(): + with raises(Exception) as excinfo: + class Character2(DjangoObjectType): + + class Meta: + model = 1 + assert 'not a Django model' in str(excinfo.value) + + +def test_should_map_fields_correctly(): + class ReporterType2(DjangoObjectType): + + class Meta: + model = Reporter + assert_equal_lists( + ReporterType2._meta.fields_map.keys(), + ['articles', 'first_name', 'last_name', 'email', 'pets', 'id'] + ) + + +def test_should_map_only_few_fields(): + class Reporter2(DjangoObjectType): + + class Meta: + model = Reporter + only_fields = ('id', 'email') + assert_equal_lists( + Reporter2._meta.fields_map.keys(), + ['id', 'email'] + ) diff --git a/tests/contrib_django/test_types.py b/graphene/contrib/django/tests/test_types.py similarity index 94% rename from tests/contrib_django/test_types.py rename to graphene/contrib/django/tests/test_types.py index bd864e0a..f1af176d 100644 --- a/tests/contrib_django/test_types.py +++ b/graphene/contrib/django/tests/test_types.py @@ -2,14 +2,16 @@ from graphene import Schema from graphene.contrib.django.types import DjangoInterface, DjangoNode -from graphene.core.fields import IntField, Field -from graphene.core.types.scalars import String, Int +from graphene.core.fields import Field, IntField +from graphene.core.types.scalars import Int from graphene.relay.fields import GlobalIDField from graphql.core.type import GraphQLInterfaceType, GraphQLObjectType from tests.utils import assert_equal_lists from .models import Article, Reporter +schema = Schema() + class Character(DjangoInterface): '''Character description''' @@ -17,6 +19,7 @@ class Character(DjangoInterface): model = Reporter +@schema.register class Human(DjangoNode): '''Human description''' @@ -28,14 +31,12 @@ class Human(DjangoNode): class Meta: model = Article -schema = Schema() - def test_django_interface(): assert DjangoNode._meta.is_interface is True -def test_pseudo_interface(): +def test_pseudo_interface_registered(): object_type = schema.T(Character) assert Character._meta.is_interface is True assert isinstance(object_type, GraphQLInterfaceType) diff --git a/tests/contrib_django/test_urls.py b/graphene/contrib/django/tests/test_urls.py similarity index 100% rename from tests/contrib_django/test_urls.py rename to graphene/contrib/django/tests/test_urls.py diff --git a/tests/contrib_django/test_views.py b/graphene/contrib/django/tests/test_views.py similarity index 82% rename from tests/contrib_django/test_views.py rename to graphene/contrib/django/tests/test_views.py index c04e8b6a..a3537802 100644 --- a/tests/contrib_django/test_views.py +++ b/graphene/contrib/django/tests/test_views.py @@ -6,7 +6,7 @@ def format_response(response): def test_client_get_no_query(settings, client): - settings.ROOT_URLCONF = 'tests.contrib_django.test_urls' + settings.ROOT_URLCONF = 'graphene.contrib.django.tests.test_urls' response = client.get('/graphql') json_response = format_response(response) assert json_response == {'errors': [ @@ -14,7 +14,7 @@ def test_client_get_no_query(settings, client): def test_client_post_no_query(settings, client): - settings.ROOT_URLCONF = 'tests.contrib_django.test_urls' + settings.ROOT_URLCONF = 'graphene.contrib.django.tests.test_urls' response = client.post('/graphql', {}) json_response = format_response(response) assert json_response == {'errors': [ @@ -22,7 +22,7 @@ def test_client_post_no_query(settings, client): def test_client_post_malformed_json(settings, client): - settings.ROOT_URLCONF = 'tests.contrib_django.test_urls' + settings.ROOT_URLCONF = 'graphene.contrib.django.tests.test_urls' response = client.post('/graphql', 'MALFORMED', 'application/json') json_response = format_response(response) assert json_response == {'errors': [ @@ -30,7 +30,7 @@ def test_client_post_malformed_json(settings, client): def test_client_post_empty_query(settings, client): - settings.ROOT_URLCONF = 'tests.contrib_django.test_urls' + settings.ROOT_URLCONF = 'graphene.contrib.django.tests.test_urls' response = client.post( '/graphql', json.dumps({'query': ''}), 'application/json') json_response = format_response(response) @@ -39,7 +39,7 @@ def test_client_post_empty_query(settings, client): def test_client_post_bad_query(settings, client): - settings.ROOT_URLCONF = 'tests.contrib_django.test_urls' + settings.ROOT_URLCONF = 'graphene.contrib.django.tests.test_urls' response = client.post( '/graphql', json.dumps({'query': '{ MALFORMED'}), 'application/json') json_response = format_response(response) @@ -49,7 +49,7 @@ def test_client_post_bad_query(settings, client): def test_client_get_good_query(settings, client): - settings.ROOT_URLCONF = 'tests.contrib_django.test_urls' + settings.ROOT_URLCONF = 'graphene.contrib.django.tests.test_urls' response = client.get('/graphql', {'query': '{ headline }'}) json_response = format_response(response) expected_json = { @@ -61,7 +61,7 @@ def test_client_get_good_query(settings, client): def test_client_get_good_query_with_raise(settings, client): - settings.ROOT_URLCONF = 'tests.contrib_django.test_urls' + settings.ROOT_URLCONF = 'graphene.contrib.django.tests.test_urls' response = client.get('/graphql', {'query': '{ raises }'}) json_response = format_response(response) assert json_response['errors'][0][ @@ -70,7 +70,7 @@ def test_client_get_good_query_with_raise(settings, client): def test_client_post_good_query(settings, client): - settings.ROOT_URLCONF = 'tests.contrib_django.test_urls' + settings.ROOT_URLCONF = 'graphene.contrib.django.tests.test_urls' response = client.post( '/graphql', json.dumps({'query': '{ headline }'}), 'application/json') json_response = format_response(response) diff --git a/graphene/core/exceptions.py b/graphene/core/exceptions.py new file mode 100644 index 00000000..ce89b521 --- /dev/null +++ b/graphene/core/exceptions.py @@ -0,0 +1,2 @@ +class SkipField(Exception): + pass diff --git a/graphene/core/fields.py b/graphene/core/fields.py index f799ec9f..463e1983 100644 --- a/graphene/core/fields.py +++ b/graphene/core/fields.py @@ -1,15 +1,17 @@ import warnings from .types.base import FieldType -from .types.field import Field -from .types.scalars import String, Int, Boolean, ID, Float 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) + 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) @@ -41,3 +43,7 @@ class ListField(DeprecatedField, List): 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 index 49147e48..c0a4f94a 100644 --- a/graphene/core/schema.py +++ b/graphene/core/schema.py @@ -2,13 +2,13 @@ import inspect from collections import OrderedDict from graphene import signals +from graphene.core.types.base import BaseType +from graphene.core.types.objecttype import BaseObjectType from graphql.core.execution.executor import Executor from graphql.core.execution.middlewares.sync import \ SynchronousExecutionMiddleware from graphql.core.type import GraphQLSchema as _GraphQLSchema from graphql.core.utils.introspection_query import introspection_query -from graphene.core.types.base import BaseType -from graphene.core.types.objecttype import BaseObjectType class GraphQLSchema(_GraphQLSchema): @@ -40,7 +40,8 @@ class Schema(object): if object_type not in self._types: internal_type = object_type.internal_type(self) self._types[object_type] = internal_type - is_objecttype = inspect.isclass(object_type) and issubclass(object_type, BaseObjectType) + is_objecttype = inspect.isclass( + object_type) and issubclass(object_type, BaseObjectType) if is_objecttype: self.register(object_type) return self._types[object_type] @@ -68,7 +69,8 @@ class Schema(object): type_name = object_type._meta.type_name registered_object_type = self._types_names.get(type_name, None) if registered_object_type: - assert registered_object_type == object_type, 'Type {} already registered with other object type'.format(type_name) + 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 diff --git a/graphene/core/tests/test_mutations.py b/graphene/core/tests/test_mutations.py index 260fe6b8..d0efac76 100644 --- a/graphene/core/tests/test_mutations.py +++ b/graphene/core/tests/test_mutations.py @@ -30,7 +30,7 @@ schema = Schema(query=Query, mutation=MyResultMutation) def test_mutation_input(): - assert schema.T(ChangeNumber.arguments).keys() == ['to'] + assert list(schema.T(ChangeNumber.arguments).keys()) == ['to'] def test_execute_mutations(): diff --git a/graphene/core/tests/test_fields.py b/graphene/core/tests/test_old_fields.py similarity index 64% rename from graphene/core/tests/test_fields.py rename to graphene/core/tests/test_old_fields.py index d919c26f..527444ae 100644 --- a/graphene/core/tests/test_fields.py +++ b/graphene/core/tests/test_old_fields.py @@ -1,16 +1,16 @@ - from py.test import raises -from pytest import raises -from graphene.core.fields import Field, NonNullField, StringField -from graphene.core.options import Options +from graphene.core.fields import (BooleanField, Field, FloatField, IDField, + IntField, NonNullField, StringField) from graphene.core.schema import Schema from graphene.core.types import ObjectType -from graphql.core.type import (GraphQLBoolean, GraphQLField, GraphQLID, - GraphQLInt, GraphQLNonNull, GraphQLString) +from graphql.core.type import (GraphQLBoolean, GraphQLField, GraphQLFloat, + GraphQLID, GraphQLInt, GraphQLNonNull, + GraphQLString) -class ot(ObjectType): +class MyOt(ObjectType): + def resolve_customdoc(self, *args, **kwargs): '''Resolver documentation''' return None @@ -23,53 +23,77 @@ schema = Schema() def test_field_no_contributed_raises_error(): f = Field(GraphQLString) - with raises(Exception) as excinfo: + with raises(Exception): schema.T(f) def test_field_type(): f = Field(GraphQLString) - f.contribute_to_class(ot, 'field_name') + f.contribute_to_class(MyOt, 'field_name') assert isinstance(schema.T(f), GraphQLField) assert schema.T(f).type == GraphQLString def test_field_name_automatic_camelcase(): f = Field(GraphQLString) - f.contribute_to_class(ot, 'field_name') + f.contribute_to_class(MyOt, 'field_name') assert f.name == 'fieldName' def test_field_name_use_name_if_exists(): f = Field(GraphQLString, name='my_custom_name') - f.contribute_to_class(ot, 'field_name') + f.contribute_to_class(MyOt, 'field_name') assert f.name == 'my_custom_name' def test_stringfield_type(): f = StringField() - f.contribute_to_class(ot, 'field_name') + 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(ot, 'field_name') + 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(ot, 'field_name') + 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(ot, 'field_name') + f.contribute_to_class(MyOt, 'field_name') field_type = schema.T(f) - assert 'RESOLVED' == field_type.resolver(ot, None, None) + assert 'RESOLVED' == field_type.resolver(MyOt, None, None) def test_field_resolve_type_custom(): @@ -123,17 +147,17 @@ def test_field_hash(): def test_field_none_type_raises_error(): s = Schema() f = Field(None) - f.contribute_to_class(ot, 'field_name') + f.contribute_to_class(MyOt, 'field_name') with raises(Exception) as excinfo: s.T(f) assert str( - excinfo.value) == "Internal type for field ot.field_name is None" + excinfo.value) == "Internal type for field MyOt.field_name is None" def test_field_str(): f = StringField().as_field() - f.contribute_to_class(ot, 'field_name') - assert str(f) == "ot.field_name" + f.contribute_to_class(MyOt, 'field_name') + assert str(f) == "MyOt.field_name" def test_field_repr(): @@ -143,12 +167,12 @@ def test_field_repr(): def test_field_repr_contributed(): f = StringField().as_field() - f.contribute_to_class(ot, 'field_name') + f.contribute_to_class(MyOt, 'field_name') assert repr(f) == "" def test_field_resolve_objecttype_cos(): f = StringField().as_field() - f.contribute_to_class(ot, 'customdoc') + f.contribute_to_class(MyOt, 'customdoc') field = schema.T(f) assert field.description == 'Resolver documentation' diff --git a/graphene/core/tests/test_options.py b/graphene/core/tests/test_options.py index 0673c289..009ae25d 100644 --- a/graphene/core/tests/test_options.py +++ b/graphene/core/tests/test_options.py @@ -1,6 +1,4 @@ - from py.test import raises -from pytest import raises from graphene.core.fields import StringField from graphene.core.options import Options diff --git a/graphene/core/tests/test_schema.py b/graphene/core/tests/test_schema.py index 2b4dc397..baa8a0bd 100644 --- a/graphene/core/tests/test_schema.py +++ b/graphene/core/tests/test_schema.py @@ -1,13 +1,9 @@ - from py.test import raises -from pytest import raises from graphene import Interface, ObjectType, Schema from graphene.core.fields import Field, ListField, StringField from graphene.core.types.base import LazyType from graphql.core import graphql -from graphql.core.type import (GraphQLInterfaceType, GraphQLObjectType, - GraphQLSchema) from tests.utils import assert_equal_lists schema = Schema(name='My own schema') diff --git a/graphene/core/types/__init__.py b/graphene/core/types/__init__.py index adf3c2a2..44bc2aa5 100644 --- a/graphene/core/types/__init__.py +++ b/graphene/core/types/__init__.py @@ -1,6 +1,9 @@ -from .objecttype import ObjectTypeMeta, BaseObjectType, ObjectType, Interface, Mutation, InputObjectType from .base import BaseType, LazyType, OrderedType -from .argument import Argument -from .field import Field, InputField -from .scalars import String, Int, Boolean, ID, Float +from .argument import Argument, ArgumentsGroup, to_arguments from .definitions import List, NonNull +from .objecttype import ObjectTypeMeta, BaseObjectType, Interface, ObjectType, Mutation, InputObjectType +from .scalars import String, ID, Boolean, Int, Float, Scalar +from .field import Field, InputField + +__all__ = ['BaseType', 'LazyType', 'OrderedType', 'Argument', 'ArgumentsGroup', 'to_arguments', 'List', 'NonNull', 'Field', 'InputField', + 'Interface', 'BaseObjectType', 'ObjectTypeMeta', 'ObjectType', 'Mutation', 'InputObjectType', 'String', 'ID', 'Boolean', 'Int', 'Float', 'Scalar'] diff --git a/graphene/core/types/argument.py b/graphene/core/types/argument.py index 1c762345..db7b58be 100644 --- a/graphene/core/types/argument.py +++ b/graphene/core/types/argument.py @@ -3,11 +3,12 @@ from itertools import chain from graphql.core.type import GraphQLArgument -from .base import BaseType, OrderedType, ArgumentType from ...utils import to_camel_case +from .base import ArgumentType, BaseType, OrderedType class Argument(OrderedType): + def __init__(self, type, description=None, default=None, name=None, _creation_counter=None): super(Argument, self).__init__(_creation_counter=_creation_counter) self.name = name @@ -23,6 +24,7 @@ class Argument(OrderedType): class ArgumentsGroup(BaseType): + def __init__(self, *args, **kwargs): arguments = to_arguments(*args, **kwargs) self.arguments = OrderedDict([(arg.name, arg) for arg in arguments]) @@ -58,7 +60,8 @@ def to_arguments(*args, **kwargs): if name: argument.name = to_camel_case(name) assert argument.name, 'Argument in field must have a name' - assert argument.name not in arguments, 'Found more than one Argument with same name {}'.format(argument.name) + assert argument.name not in arguments, 'Found more than one Argument with same name {}'.format( + argument.name) arguments[argument.name] = argument return sorted(arguments.values()) diff --git a/graphene/core/types/base.py b/graphene/core/types/base.py index b0acf52d..084c929d 100644 --- a/graphene/core/types/base.py +++ b/graphene/core/types/base.py @@ -1,8 +1,10 @@ -import six from functools import total_ordering +import six + class BaseType(object): + @classmethod def internal_type(cls, schema): return getattr(cls, 'T', None) @@ -16,6 +18,7 @@ class MountType(BaseType): class LazyType(MountType): + def __init__(self, type): self.type = type @@ -57,13 +60,13 @@ class OrderedType(MountType): def __lt__(self, other): # This is needed because bisect does not take a comparison function. - if type(self) == type(other): + 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 type(self) == type(other): + if isinstance(other, OrderedType): return self.creation_counter > other.creation_counter return NotImplemented @@ -72,6 +75,7 @@ class OrderedType(MountType): class MirroredType(OrderedType): + def __init__(self, *args, **kwargs): _creation_counter = kwargs.pop('_creation_counter', None) super(MirroredType, self).__init__(_creation_counter=_creation_counter) @@ -80,12 +84,14 @@ class MirroredType(OrderedType): class ArgumentType(MirroredType): + def as_argument(self): from .argument import Argument return Argument(self, _creation_counter=self.creation_counter, *self.args, **self.kwargs) class FieldType(MirroredType): + def contribute_to_class(self, cls, name): from ..types import BaseObjectType, InputObjectType if issubclass(cls, InputObjectType): diff --git a/graphene/core/types/definitions.py b/graphene/core/types/definitions.py index 15b03cd8..ac48f8d9 100644 --- a/graphene/core/types/definitions.py +++ b/graphene/core/types/definitions.py @@ -1,12 +1,14 @@ import six -from graphql.core.type import (GraphQLList, GraphQLNonNull) -from .base import MountType, MountedType, LazyType +from graphql.core.type import GraphQLList, GraphQLNonNull + +from .base import LazyType, MountedType, MountType class OfType(MountedType): + def __init__(self, of_type, *args, **kwargs): - if isinstance(of_type, six.string_types) and of_type != 'self': + if isinstance(of_type, six.string_types): of_type = LazyType(of_type) self.of_type = of_type super(OfType, self).__init__(*args, **kwargs) diff --git a/graphene/core/types/field.py b/graphene/core/types/field.py index 61098485..ea83aeb9 100644 --- a/graphene/core/types/field.py +++ b/graphene/core/types/field.py @@ -1,14 +1,15 @@ -import six from collections import OrderedDict from functools import wraps +import six + from graphql.core.type import GraphQLField, GraphQLInputObjectField -from .base import MountType, LazyType, OrderedType -from .argument import ArgumentsGroup -from .definitions import NonNull -from ...utils import to_camel_case, ProxySnakeDict +from ...utils import ProxySnakeDict, to_camel_case from ..types import BaseObjectType, InputObjectType +from .argument import ArgumentsGroup +from .base import LazyType, MountType, OrderedType +from .definitions import NonNull def make_args_snake_case(resolver): @@ -24,6 +25,7 @@ class Empty(object): class Field(OrderedType): + def __init__(self, type, description=None, args=None, name=None, resolver=None, required=False, default=None, *args_list, **kwargs): _creation_counter = kwargs.pop('_creation_counter', None) super(Field, self).__init__(_creation_counter=_creation_counter) @@ -40,7 +42,8 @@ class Field(OrderedType): self.default = default def contribute_to_class(self, cls, attname): - assert issubclass(cls, BaseObjectType), 'Field {} cannot be mounted in {}'.format(self, cls) + assert issubclass( + cls, BaseObjectType), 'Field {} cannot be mounted in {}'.format(self, cls) if not self.name: self.name = to_camel_case(attname) self.attname = attname @@ -104,11 +107,18 @@ class Field(OrderedType): """ 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 type(self) == type(other): + return eq and self.object_type == other.object_type + return NotImplemented + def __hash__(self): return hash((self.creation_counter, self.object_type)) class InputField(OrderedType): + def __init__(self, type, description=None, default=None, name=None, _creation_counter=None, required=False): super(InputField, self).__init__(_creation_counter=_creation_counter) self.name = name @@ -119,7 +129,8 @@ class InputField(OrderedType): self.default = default def contribute_to_class(self, cls, attname): - assert issubclass(cls, InputObjectType), 'InputField {} cannot be mounted in {}'.format(self, cls) + assert issubclass( + cls, InputObjectType), 'InputField {} cannot be mounted in {}'.format(self, cls) if not self.name: self.name = to_camel_case(attname) self.attname = attname diff --git a/graphene/core/types/objecttype.py b/graphene/core/types/objecttype.py index 8399c75d..aebf8e17 100644 --- a/graphene/core/types/objecttype.py +++ b/graphene/core/types/objecttype.py @@ -6,9 +6,10 @@ from functools import partial import six from graphene import signals +from graphene.core.exceptions import SkipField from graphene.core.options import Options -from graphene.core.types.base import BaseType from graphene.core.types.argument import ArgumentsGroup +from graphene.core.types.base import BaseType from graphql.core.type import (GraphQLArgument, GraphQLInputObjectType, GraphQLInterfaceType, GraphQLObjectType) @@ -176,23 +177,32 @@ class BaseObjectType(BaseType): @classmethod def internal_type(cls, schema): - fields = lambda: OrderedDict([(f.name, schema.T(f)) - for f in cls._meta.fields]) if cls._meta.is_interface: return GraphQLInterfaceType( cls._meta.type_name, description=cls._meta.description, resolve_type=partial(cls.resolve_type, schema), - fields=fields + fields=partial(cls.get_fields, schema) ) return GraphQLObjectType( cls._meta.type_name, description=cls._meta.description, interfaces=[schema.T(i) for i in cls._meta.interfaces], - fields=fields, + fields=partial(cls.get_fields, schema), is_type_of=getattr(cls, 'is_type_of', None) ) + @classmethod + def get_fields(cls, schema): + fields = [] + for field in cls._meta.fields: + try: + fields.append((field.name, schema.T(field))) + except SkipField: + continue + + return OrderedDict(fields) + class Interface(six.with_metaclass(ObjectTypeMeta, BaseObjectType)): pass @@ -203,14 +213,16 @@ class ObjectType(six.with_metaclass(ObjectTypeMeta, BaseObjectType)): class Mutation(six.with_metaclass(ObjectTypeMeta, BaseObjectType)): + @classmethod def _prepare_class(cls): input_class = getattr(cls, 'Input', None) if input_class: - items = dict(input_class.__dict__) + items = dict(vars(input_class)) items.pop('__dict__', None) items.pop('__doc__', None) items.pop('__module__', None) + items.pop('__weakref__', None) arguments = ArgumentsGroup(**items) cls.add_to_class('arguments', arguments) delattr(cls, 'Input') @@ -221,6 +233,7 @@ class Mutation(six.with_metaclass(ObjectTypeMeta, BaseObjectType)): class InputObjectType(ObjectType): + @classmethod def internal_type(cls, schema): fields = lambda: OrderedDict([(f.name, schema.T(f)) diff --git a/graphene/core/types/scalars.py b/graphene/core/types/scalars.py index 2dfc32b7..75cd70a3 100644 --- a/graphene/core/types/scalars.py +++ b/graphene/core/types/scalars.py @@ -25,6 +25,7 @@ class Float(MountedType): class Scalar(MountedType): + @classmethod def internal_type(cls, schema): serialize = getattr(cls, 'serialize') diff --git a/graphene/core/types/tests/test_argument.py b/graphene/core/types/tests/test_argument.py index 08a7718a..fed2f29c 100644 --- a/graphene/core/types/tests/test_argument.py +++ b/graphene/core/types/tests/test_argument.py @@ -1,10 +1,11 @@ from pytest import raises + +from graphene.core.schema import Schema +from graphene.core.types import ObjectType from graphql.core.type import GraphQLArgument from ..argument import Argument, to_arguments from ..scalars import String -from graphene.core.types import ObjectType -from graphene.core.schema import Schema def test_argument_internal_type(): @@ -26,7 +27,8 @@ def test_to_arguments(): other_kwarg=String(), ) - assert [a.name for a in arguments] == ['myArg', 'otherArg', 'myKwarg', 'otherKwarg'] + assert [a.name for a in arguments] == [ + 'myArg', 'otherArg', 'myKwarg', 'otherKwarg'] def test_to_arguments_no_name(): diff --git a/graphene/core/types/tests/test_base.py b/graphene/core/types/tests/test_base.py index 50d14c69..80be2d40 100644 --- a/graphene/core/types/tests/test_base.py +++ b/graphene/core/types/tests/test_base.py @@ -1,9 +1,10 @@ from mock import patch -from ..base import OrderedType, MountedType -from ..field import Field, InputField +from graphene.core.types import InputObjectType, ObjectType + from ..argument import Argument -from graphene.core.types import ObjectType, InputObjectType +from ..base import MountedType, OrderedType +from ..field import Field, InputField def test_orderedtype_equal(): @@ -26,14 +27,16 @@ def test_type_as_field_called(Field): resolver = lambda x: x a = MountedType(2, description='A', resolver=resolver) a.as_field() - Field.assert_called_with(a, 2, _creation_counter=a.creation_counter, description='A', resolver=resolver) + 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') + Argument.assert_called_with( + a, 2, _creation_counter=a.creation_counter, description='A') def test_type_as_field(): diff --git a/graphene/core/types/tests/test_definitions.py b/graphene/core/types/tests/test_definitions.py index 9b175a89..8382dbd0 100644 --- a/graphene/core/types/tests/test_definitions.py +++ b/graphene/core/types/tests/test_definitions.py @@ -1,8 +1,8 @@ -from graphql.core.type import (GraphQLList, GraphQLString, GraphQLNonNull) +from graphene.core.schema import Schema +from graphql.core.type import GraphQLList, GraphQLNonNull, GraphQLString from ..definitions import List, NonNull from ..scalars import String -from graphene.core.schema import Schema schema = Schema() diff --git a/graphene/core/types/tests/test_field.py b/graphene/core/types/tests/test_field.py index 1a0e90c7..c9c21586 100644 --- a/graphene/core/types/tests/test_field.py +++ b/graphene/core/types/tests/test_field.py @@ -1,11 +1,12 @@ -from graphql.core.type import GraphQLField, GraphQLInputObjectField, GraphQLString +from graphene.core.schema import Schema +from graphene.core.types import InputObjectType, ObjectType +from graphql.core.type import (GraphQLField, GraphQLInputObjectField, + GraphQLString) -from ..field import Field, InputField -from ..scalars import String from ..base import LazyType from ..definitions import List -from graphene.core.types import ObjectType, InputObjectType -from graphene.core.schema import Schema +from ..field import Field, InputField +from ..scalars import String def test_field_internal_type(): @@ -81,8 +82,10 @@ def test_field_string_reference(): class MyObjectType(ObjectType): my_field = field + schema = Schema(query=MyObjectType) + assert isinstance(field.type, LazyType) - assert field.type.type_str == 'MyObjectType' + assert schema.T(field.type) == schema.T(MyObjectType) def test_field_custom_arguments(): diff --git a/graphene/core/types/tests/test_scalars.py b/graphene/core/types/tests/test_scalars.py index 7312752a..d1fcc0ac 100644 --- a/graphene/core/types/tests/test_scalars.py +++ b/graphene/core/types/tests/test_scalars.py @@ -1,8 +1,8 @@ +from graphene.core.schema import Schema from graphql.core.type import (GraphQLBoolean, GraphQLFloat, GraphQLID, GraphQLInt, GraphQLScalarType, GraphQLString) -from ..scalars import String, Int, Boolean, ID, Float, Scalar -from graphene.core.schema import Schema +from ..scalars import ID, Boolean, Float, Int, Scalar, String schema = Schema() diff --git a/graphene/relay/fields.py b/graphene/relay/fields.py index 047ac1d5..ca33a01d 100644 --- a/graphene/relay/fields.py +++ b/graphene/relay/fields.py @@ -1,8 +1,7 @@ from collections import Iterable -from graphene.core.fields import Field, IDField -from graphene.core.types.scalars import String, ID, Int -from graphql.core.type import GraphQLArgument, GraphQLID, GraphQLNonNull +from graphene.core.fields import Field +from graphene.core.types.scalars import ID, Int, String from graphql_relay.connection.arrayconnection import connection_from_list from graphql_relay.node.node import from_global_id @@ -23,7 +22,6 @@ class ConnectionField(Field): def wrap_resolved(self, value, instance, args, info): return value - def resolver(self, instance, args, info): from graphene.relay.types import PageInfo schema = info.schema.graphene_schema @@ -59,7 +57,7 @@ class ConnectionField(Field): assert is_node(node), 'Only nodes have connections.' schema.register(node) connection_type = self.get_connection_type(node) - return schema.T(connection_type) + return connection_type class NodeField(Field): @@ -67,7 +65,8 @@ class NodeField(Field): 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) + super(NodeField, self).__init__( + object_type or Node, id=id, *args, **kwargs) self.field_object_type = object_type def id_fetcher(self, global_id, info): @@ -89,6 +88,7 @@ class NodeField(Field): class GlobalIDField(Field): '''The ID of an object''' + def __init__(self, *args, **kwargs): super(GlobalIDField, self).__init__(ID(), *args, **kwargs) self.required = True @@ -100,4 +100,4 @@ class GlobalIDField(Field): super(GlobalIDField, self).contribute_to_class(cls, name) def resolver(self, instance, args, info): - return self.object_type.to_global_id(instance, args, info) + return instance.to_global_id() diff --git a/graphene/relay/tests/__init__.py b/graphene/relay/tests/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/graphene/relay/tests/test_mutations.py b/graphene/relay/tests/test_mutations.py index 5f3a91a3..313a2c1d 100644 --- a/graphene/relay/tests/test_mutations.py +++ b/graphene/relay/tests/test_mutations.py @@ -1,8 +1,6 @@ import graphene from graphene import relay from graphene.core.schema import Schema -from graphene.core.types import InputObjectType -from graphql.core.type import GraphQLInputObjectField my_id = 0 @@ -35,7 +33,7 @@ schema = Schema(query=Query, mutation=MyResultMutation) def test_mutation_arguments(): assert ChangeNumber.arguments assert list(ChangeNumber.arguments) == ['input'] - _input = ChangeNumber.arguments['input'] + ChangeNumber.arguments['input'] # inner_type = _input.get_object_type(schema) # client_mutation_id_field = inner_type._meta.fields_map[ diff --git a/graphene/relay/types.py b/graphene/relay/types.py index 40f04d20..36820a75 100644 --- a/graphene/relay/types.py +++ b/graphene/relay/types.py @@ -1,8 +1,8 @@ from graphene.core.fields import BooleanField, Field, ListField, StringField from graphene.core.types import (InputObjectType, Interface, Mutation, ObjectType) -from graphene.core.types.base import LazyType from graphene.core.types.argument import ArgumentsGroup +from graphene.core.types.base import LazyType from graphene.core.types.definitions import NonNull from graphene.relay.fields import GlobalIDField from graphene.utils import memoize @@ -74,10 +74,9 @@ class BaseNode(object): assert hasattr( cls, 'get_node'), 'get_node classmethod not found in %s Node' % cls - @classmethod - def to_global_id(cls, instance, args, info): - type_name = cls._meta.type_name - return to_global_id(type_name, instance.id) + def to_global_id(self): + type_name = self._meta.type_name + return to_global_id(type_name, self.id) connection_type = Connection edge_type = Edge @@ -105,13 +104,16 @@ class ClientIDMutation(Mutation): @classmethod def _prepare_class(cls): - Input = getattr(cls, 'Input', None) - if Input: + input_class = getattr(cls, 'Input', None) + if input_class: assert hasattr( cls, 'mutate_and_get_payload'), 'You have to implement mutate_and_get_payload' - items = dict(Input.__dict__) + items = dict(vars(input_class)) items.pop('__dict__', None) + items.pop('__doc__', None) + items.pop('__module__', None) + items.pop('__weakref__', None) new_input_type = type('{}Input'.format( cls._meta.type_name), (MutationInputType, ), items) cls.add_to_class('input_type', new_input_type) diff --git a/tests/utils/test_misc.py b/graphene/utils/tests/test_misc.py similarity index 100% rename from tests/utils/test_misc.py rename to graphene/utils/tests/test_misc.py diff --git a/tests/utils/test_proxy_snake_dict.py b/graphene/utils/tests/test_proxy_snake_dict.py similarity index 100% rename from tests/utils/test_proxy_snake_dict.py rename to graphene/utils/tests/test_proxy_snake_dict.py diff --git a/tests/utils/test_str_converter.py b/graphene/utils/tests/test_str_converter.py similarity index 100% rename from tests/utils/test_str_converter.py rename to graphene/utils/tests/test_str_converter.py diff --git a/tests/django_settings.py b/tests/django_settings.py index d8f90141..2af62bb3 100644 --- a/tests/django_settings.py +++ b/tests/django_settings.py @@ -2,7 +2,6 @@ SECRET_KEY = 1 INSTALLED_APPS = [ 'examples.starwars_django', - 'tests.contrib_django', ] DATABASES = {