Skip to content

Commit 9a0b33c

Browse files
committed
Fixing it up to use the declarative API
1 parent 4875432 commit 9a0b33c

File tree

11 files changed

+557
-101
lines changed

11 files changed

+557
-101
lines changed

graphene/contrib/sqlalchemy/converter.py

Lines changed: 21 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -2,60 +2,62 @@
22
from sqlalchemy.orm import interfaces
33
from singledispatch import singledispatch
44

5-
from graphene.contrib.sqlalchemy.fields import ConnectionOrListField, SQLAlchemyModelField
6-
from graphene.core.fields import BooleanField, FloatField, IDField, IntField, StringField
5+
from ...core.types.scalars import Boolean, Float, ID, Int, String
6+
from .fields import ConnectionOrListField, SQLAlchemyModelField
77

88

99
def convert_sqlalchemy_relationship(relationship):
10-
model_field = SQLAlchemyModelField(field.table, description=relationship.key)
11-
if relationship.direction == interfaces.ONETOMANY:
10+
direction = relationship.direction
11+
model = relationship.mapper.entity
12+
model_field = SQLAlchemyModelField(model, description=relationship.doc)
13+
if direction == interfaces.MANYTOONE:
1214
return model_field
13-
elif (relationship.direction == interfaces.MANYTOONE or
14-
relationship.direction == interfaces.MANYTOMANY):
15+
elif (direction == interfaces.ONETOMANY or
16+
direction == interfaces.MANYTOMANY):
1517
return ConnectionOrListField(model_field)
1618

1719

1820
def convert_sqlalchemy_column(column):
1921
try:
2022
return convert_sqlalchemy_type(column.type, column)
2123
except Exception:
22-
raise
24+
raise Exception(
25+
"Don't know how to convert the SQLAlchemy field %s (%s)" % (column, column.__class__))
2326

2427

2528
@singledispatch
26-
def convert_sqlalchemy_type():
27-
raise Exception(
28-
"Don't know how to convert the SQLAlchemy column %s (%s)" % (column, column.__class__))
29+
def convert_sqlalchemy_type(type, column):
30+
raise Exception()
2931

3032

3133
@convert_sqlalchemy_type.register(types.Date)
3234
@convert_sqlalchemy_type.register(types.DateTime)
3335
@convert_sqlalchemy_type.register(types.Time)
34-
@convert_sqlalchemy_type.register(types.Text)
3536
@convert_sqlalchemy_type.register(types.String)
37+
@convert_sqlalchemy_type.register(types.Text)
3638
@convert_sqlalchemy_type.register(types.Unicode)
3739
@convert_sqlalchemy_type.register(types.UnicodeText)
3840
@convert_sqlalchemy_type.register(types.Enum)
3941
def convert_column_to_string(type, column):
40-
return StringField(description=column.description)
42+
return String(description=column.doc)
4143

4244

4345
@convert_sqlalchemy_type.register(types.SmallInteger)
4446
@convert_sqlalchemy_type.register(types.BigInteger)
4547
@convert_sqlalchemy_type.register(types.Integer)
46-
def convert_column_to_int_or_id(column):
48+
def convert_column_to_int_or_id(type, column):
4749
if column.primary_key:
48-
return IDField(description=column.description)
50+
return ID(description=column.doc)
4951
else:
50-
return IntField(description=column.description)
52+
return Int(description=column.doc)
5153

5254

5355
@convert_sqlalchemy_type.register(types.Boolean)
54-
def convert_column_to_boolean(column):
55-
return BooleanField(description=column.description)
56+
def convert_column_to_boolean(type, column):
57+
return Boolean(description=column.doc)
5658

5759

5860
@convert_sqlalchemy_type.register(types.Float)
5961
@convert_sqlalchemy_type.register(types.Numeric)
60-
def convert_column_to_float(column):
61-
return FloatField(description=column.description)
62+
def convert_column_to_float(type, column):
63+
return Float(description=column.doc)

