Skip to content

Commit 9029a68

Browse files
RSPY-723: Added recursive function in mockup to handle filters
1 parent 82d24f1 commit 9029a68

File tree

1 file changed

+94
-60
lines changed

1 file changed

+94
-60
lines changed

src/ADGS/adgs_station_mock.py

Lines changed: 94 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -256,7 +256,100 @@ def process_query(query):
256256
results.append(part.strip())
257257

258258
return results
259+
260+
261+
def is_operator_next(expression: str, position: int) -> str:
262+
"""Check in the expression if there is an operator from the list at the given position
263+
and returns it if so.
264+
"""
265+
for operator in aditional_operators:
266+
if position<len(expression)-len(operator) and expression[position:position+len(operator)] == operator:
267+
return operator
268+
return ""
269+
270+
271+
def split_composite_filter(filter_to_split: str) -> tuple[list[str], list[str]]:
272+
"""Function to split a filter made of two or more filters separated with an operator.
273+
The split is done at the first level of the filter only.
274+
275+
Examples:
276+
- used on "(field1 or condition1) and (field2 or condition2)" it will return
277+
["field1 or condition1", "field2 or condition2"] with operators = ["and"]
278+
- used on "field1 or condition1" it will return ["field1", "condition1"] with operators = ["or"]
279+
280+
Note that if the input filter is like "(field1 and condition1)" the parenthesis will be removed and
281+
it will be considered as "field1 and condition1", but if it's like "SomeInfo(field1 and condition1)"
282+
then it won't be considered as a composite filter and won't be splitted.
283+
"""
284+
splitted_filter: list[str] = []
285+
current = []
286+
operators = []
287+
depth = 0
288+
i = 0
289+
290+
# Remove parenthesis if useless (ex: "(ex1 and ex2)" but not "(ex1) and (ex2)")
291+
if re.fullmatch(r'\([^()]*\)', filter_to_split.strip()):
292+
filter_to_split = filter_to_split.removeprefix("(")
293+
filter_to_split = filter_to_split.removesuffix(")")
294+
295+
# Split filter at depth 0 based on operators (anything outside parenthesis basically)
296+
while i < len(filter_to_split):
297+
if filter_to_split[i] == '(':
298+
depth += 1
299+
current.append(filter_to_split[i])
300+
i += 1
301+
elif filter_to_split[i] == ')':
302+
depth -= 1
303+
current.append(filter_to_split[i])
304+
i += 1
305+
elif depth == 0 and (operator := is_operator_next(filter_to_split, i)):
306+
splitted_filter.append(''.join(current).strip())
307+
current = []
308+
operators.append(operator.strip())
309+
i += len(operator)
310+
else:
311+
current.append(filter_to_split[i])
312+
i += 1
313+
314+
if current:
315+
splitted_filter.append(''.join(current).strip())
316+
317+
# Return subfilters and operators found
318+
return splitted_filter, operators
319+
320+
321+
def process_filter(request, input_filter):
322+
"""Recursive function to go through any filter (composite or not) and return
323+
the result of the full filter.
324+
"""
325+
# Split the filter
326+
splitted_filters, operators = split_composite_filter(input_filter)
327+
328+
# If there is only one filter, apply it and gather results
329+
if len(splitted_filters)==1:
330+
end_filter = splitted_filters[0]
331+
if "Attributes" in end_filter or "OData.CSC" in end_filter:
332+
return process_attributes_search(end_filter, request.args)
333+
return process_products_request(str(end_filter), request.args)
259334

