Skip to content

Commit a93f93c

Browse files
committed
Support manually mapped (e.g. reflected) tables
Mapped tables must not always be sub classes of DeclarativeBase.
1 parent 030e5e1 commit a93f93c

File tree

4 files changed

+55
-7
lines changed

4 files changed

+55
-7
lines changed

graphene_sqlalchemy/tests/models.py

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
from sqlalchemy import Column, Date, ForeignKey, Integer, String, Table
44
from sqlalchemy.ext.declarative import declarative_base
5-
from sqlalchemy.orm import relationship
5+
from sqlalchemy.orm import mapper, relationship
66

77
Base = declarative_base()
88

@@ -41,3 +41,11 @@ class Article(Base):
4141
headline = Column(String(100))
4242
pub_date = Column(Date())
4343
reporter_id = Column(Integer(), ForeignKey('reporters.id'))
44+
45+
46+
class ReflectedEditor:
47+
"""Same as Editor, but using reflected table."""
48+
49+
editor_table = Table('editors', Base.metadata, autoload=True)
50+
51+
mapper(ReflectedEditor, editor_table)
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
2+
from graphene import ObjectType
3+
4+
from ..registry import Registry
5+
from ..types import SQLAlchemyObjectType
6+
from .models import ReflectedEditor
7+
8+
registry = Registry()
9+
10+
11+
class Reflected(SQLAlchemyObjectType):
12+
13+
class Meta:
14+
model = ReflectedEditor
15+
registry = registry
16+
17+
18+
def test_objecttype_registered():
19+
assert issubclass(Reflected, ObjectType)
20+
assert Reflected._meta.model == ReflectedEditor
21+
assert list(
22+
Reflected._meta.fields.keys()) == ['editor_id', 'name']
23+
24+

graphene_sqlalchemy/types.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
convert_sqlalchemy_composite,
1616
convert_sqlalchemy_relationship)
1717
from .registry import Registry, get_global_registry
18-
from .utils import get_query, is_mapped
18+
from .utils import get_query, is_mapped_class, is_mapped_instance
1919

2020

2121
def construct_fields(options):
@@ -91,7 +91,7 @@ def __new__(cls, name, bases, attrs):
9191
'The attribute registry in {}.Meta needs to be an'
9292
' instance of Registry, received "{}".'
9393
).format(name, options.registry)
94-
assert is_mapped(options.model), (
94+
assert is_mapped_class(options.model), (
9595
'You need to pass a valid SQLAlchemy Model in '
9696
'{}.Meta, received "{}".'
9797
).format(name, options.model)
@@ -120,7 +120,7 @@ class SQLAlchemyObjectType(six.with_metaclass(SQLAlchemyObjectTypeMeta, ObjectTy
120120
def is_type_of(cls, root, context, info):
121121
if isinstance(root, cls):
122122
return True
123-
if not is_mapped(type(root)):
123+
if not is_mapped_instance(root):
124124
raise Exception((
125125
'Received incompatible instance "{}".'
126126
).format(root))

graphene_sqlalchemy/utils.py

Lines changed: 19 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
1-
from sqlalchemy.ext.declarative.api import DeclarativeMeta
1+
from sqlalchemy.exc import ArgumentError
2+
from sqlalchemy.orm import class_mapper, object_mapper
3+
from sqlalchemy.orm.exc import UnmappedClassError, UnmappedInstanceError
24

35

46
def get_session(context):
@@ -16,5 +18,19 @@ def get_query(model, context):
1618
return query
1719

1820

19-
def is_mapped(obj):
20-
return isinstance(obj, DeclarativeMeta)
21+
def is_mapped_class(cls):
22+
try:
23+
class_mapper(cls)
24+
except (ArgumentError, UnmappedClassError):
25+
return False
26+
else:
27+
return True
28+
29+
30+
def is_mapped_instance(cls):
31+
try:
32+
object_mapper(cls)
33+
except (ArgumentError, UnmappedInstanceError):
34+
return False
35+
else:
36+
return True

0 commit comments

Comments
 (0)