graphene/contrib/sqlalchemy/fields.py

Lines changed: 39 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -1,67 +1,69 @@
1-
from graphene import relay
2-
from graphene.contrib.sqlalchemy.utils import get_type_for_model, lazy_map
3-
from graphene.core.fields import Field, LazyField, ListField
4-
from graphene.relay.utils import is_node
1+
from sqlalchemy.orm import Query
2+
from ...core.exceptions import SkipField
3+
from ...core.fields import Field
4+
from ...core.types.base import FieldType
5+
from ...core.types.definitions import List
6+
from ...relay import ConnectionField
7+
from ...relay.utils import is_node
8+
from ...utils import LazyMap
59

10+
from .utils import get_type_for_model
611

7-
class SQLAlchemyConnectionField(relay.ConnectionField):
12+
13+
class SQLAlchemyConnectionField(ConnectionField):
814

915
def wrap_resolved(self, value, instance, args, info):
10-
schema = info.schema.graphene_schema
11-
return lazy_map(value, self.get_object_type(schema))
16+
if isinstance(value, Query):
17+
return LazyMap(value, self.type)
18+
return value
19+
1220

21+
class LazyListField(Field):
1322

14-
class LazyListField(ListField):
23+
def get_type(self, schema):
24+
return List(self.type)
1525

16-
def resolve(self, instance, args, info):
17-
schema = info.schema.graphene_schema
18-
resolved = super(LazyListField, self).resolve(instance, args, info)
19-
return lazy_map(resolved, self.get_object_type(schema))
26+
def resolver(self, instance, args, info):
27+
resolved = super(LazyListField, self).resolver(instance, args, info)
28+
return LazyMap(resolved, self.type)
2029

2130

22-
class ConnectionOrListField(LazyField):
31+
class ConnectionOrListField(Field):
2332

24-
def get_field(self, schema):
25-
model_field = self.field_type
33+
def internal_type(self, schema):
34+
model_field = self.type
2635
field_object_type = model_field.get_object_type(schema)
36+
if not field_object_type:
37+
raise SkipField()
2738
if is_node(field_object_type):
28-
field = SQLAlchemyConnectionField(model_field)
39+
field = SQLAlchemyConnectionField(field_object_type)
2940
else:
30-
field = LazyListField(model_field)
31-
field.contribute_to_class(self.object_type, self.name)
32-
return field
41+
field = LazyListField(field_object_type)
42+
field.contribute_to_class(self.object_type, self.attname)
43+
return schema.T(field)
3344

3445

35-
class SQLAlchemyModelField(Field):
46+
class SQLAlchemyModelField(FieldType):
3647

3748
def __init__(self, model, *args, **kwargs):
38-
super(SQLAlchemyModelField, self).__init__(None, *args, **kwargs)
3949
self.model = model
40-
41-
def resolve(self, instance, args, info):
42-
resolved = super(SQLAlchemyModelField, self).resolve(instance, args, info)
43-
schema = info.schema.graphene_schema
44-
_type = self.get_object_type(schema)
45-
assert _type, ("Field %s cannot be retrieved as the "
46-
"ObjectType is not registered by the schema" % (
47-
self.attname
48-
))
49-
return _type(resolved)
50+
super(SQLAlchemyModelField, self).__init__(*args, **kwargs)
5051

