Source code for fiql_parser.expression

# -*- coding: utf-8 -*-
"""
It would be very difficult to build a FIQL ``Expressions`` without taking into
account the ``Expressions`` part of it.

The ``expression`` module includes the code used for ensuring that any FIQL
``Expression`` created with this package is a valid FIQL ``Expression``.
"""
from __future__ import unicode_literals
from __future__ import absolute_import

from .exceptions import FiqlObjectException
from .operator import Operator


[docs]class BaseExpression(object): """ Both ``Constraint`` and ``Expression`` classes extend the ``BaseExpression`` class. A FIQL ``Constraint`` is a simple FIQL ``Expression``. As such, they share certain attributes. Note: The parent of any child of ``BaseExpression`` is always an ``Expression``. This is a bit contrary to what might be expected as an ``Expression`` itself is a child class of ``BaseExpression``. This quark is a side effect of the definition of the FIQL ``Constraint``. A FIQL ``Constraint`` can not be contained within another FIQL ``Constraint`` as a sub-expression. Both a FIQL ``Constraint`` and FIQL ``Expression`` can only be sub-expressions of an actual FIQL ``Expression``. Attributes: parent (Expression): The ``Expression`` which contains this object. """ def __init__(self): """Initialize instance of ``BaseExpression``.""" self.parent = None
[docs] def set_parent(self, parent): """Set parent ``Expression`` for this object. Args: parent (Expression): The ``Expression`` which contains this object. Raises: FiqlObjectException: Parent must be of type ``Expression``. """ if not isinstance(parent, Expression): raise FiqlObjectException("Parent must be of %s not %s" % ( Expression, type(parent))) self.parent = parent
[docs] def get_parent(self): """Get the parent ``Expression`` for this object. Returns: Expression: The ``Expression`` which contains this object. Raises: FiqlObjectException: Parent is ``None``. """ if not isinstance(self.parent, Expression): raise FiqlObjectException("Parent must be of %s not %s" % ( Expression, type(self.parent))) return self.parent
[docs]class Expression(BaseExpression): """ The ``Expression`` is the largest logical unit of a FIQL ``Expression``. It must, like the ``Constraint`` evaluate to ``True`` or ``False``. The ``Expression`` can both contain and be contained by an ``Expression``. It, unlike the ``Operator`` and ``Constraint``, MUST contain specific attributes in order to be valid. This class contains the bulk of the logic to ensure that an ``Expression`` generated by this code is a valid FIQL ``Expression``. Note: This ``Expression`` class uses a single ``Operator`` to join multiple ``Constraints``. This format has the advantage of working cleanly with many ORMs and being far more easily converted to the more string friendly format of ``Constraint``, ``Operator``, ``Constraint``, etc. than the more string friendly format can be converted to the other. Attributes: elements (list): List of ``Constraint`` and ``Expression`` elements in this ``Expression``. operator (Operator): The ``Operator`` which relates the elements in this ``Expression``. """ def __init__(self): """Initialize instance of ``Expression``.""" super(Expression, self).__init__() self.elements = [] self.operator = None # Keep track of which nested fragment we are in. self._working_fragment = self # Keep track of what was last added. self._last_element = None
[docs] def has_constraint(self): """Return whether or not the working ``Expression`` has any ``Constraints``. Returns: integer: Number of logical elements within this ``Expression``. """ return len(self.elements)
[docs] def add_operator(self, operator): """Add an ``Operator`` to the ``Expression``. The ``Operator`` may result in a new ``Expression`` if an ``Operator`` already exists and is of a different precedence. There are three possibilities when adding an ``Operator`` to an ``Expression`` depending on whether an ``Operator`` already exists: - No ``Operator`` on the working ``Expression``; Simply set the ``Operator`` and return ``self``. - ``Operator`` already exists and is higher in precedence; The ``Operator`` and last ``Constraint`` belong in a sub-expression of the working ``Expression``. - ``Operator`` already exists and is lower in precedence; The ``Operator`` belongs to the parent of the working ``Expression`` whether one currently exists or not. To remain in the context of the top ``Expression``, this method will return the parent here rather than ``self``. Args: operator (Operator): What we are adding. Returns: Expression: ``self`` or related ``Expression``. Raises: FiqlObjectExpression: Operator is not a valid ``Operator``. """ if not isinstance(operator, Operator): raise FiqlObjectException("%s is not a valid element type" % ( operator.__class__)) if not self._working_fragment.operator: self._working_fragment.operator = operator elif operator > self._working_fragment.operator: last_constraint = self._working_fragment.elements.pop() self._working_fragment = self._working_fragment \ .create_nested_expression() self._working_fragment.add_element(last_constraint) self._working_fragment.add_operator(operator) elif operator < self._working_fragment.operator: if self._working_fragment.parent: return self._working_fragment.parent.add_operator(operator) return Expression().add_element(self._working_fragment) \ .add_operator(operator) return self
[docs] def add_element(self, element): """Add an element of type ``Operator``, ``Constraint``, or ``Expression`` to the ``Expression``. Args: element: ``Constraint``, ``Expression``, or ``Operator``. Returns: Expression: ``self`` Raises: FiqlObjectException: Element is not a valid type. """ if isinstance(element, BaseExpression): element.set_parent(self._working_fragment) self._working_fragment.elements.append(element) return self return self.add_operator(element)
[docs] def create_nested_expression(self): """Create a nested ``Expression``, add it as an element to this ``Expression``, and return it. Returns: Expression: The newly created nested ``Expression``. """ sub = Expression() self.add_element(sub) return sub
[docs] def op_and(self, *elements): """Update the ``Expression`` by joining the specified additional ``elements`` using an "AND" ``Operator`` Args: *elements (BaseExpression): The ``Expression`` and/or ``Constraint`` elements which the "AND" ``Operator`` applies to. Returns: Expression: ``self`` or related ``Expression``. """ expression = self.add_operator(Operator(';')) for element in elements: expression.add_element(element) return expression
[docs] def op_or(self, *elements): """Update the ``Expression`` by joining the specified additional ``elements`` using an "OR" ``Operator`` Args: *elements (BaseExpression): The ``Expression`` and/or ``Constraint`` elements which the "OR" ``Operator`` applies to. Returns: Expression: ``self`` or related ``Expression``. """ expression = self.add_operator(Operator(',')) for element in elements: expression.add_element(element) return expression
[docs] def to_python(self): """Deconstruct the ``Expression`` instance to a list or tuple (If ``Expression`` contains only one ``Constraint``). Returns: list or tuple: The deconstructed ``Expression``. """ if not self.elements: return None if len(self.elements) == 1: return self.elements[0].to_python() operator = self.operator or Operator(';') return [operator.to_python()] + \ [elem.to_python() for elem in self.elements]
def __str__(self): """Represent the ``Expression`` instance as a string. Returns: string: The represented ``Expression``. """ operator = self.operator or Operator(';') elements_str = str(operator).join( ["{0}".format(elem) for elem in self.elements]) if self.parent: parent_operator = self.parent.operator or Operator(';') if parent_operator > operator: return "(" + elements_str + ")" return elements_str