66
77from sqlalchemy import ColumnElement , Select , and_ , asc , desc , func , or_ , select
88from sqlalchemy .ext .asyncio import AsyncSession
9+ from sqlalchemy .orm import InstrumentedAttribute
910from sqlalchemy .orm .util import AliasedClass
1011
1112from sqlalchemy_crud_plus .errors import ColumnSortError , ModelColumnError , SelectOperatorError
@@ -70,7 +71,7 @@ def get_sqlalchemy_filter(operator: str, value: Any, allow_arithmetic: bool = Tr
7071 raise SelectOperatorError (f'Nested arithmetic operations are not allowed: { operator } ' )
7172
7273 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' ] :
7475 warnings .warn (
7576 f'The operator <{ operator } > is not yet supported, only { ", " .join (_SUPPORTED_FILTERS .keys ())} .' ,
7677 SyntaxWarning ,
@@ -80,48 +81,92 @@ def get_sqlalchemy_filter(operator: str, value: Any, allow_arithmetic: bool = Tr
8081 return sqlalchemy_filter
8182
8283
83- def get_column (model : Type [Model ] | AliasedClass , field_name : str ):
84+ def get_column (model : Type [Model ] | AliasedClass , field_name : str ) -> InstrumentedAttribute | None :
8485 column = getattr (model , field_name , None )
8586 if column is None :
8687 raise ModelColumnError (f'Column { field_name } is not found in { model } ' )
8788 return column
8889
8990
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+
90132def parse_filters (model : Type [Model ] | AliasedClass , ** kwargs ) -> list [ColumnElement ]:
91133 filters = []
92134
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+
93151 for key , value in kwargs .items ():
94152 if '__' in key :
95153 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 ))
118165 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 )
124168 else :
169+ # NON FILTER
125170 column = get_column (model , key )
126171 filters .append (column == value )
127172
0 commit comments