-
Notifications
You must be signed in to change notification settings - Fork 227
/
Copy pathtypes.py
278 lines (223 loc) · 10.4 KB
/
types.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
from collections import OrderedDict
import sqlalchemy
from sqlalchemy.ext.hybrid import hybrid_property
from sqlalchemy.inspection import inspect as sqlalchemyinspect
from sqlalchemy.orm.exc import NoResultFound
from graphene import Field
from graphene.relay import Connection, Node
from graphene.types.objecttype import ObjectType, ObjectTypeOptions
from graphene.types.utils import yank_fields_from_attrs
from .converter import (convert_sqlalchemy_column,
convert_sqlalchemy_composite,
convert_sqlalchemy_hybrid_method,
convert_sqlalchemy_relationship)
from .enums import (enum_for_field, sort_argument_for_object_type,
sort_enum_for_object_type)
from .fields import default_connection_field_factory
from .registry import Registry, get_global_registry
from .utils import get_query, is_mapped_class, is_mapped_instance
def construct_fields(
obj_type, model, registry, only_fields, exclude_fields, connection_field_factory
):
inspected_model = sqlalchemyinspect(model)
fields = OrderedDict()
for name, column in inspected_model.columns.items():
is_not_in_only = only_fields and name not in only_fields
# is_already_created = name in options.fields
is_excluded = name in exclude_fields # or is_already_created
if is_not_in_only or is_excluded:
# We skip this field if we specify only_fields and is not
# in there. Or when we exclude this field in exclude_fields
continue
converted_column = convert_sqlalchemy_column(column, registry)
registry.register_orm_field(obj_type, name, column)
fields[name] = converted_column
for name, composite in inspected_model.composites.items():
is_not_in_only = only_fields and name not in only_fields
# is_already_created = name in options.fields
is_excluded = name in exclude_fields # or is_already_created
if is_not_in_only or is_excluded:
# We skip this field if we specify only_fields and is not
# in there. Or when we exclude this field in exclude_fields
continue
converted_composite = convert_sqlalchemy_composite(composite, registry)
registry.register_orm_field(obj_type, name, composite)
fields[name] = converted_composite
for hybrid_item in inspected_model.all_orm_descriptors:
if type(hybrid_item) == hybrid_property:
name = hybrid_item.__name__
is_not_in_only = only_fields and name not in only_fields
# is_already_created = name in options.fields
is_excluded = name in exclude_fields # or is_already_created
if is_not_in_only or is_excluded:
# We skip this field if we specify only_fields and is not
# in there. Or when we exclude this field in exclude_fields
continue
converted_hybrid_property = convert_sqlalchemy_hybrid_method(hybrid_item)
registry.register_orm_field(obj_type, name, hybrid_item)
fields[name] = converted_hybrid_property
# Get all the columns for the relationships on the model
for relationship in inspected_model.relationships:
is_not_in_only = only_fields and relationship.key not in only_fields
# is_already_created = relationship.key in options.fields
is_excluded = relationship.key in exclude_fields # or is_already_created
if is_not_in_only or is_excluded:
# We skip this field if we specify only_fields and is not
# in there. Or when we exclude this field in exclude_fields
continue
converted_relationship = convert_sqlalchemy_relationship(
relationship, registry, connection_field_factory
)
name = relationship.key
registry.register_orm_field(obj_type, name, relationship)
fields[name] = converted_relationship
return fields
class SQLAlchemyObjectTypeOptions(ObjectTypeOptions):
model = None # type: sqlalchemy.Model
registry = None # type: sqlalchemy.Registry
connection = None # type: sqlalchemy.Type[sqlalchemy.Connection]
id = None # type: str
class SQLAlchemyObjectType(ObjectType):
@classmethod
def __init_subclass_with_meta__(
cls,
model=None,
registry=None,
skip_registry=False,
only_fields=(),
exclude_fields=(),
connection=None,
connection_class=None,
use_connection=None,
interfaces=(),
id=None,
connection_field_factory=default_connection_field_factory,
_meta=None,
**options
):
assert is_mapped_class(model), (
"You need to pass a valid SQLAlchemy Model in " '{}.Meta, received "{}".'
).format(cls.__name__, model)
if not registry:
registry = get_global_registry()
assert isinstance(registry, Registry), (
"The attribute registry in {} needs to be an instance of "
'Registry, received "{}".'
).format(cls.__name__, registry)
sqla_fields = yank_fields_from_attrs(
construct_fields(
obj_type=cls,
model=model,
registry=registry,
only_fields=only_fields,
exclude_fields=exclude_fields,
connection_field_factory=connection_field_factory,
),
_as=Field,
)
if use_connection is None and interfaces:
use_connection = any(
(issubclass(interface, Node) for interface in interfaces)
)
if use_connection and not connection:
# We create the connection automatically
if not connection_class:
connection_class = Connection
connection = connection_class.create_type(
"{}Connection".format(cls.__name__), node=cls
)
if connection is not None:
assert issubclass(connection, Connection), (
"The connection must be a Connection. Received {}"
).format(connection.__name__)
if not _meta:
_meta = SQLAlchemyObjectTypeOptions(cls)
_meta.model = model
_meta.registry = registry
if _meta.fields:
_meta.fields.update(sqla_fields)
else:
_meta.fields = sqla_fields
_meta.connection = connection
_meta.id = id or "id"
super(SQLAlchemyObjectType, cls).__init_subclass_with_meta__(
_meta=_meta, interfaces=interfaces, **options
)
if not skip_registry:
registry.register(cls)
@classmethod
def is_type_of(cls, root, info):
if isinstance(root, cls):
return True
if not is_mapped_instance(root):
raise Exception(('Received incompatible instance "{}".').format(root))
return isinstance(root, cls._meta.model)
@classmethod
def get_query(cls, info):
model = cls._meta.model
return get_query(model, info.context)
@classmethod
def get_node(cls, info, id):
try:
return cls.get_query(info).get(id)
except NoResultFound:
return None
def resolve_id(self, info):
# graphene_type = info.parent_type.graphene_type
keys = self.__mapper__.primary_key_from_instance(self)
return tuple(keys) if len(keys) > 1 else keys[0]
@classmethod
def enum_for_field(cls, field_name):
return enum_for_field(cls, field_name)
sort_enum = classmethod(sort_enum_for_object_type)
sort_argument = classmethod(sort_argument_for_object_type)
def construct_fields_for_input(
obj_type, model, registry, connection_field_factory,only_fields, exclude_fields
):
inspected_model = sqlalchemyinspect(model)
fields = OrderedDict()
for name, column in inspected_model.columns.items():
is_not_in_only = only_fields and name not in only_fields
# is_already_created = name in options.fields
is_excluded = name in exclude_fields # or is_already_created
if is_not_in_only or is_excluded:
# We skip this field if we specify only_fields and is not
# in there. Or when we exclude this field in exclude_fields
continue
converted_column = convert_sqlalchemy_column(column, registry)
fields[name] = converted_column
for name, composite in inspected_model.composites.items():
is_not_in_only = only_fields and name not in only_fields
# is_already_created = name in options.fields
is_excluded = name in exclude_fields # or is_already_created
if is_not_in_only or is_excluded:
# We skip this field if we specify only_fields and is not
# in there. Or when we exclude this field in exclude_fields
continue
converted_composite = convert_sqlalchemy_composite(composite, registry)
fields[name] = converted_composite
for hybrid_item in inspected_model.all_orm_descriptors:
if type(hybrid_item) == hybrid_property:
name = hybrid_item.__name__
is_not_in_only = only_fields and name not in only_fields
# is_already_created = name in options.fields
is_excluded = name in exclude_fields # or is_already_created
if is_not_in_only or is_excluded:
# We skip this field if we specify only_fields and is not
# in there. Or when we exclude this field in exclude_fields
continue
converted_hybrid_property = convert_sqlalchemy_hybrid_method(hybrid_item)
fields[name] = converted_hybrid_property
return fields
class SQLAlchemyInputObjectType(graphene.InputObjectType):
@classmethod
def __init_subclass_with_meta__(cls, model=None, registry=None, skip_registry=False,only_fields=(), exclude_fields=(),
connection=None,_meta=None,connection_field_factory=default_connection_field_factory,
use_connection=None, interfaces=(), id=None, **options):
if not registry:
registry = get_global_registry()
sqla_fields = yank_fields_from_attrs(
construct_fields_for_input(model=model, registry=registry,only_fields=only_fields, exclude_fields=exclude_fields,
connection_field_factory=connection_field_factory,obj_type=cls),
_as=graphene.Field,
)