Skip to content

Commit 894517d

Browse files
committed
Add SQLAlchemyList and SQLAlchemyMutation types
This brings default support for fields filtering and ordering on queries and mutations.
1 parent ecd9a91 commit 894517d

File tree

7 files changed

+1002
-27
lines changed

7 files changed

+1002
-27
lines changed

graphene_sqlalchemy/mutations.py

+268
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,268 @@
1+
from graphene import Mutation, Argument, Field, List, NonNull
2+
from sqlalchemy.inspection import inspect as sqlalchemyinspect
3+
4+
from graphene.types.objecttype import ObjectTypeOptions
5+
from graphene.types.utils import yank_fields_from_attrs
6+
from sqlalchemy.inspection import inspect as sqlalchemyinspect
7+
8+
from graphene_sqlalchemy.types import construct_fields
9+
from .registry import get_global_registry
10+
from .utils import get_session, get_snake_or_camel_attr
11+
12+
13+
class SQLAlchemyMutationOptions(ObjectTypeOptions):
14+
model = None # type: Model
15+
16+
17+
class SQLAlchemyCreate(Mutation):
18+
@classmethod
19+
def __init_subclass_with_meta__(cls, model=None, registry=None, only_fields=(), exclude_fields=None, **options):
20+
meta = SQLAlchemyMutationOptions(cls)
21+
meta.model = model
22+
23+
model_inspect = sqlalchemyinspect(model)
24+
cls._model_inspect = model_inspect
25+
26+
if not isinstance(exclude_fields, list):
27+
if exclude_fields:
28+
exclude_fields = list(exclude_fields)
29+
else:
30+
exclude_fields = []
31+
32+
for primary_key_column in model_inspect.primary_key:
33+
if primary_key_column.autoincrement:
34+
exclude_fields.append(primary_key_column.name)
35+
36+
for relationship in model_inspect.relationships:
37+
exclude_fields.append(relationship.key)
38+
39+
if not registry:
40+
registry = get_global_registry()
41+
42+
arguments = yank_fields_from_attrs(
43+
construct_fields(model, registry, only_fields, exclude_fields),
44+
_as=Argument,
45+
)
46+
47+
super(SQLAlchemyCreate, cls).__init_subclass_with_meta__(_meta=meta, arguments=arguments, **options)
48+
49+
@classmethod
50+
def mutate(cls, self, info, **kwargs):
51+
session = get_session(info.context)
52+
53+
meta = cls._meta
54+
55+
model = meta.model()
56+
session.add(model)
57+
58+
for key, value in kwargs.items():
59+
setattr(model, key, value)
60+
61+
session.commit()
62+
63+
return model
64+
65+
@classmethod
66+
def Field(cls, *args, **kwargs):
67+
return Field(
68+
cls._meta.output, args=cls._meta.arguments, resolver=cls._meta.resolver
69+
)
70+
71+
72+
class SQLAlchemyUpdate(Mutation):
73+
@classmethod
74+
def __init_subclass_with_meta__(cls, model=None, registry=None, only_fields=(), exclude_fields=None, **options):
75+
meta = SQLAlchemyMutationOptions(cls)
76+
meta.model = model
77+
78+
model_inspect = sqlalchemyinspect(model)
79+
cls._model_inspect = model_inspect
80+
81+
if not isinstance(exclude_fields, list):
82+
if exclude_fields:
83+
exclude_fields = list(exclude_fields)
84+
else:
85+
exclude_fields = []
86+
87+
for relationship in model_inspect.relationships:
88+
exclude_fields.append(relationship.key)
89+
90+
if not registry:
91+
registry = get_global_registry()
92+
93+
arguments = yank_fields_from_attrs(
94+
construct_fields(model, registry, only_fields, exclude_fields),
95+
_as=Argument
96+
)
97+
98+
super(SQLAlchemyUpdate, cls).__init_subclass_with_meta__(_meta=meta, arguments=arguments, **options)
99+
100+
@classmethod
101+
def mutate(cls, self, info, **kwargs):
102+
session = get_session(info.context)
103+
104+
meta = cls._meta
105+
106+
query = session.query(meta.model)
107+
for primary_key_column in cls._model_inspect.primary_key:
108+
query = query.filter(getattr(meta.model, primary_key_column.key) == kwargs[primary_key_column.name])
109+
model = query.one()
110+
111+
for key, value in kwargs.items():
112+
setattr(model, key, value)
113+
114+
session.commit()
115+
116+
return model
117+
118+
@classmethod
119+
def Field(cls, *args, **kwargs):
120+
return Field(
121+
cls._meta.output, args=cls._meta.arguments, resolver=cls._meta.resolver
122+
)
123+
124+
125+
class SQLAlchemyDelete(Mutation):
126+
@classmethod
127+
def __init_subclass_with_meta__(cls, model=None, registry=None, only_fields=(),
128+
exclude_fields=None, **options):
129+
meta = SQLAlchemyMutationOptions(cls)
130+
meta.model = model
131+
132+
model_inspect = sqlalchemyinspect(model)
133+
cls._model_inspect = model_inspect
134+
135+
only_fields = []
136+
exclude_fields = ()
137+
for primary_key_column in model_inspect.primary_key:
138+
only_fields.append(primary_key_column.name)
139+
140+
if not registry:
141+
registry = get_global_registry()
142+
143+
arguments = yank_fields_from_attrs(
144+
construct_fields(model, registry, only_fields, exclude_fields),
145+
_as=Argument
146+
)
147+
148+
super(SQLAlchemyDelete, cls).__init_subclass_with_meta__(_meta=meta, arguments=arguments, **options)
149+
150+
@classmethod
151+
def mutate(cls, self, info, **kwargs):
152+
session = get_session(info.context)
153+
154+
meta = cls._meta
155+
156+
query = session.query(meta.model)
157+
158+
for primary_key_column in cls._model_inspect.primary_key:
159+
query = query.filter(getattr(meta.model, primary_key_column.key) == kwargs[primary_key_column.name])
160+
model = query.one()
161+
session.delete(model)
162+
163+
session.commit()
164+
165+
return model
166+
167+
@classmethod
168+
def Field(cls, *args, **kwargs):
169+
return Field(
170+
cls._meta.output, args=cls._meta.arguments, resolver=cls._meta.resolver
171+
)
172+
173+
174+
class SQLAlchemyListDelete(Mutation):
175+
@classmethod
176+
def __init_subclass_with_meta__(cls, model=None, registry=None, only_fields=(),
177+
exclude_fields=None, **options):
178+
meta = SQLAlchemyMutationOptions(cls)
179+
meta.model = model
180+
181+
model_inspect = sqlalchemyinspect(model)
182+
for column in model_inspect.columns:
183+
column.nullable = True
184+
185+
cls._model_inspect = model_inspect
186+
187+
if not isinstance(exclude_fields, list):
188+
if exclude_fields:
189+
exclude_fields = list(exclude_fields)
190+
else:
191+
exclude_fields = []
192+
193+
for relationship in model_inspect.relationships:
194+
exclude_fields.append(relationship.key)
195+
196+
if not registry:
197+
registry = get_global_registry()
198+
199+
arguments = yank_fields_from_attrs(
200+
construct_fields(model, registry, only_fields, exclude_fields),
201+
_as=Argument
202+
)
203+
204+
super(SQLAlchemyListDelete, cls).__init_subclass_with_meta__(_meta=meta, arguments=arguments, **options)
205+
206+
@classmethod
207+
def mutate(cls, self, info, **kwargs):
208+
session = get_session(info.context)
209+
210+
meta = cls._meta
211+
212+
query = session.query(meta.model)
213+
for key, value in kwargs.items():
214+
query = query.filter(get_snake_or_camel_attr(meta.model, key) == value)
215+
216+
models = query.all()
217+
for model in models:
218+
session.delete(model)
219+
220+
session.commit()
221+
222+
return models
223+
224+
@classmethod
225+
def Field(cls, *args, **kwargs):
226+
return Field(
227+
cls._meta.output, args=cls._meta.arguments, resolver=cls._meta.resolver
228+
)
229+
230+
231+
def create(of_type):
232+
class CreateModel(SQLAlchemyCreate):
233+
class Meta:
234+
model = of_type._meta.model
235+
236+
Output = of_type
237+
238+
return CreateModel.Field()
239+
240+
241+
def update(of_type):
242+
class UpdateModel(SQLAlchemyUpdate):
243+
class Meta:
244+
model = of_type._meta.model
245+
246+
Output = of_type
247+
248+
return UpdateModel.Field()
249+
250+
251+
def delete(of_type):
252+
class DeleteModel(SQLAlchemyDelete):
253+
class Meta:
254+
model = of_type._meta.model
255+
256+
Output = of_type
257+
258+
return DeleteModel.Field()
259+
260+
261+
def delete_all(of_type):
262+
class DeleteListModel(SQLAlchemyListDelete):
263+
class Meta:
264+
model = of_type._meta.model
265+
266+
Output = List(of_type)
267+
268+
return DeleteListModel.Field()

graphene_sqlalchemy/tests/conftest.py

+7
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
import pytest
2+
from sqlalchemy import create_engine
3+
4+
5+
@pytest.fixture(scope='session')
6+
def db():
7+
return create_engine('sqlite:///test_sqlalchemy.sqlite3')

0 commit comments

Comments
 (0)