-
Notifications
You must be signed in to change notification settings - Fork 200
Closed
Description
Describe the Bug
For multi-level filters in smart playlist, the parsing would return erroneous operator for nodes that are not leaves.
Example
# input
push=1 # <--- Top level nested filters starts
push=1 # <--- Second level nested filters starts
track.userRating>>=5
and=1
track.lastViewedAt<<=-12mon
and=1
track.userRating<<=8
pop=1 # <--- Second level nested filters ends
or=1 # <--- Operator for Top level
push=1
track.userRating=8
and=1 # <--- Operator for Second level
track.lastViewedAt<<=-9w
pop=1
pop=1 # <--- Top level nested filters endsExpected Result
In this case the expected filters dict would be
{"or": [{"and":[...]}, {"and":[...]}]}but it returns
{"and": [{"and":[...]}, {"and":[...]}]}Reason
while looping when it is time to pop, filterGroups[-1].insert(0, filterOp) inserts the stack using the filterOp that was set most recently.
Hence even though the operator between the nested filters is or, since the last operator it encountered was and in the ultimate nested filters, it sets the operator erroneously as and between the both nested filters.
push=1 # <--- Top level nested filters starts
push=1
.............
pop=1
or=1 # <---- will forget
push=1 # <--- Second level nested filters starts
.............
and=1 # <---- last seen operator
.............
pop=1 # <--- Second level nested filters ends
pop=1 # <--- Top level nested filters endsCode Snippets
playlist = server.playlist('playlist with multiple nested filters with highest being or, last group being and')
playlist.filters()Additional Context
I have implemented an algorithm for this using recursion
from collections import deque
def parse_filter_groups(input_list: list[tuple[str, str]] | deque) -> dict:
"""parse filter groups from input lines between push and pop"""
current_filters_stack: list[dict] = []
operator_for_stack = None
if not isinstance(input_list, deque):
input_list = deque(input_list)
allowed_logical_operators = ["and", "or"]
while input_list:
key, value = input_list.popleft() # consume the first item
if key == "push":
# recurse and add the result to the current stack
current_filters_stack.append(parse_filter_groups(input_list))
elif key == "pop":
# stop iterating and return the current stack
break
elif key in allowed_logical_operators:
# set the operator
if operator_for_stack and not operator_for_stack == key:
raise ValueError("cannot have two logical operators in a row")
operator_for_stack = key
else:
# add the key value pair to the current filter
current_filters_stack.append({key: value})
if not operator_for_stack and len(current_filters_stack) > 1:
raise ValueError("no logical operator found for multiple filters")
if operator_for_stack:
return {operator_for_stack: current_filters_stack}
return current_filters_stack.pop()Operating System and Version
Windows 10 64-bit
Plex Media Server Version
1.32.6.7557
Python Version
3.11.0b1
PlexAPI Version
4.15.4
Metadata
Metadata
Assignees
Labels
No labels