diff --git a/README.md b/README.md index 27fd62f0..e005d337 100644 --- a/README.md +++ b/README.md @@ -28,9 +28,9 @@ Here is one example for get you started: ```python class Query(graphene.ObjectType): - hello = graphene.StringField(description='A typical hello world') - ping = graphene.StringField(description='Ping someone', - to=graphene.Argument(graphene.String)) + hello = graphene.String(description='A typical hello world') + ping = graphene.String(description='Ping someone', + to=graphene.String()) def resolve_hello(self, args, info): return 'World' diff --git a/README.rst b/README.rst index 45706105..c21f2335 100644 --- a/README.rst +++ b/README.rst @@ -35,9 +35,9 @@ Here is one example for get you started: .. code:: python class Query(graphene.ObjectType): - hello = graphene.StringField(description='A typical hello world') - ping = graphene.StringField(description='Ping someone', - to=graphene.Argument(graphene.String)) + hello = graphene.String(description='A typical hello world') + ping = graphene.String(description='Ping someone', + to=graphene.String()) def resolve_hello(self, args, info): return 'World' diff --git a/bin/autolinter b/bin/autolinter index 47d2201c..164618ae 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 --experimental --aggressive --max-line-length 120 isort -rc . diff --git a/examples/starwars/schema.py b/examples/starwars/schema.py index 9e4e2a2f..b4f05fee 100644 --- a/examples/starwars/schema.py +++ b/examples/starwars/schema.py @@ -12,10 +12,10 @@ Episode = graphene.Enum('Episode', dict( class Character(graphene.Interface): - id = graphene.IDField() - name = graphene.StringField() - friends = graphene.ListField('self') - appears_in = graphene.ListField(Episode) + id = graphene.ID() + name = graphene.String() + friends = graphene.List('Character') + appears_in = graphene.List(Episode) def resolve_friends(self, args, *_): # The character friends is a list of strings @@ -23,11 +23,11 @@ class Character(graphene.Interface): class Human(Character): - home_planet = graphene.StringField() + home_planet = graphene.String() class Droid(Character): - primary_function = graphene.StringField() + primary_function = graphene.String() class Query(graphene.ObjectType): @@ -35,10 +35,10 @@ class Query(graphene.ObjectType): episode=graphene.Argument(Episode) ) human = graphene.Field(Human, - id=graphene.Argument(graphene.String) + id=graphene.String() ) droid = graphene.Field(Droid, - id=graphene.Argument(graphene.String) + id=graphene.String() ) @resolve_only_args diff --git a/examples/starwars_django/schema.py b/examples/starwars_django/schema.py index 21b29a1f..7f267fed 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,9 +38,10 @@ class Faction(DjangoNode): class IntroduceShip(relay.ClientIDMutation): + class Input: - ship_name = graphene.StringField(required=True) - faction_id = graphene.StringField(required=True) + ship_name = graphene.String(required=True) + faction_id = graphene.String(required=True) ship = graphene.Field(Ship) faction = graphene.Field(Faction) @@ -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/examples/starwars_relay/schema.py b/examples/starwars_relay/schema.py index b5d07cbc..da31a6f7 100644 --- a/examples/starwars_relay/schema.py +++ b/examples/starwars_relay/schema.py @@ -8,7 +8,7 @@ schema = graphene.Schema(name='Starwars Relay Schema') class Ship(relay.Node): '''A ship in the Star Wars saga''' - name = graphene.StringField(description='The name of the ship.') + name = graphene.String(description='The name of the ship.') @classmethod def get_node(cls, id): @@ -17,7 +17,7 @@ class Ship(relay.Node): class Faction(relay.Node): '''A faction in the Star Wars saga''' - name = graphene.StringField(description='The name of the faction.') + name = graphene.String(description='The name of the faction.') ships = relay.ConnectionField( Ship, description='The ships used by the faction.') @@ -34,8 +34,8 @@ class Faction(relay.Node): class IntroduceShip(relay.ClientIDMutation): class Input: - ship_name = graphene.StringField(required=True) - faction_id = graphene.StringField(required=True) + ship_name = graphene.String(required=True) + faction_id = graphene.String(required=True) ship = graphene.Field(Ship) faction = graphene.Field(Faction) diff --git a/graphene/__init__.py b/graphene/__init__.py index ad3b3b10..61e73c6d 100644 --- a/graphene/__init__.py +++ b/graphene/__init__.py @@ -1,9 +1,5 @@ from graphql.core.type import ( - GraphQLEnumType as Enum, - GraphQLArgument as Argument, - GraphQLString as String, - GraphQLInt as Int, - GraphQLID as ID + GraphQLEnumType as Enum ) from graphene import signals @@ -14,12 +10,24 @@ from graphene.core.schema import ( from graphene.core.types import ( ObjectType, + InputObjectType, Interface, Mutation, + BaseType, + LazyType, + Argument, + Field, + InputField, + String, + Int, + Boolean, + ID, + Float, + List, + NonNull ) from graphene.core.fields import ( - Field, StringField, IntField, BooleanField, @@ -33,7 +41,31 @@ from graphene.decorators import ( resolve_only_args ) -__all__ = ['Enum', 'Argument', 'String', 'Int', 'ID', 'signals', 'Schema', - 'ObjectType', 'Interface', 'Mutation', 'Field', 'StringField', - 'IntField', 'BooleanField', 'IDField', 'ListField', 'NonNullField', - 'FloatField', 'resolve_only_args'] +__all__ = [ + 'Enum', + 'Argument', + 'String', + 'Int', + 'Boolean', + 'Float', + 'ID', + 'List', + 'NonNull', + 'signals', + 'Schema', + 'BaseType', + 'LazyType', + 'ObjectType', + 'InputObjectType', + 'Interface', + 'Mutation', + 'Field', + 'InputField', + 'StringField', + 'IntField', + 'BooleanField', + 'IDField', + 'ListField', + 'NonNullField', + 'FloatField', + 'resolve_only_args'] diff --git a/graphene/contrib/django/converter.py b/graphene/contrib/django/converter.py index b1ddc791..29b52dec 100644 --- a/graphene/contrib/django/converter.py +++ b/graphene/contrib/django/converter.py @@ -3,8 +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 ID, Boolean, Float, Int, String try: UUIDField = models.UUIDField @@ -17,7 +16,8 @@ except AttributeError: @singledispatch def convert_django_field(field): raise Exception( - "Don't know how to convert the Django field %s (%s)" % (field, field.__class__)) + "Don't know how to convert the Django field %s (%s)" % + (field, field.__class__)) @convert_django_field.register(models.DateField) @@ -28,12 +28,12 @@ def convert_django_field(field): @convert_django_field.register(models.URLField) @convert_django_field.register(UUIDField) def convert_field_to_string(field): - return StringField(description=field.help_text) + return String(description=field.help_text) @convert_django_field.register(models.AutoField) def convert_field_to_id(field): - return IDField(description=field.help_text) + return ID(description=field.help_text) @convert_django_field.register(models.PositiveIntegerField) @@ -42,23 +42,23 @@ def convert_field_to_id(field): @convert_django_field.register(models.BigIntegerField) @convert_django_field.register(models.IntegerField) def convert_field_to_int(field): - return IntField(description=field.help_text) + return Int(description=field.help_text) @convert_django_field.register(models.BooleanField) def convert_field_to_boolean(field): - return BooleanField(description=field.help_text, required=True) + return Boolean(description=field.help_text, required=True) @convert_django_field.register(models.NullBooleanField) def convert_field_to_nullboolean(field): - return BooleanField(description=field.help_text) + return Boolean(description=field.help_text) @convert_django_field.register(models.DecimalField) @convert_django_field.register(models.FloatField) def convert_field_to_float(field): - return FloatField(description=field.help_text) + return Float(description=field.help_text) @convert_django_field.register(models.ManyToManyField) diff --git a/graphene/contrib/django/fields.py b/graphene/contrib/django/fields.py index 3abbfa94..d76e984e 100644 --- a/graphene/contrib/django/fields.py +++ b/graphene/contrib/django/fields.py @@ -1,6 +1,9 @@ from graphene import relay from graphene.contrib.django.utils import get_type_for_model, lazy_map -from graphene.core.fields import Field, LazyField, ListField +from graphene.core.exceptions import SkipField +from graphene.core.fields import Field +from graphene.core.types.base import FieldType +from graphene.core.types.definitions import List from graphene.relay.utils import is_node @@ -8,60 +11,54 @@ class DjangoConnectionField(relay.ConnectionField): def wrap_resolved(self, value, instance, args, info): schema = info.schema.graphene_schema - return lazy_map(value, self.get_object_type(schema)) + return lazy_map(value, self.type.get_object_type(schema)) -class LazyListField(ListField): +class LazyListField(Field): - def resolve(self, instance, args, info): + def get_type(self, schema): + return List(self.type) + + def resolver(self, instance, args, info): schema = info.schema.graphene_schema - resolved = super(LazyListField, self).resolve(instance, args, info) + resolved = super(LazyListField, self).resolver(instance, args, info) return lazy_map(resolved, self.get_object_type(schema)) -class ConnectionOrListField(LazyField): +class ConnectionOrListField(Field): - def get_field(self, schema): - model_field = self.field_type + def internal_type(self, schema): + model_field = self.type field_object_type = model_field.get_object_type(schema) if is_node(field_object_type): field = DjangoConnectionField(model_field) else: field = LazyListField(model_field) field.contribute_to_class(self.object_type, self.name) - return field + return field.internal_type(schema) -class DjangoModelField(Field): +class DjangoModelField(FieldType): def __init__(self, model, *args, **kwargs): - super(DjangoModelField, self).__init__(None, *args, **kwargs) self.model = model - - def resolve(self, instance, args, info): - resolved = super(DjangoModelField, self).resolve(instance, args, info) - schema = info.schema.graphene_schema - _type = self.get_object_type(schema) - assert _type, ("Field %s cannot be retrieved as the " - "ObjectType is not registered by the schema" % ( - self.attname - )) - return _type(resolved) + super(DjangoModelField, self).__init__(*args, **kwargs) def internal_type(self, schema): _type = self.get_object_type(schema) - if not _type and self.object_type._meta.only_fields: + if not _type and self.parent._meta.only_fields: raise Exception( "Model %r is not accessible by the schema. " "You can either register the type manually " "using @schema.register. " - "Or disable the field %s in %s" % ( + "Or disable the field in %s" % ( self.model, - self.attname, - self.object_type + self.parent, ) ) - return schema.T(_type) or Field.SKIP + 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/options.py b/graphene/contrib/django/options.py index 9293af6e..a65ce52d 100644 --- a/graphene/contrib/django/options.py +++ b/graphene/contrib/django/options.py @@ -32,6 +32,7 @@ class DjangoOptions(Options): return if not self.model: raise Exception( - 'Django ObjectType %s must have a model in the Meta class attr' % cls) + 'Django ObjectType %s must have a model in the Meta class attr' % + cls) elif not inspect.isclass(self.model) or not issubclass(self.model, models.Model): raise Exception('Provided model in %s is not a Django model' % cls) 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 65% rename from tests/contrib_django/test_converter.py rename to graphene/contrib/django/tests/test_converter.py index 217849ab..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 @@ -15,8 +14,9 @@ def assert_conversion(django_field, graphene_field, *args): field = django_field(*args, help_text='Custom Help Text') graphene_type = convert_django_field(field) assert isinstance(graphene_type, graphene_field) - assert graphene_type.description == 'Custom Help Text' - return graphene_type + field = graphene_type.as_field() + assert field.description == 'Custom Help Text' + return field def test_should_unknown_django_field_raise_exception(): @@ -26,86 +26,86 @@ def test_should_unknown_django_field_raise_exception(): def test_should_date_convert_string(): - assert_conversion(models.DateField, graphene.StringField) + assert_conversion(models.DateField, graphene.String) def test_should_char_convert_string(): - assert_conversion(models.CharField, graphene.StringField) + assert_conversion(models.CharField, graphene.String) def test_should_text_convert_string(): - assert_conversion(models.TextField, graphene.StringField) + assert_conversion(models.TextField, graphene.String) def test_should_email_convert_string(): - assert_conversion(models.EmailField, graphene.StringField) + assert_conversion(models.EmailField, graphene.String) def test_should_slug_convert_string(): - assert_conversion(models.SlugField, graphene.StringField) + assert_conversion(models.SlugField, graphene.String) def test_should_url_convert_string(): - assert_conversion(models.URLField, graphene.StringField) + assert_conversion(models.URLField, graphene.String) def test_should_auto_convert_id(): - assert_conversion(models.AutoField, graphene.IDField) + assert_conversion(models.AutoField, graphene.ID) def test_should_positive_integer_convert_int(): - assert_conversion(models.PositiveIntegerField, graphene.IntField) + assert_conversion(models.PositiveIntegerField, graphene.Int) def test_should_positive_small_convert_int(): - assert_conversion(models.PositiveSmallIntegerField, graphene.IntField) + assert_conversion(models.PositiveSmallIntegerField, graphene.Int) def test_should_small_integer_convert_int(): - assert_conversion(models.SmallIntegerField, graphene.IntField) + assert_conversion(models.SmallIntegerField, graphene.Int) def test_should_big_integer_convert_int(): - assert_conversion(models.BigIntegerField, graphene.IntField) + assert_conversion(models.BigIntegerField, graphene.Int) def test_should_integer_convert_int(): - assert_conversion(models.IntegerField, graphene.IntField) + assert_conversion(models.IntegerField, graphene.Int) def test_should_boolean_convert_boolean(): - field = assert_conversion(models.BooleanField, graphene.BooleanField) + field = assert_conversion(models.BooleanField, graphene.Boolean) assert field.required is True def test_should_nullboolean_convert_boolean(): - field = assert_conversion(models.NullBooleanField, graphene.BooleanField) + field = assert_conversion(models.NullBooleanField, graphene.Boolean) assert field.required is False def test_should_float_convert_float(): - assert_conversion(models.FloatField, graphene.FloatField) + 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.field_type, DjangoModelField) - assert graphene_type.field_type.model == Reporter + assert isinstance(graphene_type.type, DjangoModelField) + assert graphene_type.type.model == Reporter def test_should_manytoone_convert_connectionorlist(): graphene_type = convert_django_field(Reporter.articles.related) assert isinstance(graphene_type, ConnectionOrListField) - assert isinstance(graphene_type.field_type, DjangoModelField) - assert graphene_type.field_type.model == Article + assert isinstance(graphene_type.type, DjangoModelField) + assert graphene_type.type.model == Article def test_should_onetoone_convert_model(): field = assert_conversion(models.OneToOneField, DjangoModelField, Article) - assert field.model == Article + assert field.type.model == Article def test_should_foreignkey_convert_model(): field = assert_conversion(models.ForeignKey, DjangoModelField, Article) - assert field.model == Article + assert field.type.model == Article 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..a8e8229b 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,42 @@ 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 +105,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 +118,7 @@ def test_should_node(): ''' expected = { 'reporter': { - 'id': 'UmVwb3J0ZXJOb2RlVHlwZTox', + 'id': 'UmVwb3J0ZXJOb2RlOjE=', 'firstName': 'ABA', 'lastName': 'X', 'email': '', @@ -172,11 +131,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 90% rename from tests/contrib_django/test_types.py rename to graphene/contrib/django/tests/test_types.py index 94472745..2cfabfcb 100644 --- a/tests/contrib_django/test_types.py +++ b/graphene/contrib/django/tests/test_types.py @@ -2,13 +2,16 @@ from graphene import Schema from graphene.contrib.django.types import DjangoInterface, DjangoNode -from graphene.core.fields import IntField +from graphene.core.fields import Field +from graphene.core.types.scalars import Int from graphene.relay.fields import GlobalIDField from 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''' @@ -16,10 +19,11 @@ class Character(DjangoInterface): model = Reporter +@schema.register class Human(DjangoNode): '''Human description''' - pub_date = IntField() + pub_date = Int() def get_node(self, id): pass @@ -27,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) @@ -57,7 +59,8 @@ def test_node_idfield(): def test_node_replacedfield(): idfield = Human._meta.fields_map['pub_date'] - assert isinstance(idfield, IntField) + assert isinstance(idfield, Field) + assert schema.T(idfield).type == schema.T(Int()) def test_interface_resolve_type(): diff --git a/tests/contrib_django/test_urls.py b/graphene/contrib/django/tests/test_urls.py similarity index 94% rename from tests/contrib_django/test_urls.py rename to graphene/contrib/django/tests/test_urls.py index 2f0801ee..409471d4 100644 --- a/tests/contrib_django/test_urls.py +++ b/graphene/contrib/django/tests/test_urls.py @@ -18,7 +18,7 @@ class Character(DjangoNode): class Human(DjangoNode): - raises = graphene.StringField() + raises = graphene.String() class Meta: model = Article diff --git a/tests/contrib_django/test_views.py b/graphene/contrib/django/tests/test_views.py similarity index 81% rename from tests/contrib_django/test_views.py rename to graphene/contrib/django/tests/test_views.py index b58efb21..28fec258 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_json(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_json(settings, client): def test_client_post_empty_query_graphql(settings, client): - settings.ROOT_URLCONF = 'tests.contrib_django.test_urls' + settings.ROOT_URLCONF = 'graphene.contrib.django.tests.test_urls' response = client.post( '/graphql', '', 'application/graphql') json_response = format_response(response) @@ -48,7 +48,7 @@ def test_client_post_empty_query_graphql(settings, client): def test_client_post_bad_query_json(settings, client): - 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) @@ -58,7 +58,7 @@ def test_client_post_bad_query_json(settings, client): def test_client_post_bad_query_graphql(settings, client): - settings.ROOT_URLCONF = 'tests.contrib_django.test_urls' + settings.ROOT_URLCONF = 'graphene.contrib.django.tests.test_urls' response = client.post( '/graphql', '{ MALFORMED', 'application/graphql') json_response = format_response(response) @@ -68,7 +68,7 @@ def test_client_post_bad_query_graphql(settings, client): def test_client_get_good_query(settings, client): - 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 = { @@ -80,7 +80,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][ @@ -89,7 +89,7 @@ def test_client_get_good_query_with_raise(settings, client): def test_client_post_good_query_json(settings, client): - settings.ROOT_URLCONF = 'tests.contrib_django.test_urls' + settings.ROOT_URLCONF = 'graphene.contrib.django.tests.test_urls' response = client.post( '/graphql', json.dumps({'query': '{ headline }'}), 'application/json') json_response = format_response(response) @@ -102,7 +102,7 @@ def test_client_post_good_query_json(settings, client): def test_client_post_good_query_graphql(settings, client): - settings.ROOT_URLCONF = 'tests.contrib_django.test_urls' + settings.ROOT_URLCONF = 'graphene.contrib.django.tests.test_urls' response = client.post( '/graphql', '{ headline }', 'application/graphql') json_response = format_response(response) @@ -115,7 +115,7 @@ def test_client_post_good_query_graphql(settings, client): # def test_client_get_bad_query(settings, client): -# 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': [{'message': 'Must provide query string.'}]} diff --git a/graphene/contrib/django/types.py b/graphene/contrib/django/types.py index f9282f14..2780d33e 100644 --- a/graphene/contrib/django/types.py +++ b/graphene/contrib/django/types.py @@ -46,11 +46,13 @@ class InstanceObjectType(BaseObjectType): return getattr(self.instance, attr) -class DjangoObjectType(six.with_metaclass(DjangoObjectTypeMeta, InstanceObjectType)): +class DjangoObjectType(six.with_metaclass( + DjangoObjectTypeMeta, InstanceObjectType)): pass -class DjangoInterface(six.with_metaclass(DjangoObjectTypeMeta, InstanceObjectType)): +class DjangoInterface(six.with_metaclass( + DjangoObjectTypeMeta, InstanceObjectType)): pass 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 7cb821aa..463e1983 100644 --- a/graphene/core/fields.py +++ b/graphene/core/fields.py @@ -1,258 +1,49 @@ -import inspect -from functools import total_ordering, wraps +import warnings -import six - -from graphene.core.scalars import GraphQLSkipField -from graphene.core.types import BaseObjectType, InputObjectType -from graphene.utils import ProxySnakeDict, enum_to_graphql_enum, to_camel_case -from graphql.core.type import (GraphQLArgument, GraphQLBoolean, GraphQLField, - GraphQLFloat, GraphQLID, - GraphQLInputObjectField, GraphQLInt, - GraphQLList, GraphQLNonNull, GraphQLString) - -try: - from enum import Enum -except ImportError: - class Enum(object): - pass +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 Empty(object): +class DeprecatedField(FieldType): + + def __init__(self, *args, **kwargs): + cls = self.__class__ + warnings.warn("Using {} is not longer supported".format( + cls.__name__), FutureWarning) + if 'resolve' in kwargs: + kwargs['resolver'] = kwargs.pop('resolve') + return super(DeprecatedField, self).__init__(*args, **kwargs) + + +class StringField(DeprecatedField, String): pass -@total_ordering -class Field(object): - SKIP = GraphQLSkipField - creation_counter = 0 - required = False - - def __init__(self, field_type, name=None, resolve=None, required=False, args=None, description='', default=None, **extra_args): - self.field_type = field_type - self.resolve_fn = resolve - self.required = self.required or required - self.args = args or {} - self.extra_args = extra_args - self._type = None - self.name = name - self.description = description or self.__doc__ - self.object_type = None - self.default = default - self.creation_counter = Field.creation_counter - Field.creation_counter += 1 - - def get_default(self): - return self.default - - def contribute_to_class(self, cls, name, add=True): - if not self.name: - self.name = to_camel_case(name) - self.attname = name - self.object_type = cls - if isinstance(self.field_type, Field) and not self.field_type.object_type: - self.field_type.contribute_to_class(cls, name, False) - if add: - cls._meta.add_field(self) - - def resolve(self, instance, args, info): - schema = info and getattr(info.schema, 'graphene_schema', None) - resolve_fn = self.get_resolve_fn(schema) - if resolve_fn: - return resolve_fn(instance, ProxySnakeDict(args), info) - else: - return getattr(instance, self.attname, self.get_default()) - - def get_resolve_fn(self, schema): - object_type = self.get_object_type(schema) - if object_type and object_type._meta.is_mutation: - return object_type.mutate - elif self.resolve_fn: - return self.resolve_fn - else: - custom_resolve_fn_name = 'resolve_%s' % self.attname - if hasattr(self.object_type, custom_resolve_fn_name): - resolve_fn = getattr(self.object_type, custom_resolve_fn_name) - - @wraps(resolve_fn) - def custom_resolve_fn(instance, args, info): - return resolve_fn(instance, args, info) - return custom_resolve_fn - - def get_object_type(self, schema): - field_type = self.field_type - if inspect.isfunction(field_type): - field_type = field_type(self) - _is_class = inspect.isclass(field_type) - if isinstance(field_type, Field): - return field_type.get_object_type(schema) - if _is_class and issubclass(field_type, BaseObjectType): - return field_type - elif isinstance(field_type, six.string_types): - if field_type == 'self': - return self.object_type - else: - return schema.get_type(field_type) - - def type_wrapper(self, field_type): - if self.required: - field_type = GraphQLNonNull(field_type) - return field_type - - def internal_type(self, schema): - field_type = self.field_type - _is_class = inspect.isclass(field_type) - if isinstance(field_type, Field): - field_type = self.field_type.internal_type(schema) - elif _is_class and issubclass(field_type, Enum): - field_type = enum_to_graphql_enum(field_type) - else: - object_type = self.get_object_type(schema) - if object_type: - field_type = schema.T(object_type) - - field_type = self.type_wrapper(field_type) - return field_type - - def internal_field(self, schema): - if not self.object_type: - raise Exception( - 'Field could not be constructed in a non graphene.ObjectType or graphene.Interface') - - extra_args = self.extra_args.copy() - for arg_name, arg_value in self.extra_args.items(): - if isinstance(arg_value, GraphQLArgument): - self.args[arg_name] = arg_value - del extra_args[arg_name] - - if extra_args != {}: - raise TypeError("Field %s.%s initiated with invalid args: %s" % ( - self.object_type, - self.attname, - ','.join(extra_args.keys()) - )) - - args = self.args - - object_type = self.get_object_type(schema) - if object_type and object_type._meta.is_mutation: - assert not self.args, 'Arguments provided for mutations are defined in Input class in Mutation' - args = object_type.get_input_type().fields_as_arguments(schema) - - internal_type = self.internal_type(schema) - if not internal_type: - raise Exception("Internal type for field %s is None" % self) - - description = self.description - resolve_fn = self.get_resolve_fn(schema) - if resolve_fn: - description = resolve_fn.__doc__ or description - - @wraps(resolve_fn) - def resolver(*args): - return self.resolve(*args) - else: - resolver = self.resolve - - if issubclass(self.object_type, InputObjectType): - return GraphQLInputObjectField( - internal_type, - description=description, - ) - - return GraphQLField( - internal_type, - description=description, - args=args, - resolver=resolver, - ) - - def __str__(self): - """ Return "object_type.name". """ - return '%s.%s' % (self.object_type.__name__, self.attname) - - def __repr__(self): - """ - Displays the module, class and name of the field. - """ - path = '%s.%s' % (self.__class__.__module__, self.__class__.__name__) - name = getattr(self, 'attname', None) - if name is not None: - return '<%s: %s>' % (path, name) - return '<%s>' % path - - def __eq__(self, other): - # Needed for @total_ordering - if isinstance(other, Field): - return self.creation_counter == other.creation_counter and \ - self.object_type == other.object_type - return NotImplemented - - def __lt__(self, other): - # This is needed because bisect does not take a comparison function. - if isinstance(other, Field): - return self.creation_counter < other.creation_counter - return NotImplemented - - def __hash__(self): - return hash((self.creation_counter, self.object_type)) - - def __copy__(self): - # We need to avoid hitting __reduce__, so define this - # slightly weird copy construct. - obj = Empty() - obj.__class__ = self.__class__ - obj.__dict__ = self.__dict__.copy() - if self.field_type == 'self': - obj.field_type = self.object_type - return obj +class IntField(DeprecatedField, Int): + pass -class LazyField(Field): - - def inner_field(self, schema): - return self.get_field(schema) - - def internal_type(self, schema): - return self.inner_field(schema).internal_type(schema) - - def internal_field(self, schema): - return self.inner_field(schema).internal_field(schema) +class BooleanField(DeprecatedField, Boolean): + pass -class TypeField(Field): - - def __init__(self, *args, **kwargs): - super(TypeField, self).__init__(self.field_type, *args, **kwargs) +class IDField(DeprecatedField, ID): + pass -class StringField(TypeField): - field_type = GraphQLString +class FloatField(DeprecatedField, Float): + pass -class IntField(TypeField): - field_type = GraphQLInt +class ListField(DeprecatedField, List): + pass -class BooleanField(TypeField): - field_type = GraphQLBoolean +class NonNullField(DeprecatedField, NonNull): + pass -class IDField(TypeField): - field_type = GraphQLID - - -class FloatField(TypeField): - field_type = GraphQLFloat - - -class ListField(Field): - - def type_wrapper(self, field_type): - return GraphQLList(field_type) - - -class NonNullField(Field): - - def type_wrapper(self, field_type): - return GraphQLNonNull(field_type) +__all__ = ['Field', 'StringField', 'IntField', 'BooleanField', + 'IDField', 'FloatField', 'ListField', 'NonNullField'] diff --git a/graphene/core/options.py b/graphene/core/options.py index 8e12abb4..fac5081a 100644 --- a/graphene/core/options.py +++ b/graphene/core/options.py @@ -52,7 +52,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())) + "'class Meta' got invalid attribute(s): %s" % + ','.join( + meta_attrs.keys())) else: self.proxy = False diff --git a/graphene/core/schema.py b/graphene/core/schema.py index 54361100..a15a10c8 100644 --- a/graphene/core/schema.py +++ b/graphene/core/schema.py @@ -1,6 +1,9 @@ +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 @@ -16,10 +19,10 @@ class GraphQLSchema(_GraphQLSchema): class Schema(object): - _query = None _executor = None - def __init__(self, query=None, mutation=None, name='Schema', executor=None): + def __init__(self, query=None, mutation=None, + name='Schema', executor=None): self._types_names = {} self._types = {} self.mutation = mutation @@ -34,32 +37,24 @@ class Schema(object): def T(self, object_type): if not object_type: return - if object_type not in self._types: - internal_type = object_type.internal_type(self) - self._types[object_type] = internal_type - self._types_names[internal_type.name] = object_type - return self._types[object_type] - - @property - def query(self): - return self._query - - @query.setter - def query(self, query): - self._query = query - - @property - def mutation(self): - return self._mutation - - @mutation.setter - def mutation(self, mutation): - self._mutation = mutation + if inspect.isclass(object_type) and issubclass( + object_type, BaseType) or isinstance( + object_type, BaseType): + 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) + if is_objecttype: + self.register(object_type) + return self._types[object_type] + else: + return object_type @property def executor(self): if not self._executor: - self.executor = Executor( + self._executor = Executor( [SynchronousExecutionMiddleware()], map_type=OrderedDict) return self._executor @@ -69,25 +64,45 @@ class Schema(object): @property def schema(self): - if not self._query: + 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)) + return GraphQLSchema( + self, query=self.T(self.query), + mutation=self.T(self.mutation)) def register(self, object_type): + type_name = object_type._meta.type_name + registered_object_type = self._types_names.get(type_name, None) + if registered_object_type: + assert registered_object_type == object_type, 'Type {} already registered with other object type'.format( + type_name) self._types_names[object_type._meta.type_name] = object_type return object_type + def objecttype(self, type): + name = getattr(type, 'name', None) + if name: + objecttype = self._types_names.get(name, None) + if objecttype and inspect.isclass( + objecttype) and issubclass(objecttype, BaseObjectType): + return objecttype + + def setup(self): + assert self.query, 'The base query type is not set' + self.T(self.query) + def get_type(self, type_name): - self.schema._build_type_map() + self.setup() if type_name not in self._types_names: - raise Exception('Type %s not found in %r' % (type_name, self)) + raise KeyError('Type %r not found in %r' % (type_name, self)) return self._types_names[type_name] @property def types(self): return self._types_names - def execute(self, request='', root=None, vars=None, operation_name=None, **kwargs): + def execute(self, request='', root=None, vars=None, + operation_name=None, **kwargs): root = root or object() return self.executor.execute( self.schema, diff --git a/tests/core/test_mutations.py b/graphene/core/tests/test_mutations.py similarity index 79% rename from tests/core/test_mutations.py rename to graphene/core/tests/test_mutations.py index 859e0444..c5a26daa 100644 --- a/tests/core/test_mutations.py +++ b/graphene/core/tests/test_mutations.py @@ -1,4 +1,3 @@ - import graphene from graphene.core.schema import Schema @@ -6,15 +5,15 @@ my_id = 0 class Query(graphene.ObjectType): - base = graphene.StringField() + base = graphene.String() class ChangeNumber(graphene.Mutation): '''Result mutation''' class Input: - to = graphene.IntField() + to = graphene.Int() - result = graphene.StringField() + result = graphene.String() @classmethod def mutate(cls, instance, args, info): @@ -31,9 +30,7 @@ schema = Schema(query=Query, mutation=MyResultMutation) def test_mutation_input(): - assert ChangeNumber.input_type - assert ChangeNumber.input_type._meta.type_name == 'ChangeNumberInput' - assert list(ChangeNumber.input_type._meta.fields_map.keys()) == ['to'] + assert list(schema.T(ChangeNumber.arguments).keys()) == ['to'] def test_execute_mutations(): diff --git a/graphene/core/tests/test_old_fields.py b/graphene/core/tests/test_old_fields.py new file mode 100644 index 00000000..527444ae --- /dev/null +++ b/graphene/core/tests/test_old_fields.py @@ -0,0 +1,178 @@ +from py.test import raises + +from graphene.core.fields import (BooleanField, Field, FloatField, IDField, + IntField, NonNullField, StringField) +from graphene.core.schema import Schema +from graphene.core.types import ObjectType +from graphql.core.type import (GraphQLBoolean, GraphQLField, GraphQLFloat, + GraphQLID, GraphQLInt, GraphQLNonNull, + GraphQLString) + + +class MyOt(ObjectType): + + def resolve_customdoc(self, *args, **kwargs): + '''Resolver documentation''' + return None + + def __str__(self): + return "ObjectType" + +schema = Schema() + + +def test_field_no_contributed_raises_error(): + f = Field(GraphQLString) + with raises(Exception): + schema.T(f) + + +def test_field_type(): + f = Field(GraphQLString) + f.contribute_to_class(MyOt, 'field_name') + assert isinstance(schema.T(f), GraphQLField) + assert schema.T(f).type == GraphQLString + + +def test_field_name_automatic_camelcase(): + f = Field(GraphQLString) + f.contribute_to_class(MyOt, 'field_name') + assert f.name == 'fieldName' + + +def test_field_name_use_name_if_exists(): + f = Field(GraphQLString, name='my_custom_name') + f.contribute_to_class(MyOt, 'field_name') + assert f.name == 'my_custom_name' + + +def test_stringfield_type(): + f = StringField() + f.contribute_to_class(MyOt, 'field_name') + assert schema.T(f) == GraphQLString + + +def test_idfield_type(): + f = IDField() + f.contribute_to_class(MyOt, 'field_name') + assert schema.T(f) == GraphQLID + + +def test_booleanfield_type(): + f = BooleanField() + f.contribute_to_class(MyOt, 'field_name') + assert schema.T(f) == GraphQLBoolean + + +def test_intfield_type(): + f = IntField() + f.contribute_to_class(MyOt, 'field_name') + assert schema.T(f) == GraphQLInt + + +def test_floatfield_type(): + f = FloatField() + f.contribute_to_class(MyOt, 'field_name') + assert schema.T(f) == GraphQLFloat + + +def test_nonnullfield_type(): + f = NonNullField(StringField()) + f.contribute_to_class(MyOt, 'field_name') + assert isinstance(schema.T(f), GraphQLNonNull) + + +def test_stringfield_type_required(): + f = StringField(required=True).as_field() + f.contribute_to_class(MyOt, 'field_name') + assert isinstance(schema.T(f), GraphQLField) + assert isinstance(schema.T(f).type, GraphQLNonNull) + + +def test_field_resolve(): + f = StringField(required=True, resolve=lambda *args: 'RESOLVED').as_field() + f.contribute_to_class(MyOt, 'field_name') + field_type = schema.T(f) + assert 'RESOLVED' == field_type.resolver(MyOt, None, None) + + +def test_field_resolve_type_custom(): + class MyCustomType(ObjectType): + pass + + f = Field('MyCustomType') + + class OtherType(ObjectType): + field_name = f + + s = Schema() + s.query = OtherType + s.register(MyCustomType) + + assert s.T(f).type == s.T(MyCustomType) + + +def test_field_orders(): + f1 = Field(None) + f2 = Field(None) + assert f1 < f2 + + +def test_field_orders_wrong_type(): + field = Field(None) + try: + assert not field < 1 + except TypeError: + # Fix exception raising in Python3+ + pass + + +def test_field_eq(): + f1 = Field(None) + f2 = Field(None) + assert f1 != f2 + + +def test_field_eq_wrong_type(): + field = Field(None) + assert field != 1 + + +def test_field_hash(): + f1 = Field(None) + f2 = Field(None) + assert hash(f1) != hash(f2) + + +def test_field_none_type_raises_error(): + s = Schema() + f = Field(None) + f.contribute_to_class(MyOt, 'field_name') + with raises(Exception) as excinfo: + s.T(f) + assert str( + excinfo.value) == "Internal type for field MyOt.field_name is None" + + +def test_field_str(): + f = StringField().as_field() + f.contribute_to_class(MyOt, 'field_name') + assert str(f) == "MyOt.field_name" + + +def test_field_repr(): + f = StringField().as_field() + assert repr(f) == "" + + +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/tests/core/test_options.py b/graphene/core/tests/test_options.py similarity index 93% rename from tests/core/test_options.py rename to graphene/core/tests/test_options.py index 0673c289..3b656bd4 100644 --- a/tests/core/test_options.py +++ b/graphene/core/tests/test_options.py @@ -1,8 +1,6 @@ - from py.test import raises -from pytest import raises -from graphene.core.fields import StringField +from graphene.core.fields import Field from graphene.core.options import Options @@ -22,7 +20,7 @@ def test_field_added_in_meta(): pass opt.contribute_to_class(ObjectType, '_meta') - f = StringField() + f = Field(None) f.attname = 'string_field' opt.add_field(f) assert f in opt.fields diff --git a/tests/core/test_query.py b/graphene/core/tests/test_query.py similarity index 60% rename from tests/core/test_query.py rename to graphene/core/tests/test_query.py index 4814460f..d56b66f7 100644 --- a/tests/core/test_query.py +++ b/graphene/core/tests/test_query.py @@ -1,23 +1,22 @@ -from graphene.core.fields import Field, ListField, StringField +from graphene.core.fields import Field from graphene.core.schema import Schema -from graphene.core.types import Interface, ObjectType +from graphene.core.types import Interface, List, ObjectType, String from graphql.core import graphql -from graphql.core.type import (GraphQLInterfaceType, GraphQLObjectType, - GraphQLSchema) +from graphql.core.type import GraphQLSchema class Character(Interface): - name = StringField() + name = String() class Pet(ObjectType): - type = StringField(resolve=lambda *_: 'Dog') + type = String(resolver=lambda *_: 'Dog') class Human(Character): - friends = ListField(Character) + friends = List(Character) pet = Field(Pet) def resolve_name(self, *args): @@ -28,8 +27,6 @@ class Human(Character): def resolve_pet(self, *args): return Pet(object()) - # def resolve_friends(self, *args, **kwargs): - # return 'HEY YOU!' schema = Schema() @@ -38,8 +35,8 @@ Human_type = schema.T(Human) def test_type(): - assert Human._meta.fields_map['name'].resolve( - Human(object()), None, None) == 'Peter' + assert Human._meta.fields_map['name'].resolver( + Human(object()), {}, None) == 'Peter' def test_query(): diff --git a/tests/core/test_schema.py b/graphene/core/tests/test_schema.py similarity index 75% rename from tests/core/test_schema.py rename to graphene/core/tests/test_schema.py index accc2f46..3dcb0098 100644 --- a/tests/core/test_schema.py +++ b/graphene/core/tests/test_schema.py @@ -1,27 +1,24 @@ - 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 import Interface, List, ObjectType, Schema, String +from graphene.core.fields import Field +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') class Character(Interface): - name = StringField() + name = String() class Pet(ObjectType): - type = StringField(resolve=lambda *_: 'Dog') + type = String(resolver=lambda *_: 'Dog') class Human(Character): - friends = ListField(Character) + friends = List(Character) pet = Field(Pet) def resolve_name(self, *args): @@ -95,9 +92,9 @@ def test_query_schema_execute(): def test_schema_get_type_map(): assert_equal_lists( schema.schema.get_type_map().keys(), - ['__Field', 'String', 'Pet', 'Character', '__InputValue', '__Directive', - '__TypeKind', '__Schema', '__Type', 'Human', '__EnumValue', 'Boolean'] - ) + ['__Field', 'String', 'Pet', 'Character', '__InputValue', + '__Directive', '__TypeKind', '__Schema', '__Type', 'Human', + '__EnumValue', 'Boolean']) def test_schema_no_query(): @@ -112,19 +109,19 @@ def test_schema_register(): @schema.register class MyType(ObjectType): - type = StringField(resolve=lambda *_: 'Dog') + type = String(resolver=lambda *_: 'Dog') schema.query = MyType assert schema.get_type('MyType') == MyType -def test_schema_register(): +def test_schema_register_no_query_type(): schema = Schema(name='My own schema') @schema.register class MyType(ObjectType): - type = StringField(resolve=lambda *_: 'Dog') + type = String(resolver=lambda *_: 'Dog') with raises(Exception) as excinfo: schema.get_type('MyType') @@ -135,9 +132,23 @@ def test_schema_introspect(): schema = Schema(name='My own schema') class MyType(ObjectType): - type = StringField(resolve=lambda *_: 'Dog') + 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) diff --git a/graphene/core/types/__init__.py b/graphene/core/types/__init__.py new file mode 100644 index 00000000..5bd49f2f --- /dev/null +++ b/graphene/core/types/__init__.py @@ -0,0 +1,30 @@ +from .base import BaseType, LazyType, OrderedType +from .argument import Argument, ArgumentsGroup, to_arguments +from .definitions import List, NonNull +from .objecttype import ObjectTypeMeta, BaseObjectType, Interface, ObjectType, Mutation, InputObjectType +from .scalars import String, ID, Boolean, Int, Float, Scalar +from .field import Field, InputField + +__all__ = [ + 'BaseType', + 'LazyType', + 'OrderedType', + 'Argument', + 'ArgumentsGroup', + 'to_arguments', + 'List', + 'NonNull', + 'Field', + 'InputField', + 'Interface', + 'BaseObjectType', + 'ObjectTypeMeta', + 'ObjectType', + 'Mutation', + 'InputObjectType', + 'String', + 'ID', + 'Boolean', + 'Int', + 'Float', + 'Scalar'] diff --git a/graphene/core/types/argument.py b/graphene/core/types/argument.py new file mode 100644 index 00000000..6f2de8f5 --- /dev/null +++ b/graphene/core/types/argument.py @@ -0,0 +1,71 @@ +from collections import OrderedDict +from itertools import chain + +from graphql.core.type import GraphQLArgument + +from ...utils import to_camel_case +from .base import ArgumentType, BaseType, OrderedType + + +class Argument(OrderedType): + + def __init__(self, type, description=None, default=None, + name=None, _creation_counter=None): + super(Argument, self).__init__(_creation_counter=_creation_counter) + self.name = name + self.type = type + self.description = description + self.default = default + + def internal_type(self, schema): + return GraphQLArgument( + schema.T(self.type), + self.default, self.description) + + def __repr__(self): + return self.name + + +class ArgumentsGroup(BaseType): + + def __init__(self, *args, **kwargs): + arguments = to_arguments(*args, **kwargs) + self.arguments = OrderedDict([(arg.name, arg) for arg in arguments]) + + def internal_type(self, schema): + return OrderedDict([(arg.name, schema.T(arg)) + for arg in self.arguments.values()]) + + def __len__(self): + return len(self.arguments) + + def __iter__(self): + return iter(self.arguments) + + def __contains__(self, *args): + return self.arguments.__contains__(*args) + + def __getitem__(self, *args): + return self.arguments.__getitem__(*args) + + +def to_arguments(*args, **kwargs): + arguments = {} + iter_arguments = chain(kwargs.items(), [(None, a) for a in args]) + + for name, arg in iter_arguments: + if isinstance(arg, Argument): + argument = arg + elif isinstance(arg, ArgumentType): + argument = arg.as_argument() + else: + raise ValueError('Unknown argument %s=%r' % (name, arg)) + + if name: + argument.name = to_camel_case(name) + assert argument.name, 'Argument in field must have a name' + assert argument.name not in arguments, 'Found more than one Argument with same name {}'.format( + argument.name) + arguments[argument.name] = argument + + return sorted(arguments.values()) diff --git a/graphene/core/types/base.py b/graphene/core/types/base.py new file mode 100644 index 00000000..fe261790 --- /dev/null +++ b/graphene/core/types/base.py @@ -0,0 +1,127 @@ +from functools import total_ordering + +import six + + +class BaseType(object): + + @classmethod + def internal_type(cls, schema): + return getattr(cls, 'T', None) + + +class MountType(BaseType): + parent = None + + def mount(self, cls): + self.parent = cls + + +class LazyType(MountType): + + def __init__(self, type): + self.type = type + + @property + def is_self(self): + return self.type == 'self' + + def internal_type(self, schema): + type = None + if callable(self.type): + type = self.type(self.parent) + elif isinstance(self.type, six.string_types): + if self.is_self: + type = self.parent + else: + type = schema.get_type(self.type) + assert type, 'Type in %s %r cannot be none' % (self.type, self.parent) + return schema.T(type) + + +@total_ordering +class OrderedType(MountType): + creation_counter = 0 + + def __init__(self, _creation_counter=None): + self.creation_counter = _creation_counter or self.gen_counter() + + @staticmethod + def gen_counter(): + counter = OrderedType.creation_counter + OrderedType.creation_counter += 1 + return counter + + def __eq__(self, other): + # Needed for @total_ordering + if isinstance(self, type(other)): + return self.creation_counter == other.creation_counter + return NotImplemented + + def __lt__(self, other): + # This is needed because bisect does not take a comparison function. + if isinstance(other, OrderedType): + return self.creation_counter < other.creation_counter + return NotImplemented + + def __gt__(self, other): + # This is needed because bisect does not take a comparison function. + if isinstance(other, OrderedType): + return self.creation_counter > other.creation_counter + return NotImplemented + + def __hash__(self): + return hash((self.creation_counter)) + + +class MirroredType(OrderedType): + + def __init__(self, *args, **kwargs): + _creation_counter = kwargs.pop('_creation_counter', None) + super(MirroredType, self).__init__(_creation_counter=_creation_counter) + self.args = args + self.kwargs = kwargs + + @property + def List(self): # noqa + from .definitions import List + return List(self, *self.args, **self.kwargs) + + @property + def NonNull(self): # noqa + from .definitions import NonNull + return NonNull(self, *self.args, **self.kwargs) + + +class ArgumentType(MirroredType): + + def as_argument(self): + from .argument import Argument + return Argument( + self, _creation_counter=self.creation_counter, *self.args, **self.kwargs) + + +class FieldType(MirroredType): + + def contribute_to_class(self, cls, name): + from ..types import BaseObjectType, InputObjectType + if issubclass(cls, InputObjectType): + inputfield = self.as_inputfield() + return inputfield.contribute_to_class(cls, name) + elif issubclass(cls, BaseObjectType): + field = self.as_field() + return field.contribute_to_class(cls, name) + + def as_field(self): + from .field import Field + return Field(self, _creation_counter=self.creation_counter, + *self.args, **self.kwargs) + + def as_inputfield(self): + from .field import InputField + return InputField( + self, _creation_counter=self.creation_counter, *self.args, **self.kwargs) + + +class MountedType(FieldType, ArgumentType): + pass diff --git a/graphene/core/types/definitions.py b/graphene/core/types/definitions.py new file mode 100644 index 00000000..ac48f8d9 --- /dev/null +++ b/graphene/core/types/definitions.py @@ -0,0 +1,30 @@ +import six + +from graphql.core.type import GraphQLList, GraphQLNonNull + +from .base import LazyType, MountedType, MountType + + +class OfType(MountedType): + + def __init__(self, of_type, *args, **kwargs): + if isinstance(of_type, six.string_types): + of_type = LazyType(of_type) + self.of_type = of_type + super(OfType, self).__init__(*args, **kwargs) + + def internal_type(self, schema): + return self.T(schema.T(self.of_type)) + + def mount(self, cls): + self.parent = cls + if isinstance(self.of_type, MountType): + self.of_type.mount(cls) + + +class List(OfType): + T = GraphQLList + + +class NonNull(OfType): + T = GraphQLNonNull diff --git a/graphene/core/types/field.py b/graphene/core/types/field.py new file mode 100644 index 00000000..5b91a1c7 --- /dev/null +++ b/graphene/core/types/field.py @@ -0,0 +1,151 @@ +from collections import OrderedDict +from functools import wraps + +import six + +from graphql.core.type import GraphQLField, GraphQLInputObjectField + +from ...utils import ProxySnakeDict, to_camel_case +from ..types import BaseObjectType, InputObjectType +from .argument import ArgumentsGroup +from .base import LazyType, MountType, OrderedType +from .definitions import NonNull + + +def make_args_snake_case(resolver): + @wraps(resolver) + def wrapped_resolver(instance, args, info): + return resolver(instance, ProxySnakeDict(args), info) + + return wrapped_resolver + + +class Empty(object): + pass + + +class Field(OrderedType): + + def __init__( + self, type, description=None, args=None, name=None, resolver=None, + required=False, default=None, *args_list, **kwargs): + _creation_counter = kwargs.pop('_creation_counter', None) + super(Field, self).__init__(_creation_counter=_creation_counter) + self.name = name + if isinstance(type, six.string_types): + type = LazyType(type) + self.required = required + self.type = type + self.description = description + args = OrderedDict(args or {}, **kwargs) + self.arguments = ArgumentsGroup(*args_list, **args) + self.object_type = None + self.resolver_fn = resolver + self.default = default + + def contribute_to_class(self, cls, attname): + assert issubclass( + cls, BaseObjectType), 'Field {} cannot be mounted in {}'.format( + self, cls) + if not self.name: + self.name = to_camel_case(attname) + self.attname = attname + self.object_type = cls + self.mount(cls) + if isinstance(self.type, MountType): + self.type.mount(cls) + cls._meta.add_field(self) + + @property + def resolver(self): + return self.resolver_fn or self.get_resolver_fn() + + @resolver.setter + def resolver(self, value): + self.resolver_fn = value + + def get_resolver_fn(self): + resolve_fn_name = 'resolve_%s' % self.attname + if hasattr(self.object_type, resolve_fn_name): + return getattr(self.object_type, resolve_fn_name) + + def default_getter(instance, args, info): + return getattr(instance, self.attname, self.default) + return default_getter + + def get_type(self, schema): + if self.required: + return NonNull(self.type) + return self.type + + def internal_type(self, schema): + resolver = self.resolver + description = self.description + arguments = self.arguments + if not description and resolver: + description = resolver.__doc__ + type = schema.T(self.get_type(schema)) + type_objecttype = schema.objecttype(type) + if type_objecttype and type_objecttype._meta.is_mutation: + assert len(arguments) == 0 + arguments = type_objecttype.get_arguments() + resolver = getattr(type_objecttype, 'mutate') + + resolver = make_args_snake_case(resolver) + assert type, 'Internal type for field %s is None' % str(self) + return GraphQLField(type, args=schema.T(arguments), resolver=resolver, + description=description,) + + def __repr__(self): + """ + Displays the module, class and name of the field. + """ + path = '%s.%s' % (self.__class__.__module__, self.__class__.__name__) + name = getattr(self, 'attname', None) + if name is not None: + return '<%s: %s>' % (path, name) + return '<%s>' % path + + def __str__(self): + """ Return "object_type.field_name". """ + return '%s.%s' % (self.object_type.__name__, self.attname) + + def __eq__(self, other): + eq = super(Field, self).__eq__(other) + if isinstance(self, type(other)): + return eq and self.object_type == other.object_type + return NotImplemented + + def __hash__(self): + return hash((self.creation_counter, self.object_type)) + + +class InputField(OrderedType): + + def __init__(self, type, description=None, default=None, + name=None, _creation_counter=None, required=False): + super(InputField, self).__init__(_creation_counter=_creation_counter) + self.name = name + if required: + type = NonNull(type) + self.type = type + self.description = description + self.default = default + + def contribute_to_class(self, cls, attname): + assert issubclass( + cls, InputObjectType), 'InputField {} cannot be mounted in {}'.format( + self, cls) + if not self.name: + self.name = to_camel_case(attname) + self.attname = attname + self.object_type = cls + self.mount(cls) + if isinstance(self.type, MountType): + self.type.mount(cls) + cls._meta.add_field(self) + + def internal_type(self, schema): + return GraphQLInputObjectField( + schema.T(self.type), + default_value=self.default, description=self.description) diff --git a/graphene/core/types.py b/graphene/core/types/objecttype.py similarity index 79% rename from graphene/core/types.py rename to graphene/core/types/objecttype.py index 2341c0fc..27628222 100644 --- a/graphene/core/types.py +++ b/graphene/core/types/objecttype.py @@ -6,7 +6,11 @@ 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.argument import ArgumentsGroup +from graphene.core.types.base import BaseType +from graphene.core.types.definitions import List, NonNull from graphql.core.type import (GraphQLArgument, GraphQLInputObjectType, GraphQLInterfaceType, GraphQLObjectType) @@ -50,10 +54,6 @@ class ObjectTypeMeta(type): assert not ( new_class._meta.is_interface and new_class._meta.is_mutation) - input_class = None - if new_class._meta.is_mutation: - input_class = attrs.pop('Input', None) - # Add all attributes to the class. for obj_name, obj in attrs.items(): new_class.add_to_class(obj_name, obj) @@ -62,13 +62,6 @@ class ObjectTypeMeta(type): assert hasattr( new_class, 'mutate'), "All mutations must implement mutate method" - if input_class: - items = dict(input_class.__dict__) - items.pop('__dict__', None) - input_type = type('{}Input'.format( - new_class._meta.type_name), (ObjectType, ), items) - new_class.add_to_class('input_type', input_type) - new_class.add_extra_fields() new_fields = new_class._meta.local_fields @@ -87,7 +80,8 @@ class ObjectTypeMeta(type): # on the base classes (we cannot handle shadowed fields at the # moment). for field in parent_fields: - if field.name in field_names and field.__class__ != field_names[field.name].__class__: + if field.name in field_names and field.type.__class__ != field_names[ + field.name].type.__class__: raise Exception( 'Local field %r in class %r (%r) clashes ' 'with field with similar name from ' @@ -106,6 +100,9 @@ class ObjectTypeMeta(type): new_class._meta.interfaces.append(base) # new_class._meta.parents.extend(base._meta.parents) + setattr(new_class, 'NonNull', NonNull(new_class)) + setattr(new_class, 'List', List(new_class)) + new_class._prepare() return new_class @@ -119,13 +116,14 @@ class ObjectTypeMeta(type): 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'): + if not inspect.isclass(value) and hasattr( + value, 'contribute_to_class'): value.contribute_to_class(cls, name) else: setattr(cls, name, value) -class BaseObjectType(object): +class BaseObjectType(BaseType): def __new__(cls, *args, **kwargs): if cls._meta.is_interface: @@ -165,14 +163,16 @@ class BaseObjectType(object): pass if kwargs: raise TypeError( - "'%s' is an invalid keyword argument for this function" % list(kwargs)[0]) + "'%s' is an invalid keyword argument for this function" % + list(kwargs)[0]) signals.post_init.send(self.__class__, instance=self) @classmethod def fields_as_arguments(cls, schema): - return OrderedDict([(f.attname, GraphQLArgument(f.internal_type(schema))) - for f in cls._meta.fields]) + return OrderedDict( + [(f.attname, GraphQLArgument(f.internal_type(schema))) + for f in cls._meta.fields]) @classmethod def resolve_objecttype(cls, schema, instance, *args): @@ -185,23 +185,32 @@ class BaseObjectType(object): @classmethod def internal_type(cls, schema): - fields = lambda: OrderedDict([(f.name, f.internal_field(schema)) - for f in cls._meta.fields]) if cls._meta.is_interface: 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 @@ -212,20 +221,33 @@ class ObjectType(six.with_metaclass(ObjectTypeMeta, BaseObjectType)): class Mutation(six.with_metaclass(ObjectTypeMeta, BaseObjectType)): + @classmethod + def _construct_arguments(cls, items): + return ArgumentsGroup(**items) @classmethod - def get_input_type(cls): - return getattr(cls, 'input_type', None) + def _prepare_class(cls): + input_class = getattr(cls, 'Input', None) + if input_class: + items = dict(vars(input_class)) + items.pop('__dict__', None) + items.pop('__doc__', None) + items.pop('__module__', None) + items.pop('__weakref__', None) + cls.add_to_class('arguments', cls._construct_arguments(items)) + delattr(cls, 'Input') + + @classmethod + def get_arguments(cls): + return cls.arguments class InputObjectType(ObjectType): @classmethod def internal_type(cls, schema): - fields = lambda: OrderedDict([(f.name, f.internal_field(schema)) - for f in cls._meta.fields]) return GraphQLInputObjectType( cls._meta.type_name, description=cls._meta.description, - fields=fields, + fields=partial(cls.get_fields, schema), ) diff --git a/graphene/core/types/scalars.py b/graphene/core/types/scalars.py new file mode 100644 index 00000000..75cd70a3 --- /dev/null +++ b/graphene/core/types/scalars.py @@ -0,0 +1,41 @@ +from graphql.core.type import (GraphQLBoolean, GraphQLFloat, GraphQLID, + GraphQLInt, GraphQLScalarType, GraphQLString) + +from .base import MountedType + + +class String(MountedType): + T = GraphQLString + + +class Int(MountedType): + T = GraphQLInt + + +class Boolean(MountedType): + T = GraphQLBoolean + + +class ID(MountedType): + T = GraphQLID + + +class Float(MountedType): + T = GraphQLFloat + + +class Scalar(MountedType): + + @classmethod + def internal_type(cls, schema): + serialize = getattr(cls, 'serialize') + parse_literal = getattr(cls, 'parse_literal') + parse_value = getattr(cls, 'parse_value') + + return GraphQLScalarType( + name=cls.__name__, + description=cls.__doc__, + serialize=serialize, + parse_value=parse_value, + parse_literal=parse_literal + ) diff --git a/graphene/core/types/tests/__init__.py b/graphene/core/types/tests/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/graphene/core/types/tests/test_argument.py b/graphene/core/types/tests/test_argument.py new file mode 100644 index 00000000..fed2f29c --- /dev/null +++ b/graphene/core/types/tests/test_argument.py @@ -0,0 +1,47 @@ +from pytest import raises + +from graphene.core.schema import Schema +from graphene.core.types import ObjectType +from graphql.core.type import GraphQLArgument + +from ..argument import Argument, to_arguments +from ..scalars import String + + +def test_argument_internal_type(): + class MyObjectType(ObjectType): + pass + schema = Schema(query=MyObjectType) + a = Argument(MyObjectType, description='My argument', default='3') + type = schema.T(a) + assert isinstance(type, GraphQLArgument) + assert type.description == 'My argument' + assert type.default_value == '3' + + +def test_to_arguments(): + arguments = to_arguments( + Argument(String, name='myArg'), + String(name='otherArg'), + my_kwarg=String(), + other_kwarg=String(), + ) + + assert [a.name for a in arguments] == [ + 'myArg', 'otherArg', 'myKwarg', 'otherKwarg'] + + +def test_to_arguments_no_name(): + with raises(AssertionError) as excinfo: + to_arguments( + String(), + ) + assert 'must have a name' in str(excinfo.value) + + +def test_to_arguments_wrong_type(): + with raises(ValueError) as excinfo: + to_arguments( + p=3 + ) + assert 'Unknown argument p=3' == str(excinfo.value) diff --git a/graphene/core/types/tests/test_base.py b/graphene/core/types/tests/test_base.py new file mode 100644 index 00000000..79d24aca --- /dev/null +++ b/graphene/core/types/tests/test_base.py @@ -0,0 +1,94 @@ +from mock import patch + +from graphene.core.types import InputObjectType, ObjectType + +from ..argument import Argument +from ..base import MountedType, OrderedType +from ..field import Field, InputField +from ..definitions import List, NonNull + + +def test_orderedtype_equal(): + a = OrderedType() + assert a == a + assert hash(a) == hash(a) + + +def test_orderedtype_different(): + a = OrderedType() + b = OrderedType() + assert a != b + assert hash(a) != hash(b) + assert a < b + assert b > a + + +@patch('graphene.core.types.field.Field') +def test_type_as_field_called(Field): + resolver = lambda x: x + a = MountedType(2, description='A', resolver=resolver) + a.as_field() + Field.assert_called_with( + a, + 2, + _creation_counter=a.creation_counter, + description='A', + resolver=resolver) + + +@patch('graphene.core.types.argument.Argument') +def test_type_as_argument_called(Argument): + a = MountedType(2, description='A') + a.as_argument() + Argument.assert_called_with( + a, 2, _creation_counter=a.creation_counter, description='A') + + +def test_type_as_field(): + resolver = lambda x: x + + class MyObjectType(ObjectType): + t = MountedType(description='A', resolver=resolver) + + fields_map = MyObjectType._meta.fields_map + field = fields_map.get('t') + assert isinstance(field, Field) + assert field.description == 'A' + assert field.object_type == MyObjectType + + +def test_type_as_inputfield(): + class MyObjectType(InputObjectType): + t = MountedType(description='A') + + fields_map = MyObjectType._meta.fields_map + field = fields_map.get('t') + assert isinstance(field, InputField) + assert field.description == 'A' + assert field.object_type == MyObjectType + + +def test_type_as_argument(): + a = MountedType(description='A') + argument = a.as_argument() + assert isinstance(argument, Argument) + + +def test_type_as_list(): + m = MountedType(2, 3, my_c='A') + a = m.List + + assert isinstance(a, List) + assert a.of_type == m + assert a.args == (2, 3) + assert a.kwargs == {'my_c': 'A'} + + +def test_type_as_nonnull(): + m = MountedType(2, 3, my_c='A') + a = m.NonNull + + assert isinstance(a, NonNull) + assert a.of_type == m + assert a.args == (2, 3) + assert a.kwargs == {'my_c': 'A'} diff --git a/graphene/core/types/tests/test_definitions.py b/graphene/core/types/tests/test_definitions.py new file mode 100644 index 00000000..8382dbd0 --- /dev/null +++ b/graphene/core/types/tests/test_definitions.py @@ -0,0 +1,26 @@ +from graphene.core.schema import Schema +from graphql.core.type import GraphQLList, GraphQLNonNull, GraphQLString + +from ..definitions import List, NonNull +from ..scalars import String + +schema = Schema() + + +def test_list_scalar(): + type = schema.T(List(String())) + assert isinstance(type, GraphQLList) + assert type.of_type == GraphQLString + + +def test_nonnull_scalar(): + type = schema.T(NonNull(String())) + assert isinstance(type, GraphQLNonNull) + assert type.of_type == GraphQLString + + +def test_nested_scalars(): + type = schema.T(NonNull(List(String()))) + assert isinstance(type, GraphQLNonNull) + assert isinstance(type.of_type, GraphQLList) + assert type.of_type.of_type == GraphQLString diff --git a/graphene/core/types/tests/test_field.py b/graphene/core/types/tests/test_field.py new file mode 100644 index 00000000..c9c21586 --- /dev/null +++ b/graphene/core/types/tests/test_field.py @@ -0,0 +1,114 @@ +from graphene.core.schema import Schema +from graphene.core.types import InputObjectType, ObjectType +from graphql.core.type import (GraphQLField, GraphQLInputObjectField, + GraphQLString) + +from ..base import LazyType +from ..definitions import List +from ..field import Field, InputField +from ..scalars import String + + +def test_field_internal_type(): + resolver = lambda *args: 'RESOLVED' + + field = Field(String, description='My argument', resolver=resolver) + + class Query(ObjectType): + my_field = field + schema = Schema(query=Query) + + type = schema.T(field) + assert field.name == 'myField' + assert field.attname == 'my_field' + assert isinstance(type, GraphQLField) + assert type.description == 'My argument' + assert type.resolver(None, {}, None) == 'RESOLVED' + assert type.type == GraphQLString + + +def test_field_objectype_resolver(): + field = Field(String) + + class Query(ObjectType): + my_field = field + + def resolve_my_field(self, *args, **kwargs): + '''Custom description''' + return 'RESOLVED' + + schema = Schema(query=Query) + + type = schema.T(field) + assert isinstance(type, GraphQLField) + assert type.description == 'Custom description' + assert type.resolver(Query(), {}, None) == 'RESOLVED' + + +def test_field_custom_name(): + field = Field(None, name='my_customName') + + class MyObjectType(ObjectType): + my_field = field + + assert field.name == 'my_customName' + assert field.attname == 'my_field' + + +def test_field_self(): + field = Field('self', name='my_customName') + + class MyObjectType(ObjectType): + my_field = field + + schema = Schema() + + assert schema.T(field).type == schema.T(MyObjectType) + + +def test_field_mounted(): + field = Field(List('MyObjectType'), name='my_customName') + + class MyObjectType(ObjectType): + my_field = field + + assert field.parent == MyObjectType + assert field.type.parent == MyObjectType + + +def test_field_string_reference(): + field = Field('MyObjectType', name='my_customName') + + class MyObjectType(ObjectType): + my_field = field + + schema = Schema(query=MyObjectType) + + assert isinstance(field.type, LazyType) + assert schema.T(field.type) == schema.T(MyObjectType) + + +def test_field_custom_arguments(): + field = Field(None, name='my_customName', p=String()) + + args = field.arguments + assert 'p' in args + + +def test_inputfield_internal_type(): + field = InputField(String, description='My input field', default='3') + + class MyObjectType(InputObjectType): + my_field = field + + class Query(ObjectType): + input_ot = Field(MyObjectType) + + schema = Schema(query=MyObjectType) + + type = schema.T(field) + assert field.name == 'myField' + assert field.attname == 'my_field' + assert isinstance(type, GraphQLInputObjectField) + assert type.description == 'My input field' + assert type.default_value == '3' diff --git a/tests/core/test_types.py b/graphene/core/types/tests/test_objecttype.py similarity index 85% rename from tests/core/test_types.py rename to graphene/core/types/tests/test_objecttype.py index f7c29883..88532e12 100644 --- a/tests/core/test_types.py +++ b/graphene/core/types/tests/test_objecttype.py @@ -1,9 +1,7 @@ from py.test import raises -from pytest import raises -from graphene.core.fields import IntField, StringField from graphene.core.schema import Schema -from graphene.core.types import Interface +from graphene.core.types import Int, Interface, String from graphql.core.execution.middlewares.utils import (resolver_has_tag, tag_resolver) from graphql.core.type import GraphQLInterfaceType, GraphQLObjectType @@ -11,7 +9,7 @@ from graphql.core.type import GraphQLInterfaceType, GraphQLObjectType class Character(Interface): '''Character description''' - name = StringField() + name = String() class Meta: type_name = 'core_Character' @@ -19,7 +17,7 @@ class Character(Interface): class Human(Character): '''Human description''' - friends = StringField() + friends = String() class Meta: type_name = 'core_Human' @@ -66,9 +64,6 @@ def test_object_type(): assert isinstance(object_type, GraphQLObjectType) assert object_type.description == 'Human description' assert list(object_type.get_fields().keys()) == ['name', 'friends'] - # assert object_type.get_fields() == {'name': Human._meta.fields_map['name'].internal_field( - # schema), 'friends': - # Human._meta.fields_map['friends'].internal_field(schema)} assert object_type.get_interfaces() == [schema.T(Character)] assert Human._meta.fields_map['name'].object_type == Human @@ -97,7 +92,7 @@ def test_object_type_container_too_many_args(): def test_field_clashes(): with raises(Exception) as excinfo: class Droid(Character): - name = IntField() + name = Int() assert 'clashes' in str(excinfo.value) @@ -108,12 +103,26 @@ def test_fields_inherited_should_be_different(): def test_field_mantain_resolver_tags(): class Droid(Character): - name = StringField() + name = String() def resolve_name(self, *args): return 'My Droid' tag_resolver(resolve_name, 'test') - field = Droid._meta.fields_map['name'].internal_field(schema) + field = schema.T(Droid._meta.fields_map['name']) assert resolver_has_tag(field.resolver, 'test') + + +def test_type_has_nonnull(): + class Droid(Character): + name = String() + + assert Droid.NonNull.of_type == Droid + + +def test_type_has_list(): + class Droid(Character): + name = String() + + assert Droid.List.of_type == Droid diff --git a/graphene/core/types/tests/test_scalars.py b/graphene/core/types/tests/test_scalars.py new file mode 100644 index 00000000..cedae96f --- /dev/null +++ b/graphene/core/types/tests/test_scalars.py @@ -0,0 +1,53 @@ +from graphene.core.schema import Schema +from graphql.core.type import (GraphQLBoolean, GraphQLFloat, GraphQLID, + GraphQLInt, GraphQLScalarType, GraphQLString) + +from ..scalars import ID, Boolean, Float, Int, Scalar, String + +schema = Schema() + + +def test_string_scalar(): + assert schema.T(String()) == GraphQLString + + +def test_int_scalar(): + assert schema.T(Int()) == GraphQLInt + + +def test_boolean_scalar(): + assert schema.T(Boolean()) == GraphQLBoolean + + +def test_id_scalar(): + assert schema.T(ID()) == GraphQLID + + +def test_float_scalar(): + assert schema.T(Float()) == GraphQLFloat + + +def test_custom_scalar(): + import datetime + from graphql.core.language import ast + + class DateTimeScalar(Scalar): + '''DateTimeScalar Documentation''' + @staticmethod + def serialize(dt): + return dt.isoformat() + + @staticmethod + def parse_literal(node): + if isinstance(node, ast.StringValue): + return datetime.datetime.strptime( + node.value, "%Y-%m-%dT%H:%M:%S.%f") + + @staticmethod + def parse_value(value): + return datetime.datetime.strptime(value, "%Y-%m-%dT%H:%M:%S.%f") + + scalar_type = schema.T(DateTimeScalar) + assert isinstance(scalar_type, GraphQLScalarType) + assert scalar_type.name == 'DateTimeScalar' + assert scalar_type.description == 'DateTimeScalar Documentation' diff --git a/graphene/relay/fields.py b/graphene/relay/fields.py index 7efa30d8..98154fd3 100644 --- a/graphene/relay/fields.py +++ b/graphene/relay/fields.py @@ -1,41 +1,50 @@ from collections import Iterable -from graphene.core.fields import Field, IDField -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.connection.connection import connection_args from graphql_relay.node.node import from_global_id class ConnectionField(Field): - def __init__(self, field_type, resolve=None, description='', + def __init__(self, field_type, resolver=None, description='', connection_type=None, edge_type=None, **kwargs): - super(ConnectionField, self).__init__(field_type, resolve=resolve, - args=connection_args, - description=description, **kwargs) + super( + ConnectionField, + self).__init__( + field_type, + resolver=resolver, + before=String(), + after=String(), + first=Int(), + last=Int(), + description=description, + **kwargs) self.connection_type = connection_type self.edge_type = edge_type def wrap_resolved(self, value, instance, args, info): return value - def resolve(self, instance, args, info): + def resolver(self, instance, args, info): from graphene.relay.types import PageInfo schema = info.schema.graphene_schema - resolved = super(ConnectionField, self).resolve(instance, args, info) + resolved = super(ConnectionField, self).resolver(instance, args, info) if resolved: resolved = self.wrap_resolved(resolved, instance, args, info) assert isinstance( resolved, Iterable), 'Resolved value from the connection field have to be iterable' - node = self.get_object_type(schema) + type = schema.T(self.type) + node = schema.objecttype(type) connection_type = self.get_connection_type(node) edge_type = self.get_edge_type(node) - connection = connection_from_list(resolved, args, connection_type=connection_type, - edge_type=edge_type, pageinfo_type=PageInfo) + connection = connection_from_list( + resolved, args, connection_type=connection_type, + edge_type=edge_type, pageinfo_type=PageInfo) connection.set_connection_data(resolved) return connection @@ -47,25 +56,24 @@ class ConnectionField(Field): def get_edge_type(self, node): return self.edge_type or node.get_edge_type() - def internal_type(self, schema): + def get_type(self, schema): from graphene.relay.utils import is_node - node = self.get_object_type(schema) + 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 schema.T(connection_type) + return connection_type class NodeField(Field): def __init__(self, object_type=None, *args, **kwargs): from graphene.relay.types import Node - super(NodeField, self).__init__(object_type or Node, *args, **kwargs) + id = kwargs.pop('id', None) or ID(description='The ID of an object') + super(NodeField, self).__init__( + object_type or Node, id=id, *args, **kwargs) self.field_object_type = object_type - self.args['id'] = GraphQLArgument( - GraphQLNonNull(GraphQLID), - description='The ID of an object' - ) def id_fetcher(self, global_id, info): from graphene.relay.utils import is_node @@ -79,20 +87,23 @@ class NodeField(Field): return object_type.get_node(_id) - def resolve(self, instance, args, info): + def resolver(self, instance, args, info): global_id = args.get('id') return self.id_fetcher(global_id, info) -class GlobalIDField(IDField): +class GlobalIDField(Field): '''The ID of an object''' - required = True - def contribute_to_class(self, cls, name, add=True): + def __init__(self, *args, **kwargs): + super(GlobalIDField, self).__init__(ID(), *args, **kwargs) + self.required = True + + def contribute_to_class(self, cls, name): from graphene.relay.utils import is_node, is_node_type in_node = is_node(cls) or is_node_type(cls) assert in_node, 'GlobalIDField could only be inside a Node, but got %r' % cls - super(GlobalIDField, self).contribute_to_class(cls, name, add) + super(GlobalIDField, self).contribute_to_class(cls, name) - def resolve(self, instance, args, info): - return self.object_type.to_global_id(instance, args, info) + def resolver(self, 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/tests/relay/test_relay_mutations.py b/graphene/relay/tests/test_mutations.py similarity index 69% rename from tests/relay/test_relay_mutations.py rename to graphene/relay/tests/test_mutations.py index de206d70..f8c840f2 100644 --- a/tests/relay/test_relay_mutations.py +++ b/graphene/relay/tests/test_mutations.py @@ -1,22 +1,21 @@ 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 class Query(graphene.ObjectType): - base = graphene.StringField() + base = graphene.String() class ChangeNumber(relay.ClientIDMutation): '''Result mutation''' class Input: - to = graphene.IntField() + to = graphene.Int() - result = graphene.StringField() + result = graphene.String() @classmethod def mutate_and_get_payload(cls, input, info): @@ -32,19 +31,18 @@ class MyResultMutation(graphene.ObjectType): schema = Schema(query=Query, mutation=MyResultMutation) -def test_mutation_input(): - assert ChangeNumber.input_type - assert ChangeNumber.input_type._meta.type_name == 'ChangeNumberInput' - assert list(ChangeNumber.input_type._meta.fields_map.keys()) == ['input'] - _input = ChangeNumber.input_type._meta.fields_map['input'] - inner_type = _input.get_object_type(schema) +def test_mutation_arguments(): + assert ChangeNumber.arguments + assert list(ChangeNumber.arguments) == ['input'] + assert 'input' in ChangeNumber.arguments + inner_type = ChangeNumber.input_type client_mutation_id_field = inner_type._meta.fields_map[ 'client_mutation_id'] - assert issubclass(inner_type, InputObjectType) - assert isinstance(client_mutation_id_field, graphene.StringField) + 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(client_mutation_id_field.internal_field( - schema), GraphQLInputObjectField) + assert isinstance(schema.T(client_mutation_id_field), GraphQLInputObjectField) def test_execute_mutations(): diff --git a/tests/relay/test_relayfields.py b/graphene/relay/tests/test_query.py similarity index 80% rename from tests/relay/test_relayfields.py rename to graphene/relay/tests/test_query.py index 34e6d305..5a78d4ba 100644 --- a/tests/relay/test_relayfields.py +++ b/graphene/relay/tests/test_query.py @@ -6,22 +6,22 @@ schema = graphene.Schema() class MyConnection(relay.Connection): - my_custom_field = graphene.StringField( - resolve=lambda instance, *_: 'Custom') + my_custom_field = graphene.String( + resolver=lambda instance, *_: 'Custom') class MyNode(relay.Node): - name = graphene.StringField() + name = graphene.String() @classmethod def get_node(cls, id): - return MyNode(name='mo') + return MyNode(id=id, name='mo') class Query(graphene.ObjectType): my_node = relay.NodeField(MyNode) all_my_nodes = relay.ConnectionField( - MyNode, connection_type=MyConnection, customArg=graphene.Argument(graphene.String)) + MyNode, connection_type=MyConnection, customArg=graphene.String()) def resolve_all_my_nodes(self, args, info): custom_arg = args.get('customArg') @@ -35,6 +35,7 @@ def test_nodefield_query(): query = ''' query RebelsShipsQuery { myNode(id:"TXlOb2RlOjE=") { + id name }, allMyNodes (customArg:"1") { @@ -52,6 +53,7 @@ def test_nodefield_query(): ''' expected = { 'myNode': { + 'id': 'TXlOb2RlOjE=', 'name': 'mo' }, 'allMyNodes': { @@ -73,5 +75,6 @@ def test_nodefield_query(): def test_nodeidfield(): id_field = MyNode._meta.fields_map['id'] - assert isinstance(id_field.internal_field(schema).type, GraphQLNonNull) - assert id_field.internal_field(schema).type.of_type == GraphQLID + id_field_type = schema.T(id_field) + assert isinstance(id_field_type.type, GraphQLNonNull) + assert id_field_type.type.of_type == GraphQLID diff --git a/tests/relay/test_relay.py b/graphene/relay/tests/test_types.py similarity index 89% rename from tests/relay/test_relay.py rename to graphene/relay/tests/test_types.py index 0c011b0e..19c055a9 100644 --- a/tests/relay/test_relay.py +++ b/graphene/relay/tests/test_types.py @@ -7,7 +7,7 @@ schema = graphene.Schema() class OtherNode(relay.Node): - name = graphene.StringField() + name = graphene.String() @classmethod def get_node(cls, id): @@ -17,7 +17,7 @@ class OtherNode(relay.Node): def test_field_no_contributed_raises_error(): with raises(Exception) as excinfo: class Part(relay.Node): - x = graphene.StringField() + x = graphene.String() assert 'get_node' in str(excinfo.value) diff --git a/graphene/relay/types.py b/graphene/relay/types.py index 748db640..f1dfacc0 100644 --- a/graphene/relay/types.py +++ b/graphene/relay/types.py @@ -1,19 +1,23 @@ -from graphene.core.fields import BooleanField, Field, ListField, StringField -from graphene.core.types import (InputObjectType, Interface, Mutation, - ObjectType) +from graphene.core.types import (Boolean, Field, InputObjectType, Interface, + List, Mutation, ObjectType, String) +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 from graphql_relay.node.node import to_global_id class PageInfo(ObjectType): - has_next_page = BooleanField( - required=True, description='When paginating forwards, are there more items?') - has_previous_page = BooleanField( - required=True, description='When paginating backwards, are there more items?') - start_cursor = StringField( + has_next_page = Boolean( + required=True, + description='When paginating forwards, are there more items?') + has_previous_page = Boolean( + required=True, + description='When paginating backwards, are there more items?') + start_cursor = String( description='When paginating backwards, the cursor to continue.') - end_cursor = StringField( + end_cursor = String( description='When paginating forwards, the cursor to continue.') @@ -22,9 +26,9 @@ class Edge(ObjectType): class Meta: type_name = 'DefaultEdge' - node = Field(lambda field: field.object_type.node_type, + node = Field(LazyType(lambda object_type: object_type.node_type), description='The item at the end of the edge') - cursor = StringField( + cursor = String( required=True, description='A cursor for use in pagination') @classmethod @@ -32,7 +36,10 @@ class Edge(ObjectType): def for_node(cls, node): from graphene.relay.utils import is_node assert is_node(node), 'ObjectTypes in a edge have to be Nodes' - return type('%s%s' % (node._meta.type_name, cls._meta.type_name), (cls, ), {'node_type': node}) + return type( + '%s%s' % (node._meta.type_name, cls._meta.type_name), + (cls,), + {'node_type': node}) class Connection(ObjectType): @@ -42,8 +49,8 @@ class Connection(ObjectType): page_info = Field(PageInfo, required=True, description='The Information to aid in pagination') - edges = ListField(lambda field: field.object_type.edge_type, - description='Information to aid in pagination.') + edges = List(LazyType(lambda object_type: object_type.edge_type), + description='Information to aid in pagination.') _connection_data = None @@ -53,7 +60,10 @@ class Connection(ObjectType): from graphene.relay.utils import is_node edge_type = edge_type or Edge assert is_node(node), 'ObjectTypes in a connection have to be Nodes' - return type('%s%s' % (node._meta.type_name, cls._meta.type_name), (cls, ), {'edge_type': edge_type.for_node(node)}) + return type( + '%s%s' % (node._meta.type_name, cls._meta.type_name), + (cls,), + {'edge_type': edge_type.for_node(node)}) def set_connection_data(self, data): self._connection_data = data @@ -71,10 +81,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 @@ -90,31 +99,24 @@ class BaseNode(object): class Node(BaseNode, Interface): '''An object with an ID''' - id = GlobalIDField() + id = GlobalIDField(required=True) class MutationInputType(InputObjectType): - client_mutation_id = StringField(required=True) + client_mutation_id = String(required=True) class ClientIDMutation(Mutation): - client_mutation_id = StringField(required=True) + client_mutation_id = String(required=True) @classmethod - def _prepare_class(cls): - input_type = getattr(cls, 'input_type', None) - if input_type: - assert hasattr( - cls, 'mutate_and_get_payload'), 'You have to implement mutate_and_get_payload' - new_input_inner_type = type('{}InnerInput'.format( - cls._meta.type_name), (MutationInputType, input_type, ), {}) - items = { - 'input': Field(new_input_inner_type) - } - assert issubclass(new_input_inner_type, InputObjectType) - input_type = type('{}Input'.format( - cls._meta.type_name), (ObjectType, ), items) - setattr(cls, 'input_type', input_type) + def _construct_arguments(cls, items): + assert hasattr( + cls, 'mutate_and_get_payload'), 'You have to implement mutate_and_get_payload' + 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)) @classmethod def mutate(cls, instance, args, info): diff --git a/graphene/relay/utils.py b/graphene/relay/utils.py index 0689d86b..af9d0f6c 100644 --- a/graphene/relay/utils.py +++ b/graphene/relay/utils.py @@ -2,7 +2,8 @@ from graphene.relay.types import BaseNode def is_node(object_type): - return object_type and issubclass(object_type, BaseNode) and not is_node_type(object_type) + return object_type and issubclass( + object_type, BaseNode) and not is_node_type(object_type) def is_node_type(object_type): 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 77% rename from tests/utils/test_proxy_snake_dict.py rename to graphene/utils/tests/test_proxy_snake_dict.py index 33b7a226..6020d52f 100644 --- a/tests/utils/test_proxy_snake_dict.py +++ b/graphene/utils/tests/test_proxy_snake_dict.py @@ -19,11 +19,14 @@ def test_proxy_snake_dict(): 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)])) + 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(): 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/setup.cfg b/setup.cfg index 15bcaf1b..11382a78 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,3 +1,6 @@ [flake8] -exclude = tests/*,setup.py -max-line-length = 160 +exclude = setup.py +max-line-length = 120 + +[coverage:run] +omit = core/ntypes/tests/* diff --git a/setup.py b/setup.py index 8d626215..5e6a44df 100644 --- a/setup.py +++ b/setup.py @@ -62,6 +62,7 @@ setup( tests_require=[ 'pytest>=2.7.2', 'pytest-django', + 'mock', ], extras_require={ 'django': [ diff --git a/tests/core/test_fields.py b/tests/core/test_fields.py deleted file mode 100644 index be565dd2..00000000 --- a/tests/core/test_fields.py +++ /dev/null @@ -1,166 +0,0 @@ - -from py.test import raises -from pytest import raises - -from graphene.core.fields import Field, NonNullField, StringField -from graphene.core.options import Options -from graphene.core.schema import Schema -from graphene.core.types import ObjectType -from graphql.core.type import (GraphQLBoolean, GraphQLField, GraphQLID, - GraphQLInt, GraphQLNonNull, GraphQLString) - - -class ObjectType(object): - _meta = Options() - - def resolve_customdoc(self, *args, **kwargs): - '''Resolver documentation''' - return None - - def __str__(self): - return "ObjectType" - -ot = ObjectType - -schema = Schema() - - -def test_field_no_contributed_raises_error(): - f = Field(GraphQLString) - with raises(Exception) as excinfo: - f.internal_field(schema) - - -def test_field_type(): - f = Field(GraphQLString) - f.contribute_to_class(ot, 'field_name') - assert isinstance(f.internal_field(schema), GraphQLField) - assert f.internal_type(schema) == GraphQLString - - -def test_field_name_automatic_camelcase(): - f = Field(GraphQLString) - f.contribute_to_class(ot, 'field_name') - assert f.name == 'fieldName' - - -def test_field_name_use_name_if_exists(): - f = Field(GraphQLString, name='my_custom_name') - f.contribute_to_class(ot, 'field_name') - assert f.name == 'my_custom_name' - - -def test_stringfield_type(): - f = StringField() - f.contribute_to_class(ot, 'field_name') - assert f.internal_type(schema) == GraphQLString - - -def test_nonnullfield_type(): - f = NonNullField(StringField()) - f.contribute_to_class(ot, 'field_name') - assert isinstance(f.internal_type(schema), GraphQLNonNull) - - -def test_stringfield_type_required(): - f = StringField(required=True) - f.contribute_to_class(ot, 'field_name') - assert isinstance(f.internal_field(schema), GraphQLField) - assert isinstance(f.internal_type(schema), GraphQLNonNull) - - -def test_field_resolve(): - f = StringField(required=True, resolve=lambda *args: 'RESOLVED') - f.contribute_to_class(ot, 'field_name') - field_type = f.internal_field(schema) - assert 'RESOLVED' == field_type.resolver(ot, None, None) - - -def test_field_resolve_type_custom(): - class MyCustomType(ObjectType): - pass - - class OtherType(ObjectType): - pass - - s = Schema() - - f = Field('MyCustomType') - f.contribute_to_class(OtherType, 'field_name') - field_type = f.get_object_type(s) - assert field_type == MyCustomType - - -def test_field_resolve_type_custom(): - s = Schema() - - f = Field('self') - f.contribute_to_class(ot, 'field_name') - field_type = f.get_object_type(s) - assert field_type == ot - - -def test_field_orders(): - f1 = Field(None) - f2 = Field(None) - assert f1 < f2 - - -def test_field_orders_wrong_type(): - field = Field(None) - try: - assert not field < 1 - except TypeError: - # Fix exception raising in Python3+ - pass - - -def test_field_eq(): - f1 = Field(None) - f2 = Field(None) - assert f1 != f2 - - -def test_field_eq_wrong_type(): - field = Field(None) - assert field != 1 - - -def test_field_hash(): - f1 = Field(None) - f2 = Field(None) - assert hash(f1) != hash(f2) - - -def test_field_none_type_raises_error(): - s = Schema() - f = Field(None) - f.contribute_to_class(ot, 'field_name') - with raises(Exception) as excinfo: - f.internal_field(s) - assert str( - excinfo.value) == "Internal type for field ObjectType.field_name is None" - - -def test_field_str(): - f = StringField() - f.contribute_to_class(ot, 'field_name') - assert str(f) == "ObjectType.field_name" - - -def test_field_repr(): - f = StringField() - assert repr(f) == "" - - -def test_field_repr_contributed(): - f = StringField() - f.contribute_to_class(ot, 'field_name') - assert repr(f) == "" - - -def test_field_resolve_objecttype_cos(): - f = StringField() - f.contribute_to_class(ot, 'customdoc') - field = f.internal_field(schema) - assert field.description == 'Resolver documentation' diff --git a/tests/core/test_scalars.py b/tests/core/test_scalars.py deleted file mode 100644 index 1252741d..00000000 --- a/tests/core/test_scalars.py +++ /dev/null @@ -1,16 +0,0 @@ -from graphene.core.scalars import GraphQLSkipField - - -def test_skipfield_serialize(): - f = GraphQLSkipField - assert f.serialize('a') is None - - -def test_skipfield_parse_value(): - f = GraphQLSkipField - assert f.parse_value('a') is None - - -def test_skipfield_parse_literal(): - f = GraphQLSkipField - assert f.parse_literal('a') is None 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 = {