1
1
import datetime
2
2
from decimal import Decimal
3
- from typing import Any
3
+ from typing import Any , Optional , Union , Dict , Type
4
4
5
5
import sqlalchemy
6
6
from sqlalchemy import cast , String , Integer , Boolean , DECIMAL , not_
18
18
from combojsonapi .postgresql_jsonb .schema import SchemaJSONB
19
19
20
20
21
+ TYPE_MARSHMALLOW_FIELDS = Type [Union [
22
+ ma_fields .Email , ma_fields .Dict , ma_fields .List ,
23
+ ma_fields .Decimal , ma_fields .Url , ma_fields .DateTime , Any
24
+ ]]
25
+ TYPE_PYTHON = Type [Union [str , dict , list , Decimal , datetime .datetime ]]
26
+
27
+
21
28
def is_seq_collection (obj ):
22
29
"""
23
30
является ли переданный объект set, list, tuple
@@ -28,6 +35,27 @@ def is_seq_collection(obj):
28
35
29
36
30
37
class PostgreSqlJSONB (BasePlugin ):
38
+ mapping_ma_field_to_type : Dict [TYPE_MARSHMALLOW_FIELDS , TYPE_PYTHON ] = {
39
+ ma_fields .Email : str ,
40
+ ma_fields .Dict : dict ,
41
+ ma_fields .List : list ,
42
+ ma_fields .Decimal : Decimal ,
43
+ ma_fields .Url : str ,
44
+ ma_fields .DateTime : datetime .datetime ,
45
+ }
46
+
47
+ def get_property_type (
48
+ self , marshmallow_field : TYPE_MARSHMALLOW_FIELDS , schema : Optional [Schema ] = None
49
+ ) -> TYPE_PYTHON :
50
+ if schema is not None :
51
+ self .mapping_ma_field_to_type .update ({
52
+ v : k for k , v in schema .TYPE_MAPPING .items ()
53
+ })
54
+ return self .mapping_ma_field_to_type [type (marshmallow_field )]
55
+
56
+ def add_mapping_field_to_python_type (self , marshmallow_field : Any , type_python : TYPE_PYTHON ) -> None :
57
+ self .mapping_ma_field_to_type [marshmallow_field ] = type_python
58
+
31
59
def before_data_layers_sorting_alchemy_nested_resolve (self , self_nested : Any ) -> Any :
32
60
"""
33
61
Вызывается до создания сортировки в функции Nested.resolve, если после выполнения вернёт None, то
@@ -77,7 +105,7 @@ def _isinstance_jsonb(cls, schema: Schema, filter_name):
77
105
fields = filter_name .split (SPLIT_REL )
78
106
for i , i_field in enumerate (fields ):
79
107
if isinstance (getattr (schema ._declared_fields [i_field ], "schema" , None ), SchemaJSONB ):
80
- if i != (len (fields ) - 2 ):
108
+ if i == (len (fields ) - 1 ):
81
109
raise InvalidFilters (f"Invalid JSONB filter: { filter_name } " )
82
110
return True
83
111
elif isinstance (schema ._declared_fields [i_field ], Relationship ):
@@ -86,8 +114,7 @@ def _isinstance_jsonb(cls, schema: Schema, filter_name):
86
114
return False
87
115
return False
88
116
89
- @classmethod
90
- def _create_sort (cls , self_nested : Any , marshmallow_field , model_column , order ):
117
+ def _create_sort (self , self_nested : Any , marshmallow_field , model_column , order ):
91
118
"""
92
119
Create sqlalchemy sort
93
120
:param Nested self_nested:
@@ -106,14 +133,15 @@ def _create_sort(cls, self_nested: Any, marshmallow_field, model_column, order):
106
133
self_nested .sort_ ["field" ] = SPLIT_REL .join (fields [1 :])
107
134
marshmallow_field = marshmallow_field .schema ._declared_fields [fields [1 ]]
108
135
model_column = getattr (mapper , sqlalchemy_relationship_name )
109
- return cls ._create_sort (self_nested , marshmallow_field , model_column , order )
136
+ return self ._create_sort (self_nested , marshmallow_field , model_column , order )
110
137
elif not isinstance (getattr (marshmallow_field , "schema" , None ), SchemaJSONB ):
111
138
raise InvalidFilters (f"Invalid JSONB sort: { SPLIT_REL .join (self_nested .fields )} " )
112
139
fields = self_nested .sort_ ["field" ].split (SPLIT_REL )
113
140
self_nested .sort_ ["field" ] = SPLIT_REL .join (fields [:- 1 ])
114
141
field_in_jsonb = fields [- 1 ]
115
142
116
- marshmallow_field = marshmallow_field .schema ._declared_fields [field_in_jsonb ]
143
+ for field in fields [1 :]:
144
+ marshmallow_field = marshmallow_field .schema ._declared_fields [field ]
117
145
if hasattr (marshmallow_field , f"_{ order } _sql_filter_" ):
118
146
"""
119
147
У marshmallow field может быть реализована своя логика создания сортировки для sqlalchemy
@@ -123,19 +151,20 @@ def _create_sort(cls, self_nested: Any, marshmallow_field, model_column, order):
123
151
* marshmallow_field - объект класса поля marshmallow
124
152
* model_column - объект класса поля sqlalchemy
125
153
"""
154
+ # All values between the first and last field will be the path to the desired value by which to sort,
155
+ # so we write the path through "->"
156
+ for field in fields [1 :- 1 ]:
157
+ model_column = model_column .op ("->" )(field )
158
+ model_column = model_column .op ("->>" )(field_in_jsonb )
126
159
return getattr (marshmallow_field , f"_{ order } _sql_filter_" )(
127
160
marshmallow_field = marshmallow_field , model_column = model_column
128
161
)
129
- mapping_ma_field_to_type = {v : k for k , v in self_nested .schema .TYPE_MAPPING .items ()}
130
- mapping_ma_field_to_type [ma_fields .Email ] = str
131
- mapping_ma_field_to_type [ma_fields .Dict ] = dict
132
- mapping_ma_field_to_type [ma_fields .List ] = list
133
- mapping_ma_field_to_type [ma_fields .Decimal ] = Decimal
134
- mapping_ma_field_to_type [ma_fields .Url ] = str
135
- mapping_ma_field_to_type [ma_fields .DateTime ] = datetime .datetime
162
+
163
+ property_type = self .get_property_type (marshmallow_field = marshmallow_field , schema = self_nested .schema )
136
164
mapping_type_to_sql_type = {str : String , bytes : String , Decimal : DECIMAL , int : Integer , bool : Boolean }
137
165
138
- property_type = mapping_ma_field_to_type [type (marshmallow_field )]
166
+ for field in fields [1 :- 1 ]:
167
+ model_column = model_column .op ("->" )(field )
139
168
extra_field = model_column .op ("->>" )(field_in_jsonb )
140
169
sort = ""
141
170
order_op = desc_op if order == "desc" else asc_op
@@ -146,8 +175,7 @@ def _create_sort(cls, self_nested: Any, marshmallow_field, model_column, order):
146
175
sort = order_op (extra_field .cast (mapping_type_to_sql_type [property_type ]))
147
176
return sort
148
177
149
- @classmethod
150
- def _create_filter (cls , self_nested : Any , marshmallow_field , model_column , operator , value ):
178
+ def _create_filter (self , self_nested : Any , marshmallow_field , model_column , operator , value ):
151
179
"""
152
180
Create sqlalchemy filter
153
181
:param Nested self_nested:
@@ -168,14 +196,15 @@ def _create_filter(cls, self_nested: Any, marshmallow_field, model_column, opera
168
196
marshmallow_field = marshmallow_field .schema ._declared_fields [fields [1 ]]
169
197
join_list = [[model_column ]]
170
198
model_column = getattr (mapper , sqlalchemy_relationship_name )
171
- filter , joins = cls ._create_filter (self_nested , marshmallow_field , model_column , operator , value )
199
+ filter , joins = self ._create_filter (self_nested , marshmallow_field , model_column , operator , value )
172
200
join_list += joins
173
201
return filter , join_list
174
202
elif not isinstance (getattr (marshmallow_field , "schema" , None ), SchemaJSONB ):
175
203
raise InvalidFilters (f"Invalid JSONB filter: { SPLIT_REL .join (field_in_jsonb )} " )
176
204
self_nested .filter_ ["name" ] = SPLIT_REL .join (fields [:- 1 ])
177
205
try :
178
- marshmallow_field = marshmallow_field .schema ._declared_fields [field_in_jsonb ]
206
+ for field in fields [1 :]:
207
+ marshmallow_field = marshmallow_field .schema ._declared_fields [field ]
179
208
except KeyError :
180
209
raise InvalidFilters (f'There is no "{ field_in_jsonb } " attribute in the "{ fields [- 2 ]} " field.' )
181
210
if hasattr (marshmallow_field , f"_{ operator } _sql_filter_" ):
@@ -189,49 +218,47 @@ def _create_filter(cls, self_nested: Any, marshmallow_field, model_column, opera
189
218
* value - значения для фильтра
190
219
* operator - сам оператор, например: "eq", "in"...
191
220
"""
221
+ for field in fields [1 :- 1 ]:
222
+ model_column = model_column .op ("->" )(field )
223
+ model_column = model_column .op ("->>" )(field_in_jsonb )
192
224
return (
193
225
getattr (marshmallow_field , f"_{ operator } _sql_filter_" )(
194
226
marshmallow_field = marshmallow_field ,
195
- model_column = model_column . op ( "->>" )( field_in_jsonb ) ,
227
+ model_column = model_column ,
196
228
value = value ,
197
229
operator = self_nested .operator ,
198
230
),
199
231
[],
200
232
)
201
- mapping = {v : k for k , v in self_nested .schema .TYPE_MAPPING .items ()}
202
- mapping [ma_fields .Email ] = str
203
- mapping [ma_fields .Dict ] = dict
204
- mapping [ma_fields .List ] = list
205
- mapping [ma_fields .Decimal ] = Decimal
206
- mapping [ma_fields .Url ] = str
207
- mapping [ma_fields .DateTime ] = datetime .datetime
208
233
209
234
# Нужно проводить валидацию и делать десериализацию значение указанных в фильтре, так как поля Enum
210
235
# например выгружаются как 'name_value(str)', а в БД хранится как просто число
211
236
value = deserialize_field (marshmallow_field , value )
212
237
213
- property_type = mapping [type (marshmallow_field )]
238
+ property_type = self .get_property_type (marshmallow_field = marshmallow_field , schema = self_nested .schema )
239
+ for field in fields [1 :- 1 ]:
240
+ model_column = model_column .op ("->" )(field )
214
241
extra_field = model_column .op ("->>" )(field_in_jsonb )
215
- filter = ""
242
+ filter_ = ""
216
243
if property_type == Decimal :
217
- filter = getattr (cast (extra_field , DECIMAL ), self_nested .operator )(value )
244
+ filter_ = getattr (cast (extra_field , DECIMAL ), self_nested .operator )(value )
218
245
219
246
if property_type in {str , bytes }:
220
- filter = getattr (cast (extra_field , String ), self_nested .operator )(value )
247
+ filter_ = getattr (cast (extra_field , String ), self_nested .operator )(value )
221
248
222
249
if property_type == int :
223
250
field = cast (extra_field , Integer )
224
251
if value :
225
- filter = getattr (field , self_nested .operator )(value )
252
+ filter_ = getattr (field , self_nested .operator )(value )
226
253
else :
227
- filter = or_ (getattr (field , self_nested .operator )(value ), field .is_ (None ))
254
+ filter_ = or_ (getattr (field , self_nested .operator )(value ), field .is_ (None ))
228
255
229
256
if property_type == bool :
230
- filter = cast (extra_field , Boolean ) == value
257
+ filter_ = cast (extra_field , Boolean ) == value
231
258
232
259
if property_type == list :
233
- filter = model_column .op ("->" )(field_in_jsonb ).op ("?" )(value [0 ] if is_seq_collection (value ) else value )
260
+ filter_ = model_column .op ("->" )(field_in_jsonb ).op ("?" )(value [0 ] if is_seq_collection (value ) else value )
234
261
if operator in ["notin" , "notin_" ]:
235
- filter = not_ (filter )
262
+ filter_ = not_ (filter_ )
236
263
237
- return filter , []
264
+ return filter_ , []
0 commit comments