Skip to content

Commit d3f8e08

Browse files
authored
Merge pull request #51 from dfee/master
Allow subclassing of SQLAlchemyObjectType without major API changes
2 parents 9ed04d4 + 37d4331 commit d3f8e08

File tree

5 files changed

+93
-8
lines changed

5 files changed

+93
-8
lines changed

README.md

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,38 @@ query = '''
6868
result = schema.execute(query, context_value={'session': db_session})
6969
```
7070

71+
You may also subclass SQLAlchemyObjectType by providing `abstract = True` in
72+
your subclasses Meta:
73+
```python
74+
from graphene_sqlalchemy import SQLAlchemyObjectType
75+
76+
class ActiveSQLAlchemyObjectType(SQLAlchemyObjectType):
77+
class Meta:
78+
abstract = True
79+
80+
@classmethod
81+
def get_node(cls, id, context, info):
82+
return cls.get_query(context).\
83+
filter(and_(
84+
cls._meta.model.deleted_at==None,
85+
cls._meta.model.id==id,
86+
)).\
87+
first()
88+
89+
class User(ActiveSQLAlchemyObjectType):
90+
class Meta:
91+
model = UserModel
92+
93+
class Query(graphene.ObjectType):
94+
users = graphene.List(User)
95+
96+
def resolve_users(self, args, context, info):
97+
query = User.get_query(context) # SQLAlchemy query
98+
return query.all()
99+
100+
schema = graphene.Schema(query=Query)
101+
```
102+
71103
To learn more check out the following [examples](examples/):
72104

73105
* **Full example**: [Flask SQLAlchemy example](examples/flask_sqlalchemy)

graphene_sqlalchemy/registry.py

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,11 @@ def __init__(self):
66
self._registry_composites = {}
77

88
def register(self, cls):
9-
from .types import SQLAlchemyObjectType
10-
assert issubclass(
11-
cls, SQLAlchemyObjectType), 'Only SQLAlchemyObjectType can be registered, received "{}"'.format(
12-
cls.__name__)
9+
from .types import SQLAlchemyObjectTypeMeta
10+
assert issubclass(type(cls), SQLAlchemyObjectTypeMeta), (
11+
'Only classes of type SQLAlchemyObjectTypeMeta can be registered, ',
12+
'received "{}"'
13+
).format(cls.__name__)
1314
assert cls._meta.registry == self, 'Registry for a Model have to match.'
1415
# assert self.get_type_for_model(cls._meta.model) in [None, cls], (
1516
# 'SQLAlchemy model "{}" already associated with '

graphene_sqlalchemy/tests/test_types.py

Lines changed: 30 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
11

22
from graphene import Field, Int, Interface, ObjectType
33
from graphene.relay import Node, is_node
4+
import six
45

56
from ..registry import Registry
6-
from ..types import SQLAlchemyObjectType
7+
from ..types import SQLAlchemyObjectType, SQLAlchemyObjectTypeMeta
78
from .models import Article, Reporter
89

910
registry = Registry()
@@ -74,3 +75,31 @@ def test_object_type():
7475
assert issubclass(Human, ObjectType)
7576
assert list(Human._meta.fields.keys()) == ['id', 'headline', 'reporter_id', 'reporter', 'pub_date']
7677
assert is_node(Human)
78+
79+
80+
81+
# Test Custom SQLAlchemyObjectType Implementation
82+
class CustomSQLAlchemyObjectType(SQLAlchemyObjectType):
83+
class Meta:
84+
abstract = True
85+
86+
87+
class CustomCharacter(CustomSQLAlchemyObjectType):
88+
'''Character description'''
89+
class Meta:
90+
model = Reporter
91+
registry = registry
92+
93+
94+
def test_custom_objecttype_registered():
95+
assert issubclass(CustomCharacter, ObjectType)
96+
assert CustomCharacter._meta.model == Reporter
97+
assert list(
98+
CustomCharacter._meta.fields.keys()) == [
99+
'id',
100+
'first_name',
101+
'last_name',
102+
'email',
103+
'pets',
104+
'articles',
105+
'favorite_article']

graphene_sqlalchemy/types.py

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -102,9 +102,15 @@ def __new__(cls, name, bases, attrs):
102102
exclude_fields=(),
103103
id='id',
104104
interfaces=(),
105-
registry=None
105+
registry=None,
106+
abstract=False,
106107
)
107108

109+
cls = ObjectTypeMeta.__new__(cls, name, bases, dict(attrs, _meta=options))
110+
111+
if options.abstract:
112+
return cls
113+
108114
if not options.registry:
109115
options.registry = get_global_registry()
110116
assert isinstance(options.registry, Registry), (
@@ -116,8 +122,6 @@ def __new__(cls, name, bases, attrs):
116122
'{}.Meta, received "{}".'
117123
).format(name, options.model)
118124

119-
cls = ObjectTypeMeta.__new__(cls, name, bases, dict(attrs, _meta=options))
120-
121125
options.registry.register(cls)
122126

123127
options.sqlalchemy_fields = yank_fields_from_attrs(
@@ -135,6 +139,8 @@ def __new__(cls, name, bases, attrs):
135139

136140

137141
class SQLAlchemyObjectType(six.with_metaclass(SQLAlchemyObjectTypeMeta, ObjectType)):
142+
class Meta:
143+
abstract = True
138144

139145
@classmethod
140146
def is_type_of(cls, root, context, info):

setup.cfg

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,3 +7,20 @@ omit = */tests/*
77

88
[isort]
99
known_first_party=graphene,graphene_sqlalchemy
10+
11+
[tool:pytest]
12+
testpaths = graphene_sqlalchemy/
13+
addopts =
14+
-s
15+
; --cov graphene-sqlalchemy
16+
norecursedirs =
17+
__pycache__
18+
*.egg-info
19+
.cache
20+
.git
21+
.tox
22+
appdir
23+
docs
24+
filterwarnings =
25+
error
26+
ignore::DeprecationWarning

0 commit comments

Comments
 (0)