1
+ import datetime
2
+ import typing
3
+ import warnings
4
+ from decimal import Decimal
1
5
from functools import singledispatch
6
+ from typing import Any
2
7
3
8
from sqlalchemy import types
4
9
from sqlalchemy .dialects import postgresql
5
10
from sqlalchemy .orm import interfaces , strategies
6
11
7
- from graphene import (ID , Boolean , Dynamic , Enum , Field , Float , Int , List ,
8
- String )
12
+ from graphene import (ID , Boolean , Date , DateTime , Dynamic , Enum , Field , Float ,
13
+ Int , List , String , Time )
9
14
from graphene .types .json import JSONString
10
15
11
16
from .batching import get_batch_resolver
14
19
default_connection_field_factory )
15
20
from .registry import get_global_registry
16
21
from .resolvers import get_attr_resolver , get_custom_resolver
22
+ from .utils import (registry_sqlalchemy_model_from_str , safe_isinstance ,
23
+ singledispatchbymatchfunction , value_equals )
24
+
25
+ try :
26
+ from typing import ForwardRef
27
+ except ImportError :
28
+ # python 3.6
29
+ from typing import _ForwardRef as ForwardRef
17
30
18
31
try :
19
32
from sqlalchemy_utils import ChoiceType , JSONType , ScalarListType , TSVectorType
25
38
except ImportError :
26
39
EnumTypeImpl = object
27
40
28
-
29
41
is_selectin_available = getattr (strategies , 'SelectInLoader' , None )
30
42
31
43
@@ -48,6 +60,7 @@ def convert_sqlalchemy_relationship(relationship_prop, obj_type, connection_fiel
48
60
:param dict field_kwargs:
49
61
:rtype: Dynamic
50
62
"""
63
+
51
64
def dynamic_type ():
52
65
""":rtype: Field|None"""
53
66
direction = relationship_prop .direction
@@ -115,8 +128,7 @@ def _convert_o2m_or_m2m_relationship(relationship_prop, obj_type, batching, conn
115
128
116
129
def convert_sqlalchemy_hybrid_method (hybrid_prop , resolver , ** field_kwargs ):
117
130
if 'type_' not in field_kwargs :
118
- # TODO The default type should be dependent on the type of the property propety.
119
- field_kwargs ['type_' ] = String
131
+ field_kwargs ['type_' ] = convert_hybrid_property_return_type (hybrid_prop )
120
132
121
133
return Field (
122
134
resolver = resolver ,
@@ -240,7 +252,7 @@ def convert_scalar_list_to_list(type, column, registry=None):
240
252
241
253
242
254
def init_array_list_recursive (inner_type , n ):
243
- return inner_type if n == 0 else List (init_array_list_recursive (inner_type , n - 1 ))
255
+ return inner_type if n == 0 else List (init_array_list_recursive (inner_type , n - 1 ))
244
256
245
257
246
258
@convert_sqlalchemy_type .register (types .ARRAY )
@@ -260,3 +272,103 @@ def convert_json_to_string(type, column, registry=None):
260
272
@convert_sqlalchemy_type .register (JSONType )
261
273
def convert_json_type_to_string (type , column , registry = None ):
262
274
return JSONString
275
+
276
+
277
+ @singledispatchbymatchfunction
278
+ def convert_sqlalchemy_hybrid_property_type (arg : Any ):
279
+ existing_graphql_type = get_global_registry ().get_type_for_model (arg )
280
+ if existing_graphql_type :
281
+ return existing_graphql_type
282
+
283
+ # No valid type found, warn and fall back to graphene.String
284
+ warnings .warn (
285
+ (f"I don't know how to generate a GraphQL type out of a \" { arg } \" type."
286
+ "Falling back to \" graphene.String\" " )
287
+ )
288
+ return String
289
+
290
+
291
+ @convert_sqlalchemy_hybrid_property_type .register (value_equals (str ))
292
+ def convert_sqlalchemy_hybrid_property_type_str (arg ):
293
+ return String
294
+
295
+
296
+ @convert_sqlalchemy_hybrid_property_type .register (value_equals (int ))
297
+ def convert_sqlalchemy_hybrid_property_type_int (arg ):
298
+ return Int
299
+
300
+
301
+ @convert_sqlalchemy_hybrid_property_type .register (value_equals (float ))
302
+ def convert_sqlalchemy_hybrid_property_type_float (arg ):
303
+ return Float
304
+
305
+
306
+ @convert_sqlalchemy_hybrid_property_type .register (value_equals (Decimal ))
307
+ def convert_sqlalchemy_hybrid_property_type_decimal (arg ):
308
+ # The reason Decimal should be serialized as a String is because this is a
309
+ # base10 type used in things like money, and string allows it to not
310
+ # lose precision (which would happen if we downcasted to a Float, for example)
311
+ return String
312
+
313
+
314
+ @convert_sqlalchemy_hybrid_property_type .register (value_equals (bool ))
315
+ def convert_sqlalchemy_hybrid_property_type_bool (arg ):
316
+ return Boolean
317
+
318
+
319
+ @convert_sqlalchemy_hybrid_property_type .register (value_equals (datetime .datetime ))
320
+ def convert_sqlalchemy_hybrid_property_type_datetime (arg ):
321
+ return DateTime
322
+
323
+
324
+ @convert_sqlalchemy_hybrid_property_type .register (value_equals (datetime .date ))
325
+ def convert_sqlalchemy_hybrid_property_type_date (arg ):
326
+ return Date
327
+
328
+
329
+ @convert_sqlalchemy_hybrid_property_type .register (value_equals (datetime .time ))
330
+ def convert_sqlalchemy_hybrid_property_type_time (arg ):
331
+ return Time
332
+
333
+
334
+ @convert_sqlalchemy_hybrid_property_type .register (lambda x : getattr (x , '__origin__' , None ) in [list , typing .List ])
335
+ def convert_sqlalchemy_hybrid_property_type_list_t (arg ):
336
+ # type is either list[T] or List[T], generic argument at __args__[0]
337
+ internal_type = arg .__args__ [0 ]
338
+
339
+ graphql_internal_type = convert_sqlalchemy_hybrid_property_type (internal_type )
340
+
341
+ return List (graphql_internal_type )
342
+
343
+
344
+ @convert_sqlalchemy_hybrid_property_type .register (safe_isinstance (ForwardRef ))
345
+ def convert_sqlalchemy_hybrid_property_forwardref (arg ):
346
+ """
347
+ Generate a lambda that will resolve the type at runtime
348
+ This takes care of self-references
349
+ """
350
+
351
+ def forward_reference_solver ():
352
+ model = registry_sqlalchemy_model_from_str (arg .__forward_arg__ )
353
+ if not model :
354
+ return String
355
+ # Always fall back to string if no ForwardRef type found.
356
+ return get_global_registry ().get_type_for_model (model )
357
+
358
+ return forward_reference_solver
359
+
360
+
361
+ @convert_sqlalchemy_hybrid_property_type .register (safe_isinstance (str ))
362
+ def convert_sqlalchemy_hybrid_property_bare_str (arg ):
363
+ """
364
+ Convert Bare String into a ForwardRef
365
+ """
366
+
367
+ return convert_sqlalchemy_hybrid_property_type (ForwardRef (arg ))
368
+
369
+
370
+ def convert_hybrid_property_return_type (hybrid_prop ):
371
+ # Grab the original method's return type annotations from inside the hybrid property
372
+ return_type_annotation = hybrid_prop .fget .__annotations__ .get ('return' , str )
373
+
374
+ return convert_sqlalchemy_hybrid_property_type (return_type_annotation )
0 commit comments