mirror of
				https://github.com/graphql-python/graphene.git
				synced 2025-11-01 00:17:26 +03:00 
			
		
		
		
	Fixing it up to use the declarative API
This commit is contained in:
		
							parent
							
								
									487543206c
								
							
						
					
					
						commit
						9a0b33ca77
					
				|  | @ -2,16 +2,18 @@ from sqlalchemy import types | ||||||
| from sqlalchemy.orm import interfaces | from sqlalchemy.orm import interfaces | ||||||
| from singledispatch import singledispatch | from singledispatch import singledispatch | ||||||
| 
 | 
 | ||||||
| from graphene.contrib.sqlalchemy.fields import ConnectionOrListField, SQLAlchemyModelField | from ...core.types.scalars import Boolean, Float, ID, Int, String | ||||||
| from graphene.core.fields import BooleanField, FloatField, IDField, IntField, StringField | from .fields import ConnectionOrListField, SQLAlchemyModelField | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| def convert_sqlalchemy_relationship(relationship): | def convert_sqlalchemy_relationship(relationship): | ||||||
|     model_field = SQLAlchemyModelField(field.table, description=relationship.key) |     direction = relationship.direction | ||||||
|     if relationship.direction == interfaces.ONETOMANY: |     model = relationship.mapper.entity | ||||||
|  |     model_field = SQLAlchemyModelField(model, description=relationship.doc) | ||||||
|  |     if direction == interfaces.MANYTOONE: | ||||||
|         return model_field |         return model_field | ||||||
|     elif (relationship.direction == interfaces.MANYTOONE or |     elif (direction == interfaces.ONETOMANY or | ||||||
|           relationship.direction == interfaces.MANYTOMANY): |           direction == interfaces.MANYTOMANY): | ||||||
|         return ConnectionOrListField(model_field) |         return ConnectionOrListField(model_field) | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|  | @ -19,43 +21,43 @@ def convert_sqlalchemy_column(column): | ||||||
|     try: |     try: | ||||||
|         return convert_sqlalchemy_type(column.type, column) |         return convert_sqlalchemy_type(column.type, column) | ||||||
|     except Exception: |     except Exception: | ||||||
|         raise |         raise Exception( | ||||||
|  |             "Don't know how to convert the SQLAlchemy field %s (%s)" % (column, column.__class__)) | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| @singledispatch | @singledispatch | ||||||
| def convert_sqlalchemy_type(): | def convert_sqlalchemy_type(type, column): | ||||||
|     raise Exception( |     raise Exception() | ||||||
|         "Don't know how to convert the SQLAlchemy column %s (%s)" % (column, column.__class__)) |  | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| @convert_sqlalchemy_type.register(types.Date) | @convert_sqlalchemy_type.register(types.Date) | ||||||
| @convert_sqlalchemy_type.register(types.DateTime) | @convert_sqlalchemy_type.register(types.DateTime) | ||||||
| @convert_sqlalchemy_type.register(types.Time) | @convert_sqlalchemy_type.register(types.Time) | ||||||
| @convert_sqlalchemy_type.register(types.Text) |  | ||||||
| @convert_sqlalchemy_type.register(types.String) | @convert_sqlalchemy_type.register(types.String) | ||||||
|  | @convert_sqlalchemy_type.register(types.Text) | ||||||
| @convert_sqlalchemy_type.register(types.Unicode) | @convert_sqlalchemy_type.register(types.Unicode) | ||||||
| @convert_sqlalchemy_type.register(types.UnicodeText) | @convert_sqlalchemy_type.register(types.UnicodeText) | ||||||
| @convert_sqlalchemy_type.register(types.Enum) | @convert_sqlalchemy_type.register(types.Enum) | ||||||
| def convert_column_to_string(type, column): | def convert_column_to_string(type, column): | ||||||
|     return StringField(description=column.description) |     return String(description=column.doc) | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| @convert_sqlalchemy_type.register(types.SmallInteger) | @convert_sqlalchemy_type.register(types.SmallInteger) | ||||||
| @convert_sqlalchemy_type.register(types.BigInteger) | @convert_sqlalchemy_type.register(types.BigInteger) | ||||||
| @convert_sqlalchemy_type.register(types.Integer) | @convert_sqlalchemy_type.register(types.Integer) | ||||||
| def convert_column_to_int_or_id(column): | def convert_column_to_int_or_id(type, column): | ||||||
|     if column.primary_key: |     if column.primary_key: | ||||||
|         return IDField(description=column.description) |         return ID(description=column.doc) | ||||||
|     else: |     else: | ||||||
|         return IntField(description=column.description) |         return Int(description=column.doc) | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| @convert_sqlalchemy_type.register(types.Boolean) | @convert_sqlalchemy_type.register(types.Boolean) | ||||||
| def convert_column_to_boolean(column): | def convert_column_to_boolean(type, column): | ||||||
|     return BooleanField(description=column.description) |     return Boolean(description=column.doc) | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| @convert_sqlalchemy_type.register(types.Float) | @convert_sqlalchemy_type.register(types.Float) | ||||||
| @convert_sqlalchemy_type.register(types.Numeric) | @convert_sqlalchemy_type.register(types.Numeric) | ||||||
| def convert_column_to_float(column): | def convert_column_to_float(type, column): | ||||||
|     return FloatField(description=column.description) |     return Float(description=column.doc) | ||||||
|  |  | ||||||
|  | @ -1,67 +1,69 @@ | ||||||
| from graphene import relay | from sqlalchemy.orm import Query | ||||||
| from graphene.contrib.sqlalchemy.utils import get_type_for_model, lazy_map | from ...core.exceptions import SkipField | ||||||
| from graphene.core.fields import Field, LazyField, ListField | from ...core.fields import Field | ||||||
| from graphene.relay.utils import is_node | from ...core.types.base import FieldType | ||||||
|  | from ...core.types.definitions import List | ||||||
|  | from ...relay import ConnectionField | ||||||
|  | from ...relay.utils import is_node | ||||||
|  | from ...utils import LazyMap | ||||||
|  | 
 | ||||||
