6
6
7
7
from sqlalchemy import ColumnElement , Select , and_ , asc , desc , func , or_ , select
8
8
from sqlalchemy .ext .asyncio import AsyncSession
9
+ from sqlalchemy .orm import InstrumentedAttribute
9
10
from sqlalchemy .orm .util import AliasedClass
10
11
11
12
from sqlalchemy_crud_plus .errors import ColumnSortError , ModelColumnError , SelectOperatorError
@@ -70,7 +71,7 @@ def get_sqlalchemy_filter(operator: str, value: Any, allow_arithmetic: bool = Tr
70
71
raise SelectOperatorError (f'Nested arithmetic operations are not allowed: { operator } ' )
71
72
72
73
sqlalchemy_filter = _SUPPORTED_FILTERS .get (operator )
73
- if sqlalchemy_filter is None :
74
+ if sqlalchemy_filter is None and operator not in [ 'or' , 'mor' , '__gor' ] :
74
75
warnings .warn (
75
76
f'The operator <{ operator } > is not yet supported, only { ", " .join (_SUPPORTED_FILTERS .keys ())} .' ,
76
77
SyntaxWarning ,
@@ -80,48 +81,92 @@ def get_sqlalchemy_filter(operator: str, value: Any, allow_arithmetic: bool = Tr
80
81
return sqlalchemy_filter
81
82
82
83
83
- def get_column (model : Type [Model ] | AliasedClass , field_name : str ):
84
+ def get_column (model : Type [Model ] | AliasedClass , field_name : str ) -> InstrumentedAttribute | None :
84
85
column = getattr (model , field_name , None )
85
86
if column is None :
86
87
raise ModelColumnError (f'Column { field_name } is not found in { model } ' )
87
88
return column
88
89
89
90
91
+ def _create_or_filters (column : str , op : str , value : Any ) -> list [ColumnElement | None ]:
92
+ or_filters = []
93
+ if op == 'or' :
94
+ for or_op , or_value in value .items ():
95
+ sqlalchemy_filter = get_sqlalchemy_filter (or_op , or_value )
96
+ if sqlalchemy_filter is not None :
97
+ or_filters .append (sqlalchemy_filter (column )(or_value ))
98
+ elif op == 'mor' :
99
+ for or_op , or_values in value .items ():
100
+ for or_value in or_values :
101
+ sqlalchemy_filter = get_sqlalchemy_filter (or_op , or_value )
102
+ if sqlalchemy_filter is not None :
103
+ or_filters .append (sqlalchemy_filter (column )(or_value ))
104
+ return or_filters
105
+
106
+
107
+ def _create_arithmetic_filters (column : str , op : str , value : Any ) -> list [ColumnElement | None ]:
108
+ arithmetic_filters = []
109
+ if isinstance (value , dict ) and {'value' , 'condition' }.issubset (value ):
110
+ arithmetic_value = value ['value' ]
111
+ condition = value ['condition' ]
112
+ sqlalchemy_filter = get_sqlalchemy_filter (op , arithmetic_value )
113
+ if sqlalchemy_filter is not None :
114
+ for cond_op , cond_value in condition .items ():
115
+ arithmetic_filter = get_sqlalchemy_filter (cond_op , cond_value , allow_arithmetic = False )
116
+ arithmetic_filters .append (
117
+ arithmetic_filter (sqlalchemy_filter (column )(arithmetic_value ))(cond_value )
118
+ if cond_op != 'between'
119
+ else arithmetic_filter (sqlalchemy_filter (column )(arithmetic_value ))(* cond_value )
120
+ )
121
+ return arithmetic_filters
122
+
123
+
124
+ def _create_and_filters (column : str , op : str , value : Any ) -> list [ColumnElement | None ]:
125
+ and_filters = []
126
+ sqlalchemy_filter = get_sqlalchemy_filter (op , value )
127
+ if sqlalchemy_filter is not None :
128
+ and_filters .append (sqlalchemy_filter (column )(value ) if op != 'between' else sqlalchemy_filter (column )(* value ))
129
+ return and_filters
130
+
131
+
90
132
def parse_filters (model : Type [Model ] | AliasedClass , ** kwargs ) -> list [ColumnElement ]:
91
133
filters = []
92
134
135
+ def process_filters (target_column : str , target_op : str , target_value : Any ):
136
+ # OR / MOR
137
+ or_filters = _create_or_filters (target_column , target_op , target_value )
138
+ if or_filters :
139
+ filters .append (or_ (* or_filters ))
140
+
141
+ # ARITHMETIC
142
+ arithmetic_filters = _create_arithmetic_filters (target_column , target_op , target_value )
143
+ if arithmetic_filters :
144
+ filters .append (and_ (* arithmetic_filters ))
145
+ else :
146
+ # AND
147
+ and_filters = _create_and_filters (target_column , target_op , target_value )
148
+ if and_filters :
149
+ filters .append (* and_filters )
150
+
93
151
for key , value in kwargs .items ():
94
152
if '__' in key :
95
153
field_name , op = key .rsplit ('__' , 1 )
96
- column = get_column (model , field_name )
97
- if op == 'or' :
98
- or_filters = [
99
- sqlalchemy_filter (column )(or_value )
100
- for or_op , or_value in value .items ()
101
- if (sqlalchemy_filter := get_sqlalchemy_filter (or_op , or_value )) is not None
102
- ]
103
- filters .append (or_ (* or_filters ))
104
- elif isinstance (value , dict ) and {'value' , 'condition' }.issubset (value ):
105
- advanced_value = value ['value' ]
106
- condition = value ['condition' ]
107
- sqlalchemy_filter = get_sqlalchemy_filter (op , advanced_value )
108
- if sqlalchemy_filter is not None :
109
- condition_filters = []
110
- for cond_op , cond_value in condition .items ():
111
- condition_filter = get_sqlalchemy_filter (cond_op , cond_value , allow_arithmetic = False )
112
- condition_filters .append (
113
- condition_filter (sqlalchemy_filter (column )(advanced_value ))(cond_value )
114
- if cond_op != 'between'
115
- else condition_filter (sqlalchemy_filter (column )(advanced_value ))(* cond_value )
116
- )
117
- filters .append (and_ (* condition_filters ))
154
+
155
+ # OR GROUP
156
+ if field_name == '__gor' and op == '' :
157
+ _or_filters = []
158
+ for field_or in value :
159
+ for _key , _value in field_or .items ():
160
+ _field_name , _op = _key .rsplit ('__' , 1 )
161
+ _column = get_column (model , _field_name )
162
+ process_filters (_column , _op , _value )
163
+ if _or_filters :
164
+ filters .append (or_ (* _or_filters ))
118
165
else :
119
- sqlalchemy_filter = get_sqlalchemy_filter (op , value )
120
- if sqlalchemy_filter is not None :
121
- filters .append (
122
- sqlalchemy_filter (column )(value ) if op != 'between' else sqlalchemy_filter (column )(* value )
123
- )
166
+ column = get_column (model , field_name )
167
+ process_filters (column , op , value )
124
168
else :
169
+ # NON FILTER
125
170
column = get_column (model , key )
126
171
filters .append (column == value )
127
172
0 commit comments