def construct_single_q(rule): operator = rule['operator'] neg = False if operator.startswith("not_"): neg = True operator = operator[4:] cond = { "equal": "exact", "begins_with": "istartswith", "contains": "icontains", "ends_with": "iendswith", "less": "lt", "less_or_equal": "lte", "greater": "gt", "greater_or_equal": "gte", "between": "range" }[operator] if cond != "range": cond_dict = {"{}__{}".format(rule['id'], cond): rule['value']} else: cond_dict = {"{}__{}".format(rule['id'], cond): tuple(rule['value'])} q_obj = Q(**cond_dict) if neg: q_obj = ~q_obj return q_obj
def _parse_relationals(cls, expr_string, escape='__'): """ Based on shunting-yard algorithm (see http://en.wikipedia.org/wiki/Shunting-yard_algorithm) with modifications for skipping over non logical/relational operators and associated parentheses. """ if isinstance(expr_string, bool): return expr_string # Splits and throws away empty tokens (between parens and operators) # and encloses the whole expression in parens tokens = (['('] + [ t for t in cls._tokenize_logic_re.split(expr_string.strip()) if t ] + [')']) if len([1 for t in tokens if t.strip().endswith('(') ]) != tokens.count(')'): raise NineMLMathParseError( "Unbalanced parentheses in expression: {}".format(expr_string)) tokens = tokens operators = [] # stack (in SY algorithm terminology) operands = [] # output stream is_relational = [] # whether the current parens should be parsed # Num operands to concat when not parsing relation/bool. Because we are # also splitting on parenthesis, non-logic/relational expressions will # still be split on parenthesis and we need to keep track of how many # pieces they are in. num_args = [0] # top-level should always be set to 1 for tok in tokens: # If opening paren or function name + paren if cls._left_paren_func_re.match(tok): operators.append(tok) is_relational.append(False) num_args.append(0) # Closing paren. elif tok == ')': # Join together sub-expressions that have been split by parens # not used for relational/boolean expressions (e.g. functions) n = num_args.pop() if n > 1: operands = operands[:-n] + [''.join(operands[-n:])] # If parens enclosed relat/logic (i.e. '&', '|', '<', '>', etc) if is_relational.pop(): # Get all the operators within the enclosing parens. # Need to sort by order of precedence try: # Get index of last open paren i = -1 while not cls._left_paren_func_re.match(operators[i]): i -= 1 except IndexError: raise NineMLMathParseError( "Unbalanced parentheses in expression: {}".format( expr_string)) # Get lists of operators and operands at this level # (i.e. after the last left paren) level_operators = operators[(i + 1):] level_operands = operands[i:] # Pop these operators and operands off the list # (along with the paren/function-call) open_paren = operators[i] operators = operators[:i] operands = operands[:i] while level_operators: # Sort in order of precedence prec = [cls._precedence[o] for o in level_operators] i = sorted(list(range(len(prec))), key=prec.__getitem__)[0] # Pop first operator operator = level_operators.pop(i) arg1, arg2 = (level_operands.pop(i), level_operands.pop(i)) if operator.startswith('&'): func = "And{}({}, {})".format(escape, arg1, arg2) elif operator.startswith('|'): func = "Or{}({}, {})".format(escape, arg1, arg2) elif operator.startswith('='): func = "Eq{}({}, {})".format(escape, arg1, arg2) elif operator == '<': func = "Lt{}({}, {})".format(escape, arg1, arg2) elif operator == '>': func = "Gt{}({}, {})".format(escape, arg1, arg2) elif operator == '<=': func = "Le{}({}, {})".format(escape, arg1, arg2) elif operator == '>=': func = "Ge{}({}, {})".format(escape, arg1, arg2) else: assert False level_operands.insert(i, func) new_operand = level_operands[0] if open_paren != '(': # Function/logical negation new_operand = open_paren + new_operand + ')' operands.append(new_operand) # If parens enclosed something else else: try: # Apply the function/parens to the last operand operands[-1] = operators.pop() + operands[-1] + ')' except IndexError: raise NineMLMathParseError( "Unbalanced parentheses in expression: {}".format( expr_string)) num_args[-1] += 1 # If the token is one of ('&', '|', '<', '>', '<=', '>=' or '==') elif cls._tokenize_logic_re.match(tok): operators.append(tok) is_relational[-1] = True # parse the last set of parenthesis # Check if there are more than one LHS sub-expr. to concatenate n = num_args[-1] if n == 0: raise NineMLMathParseError( "Logical/relational operator directly after a " "parenthesis or start of expression: {}".format( expr_string)) elif n > 1: operands = operands[:-n] + [''.join(operands[-n:])] num_args[-1] = 0 # If the token is an atom or a subexpr not containing any # logic/relational operators or parens. else: operands.append(tok) num_args[-1] += 1 # After it is processed, operands should contain the parsed expression # as a single item return operands[0]
def _parse_relationals(cls, expr_string, escape='__'): """ Based on shunting-yard algorithm (see http://en.wikipedia.org/wiki/Shunting-yard_algorithm) with modifications for skipping over non logical/relational operators and associated parentheses. """ if isinstance(expr_string, bool): return expr_string # Splits and throws away empty tokens (between parens and operators) # and encloses the whole expression in parens tokens = (['('] + [t for t in cls._tokenize_logic_re.split(expr_string.strip()) if t] + [')']) if len([1 for t in tokens if t.strip().endswith('(')]) != tokens.count(')'): raise NineMLMathParseError( "Unbalanced parentheses in expression: {}".format(expr_string)) tokens = tokens operators = [] # stack (in SY algorithm terminology) operands = [] # output stream is_relational = [] # whether the current parens should be parsed # Num operands to concat when not parsing relation/bool. Because we are # also splitting on parenthesis, non-logic/relational expressions will # still be split on parenthesis and we need to keep track of how many # pieces they are in. num_args = [0] # top-level should always be set to 1 for tok in tokens: # If opening paren or function name + paren if cls._left_paren_func_re.match(tok): operators.append(tok) is_relational.append(False) num_args.append(0) # Closing paren. elif tok == ')': # Join together sub-expressions that have been split by parens # not used for relational/boolean expressions (e.g. functions) n = num_args.pop() if n > 1: operands = operands[:-n] + [''.join(operands[-n:])] # If parens enclosed relat/logic (i.e. '&', '|', '<', '>', etc) if is_relational.pop(): # Get all the operators within the enclosing parens. # Need to sort by order of precedence try: # Get index of last open paren i = -1 while not cls._left_paren_func_re.match(operators[i]): i -= 1 except IndexError: raise NineMLMathParseError( "Unbalanced parentheses in expression: {}" .format(expr_string)) # Get lists of operators and operands at this level # (i.e. after the last left paren) level_operators = operators[(i + 1):] level_operands = operands[i:] # Pop these operators and operands off the list # (along with the paren/function-call) open_paren = operators[i] operators = operators[:i] operands = operands[:i] while level_operators: # Sort in order of precedence prec = [cls._precedence[o] for o in level_operators] i = sorted(list(range(len(prec))), key=prec.__getitem__)[0] # Pop first operator operator = level_operators.pop(i) arg1, arg2 = (level_operands.pop(i), level_operands.pop(i)) if operator.startswith('&'): func = "And{}({}, {})".format(escape, arg1, arg2) elif operator.startswith('|'): func = "Or{}({}, {})".format(escape, arg1, arg2) elif operator.startswith('='): func = "Eq{}({}, {})".format(escape, arg1, arg2) elif operator == '<': func = "Lt{}({}, {})".format(escape, arg1, arg2) elif operator == '>': func = "Gt{}({}, {})".format(escape, arg1, arg2) elif operator == '<=': func = "Le{}({}, {})".format(escape, arg1, arg2) elif operator == '>=': func = "Ge{}({}, {})".format(escape, arg1, arg2) else: assert False level_operands.insert(i, func) new_operand = level_operands[0] if open_paren != '(': # Function/logical negation new_operand = open_paren + new_operand + ')' operands.append(new_operand) # If parens enclosed something else else: try: # Apply the function/parens to the last operand operands[-1] = operators.pop() + operands[-1] + ')' except IndexError: raise NineMLMathParseError( "Unbalanced parentheses in expression: {}" .format(expr_string)) num_args[-1] += 1 # If the token is one of ('&', '|', '<', '>', '<=', '>=' or '==') elif cls._tokenize_logic_re.match(tok): operators.append(tok) is_relational[-1] = True # parse the last set of parenthesis # Check if there are more than one LHS sub-expr. to concatenate n = num_args[-1] if n == 0: raise NineMLMathParseError( "Logical/relational operator directly after a " "parenthesis or start of expression: {}" .format(expr_string)) elif n > 1: operands = operands[:-n] + [''.join(operands[-n:])] num_args[-1] = 0 # If the token is an atom or a subexpr not containing any # logic/relational operators or parens. else: operands.append(tok) num_args[-1] += 1 # After it is processed, operands should contain the parsed expression # as a single item return operands[0]