5152
def internal_type(self, schema):
5253
_type = self.get_object_type(schema)
53-
if not _type and self.object_type._meta.only_fields:
54+
if not _type and self.parent._meta.only_fields:
5455
raise Exception(
55-
"Model %r is not accessible by the schema. "
56+
"Table %r is not accessible by the schema. "
5657
"You can either register the type manually "
5758
"using @schema.register. "
58-
"Or disable the field %s in %s" % (
59+
"Or disable the field in %s" % (
5960
self.model,
60-
self.attname,
61-
self.object_type
61+
self.parent,
6262
)
6363
)
64-
return schema.T(_type) or Field.SKIP
64+
if not _type:
65+
raise SkipField()
66+
return schema.T(_type)
6567

6668
def get_object_type(self, schema):
6769
return get_type_for_model(schema, self.model)
Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,23 @@
11
import inspect
22

3-
from sqlalchemy import Table
3+
from sqlalchemy.ext.declarative.api import DeclarativeMeta
44

5-
from graphene.core.options import Options
6-
from graphene.relay.types import Node
7-
from graphene.relay.utils import is_node
5+
from ...core.options import Options
6+
from ...relay.types import Node
7+
from ...relay.utils import is_node
88

9-
VALID_ATTRS = ('table', 'only_columns', 'exclude_columns')
9+
VALID_ATTRS = ('model', 'only_fields', 'exclude_fields')
1010

1111

1212
def is_base(cls):
13-
from graphene.contrib.SQLAlchemy.types import SQLAlchemyObjectType
13+
from graphene.contrib.sqlalchemy.types import SQLAlchemyObjectType
1414
return SQLAlchemyObjectType in cls.__bases__
1515

1616

1717
class SQLAlchemyOptions(Options):
1818

1919
def __init__(self, *args, **kwargs):
20-
self.table = None
20+
self.model = None
2121
super(SQLAlchemyOptions, self).__init__(*args, **kwargs)
2222
self.valid_attrs += VALID_ATTRS
2323
self.only_fields = None
@@ -30,8 +30,8 @@ def contribute_to_class(self, cls, name):
3030
self.interfaces.append(Node)
3131
if not is_node(cls) and not is_base(cls):
3232
return
33-
if not self.table:
33+
if not self.model:
3434
raise Exception(
35-
'SQLAlchemy ObjectType %s must have a table in the Meta class attr' % cls)
36-
elif not inspect.isclass(self.table) or not issubclass(self.table, Table):
37-
raise Exception('Provided table in %s is not a SQLAlchemy table' % cls)
35+
'SQLAlchemy ObjectType %s must have a model in the Meta class attr' % cls)
36+
elif not inspect.isclass(self.model) or not isinstance(self.model, DeclarativeMeta):
37+
raise Exception('Provided model in %s is not a SQLAlchemy model' % cls)

graphene/contrib/sqlalchemy/tests/__init__.py