335+
# If there are two filters, repeat operation on both of them and combine their
336+
# results with the operator retrieved
337+
# elif len(splitted_filters)==2:
338+
# result_filter_1 = process_filter(request, splitted_filters[0])
339+
# result_filter_2 = process_filter(request, splitted_filters[1])
340+
# return process_common_elements(result_filter_1, result_filter_2, operators[0])
341+
342+
# If there are more than two filters, repeat operation on each one and combine its
343+
# results with the ones of the previous one using the correct operator
344+
else:
345+
i=1
346+
final_results = process_filter(request, splitted_filters[0])
347+
while i < len(splitted_filters):
348+
current_filter_results = process_filter(request, splitted_filters[i])
349+
final_results = process_common_elements(final_results, current_filter_results, operators[i-1])
350+
return final_results
351+
352+
260353
def extract_values_and_operation(part1, part2):
261354
# Regular expression to capture the operation and value between single quotes
262355
pattern = r"(\b(eq|gt|lt)\b)\s+'(.*?)'"
@@ -447,66 +540,7 @@ def query_products():
447540
logger.error(msg)
448541
return Response ("Too complex for adgs sim", status=HTTPStatus.BAD_REQUEST)
449542

450-
if len(qs_parser := request.args['$filter'].split(' and ')) > 2:
451-
outputs = []
452-
properties_filter = []
453-
attributes_filter = []
454-
for filter in qs_parser:
455-
if any(property in filter for property in ["contains", "PublicationDate"]):
456-
properties_filter.append(filter)
457-
elif "OData.CSC.StringAttribute" in filter:
458-
attributes_filter.append(filter)
459-
460-
if len(properties_filter) > 4 or len(attributes_filter) > 4:
461-
msg = "Too complex for adgs sim"
462-
logger.error(msg)
463-
return Response ("Too complex for adgs sim", status=HTTPStatus.BAD_REQUEST)
464-
# Tempfix, when filter is very complex, use only GT / LT
465-
properties_filter = [f.split(" or ")[0].strip("'\"()") if " or " in f else f for f in properties_filter]
466-
# Process each property in the filter
467-
processed_requests = [
468-
process_products_request(prop.strip("'\""), request.args)
469-
for prop in properties_filter
470-
]
471-
472-
# Combine the processed requests based on the number of filters
473-
if len(processed_requests) in {1, 2, 3}:
474-
if len(processed_requests) == 1:
475-
outputs.append(processed_requests[0])
476-
else:
477-
common_elements = processed_requests[0]
478-
for req in processed_requests[1:]:
479-
common_elements = process_common_elements(common_elements, req, "and")
480-
outputs.append(common_elements)
481-
if not attributes_filter:
482-
# If there are no attributes to process, just return this
483-
return Response(status=outputs[0].status, response=outputs[0].data, headers=request.args)
484-
485-
486-
# Handle attributes_filter processing
487-
if len(attributes_filter) in {2, 4}:
488-
for i in range(0, len(attributes_filter), 2):
489-
outputs.append(process_attributes_search(f"{attributes_filter[i]} and {attributes_filter[i + 1]}", request.args))
490-
491-
try:
492-
return process_common_elements(outputs[0], outputs[1], "and")
493-
except IndexError:
494-
return Response(status=HTTPStatus.OK, response=json.dumps({"value": []}))
495-
496-
if "Attributes" in request.args['$filter'] or "OData.CSC" in request.args['$filter']:
497-
return process_attributes_search(request.args['$filter'], request.args)
498-
if any(header in request.args["$filter"] for header in aditional_operators):
499-
pattern = r"(\S+ \S+ \S+) (\S+) (\S+ \S+ \S+)"
500-
groups = re.search(pattern, request.args["$filter"])
501-
if groups:
502-
first_request, operator, second_request = groups.group(1), groups.group(2), groups.group(3)
503-
# split and processes the requests
504-
first_response = process_products_request(first_request.replace('"', ""), request.args)
505-
second_response = process_products_request(second_request.replace('"', ""), request.args)
506-
# Load response data to a json dict
507-
return process_common_elements(first_response, second_response, operator)
508-
509-
return process_products_request(str(request.args["$filter"]), request.args)
543+
return process_filter(request, request.args['$filter'])
510544

511545

512546
@app.route("/Products(<Id>)/$value", methods=["GET"])

0 commit comments

Comments
 (0)