|  | from .utils import get_type_for_model | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| class SQLAlchemyConnectionField(relay.ConnectionField): | class SQLAlchemyConnectionField(ConnectionField): | ||||||
| 
 | 
 | ||||||
|     def wrap_resolved(self, value, instance, args, info): |     def wrap_resolved(self, value, instance, args, info): | ||||||
|         schema = info.schema.graphene_schema |         if isinstance(value, Query): | ||||||
|         return lazy_map(value, self.get_object_type(schema)) |             return LazyMap(value, self.type) | ||||||
|  |         return value | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| class LazyListField(ListField): | class LazyListField(Field): | ||||||
| 
 | 
 | ||||||
|     def resolve(self, instance, args, info): |     def get_type(self, schema): | ||||||
|         schema = info.schema.graphene_schema |         return List(self.type) | ||||||
|         resolved = super(LazyListField, self).resolve(instance, args, info) | 
 | ||||||
|         return lazy_map(resolved, self.get_object_type(schema)) |     def resolver(self, instance, args, info): | ||||||
|  |         resolved = super(LazyListField, self).resolver(instance, args, info) | ||||||
|  |         return LazyMap(resolved, self.type) | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| class ConnectionOrListField(LazyField): | class ConnectionOrListField(Field): | ||||||
| 
 | 
 | ||||||
|     def get_field(self, schema): |     def internal_type(self, schema): | ||||||
|         model_field = self.field_type |         model_field = self.type | ||||||
|         field_object_type = model_field.get_object_type(schema) |         field_object_type = model_field.get_object_type(schema) | ||||||
|  |         if not field_object_type: | ||||||
|  |             raise SkipField() | ||||||
|         if is_node(field_object_type): |         if is_node(field_object_type): | ||||||
|             field = SQLAlchemyConnectionField(model_field) |             field = SQLAlchemyConnectionField(field_object_type) | ||||||
|         else: |         else: | ||||||
|             field = LazyListField(model_field) |             field = LazyListField(field_object_type) | ||||||
|         field.contribute_to_class(self.object_type, self.name) |         field.contribute_to_class(self.object_type, self.attname) | ||||||
|         return field |         return schema.T(field) | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| class SQLAlchemyModelField(Field): | class SQLAlchemyModelField(FieldType): | ||||||
| 
 | 
 | ||||||
|     def __init__(self, model, *args, **kwargs): |     def __init__(self, model, *args, **kwargs): | ||||||
|         super(SQLAlchemyModelField, self).__init__(None, *args, **kwargs) |  | ||||||
|         self.model = model |         self.model = model | ||||||
| 
 |         super(SQLAlchemyModelField, self).__init__(*args, **kwargs) | ||||||
|     def resolve(self, instance, args, info): |  | ||||||
|         resolved = super(SQLAlchemyModelField, 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) |  | ||||||
| 
 | 
 | ||||||