Whitespace-only changes.
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
from __future__ import absolute_import
2+
3+
from sqlalchemy import Table, Column, Integer, String, Date, ForeignKey
4+
from sqlalchemy.ext.declarative import declarative_base
5+
from sqlalchemy.orm import relationship
6+
7+
8+
Base = declarative_base()
9+
10+
association_table = Table('association', Base.metadata,
11+
Column('pet_id', Integer, ForeignKey('pets.id')),
12+
Column('reporter_id', Integer, ForeignKey('reporters.id')))
13+
14+
15+
class Pet(Base):
16+
__tablename__ = 'pets'
17+
id = Column(Integer(), primary_key=True)
18+
name = Column(String(30))
19+
reporter_id = Column(Integer(), ForeignKey('reporters.id'))
20+
21+
22+
class Reporter(Base):
23+
__tablename__ = 'reporters'
24+
id = Column(Integer(), primary_key=True)
25+
first_name = Column(String(30))
26+
last_name = Column(String(30))
27+
email = Column(String())
28+
pets = relationship('Pet', secondary=association_table, backref='reporters')
29+
articles = relationship('Article', backref='reporter')
30+
31+
32+
class Article(Base):
33+
__tablename__ = 'articles'
34+
id = Column(Integer(), primary_key=True)
35+
headline = Column(String(100))
36+
pub_date = Column(Date())
37+
reporter_id = Column(Integer(), ForeignKey('reporters.id'))
Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
from sqlalchemy import types, Column
2+
from py.test import raises
3+
4+
import graphene
5+
from graphene.contrib.sqlalchemy.converter import convert_sqlalchemy_column, convert_sqlalchemy_relationship
6+
from graphene.contrib.sqlalchemy.fields import ConnectionOrListField, SQLAlchemyModelField
7+
8+
from .models import Article, Reporter, Pet
9+
10+
11+
def assert_column_conversion(sqlalchemy_type, graphene_field, **kwargs):
12+
column = Column(sqlalchemy_type, doc='Custom Help Text', **kwargs)
13+
graphene_type = convert_sqlalchemy_column(column)
14+
assert isinstance(graphene_type, graphene_field)
15+
field = graphene_type.as_field()
16+
assert field.description == 'Custom Help Text'
17+
return field
18+
19+
20+
def test_should_unknown_sqlalchemy_field_raise_exception():
21+
with raises(Exception) as excinfo:
22+
convert_sqlalchemy_column(None)
23+
assert 'Don\'t know how to convert the SQLAlchemy field' in str(excinfo.value)
24+
25+
26+
def test_should_date_convert_string():
27+
assert_column_conversion(types.Date(), graphene.String)
28+
29+
30+
def test_should_datetime_convert_string():
31+
assert_column_conversion(types.DateTime(), graphene.String)
32+
33+
34+
def test_should_time_convert_string():
35+
assert_column_conversion(types.Time(), graphene.String)
36+
37+
38+
def test_should_string_convert_string():
39+
assert_column_conversion(types.String(), graphene.String)
40+
41+
42+
def test_should_text_convert_string():
43+
assert_column_conversion(types.Text(), graphene.String)
44+
45+
46+
def test_should_unicode_convert_string():
47+
assert_column_conversion(types.Unicode(), graphene.String)
48+
49+
50+
def test_should_unicodetext_convert_string():
51+
assert_column_conversion(types.UnicodeText(), graphene.String)
52+
53+
54+
def test_should_enum_convert_string():
55+
assert_column_conversion(types.Enum(), graphene.String)
56+
57+
58+
def test_should_small_integer_convert_int():
59+
assert_column_conversion(types.SmallInteger(), graphene.Int)
60+
61+
62+
def test_should_big_integer_convert_int():
63+
assert_column_conversion(types.BigInteger(), graphene.Int)
64+
65+
66+
def test_should_integer_convert_int():
67+
assert_column_conversion(types.Integer(), graphene.Int)
68+
69+
70+
def test_should_integer_convert_id():
71+
assert_column_conversion(types.Integer(), graphene.ID, primary_key=True)
72+
73+
74+
def test_should_boolean_convert_boolean():
75+
field = assert_column_conversion(types.Boolean(), graphene.Boolean)
76+
77+
78+
def test_should_float_convert_float():
79+
assert_column_conversion(types.Float(), graphene.Float)
80+
81+
82+
def test_should_numeric_convert_float():
83+
assert_column_conversion(types.Numeric(), graphene.Float)
84+
85+
86+
def test_should_manytomany_convert_connectionorlist():
87+
graphene_type = convert_sqlalchemy_relationship(Reporter.pets.property)
88+
assert isinstance(graphene_type, ConnectionOrListField)
89+
assert isinstance(graphene_type.type, SQLAlchemyModelField)
90+
assert graphene_type.type.model == Pet
91+
92+
93+
def test_should_manytoone_convert_connectionorlist():
94+
field = convert_sqlalchemy_relationship(Article.reporter.property)
95+
assert isinstance(field, SQLAlchemyModelField)
96+
assert field.model == Reporter
97+
98+
99+
def test_should_onetomany_convert_model():
100+
graphene_type = convert_sqlalchemy_relationship(Reporter.articles.property)
101+
assert isinstance(graphene_type, ConnectionOrListField)
102+
assert isinstance(graphene_type.type, SQLAlchemyModelField)
103+
assert graphene_type.type.model == Article

0 commit comments

Comments
 (0)