@@ -314,8 +314,9 @@ def execute(self):
314314 if body :
315315 self ._logger .debug (' body: %s' , body )
316316
317+ params = "&" .join ("%s=%s" % (k , v ) for k , v in self .get_query_params ().items ())
317318 response = self ._connection .request (
318- self .get_method (), url , headers = headers , params = self . get_query_params () , data = body )
319+ self .get_method (), url , headers = headers , params = params , data = body )
319320
320321 self ._logger .debug ('Received response' )
321322 self ._logger .debug (' url: %s' , response .url )
@@ -623,7 +624,7 @@ def expand(self, expand):
623624 def filter (self , filter_val ):
624625 """Sets the filter expression."""
625626 # returns QueryRequest
626- self ._filter = quote ( filter_val )
627+ self ._filter = filter_val
627628 return self
628629
629630 # def nav(self, key_value, nav_property):
@@ -993,6 +994,212 @@ def __gt__(self, value):
993994 return GetEntitySetFilter .format_filter (self ._proprty , 'gt' , value )
994995
995996
997+ class FilterExpression :
998+ """A class representing named expression of OData $filter"""
999+
1000+ def __init__ (self , ** kwargs ):
1001+ self ._expressions = kwargs
1002+ self ._other = None
1003+ self ._operator = None
1004+
1005+ @property
1006+ def expressions (self ):
1007+ """Get expressions where key is property name with the operator suffix
1008+ and value is the left hand side operand.
1009+ """
1010+
1011+ return self ._expressions .items ()
1012+
1013+ @property
1014+ def other (self ):
1015+ """Get an instance of the other operand"""
1016+
1017+ return self ._other
1018+
1019+ @property
1020+ def operator (self ):
1021+ """The other operand"""
1022+
1023+ return self ._operator
1024+
1025+ def __or__ (self , other ):
1026+ if self ._other is not None :
1027+ raise RuntimeError ('The FilterExpression already initialized' )
1028+
1029+ self ._other = other
1030+ self ._operator = "or"
1031+ return self
1032+
1033+ def __and__ (self , other ):
1034+ if self ._other is not None :
1035+ raise RuntimeError ('The FilterExpression already initialized' )
1036+
1037+ self ._other = other
1038+ self ._operator = "and"
1039+ return self
1040+
1041+
1042+ class GetEntitySetFilterChainable :
1043+ """
1044+ Example expressions
1045+ FirstName='Tim'
1046+ FirstName__contains='Tim'
1047+ Age__gt=56
1048+ Age__gte=6
1049+ Age__lt=78
1050+ Age__lte=90
1051+ Age__range=(5,9)
1052+ FirstName__in=['Tim', 'Bob', 'Sam']
1053+ FirstName__startswith='Tim'
1054+ FirstName__endswith='mothy'
1055+ Addresses__Suburb='Chatswood'
1056+ Addresses__Suburb__contains='wood'
1057+ """
1058+
1059+ OPERATORS = [
1060+ 'startswith' ,
1061+ 'endswith' ,
1062+ 'lt' ,
1063+ 'lte' ,
1064+ 'gt' ,
1065+ 'gte' ,
1066+ 'contains' ,
1067+ 'range' ,
1068+ 'in' ,
1069+ 'length' ,
1070+ 'eq'
1071+ ]
1072+
1073+ def __init__ (self , entity_type , filter_expressions , exprs ):
1074+ self ._entity_type = entity_type
1075+ self ._filter_expressions = filter_expressions
1076+ self ._expressions = exprs
1077+
1078+ @property
1079+ def expressions (self ):
1080+ """Get expressions as a list of tuples where the first item
1081+ is a property name with the operator suffix and the second item
1082+ is a left hand side value.
1083+ """
1084+
1085+ return self ._expressions .items ()
1086+
1087+ def proprty_obj (self , name ):
1088+ """Returns a model property for a particular property"""
1089+
1090+ return self ._entity_type .proprty (name )
1091+
1092+ def _decode_and_combine_filter_expression (self , filter_expression ):
1093+ filter_expressions = [self ._decode_expression (expr , val ) for expr , val in filter_expression .expressions ]
1094+ return self ._combine_expressions (filter_expressions )
1095+
1096+ def _process_query_objects (self ):
1097+ """Processes FilterExpression objects to OData lookups"""
1098+
1099+ filter_expressions = []
1100+
1101+ for expr in self ._filter_expressions :
1102+ lhs_expressions = self ._decode_and_combine_filter_expression (expr )
1103+
1104+ if expr .other is not None :
1105+ rhs_expressions = self ._decode_and_combine_filter_expression (expr .other )
1106+ filter_expressions .append (f'({ lhs_expressions } ) { expr .operator } ({ rhs_expressions } )' )
1107+ else :
1108+ filter_expressions .append (lhs_expressions )
1109+
1110+ return filter_expressions
1111+
1112+ def _process_expressions (self ):
1113+ filter_expressions = [self ._decode_expression (expr , val ) for expr , val in self .expressions ]
1114+
1115+ filter_expressions .extend (self ._process_query_objects ())
1116+
1117+ return filter_expressions
1118+
1119+ def _decode_expression (self , expr , val ):
1120+ field = None
1121+ # field_heirarchy = []
1122+ operator = 'eq'
1123+ exprs = expr .split ('__' )
1124+
1125+ for part in exprs :
1126+ if self ._entity_type .has_proprty (part ):
1127+ field = part
1128+ # field_heirarchy.append(part)
1129+ elif part in self .__class__ .OPERATORS :
1130+ operator = part
1131+ else :
1132+ raise ValueError (f'"{ part } " is not a valid property or operator' )
1133+ # field = '/'.join(field_heirarchy)
1134+
1135+ # target_field = self.proprty_obj(field_heirarchy[-1])
1136+ expression = self ._build_expression (field , operator , val )
1137+
1138+ return expression
1139+
1140+ # pylint: disable=no-self-use
1141+ def _combine_expressions (self , expressions ):
1142+ return ' and ' .join (expressions )
1143+
1144+ # pylint: disable=too-many-return-statements, too-many-branches
1145+ def _build_expression (self , field_name , operator , value ):
1146+ target_field = self .proprty_obj (field_name )
1147+
1148+ if operator not in ['length' , 'in' , 'range' ]:
1149+ value = target_field .to_literal (value )
1150+
1151+ if operator == 'lt' :
1152+ return f'{ field_name } lt { value } '
1153+
1154+ if operator == 'lte' :
1155+ return f'{ field_name } le { value } '
1156+
1157+ if operator == 'gte' :
1158+ return f'{ field_name } ge { value } '
1159+
1160+ if operator == 'gt' :
1161+ return f'{ field_name } gt { value } '
1162+
1163+ if operator == 'startswith' :
1164+ return f'startswith({ field_name } , { value } ) eq true'
1165+
1166+ if operator == 'endswith' :
1167+ return f'endswith({ field_name } , { value } ) eq true'
1168+
1169+ if operator == 'length' :
1170+ value = int (value )
1171+ return f'length({ field_name } ) eq { value } '
1172+
1173+ if operator in ['contains' ]:
1174+ return f'substringof({ value } , { field_name } ) eq true'
1175+
1176+ if operator == 'range' :
1177+ if not isinstance (value , (tuple , list )):
1178+ raise TypeError ('Range must be tuple or list not {}' .format (type (value )))
1179+
1180+ if len (value ) != 2 :
1181+ raise ValueError ('Only two items can be passed in a range.' )
1182+
1183+ low_bound = target_field .to_literal (value [0 ])
1184+ high_bound = target_field .to_literal (value [1 ])
1185+
1186+ return f'{ field_name } gte { low_bound } and { field_name } lte { high_bound } '
1187+
1188+ if operator == 'in' :
1189+ literal_values = (f'{ field_name } eq { target_field .to_literal (item )} ' for item in value )
1190+ return ' or ' .join (literal_values )
1191+
1192+ if operator == 'eq' :
1193+ return f'{ field_name } eq { value } '
1194+
1195+ raise ValueError (f'Invalid expression { operator } ' )
1196+
1197+ def __str__ (self ):
1198+ expressions = self ._process_expressions ()
1199+ result = self ._combine_expressions (expressions )
1200+ return quote (result )
1201+
1202+
9961203class GetEntitySetRequest (QueryRequest ):
9971204 """GET on EntitySet"""
9981205
@@ -1005,6 +1212,19 @@ def __getattr__(self, name):
10051212 proprty = self ._entity_type .proprty (name )
10061213 return GetEntitySetFilter (proprty )
10071214
1215+ def _set_filter (self , filter_val ):
1216+ filter_text = self ._filter + ' and ' if self ._filter else ''
1217+ filter_text += filter_val
1218+ self ._filter = filter_text
1219+
1220+ def filter (self , * args , ** kwargs ):
1221+ if args and len (args ) == 1 and isinstance (args [0 ], str ):
1222+ self ._filter = args [0 ]
1223+ else :
1224+ self ._set_filter (str (GetEntitySetFilterChainable (self ._entity_type , args , kwargs )))
1225+
1226+ return self
1227+
10081228
10091229class EntitySetProxy :
10101230 """EntitySet Proxy"""
0 commit comments