|     def internal_type(self, schema): |     def internal_type(self, schema): | ||||||
|         _type = self.get_object_type(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( |             raise Exception( | ||||||
|                 "Model %r is not accessible by the schema. " |                 "Table %r is not accessible by the schema. " | ||||||
|                 "You can either register the type manually " |                 "You can either register the type manually " | ||||||
|                 "using @schema.register. " |                 "using @schema.register. " | ||||||
|                 "Or disable the field %s in %s" % ( |                 "Or disable the field in %s" % ( | ||||||
|                     self.model, |                     self.model, | ||||||
|                     self.attname, |                     self.parent, | ||||||
|                     self.object_type |  | ||||||
|                 ) |                 ) | ||||||
|             ) |             ) | ||||||
|         return schema.T(_type) or Field.SKIP |         if not _type: | ||||||
|  |             raise SkipField() | ||||||
|  |         return schema.T(_type) | ||||||
| 
 | 
 | ||||||
|     def get_object_type(self, schema): |     def get_object_type(self, schema): | ||||||
|         return get_type_for_model(schema, self.model) |         return get_type_for_model(schema, self.model) | ||||||
|  |  | ||||||
|  | @ -1,23 +1,23 @@ | ||||||
| import inspect | import inspect | ||||||
| 
 | 
 | ||||||
| from sqlalchemy import Table | from sqlalchemy.ext.declarative.api import DeclarativeMeta | ||||||
| 
 | 
 | ||||||
| from graphene.core.options import Options | from ...core.options import Options | ||||||
| from graphene.relay.types import Node | from ...relay.types import Node | ||||||
| from graphene.relay.utils import is_node | from ...relay.utils import is_node | ||||||
| 
 | 
 | ||||||
| VALID_ATTRS = ('table', 'only_columns', 'exclude_columns') | VALID_ATTRS = ('model', 'only_fields', 'exclude_fields') | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| def is_base(cls): | def is_base(cls): | ||||||
|     from graphene.contrib.SQLAlchemy.types import SQLAlchemyObjectType |     from graphene.contrib.sqlalchemy.types import SQLAlchemyObjectType | ||||||
|     return SQLAlchemyObjectType in cls.__bases__ |     return SQLAlchemyObjectType in cls.__bases__ | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| class SQLAlchemyOptions(Options): | class SQLAlchemyOptions(Options): | ||||||
| 
 | 
 | ||||||
|     def __init__(self, *args, **kwargs): |     def __init__(self, *args, **kwargs): | ||||||
|         self.table = None |         self.model = None | ||||||
|         super(SQLAlchemyOptions, self).__init__(*args, **kwargs) |         super(SQLAlchemyOptions, self).__init__(*args, **kwargs) | ||||||
|         self.valid_attrs += VALID_ATTRS |         self.valid_attrs += VALID_ATTRS | ||||||
|         self.only_fields = None |         self.only_fields = None | ||||||
|  | @ -30,8 +30,8 @@ class SQLAlchemyOptions(Options): | ||||||
|             self.interfaces.append(Node) |             self.interfaces.append(Node) | ||||||
|         if not is_node(cls) and not is_base(cls): |         if not is_node(cls) and not is_base(cls): | ||||||
|             return |             return | ||||||
|         if not self.table: |         if not self.model: | ||||||
|             raise Exception( |             raise Exception( | ||||||
|                 'SQLAlchemy ObjectType %s must have a table in the Meta class attr' % cls) |                 'SQLAlchemy ObjectType %s must have a model in the Meta class attr' % cls) | ||||||
|         elif not inspect.isclass(self.table) or not issubclass(self.table, Table): |         elif not inspect.isclass(self.model) or not isinstance(self.model, DeclarativeMeta): | ||||||
|             raise Exception('Provided table in %s is not a SQLAlchemy table' % cls) |             raise Exception('Provided model in %s is not a SQLAlchemy model' % cls) | ||||||
|  |  | ||||||
							
								
								
									
										0
									
								
								graphene/contrib/sqlalchemy/tests/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										0
									
								
								graphene/contrib/sqlalchemy/tests/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
								
								
									
										37
									
								
								graphene/contrib/sqlalchemy/tests/models.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										37
									
								
								graphene/contrib/sqlalchemy/tests/models.py
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,37 @@ | ||||||
|  | from __future__ import absolute_import | ||||||
|  | 
 | ||||||
|  | from sqlalchemy import Table, Column, Integer, String, Date, ForeignKey | ||||||
|  | from sqlalchemy.ext.declarative import declarative_base | ||||||
|  | from sqlalchemy.orm import relationship | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | Base = declarative_base() | ||||||
|  | 
 | ||||||
|  | association_table = Table('association', Base.metadata, | ||||||
|  |     Column('pet_id', Integer, ForeignKey('pets.id')), | ||||||
|  |     Column('reporter_id', Integer, ForeignKey('reporters.id'))) | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | class Pet(Base): | ||||||
|  |     __tablename__ = 'pets' | ||||||
|  |     id = Column(Integer(), primary_key=True) | ||||||
|  |     name = Column(String(30)) | ||||||
|  |     reporter_id = Column(Integer(), ForeignKey('reporters.id')) | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | class Reporter(Base): | ||||||
|  |     __tablename__ = 'reporters' | ||||||
|  |     id = Column(Integer(), primary_key=True) | ||||||
|  |     first_name = Column(String(30)) | ||||||
|  |     last_name = Column(String(30)) | ||||||
|  |     email = Column(String()) | ||||||
|  |     pets = relationship('Pet', secondary=association_table, backref='reporters') | ||||||
|  |     articles = relationship('Article', backref='reporter') | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | class Article(Base): | ||||||
|  |     __tablename__ = 'articles' | ||||||
|  |     id = Column(Integer(), primary_key=True) | ||||||
|  |     headline = Column(String(100)) | ||||||
|  |     pub_date = Column(Date()) | ||||||
|  |     reporter_id = Column(Integer(), ForeignKey('reporters.id')) | ||||||
							
								
								
									
										103
									
								
								graphene/contrib/sqlalchemy/tests/test_converter.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										103
									
								
								graphene/contrib/sqlalchemy/tests/test_converter.py
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,103 @@ | ||||||
|  | from sqlalchemy import types, Column | ||||||
|  | from py.test import raises | ||||||
|  | 
 | ||||||
|  | import graphene | ||||||
|  | from graphene.contrib.sqlalchemy.converter import convert_sqlalchemy_column, convert_sqlalchemy_relationship | ||||||
|  | from graphene.contrib.sqlalchemy.fields import ConnectionOrListField, SQLAlchemyModelField | ||||||
|  | 
 | ||||||
|  | from .models import Article, Reporter, Pet | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | def assert_column_conversion(sqlalchemy_type, graphene_field, **kwargs): | ||||||
|  |     column = Column(sqlalchemy_type, doc='Custom Help Text', **kwargs) | ||||||
|  |     graphene_type = convert_sqlalchemy_column(column) | ||||||
|  |     assert isinstance(graphene_type, graphene_field) | ||||||
|  |     field = graphene_type.as_field() | ||||||
|  |     assert field.description == 'Custom Help Text' | ||||||
|  |     return field | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | def test_should_unknown_sqlalchemy_field_raise_exception(): | ||||||
|  |     with raises(Exception) as excinfo: | ||||||
|  |         convert_sqlalchemy_column(None) | ||||||
|  |     assert 'Don\'t know how to convert the SQLAlchemy field' in str(excinfo.value) | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | def test_should_date_convert_string(): | ||||||
|  |     assert_column_conversion(types.Date(), graphene.String) | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | def test_should_datetime_convert_string(): | ||||||
|  |     assert_column_conversion(types.DateTime(), graphene.String) | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | def test_should_time_convert_string(): | ||||||
|  |     assert_column_conversion(types.Time(), graphene.String) | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | def test_should_string_convert_string(): | ||||||
|  |     assert_column_conversion(types.String(), graphene.String) | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | def test_should_text_convert_string(): | ||||||
|  |     assert_column_conversion(types.Text(), graphene.String) | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | def test_should_unicode_convert_string(): | ||||||
|  |     assert_column_conversion(types.Unicode(), graphene.String) | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | def test_should_unicodetext_convert_string(): | ||||||
|  |     assert_column_conversion(types.UnicodeText(), graphene.String) | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | def test_should_enum_convert_string(): | ||||||
|  |     assert_column_conversion(types.Enum(), graphene.String) | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | def test_should_small_integer_convert_int(): | ||||||
|  |     assert_column_conversion(types.SmallInteger(), graphene.Int) | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | def test_should_big_integer_convert_int(): | ||||||
|  |     assert_column_conversion(types.BigInteger(), graphene.Int) | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | def test_should_integer_convert_int(): | ||||||
|  |     assert_column_conversion(types.Integer(), graphene.Int) | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | def test_should_integer_convert_id(): | ||||||
|  |     assert_column_conversion(types.Integer(), graphene.ID, primary_key=True) | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | def test_should_boolean_convert_boolean(): | ||||||
|  |     field = assert_column_conversion(types.Boolean(), graphene.Boolean) | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | def test_should_float_convert_float(): | ||||||
|  |     assert_column_conversion(types.Float(), graphene.Float) | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | def test_should_numeric_convert_float(): | ||||||
|  |     assert_column_conversion(types.Numeric(), graphene.Float) | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | def test_should_manytomany_convert_connectionorlist(): | ||||||
|  |     graphene_type = convert_sqlalchemy_relationship(Reporter.pets.property) | ||||||
|  |     assert isinstance(graphene_type, ConnectionOrListField) | ||||||
|  |     assert isinstance(graphene_type.type, SQLAlchemyModelField) | ||||||
|  |     assert graphene_type.type.model == Pet | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | def test_should_manytoone_convert_connectionorlist(): | ||||||
|  |     field = convert_sqlalchemy_relationship(Article.reporter.property) | ||||||
|  |     assert isinstance(field, SQLAlchemyModelField) | ||||||
|  |     assert field.model == Reporter | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | def test_should_onetomany_convert_model(): | ||||||
|  |     graphene_type = convert_sqlalchemy_relationship(Reporter.articles.property) | ||||||
|  |     assert isinstance(graphene_type, ConnectionOrListField) | ||||||
|  |     assert isinstance(graphene_type.type, SQLAlchemyModelField) | ||||||
|  |     assert graphene_type.type.model == Article | ||||||
							
								
								
									
										141
									
								
								graphene/contrib/sqlalchemy/tests/test_query.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										141
									
								
								graphene/contrib/sqlalchemy/tests/test_query.py
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,141 @@ | ||||||
|  | from py.test import raises | ||||||
|  | 
 | ||||||
|  | import graphene | ||||||
|  | from graphene import relay | ||||||
|  | from graphene.contrib.sqlalchemy import SQLAlchemyNode, SQLAlchemyObjectType | ||||||
|  | from .models import Article, Reporter | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | def test_should_query_only_fields(): | ||||||
|  |     with raises(Exception): | ||||||
|  |         class ReporterType(SQLAlchemyObjectType): | ||||||
|  | 
 | ||||||
|  |             class Meta: | ||||||
|  |                 model = Reporter | ||||||
|  |                 only_fields = ('articles', ) | ||||||
|  | 
 | ||||||
|  |         schema = graphene.Schema(query=ReporterType) | ||||||
|  |         query = ''' | ||||||
|  |             query ReporterQuery { | ||||||
|  |               articles | ||||||
|  |             } | ||||||
|  |         ''' | ||||||
|  |         result = schema.execute(query) | ||||||
|  |         assert not result.errors | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | def test_should_query_well(): | ||||||
|  |     class ReporterType(SQLAlchemyObjectType): | ||||||
|  | 
 | ||||||
|  |         class Meta: | ||||||
|  |             model = Reporter | ||||||
|  | 
 | ||||||
|  |     class Query(graphene.ObjectType): | ||||||
|  |         reporter = graphene.Field(ReporterType) | ||||||
|  | 
 | ||||||
|  |         def resolve_reporter(self, *args, **kwargs): | ||||||
|  |             return ReporterType(Reporter(first_name='ABA', last_name='X')) | ||||||
|  | 
 | ||||||
|  |     query = ''' | ||||||
|  |         query ReporterQuery { | ||||||
|  |           reporter { | ||||||
|  |             firstName, | ||||||
|  |             lastName, | ||||||
|  |             email | ||||||
|  |           } | ||||||
|  |         } | ||||||
|  |     ''' | ||||||
|  |     expected = { | ||||||
|  |         'reporter': { | ||||||
|  |             'firstName': 'ABA', | ||||||
|  |             'lastName': 'X', | ||||||
|  |             'email': None | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |     schema = graphene.Schema(query=Query) | ||||||
|  |     result = schema.execute(query) | ||||||
|  |     assert not result.errors | ||||||
|  |     assert result.data == expected | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | def test_should_node(): | ||||||
|  |     class ReporterNode(SQLAlchemyNode): | ||||||
|  | 
 | ||||||
|  |         class Meta: | ||||||
|  |             model = Reporter | ||||||
|  |             exclude_fields = ('id', ) | ||||||
|  | 
 | ||||||
|  |         @classmethod | ||||||
|  |         def get_node(cls, id, info): | ||||||
|  |             return ReporterNode(Reporter(id=2, first_name='Cookie Monster')) | ||||||
|  | 
 | ||||||
|  |         def resolve_articles(self, *args, **kwargs): | ||||||
|  |             return [ArticleNode(Article(headline='Hi!'))] | ||||||
|  | 
 | ||||||
|  |     class ArticleNode(SQLAlchemyNode): | ||||||
|  | 
 | ||||||
|  |         class Meta: | ||||||
|  |             model = Article | ||||||
|  |             exclude_fields = ('id', ) | ||||||
|  | 
 | ||||||
|  |         @classmethod | ||||||
|  |         def get_node(cls, id, info): | ||||||
|  |             return ArticleNode(Article(id=1, headline='Article node')) | ||||||
|  | 
 | ||||||
|  |     class Query(graphene.ObjectType): | ||||||
|  |         node = relay.NodeField() | ||||||
|  |         reporter = graphene.Field(ReporterNode) | ||||||
|  |         article = graphene.Field(ArticleNode) | ||||||
|  | 
 | ||||||
|  |         def resolve_reporter(self, *args, **kwargs): | ||||||
|  |             return ReporterNode(Reporter(id=1, first_name='ABA', last_name='X')) | ||||||
|  | 
 | ||||||
|  |     query = ''' | ||||||
|  |         query ReporterQuery { | ||||||
|  |           reporter { | ||||||
|  |             id, | ||||||
|  |             firstName, | ||||||
|  |             articles { | ||||||
|  |               edges { | ||||||
|  |                 node { | ||||||
|  |                   headline | ||||||
|  |                 } | ||||||
|  |               } | ||||||
|  |             } | ||||||
|  |             lastName, | ||||||
|  |             email | ||||||
|  |           } | ||||||
|  |           myArticle: node(id:"QXJ0aWNsZU5vZGU6MQ==") { | ||||||
|  |             id | ||||||
|  |             ... on ReporterNode { | ||||||
|  |                 firstName | ||||||
|  |             } | ||||||
|  |             ... on ArticleNode { | ||||||
|  |                 headline | ||||||
|  |             } | ||||||
|  |           } | ||||||
|  |         } | ||||||
|  |     ''' | ||||||
|  |     expected = { | ||||||
|  |         'reporter': { | ||||||
|  |             'id': 'UmVwb3J0ZXJOb2RlOjE=', | ||||||
|  |             'firstName': 'ABA', | ||||||
|  |             'lastName': 'X', | ||||||
|  |             'email': None, | ||||||
|  |             'articles': { | ||||||
|  |                 'edges': [{ | ||||||
|  |                   'node': { | ||||||
|  |                       'headline': 'Hi!' | ||||||
|  |                   } | ||||||
|  |                 }] | ||||||
|  |             }, | ||||||
|  |         }, | ||||||
|  |         'myArticle': { | ||||||
|  |             'id': 'QXJ0aWNsZU5vZGU6MQ==', | ||||||
|  |             'headline': 'Article node' | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |     schema = graphene.Schema(query=Query) | ||||||
|  |     result = schema.execute(query) | ||||||
|  |     assert not result.errors | ||||||
|  |     assert result.data == expected | ||||||
							
								
								
									
										45
									
								
								graphene/contrib/sqlalchemy/tests/test_schema.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										45
									
								
								graphene/contrib/sqlalchemy/tests/test_schema.py
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,45 @@ | ||||||
|  | from py.test import raises | ||||||
|  | 
 | ||||||
|  | from graphene.contrib.sqlalchemy import SQLAlchemyObjectType | ||||||
|  | from tests.utils import assert_equal_lists | ||||||
|  | 
 | ||||||
|  | from .models import Reporter | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | def test_should_raise_if_no_model(): | ||||||
|  |     with raises(Exception) as excinfo: | ||||||
|  |         class Character1(SQLAlchemyObjectType): | ||||||
|  |             pass | ||||||
|  |     assert 'model in the Meta' in str(excinfo.value) | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | def test_should_raise_if_model_is_invalid(): | ||||||
|  |     with raises(Exception) as excinfo: | ||||||
|  |         class Character2(SQLAlchemyObjectType): | ||||||
|  | 
 | ||||||
|  |             class Meta: | ||||||
|  |                 model = 1 | ||||||
|  |     assert 'not a SQLAlchemy model' in str(excinfo.value) | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | def test_should_map_fields_correctly(): | ||||||
|  |     class ReporterType2(SQLAlchemyObjectType): | ||||||
|  | 
 | ||||||
|  |         class Meta: | ||||||
|  |             model = Reporter | ||||||
|  |     assert_equal_lists( | ||||||
|  |         ReporterType2._meta.fields_map.keys(), | ||||||
|  |         ['articles', 'first_name', 'last_name', 'email', 'pets', 'id'] | ||||||
|  |     ) | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | def test_should_map_only_few_fields(): | ||||||
|  |     class Reporter2(SQLAlchemyObjectType): | ||||||
|  | 
 | ||||||
|  |         class Meta: | ||||||
|  |             model = Reporter | ||||||
|  |             only_fields = ('id', 'email') | ||||||
|  |     assert_equal_lists( | ||||||
|  |         Reporter2._meta.fields_map.keys(), | ||||||
|  |         ['id', 'email'] | ||||||
|  |     ) | ||||||
							
								
								
									
										106
									
								
								graphene/contrib/sqlalchemy/tests/test_types.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										106
									
								
								graphene/contrib/sqlalchemy/tests/test_types.py
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,106 @@ | ||||||
|  | from graphql.core.type import GraphQLInterfaceType, GraphQLObjectType | ||||||
|  | from pytest import raises | ||||||
|  | 
 | ||||||
|  | from graphene import Schema | ||||||
|  | from graphene.contrib.sqlalchemy.types import SQLAlchemyInterface, SQLAlchemyNode | ||||||
|  | from graphene.core.fields import Field | ||||||
|  | from graphene.core.types.scalars import Int | ||||||
|  | from graphene.relay.fields import GlobalIDField | ||||||
|  | from tests.utils import assert_equal_lists | ||||||
|  | 
 | ||||||
|  | from .models import Article, Reporter | ||||||
|  | 
 | ||||||
|  | schema = Schema() | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | class Character(SQLAlchemyInterface): | ||||||
|  |     '''Character description''' | ||||||
|  |     class Meta: | ||||||
|  |         model = Reporter | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | @schema.register | ||||||
|  | class Human(SQLAlchemyNode): | ||||||
|  |     '''Human description''' | ||||||
|  | 
 | ||||||
|  |     pub_date = Int() | ||||||
|  | 
 | ||||||
|  |     class Meta: | ||||||
|  |         model = Article | ||||||
|  |         exclude_fields = ('id', ) | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | def test_sqlalchemy_interface(): | ||||||
|  |     assert SQLAlchemyNode._meta.is_interface is True | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | def test_sqlalchemy_get_node(get): | ||||||
|  |     human = Human.get_node(1, None) | ||||||
|  |     get.assert_called_with(id=1) | ||||||
|  |     assert human.id == 1 | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | def test_pseudo_interface_registered(): | ||||||
|  |     object_type = schema.T(Character) | ||||||
|  |     assert Character._meta.is_interface is True | ||||||
|  |     assert isinstance(object_type, GraphQLInterfaceType) | ||||||
|  |     assert Character._meta.model == Reporter | ||||||
|  |     assert_equal_lists( | ||||||
|  |         object_type.get_fields().keys(), | ||||||
|  |         ['articles', 'firstName', 'lastName', 'email', 'pets', 'id'] | ||||||
|  |     ) | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | def test_sqlalchemynode_idfield(): | ||||||
|  |     idfield = SQLAlchemyNode._meta.fields_map['id'] | ||||||
|  |     assert isinstance(idfield, GlobalIDField) | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | def test_node_idfield(): | ||||||
|  |     idfield = Human._meta.fields_map['id'] | ||||||
|  |     assert isinstance(idfield, GlobalIDField) | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | def test_node_replacedfield(): | ||||||
|  |     idfield = Human._meta.fields_map['pub_date'] | ||||||
|  |     assert isinstance(idfield, Field) | ||||||
|  |     assert schema.T(idfield).type == schema.T(Int()) | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | def test_interface_resolve_type(): | ||||||
|  |     resolve_type = Character.resolve_type(schema, Human()) | ||||||
|  |     assert isinstance(resolve_type, GraphQLObjectType) | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | def test_interface_objecttype_init_none(): | ||||||
|  |     h = Human() | ||||||
|  |     assert h._root is None | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | def test_interface_objecttype_init_good(): | ||||||
|  |     instance = Article() | ||||||
|  |     h = Human(instance) | ||||||
|  |     assert h._root == instance | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | def test_interface_objecttype_init_unexpected(): | ||||||
|  |     with raises(AssertionError) as excinfo: | ||||||
|  |         Human(object()) | ||||||
|  |     assert str(excinfo.value) == "Human received a non-compatible instance (object) when expecting Article" | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | def test_object_type(): | ||||||
|  |     object_type = schema.T(Human) | ||||||
|  |     Human._meta.fields_map | ||||||
|  |     assert Human._meta.is_interface is False | ||||||
|  |     assert isinstance(object_type, GraphQLObjectType) | ||||||
|  |     assert_equal_lists( | ||||||
|  |         object_type.get_fields().keys(), | ||||||
|  |         ['headline', 'id', 'reporter', 'reporterId', 'pubDate'] | ||||||
|  |     ) | ||||||
|  |     assert schema.T(SQLAlchemyNode) in object_type.get_interfaces() | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | def test_node_notinterface(): | ||||||
|  |     assert Human._meta.is_interface is False | ||||||
|  |     assert SQLAlchemyNode in Human._meta.interfaces | ||||||
|  | @ -1,13 +1,11 @@ | ||||||
| import six | import six | ||||||
| from sqlalchemy.inspection import inspect | from sqlalchemy.inspection import inspect | ||||||
| 
 | 
 | ||||||
| from graphene.contrib.sqlalchemy.converter import convert_sqlalchemy_column, | from ...core.types import BaseObjectType, ObjectTypeMeta | ||||||
|     convert_sqlalchemy_relationship | from ...relay.fields import GlobalIDField | ||||||
| from graphene.contrib.sqlalchemy.options import SQLAlchemyOptions | from ...relay.types import BaseNode | ||||||
| from graphene.contrib.sqlalchemy.utils import get_reverse_columns | from .converter import convert_sqlalchemy_column, convert_sqlalchemy_relationship | ||||||
| from graphene.core.types import BaseObjectType, ObjectTypeMeta | from .options import SQLAlchemyOptions | ||||||
| from graphene.relay.fields import GlobalIDField |  | ||||||
| from graphene.relay.types import BaseNode |  | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| class SQLAlchemyObjectTypeMeta(ObjectTypeMeta): | class SQLAlchemyObjectTypeMeta(ObjectTypeMeta): | ||||||
|  | @ -17,26 +15,60 @@ class SQLAlchemyObjectTypeMeta(ObjectTypeMeta): | ||||||
|         return SQLAlchemyInterface in parents |         return SQLAlchemyInterface in parents | ||||||
| 
 | 
 | ||||||
|     def add_extra_fields(cls): |     def add_extra_fields(cls): | ||||||
|         if not cls._meta.table: |         if not cls._meta.model: | ||||||
|             return |             return | ||||||
|         inspected_table = inspect(cls._meta.table) |         only_fields = cls._meta.only_fields | ||||||
|         # Get all the columns for the relationships on the table |         exclude_fields = cls._meta.exclude_fields | ||||||
|         for relationship in inspected_table.relationships: |         already_created_fields = {f.attname for f in cls._meta.local_fields} | ||||||
|  |         inspected_model = inspect(cls._meta.model) | ||||||
|  | 
 | ||||||
|  |         # Get all the columns for the relationships on the model | ||||||
|  |         for relationship in inspected_model.relationships: | ||||||
|  |             is_not_in_only = only_fields and relationship.key not in only_fields | ||||||
|  |             is_already_created = relationship.key in already_created_fields | ||||||
|  |             is_excluded = relationship.key in exclude_fields or is_already_created | ||||||
|  |             if is_not_in_only or is_excluded: | ||||||
|  |                 # We skip this field if we specify only_fields and is not | ||||||
|  |                 # in there. Or when we excldue this field in exclude_fields | ||||||
|  |                 continue | ||||||
|             converted_relationship = convert_sqlalchemy_relationship(relationship) |             converted_relationship = convert_sqlalchemy_relationship(relationship) | ||||||
|             cls.add_to_class(relationship.key, converted_relationship) |             cls.add_to_class(relationship.key, converted_relationship) | ||||||
|         for column in inspected_table.columns: | 
 | ||||||
|  |         for column in inspected_model.columns: | ||||||
|  |             is_not_in_only = only_fields and column.name not in only_fields | ||||||
|  |             is_already_created = column.name in already_created_fields | ||||||
|  |             is_excluded = column.name in exclude_fields or is_already_created | ||||||
|  |             if is_not_in_only or is_excluded: | ||||||
|  |                 # We skip this field if we specify only_fields and is not | ||||||
|  |                 # in there. Or when we excldue this field in exclude_fields | ||||||
|  |                 continue | ||||||
|             converted_column = convert_sqlalchemy_column(column) |             converted_column = convert_sqlalchemy_column(column) | ||||||
|             cls.add_to_class(column.name, converted_column) |             cls.add_to_class(column.name, converted_column) | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| class InstanceObjectType(BaseObjectType): | class InstanceObjectType(BaseObjectType): | ||||||
| 
 | 
 | ||||||
|     def __init__(self, instance=None): |     def __init__(self, _root=None): | ||||||
|         self.instance = instance |         if _root: | ||||||
|         super(InstanceObjectType, self).__init__() |             assert isinstance(_root, self._meta.model), ( | ||||||
|  |                 '{} received a non-compatible instance ({}) ' | ||||||
|  |                 'when expecting {}'.format( | ||||||
|  |                     self.__class__.__name__, | ||||||
|  |                     _root.__class__.__name__, | ||||||
|  |                     self._meta.model.__name__ | ||||||
|  |                 )) | ||||||
|  |         super(InstanceObjectType, self).__init__(_root=_root) | ||||||
|  | 
 | ||||||
|  |     @property | ||||||
|  |     def instance(self): | ||||||
|  |         return self._root | ||||||
|  | 
 | ||||||
|  |     @instance.setter | ||||||
|  |     def instance(self, value): | ||||||
|  |         self._root = value | ||||||
| 
 | 
 | ||||||
|     def __getattr__(self, attr): |     def __getattr__(self, attr): | ||||||
|         return getattr(self.instance, attr) |         return getattr(self._root, attr) | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| class SQLAlchemyObjectType(six.with_metaclass(SQLAlchemyObjectTypeMeta, InstanceObjectType)): | class SQLAlchemyObjectType(six.with_metaclass(SQLAlchemyObjectTypeMeta, InstanceObjectType)): | ||||||
|  | @ -51,6 +83,9 @@ class SQLAlchemyNode(BaseNode, SQLAlchemyInterface): | ||||||
|     id = GlobalIDField() |     id = GlobalIDField() | ||||||
| 
 | 
 | ||||||
|     @classmethod |     @classmethod | ||||||
|     def get_node(cls, id): |     def get_node(cls, id, info=None): | ||||||
|         instance = cls._meta.table.objects.filter(id=id).first() |         try: | ||||||
|         return cls(instance) |             instance = cls._meta.model.filter(id=id).one() | ||||||
|  |             return cls(instance) | ||||||
|  |         except cls._meta.model.DoesNotExist: | ||||||
|  |             return None | ||||||
|  |  | ||||||
|  | @ -1,10 +1,3 @@ | ||||||
| from django.db import models |  | ||||||
| from django.db.models.manager import Manager |  | ||||||
| from django.db.models.query import QuerySet |  | ||||||
| 
 |  | ||||||
| from graphene.utils import LazyMap |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| def get_type_for_model(schema, model): | def get_type_for_model(schema, model): | ||||||
|     schema = schema |     schema = schema | ||||||
|     types = schema.types.values() |     types = schema.types.values() | ||||||
|  | @ -13,11 +6,3 @@ def get_type_for_model(schema, model): | ||||||
|             _type._meta, 'model', None) |             _type._meta, 'model', None) | ||||||
|         if model == type_model: |         if model == type_model: | ||||||
|             return _type |             return _type | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| def lazy_map(value, func): |  | ||||||
|     if isinstance(value, Manager): |  | ||||||
|         value = value.get_queryset() |  | ||||||
|     if isinstance(value, QuerySet): |  | ||||||
|         return LazyMap(value, func) |  | ||||||
|     return value |  | ||||||
|  |  | ||||||
		Loading…
	
		Reference in New Issue
	
	Block a user