Source code for fiql_parser.parser

# -*- coding: utf-8 -*-
"""
The ``parser`` module includes the code used to convert a string representing
a FIQL ``Expression`` into an object representing the same FIQL
``Expression``.

The ``Expression`` object returned is ideally suited for use in filtering
database queries with many ORMs.
"""
from __future__ import unicode_literals
from __future__ import absolute_import

try:
    # pylint: disable=no-name-in-module
    from urllib import unquote_plus
except ImportError:
    # pylint: disable=import-error,no-name-in-module
    from urllib.parse import unquote_plus

from .constants import CONSTRAINT_COMP, COMPARISON_MAP
from .exceptions import FiqlFormatException, FiqlParserException
from .expression import BaseExpression, Expression
from .constraint import Constraint
from .operator import Operator


[docs]def iter_parse(fiql_str): """Iterate through the FIQL string. Yield a tuple containing the following FIQL components for each iteration: - preamble: Any operator or opening/closing parenthesis preceding a constraint or at the very end of the FIQL string. - selector: The selector portion of a FIQL constraint or ``None`` if yielding the last portion of the string. - comparison: The comparison portion of a FIQL constraint or ``None`` if yielding the last portion of the string. - argument: The argument portion of a FIQL constraint or ``None`` if yielding the last portion of the string. For usage see :func:`parse_str_to_expression`. Args: fiql_str (string): The FIQL formatted string we want to parse. Yields: tuple: Preamble, selector, comparison, argument. """ while fiql_str: constraint_match = CONSTRAINT_COMP.split(fiql_str, 1) if len(constraint_match) < 2: yield (constraint_match[0], None, None, None) break yield ( constraint_match[0], unquote_plus(constraint_match[1]), constraint_match[4], unquote_plus(constraint_match[6]) if constraint_match[6] else None ) fiql_str = constraint_match[8]
[docs]def parse_str_to_expression(fiql_str): """Parse a FIQL formatted string into an ``Expression``. Args: fiql_str (string): The FIQL formatted string we want to parse. Returns: Expression: An ``Expression`` object representing the parsed FIQL string. Raises: FiqlFormatException: Unable to parse string due to incorrect formatting. Example: >>> expression = parse_str_to_expression( ... "name==bar,dob=gt=1990-01-01" ... ) """ # pylint: disable=too-many-branches nesting_lvl = 0 last_element = None expression = Expression() for (preamble, selector, comparison, argument) in iter_parse(fiql_str): if preamble: for char in preamble: if char == '(': if isinstance(last_element, BaseExpression): raise FiqlFormatException( "%s can not be followed by %s" % ( last_element.__class__, Expression)) expression = expression.create_nested_expression() nesting_lvl += 1 elif char == ')': expression = expression.get_parent() last_element = expression nesting_lvl -= 1 else: if not expression.has_constraint(): raise FiqlFormatException( "%s proceeding initial %s" % ( Operator, Constraint)) if isinstance(last_element, Operator): raise FiqlFormatException( "%s can not be followed by %s" % ( Operator, Operator)) last_element = Operator(char) expression = expression.add_operator(last_element) if selector: if isinstance(last_element, BaseExpression): raise FiqlFormatException("%s can not be followed by %s" % ( last_element.__class__, Constraint)) last_element = Constraint(selector, comparison, argument) expression.add_element(last_element) if nesting_lvl != 0: raise FiqlFormatException( "At least one nested expression was not correctly closed") if not expression.has_constraint(): raise FiqlFormatException( "Parsed string '%s' contained no constraint" % fiql_str) return expression
[docs]def from_python_to_expression(constraints): """Construct the ``Expression`` instance from a list or tuple (If it contains only one constraint). Args: constraints (list or tuple): Expression as a tuple or list containing the constraints. Returns: Expression: The constructed ``Expression``. Raises: FiqlParserException: Unable to determine the input is expression or constraint. Example: >>> expression = from_python_to_expression( ... [ ... 'OR', ... ('a', '==', 'wee'), ... ['AND', ... ['AND', ('foo', None, None), ('bar', '>', '45')], ... ('key', None, None) ... ] ... ] ... ) """ if not constraints: return None if len(constraints) == 1: raise FiqlParserException( "Expression or constraint must contain at lest two items. - %s" \ % constraints ) if len(constraints) == 3 and ( constraints[1] is None or constraints[1] in COMPARISON_MAP.values() ): return Constraint(*constraints) if constraints[0] == "OR": return Expression().op_or( *[from_python_to_expression(c) for c in constraints[1:]] ) if constraints[0] == "AND": return Expression().op_and( *[from_python_to_expression(c) for c in constraints[1:]] ) raise FiqlParserException( "Unable to determine the input is expression or constraint. - %s" \ % constraints )