Skip to content

Commit 9936191

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 9936191

File tree

7 files changed

+847
-27
lines changed

7 files changed

+847
-27
lines changed

graphene_sqlalchemy/mutations.py

+208
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,208 @@
1+
from graphene import Mutation, Argument, Field
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
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 registry:
82+
registry = get_global_registry()
83+
84+
if not isinstance(exclude_fields, list):
85+
if exclude_fields:
86+
exclude_fields = list(exclude_fields)
87+
else:
88+
exclude_fields = []
89+
90+
for relationship in model_inspect.relationships:
91+
exclude_fields.append(relationship.key)
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 SQLAlchemyDeleteOptions(SQLAlchemyMutationOptions):
126+
from_primary_key = True
127+
128+
129+
class SQLAlchemyDelete(Mutation):
130+
@classmethod
131+
def __init_subclass_with_meta__(cls, model=None, from_primary_key=True, registry=None, only_fields=(),
132+
exclude_fields=None, **options):
133+
meta = SQLAlchemyMutationOptions(cls)
134+
meta.model = model
135+
meta.from_primary_key = from_primary_key
136+
137+
model_inspect = sqlalchemyinspect(model)
138+
cls._model_inspect = model_inspect
139+
140+
only_fields = []
141+
exclude_fields = ()
142+
if from_primary_key:
143+
for primary_key_column in model_inspect.primary_key:
144+
only_fields.append(primary_key_column.name)
145+
146+
if not registry:
147+
registry = get_global_registry()
148+
149+
arguments = yank_fields_from_attrs(
150+
construct_fields(model, registry, only_fields, exclude_fields),
151+
_as=Argument
152+
)
153+
154+
super(SQLAlchemyDelete, cls).__init_subclass_with_meta__(_meta=meta, arguments=arguments, **options)
155+
156+
@classmethod
157+
def mutate(cls, self, info, **kwargs):
158+
session = get_session(info.context)
159+
160+
meta = cls._meta
161+
162+
query = session.query(meta.model)
163+
for primary_key_column in cls._model_inspect.primary_key:
164+
query = query.filter(getattr(meta.model, primary_key_column.key) == kwargs[primary_key_column.name])
165+
model = query.one()
166+
session.delete(model)
167+
168+
session.commit()
169+
170+
return model
171+
172+
@classmethod
173+
def Field(cls, *args, **kwargs):
174+
return Field(
175+
cls._meta.output, args=cls._meta.arguments, resolver=cls._meta.resolver
176+
)
177+
178+
179+
def create(of_type):
180+
class CreateModel(SQLAlchemyCreate):
181+
class Meta:
182+
model = of_type._meta.model
183+
184+
Output = of_type
185+
186+
return CreateModel.Field()
187+
188+
189+
def update(of_type):
190+
class UpdateModel(SQLAlchemyUpdate):
191+
class Meta:
192+
model = of_type._meta.model
193+
194+
Output = of_type
195+
196+
return UpdateModel.Field()
197+
198+
199+
def delete(of_type, from_primary_key=True):
200+
class DeleteModel(SQLAlchemyDelete):
201+
class Meta:
202+
nonlocal from_primary_key
203+
model = of_type._meta.model
204+
from_primary_key = from_primary_key
205+
206+
Output = of_type
207+
208+
return DeleteModel.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)