Merge branch 'onelab' of ssh://git.onelab.eu/git/myslice into onelab
[myslice.git] / manifold / util / predicate.py
index c9e2856..fb32e4d 100644 (file)
@@ -1,45 +1,69 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+#
+# Class Predicate: 
+# Define a condition to join for example to Table instances.
+# If this condition involves several fields, you may define a
+# single Predicate using tuple of fields. 
+#
+# Copyright (C) UPMC Paris Universitas
+# Authors:
+#   Jordan AugĂ©       <jordan.auge@lip6.fr>
+#   Marc-Olivier Buob <marc-olivier.buob@lip6.fr>
+
+from types                      import StringTypes
+from manifold.util.type         import returns, accepts 
+
 from operator import (
     and_, or_, inv, add, mul, sub, mod, truediv, lt, le, ne, gt, ge, eq, neg
-    )
-from manifold.util.misc import contains
-from types import StringTypes
+)
+
+# Define the inclusion operators
+class contains(type): pass
+class included(type): pass
 
 # New modifier: { contains 
 class Predicate:
 
     operators = {
-        "=="       : eq,
-        "!="       : ne,
-        "<"        : lt,
-        "<="       : le,
-        ">"        : gt,
-        ">="       : ge,
-        "&&"       : and_,
-        "||"       : or_,
-        "contains" : contains
+        '=='       : eq,
+        '!='       : ne,
+        '<'        : lt,
+        '<='       : le,
+        '>'        : gt,
+        '>='       : ge,
+        '&&'       : and_,
+        '||'       : or_,
+        'CONTAINS' : contains,
+        'INCLUDED' : included
     }
 
     operators_short = {
-        "=" : eq,
-        "~" : ne,
-        "<" : lt,
-        "[" : le,
-        ">" : gt,
-        "]" : ge,
-        "&" : and_,
-        "|" : or_,
-        "}" : contains
+        '=' : eq,
+        '~' : ne,
+        '<' : lt,
+        '[' : le,
+        '>' : gt,
+        ']' : ge,
+        '&' : and_,
+        '|' : or_,
+        '}' : contains,
+        '{' : included
     }
 
     def __init__(self, *args, **kwargs):
         """
-        \brief Build a predicate (left, operator, right)
-        \param You can pass:
-            - three args (left, operator, right)
-            - one argument (list or tuple) containing three elements (variable, operator, value)
-            "operator" is a String defined in operators or in operators_short and refers
-                tMao a binary operation.
-            "left" and "right" refers to a variable/constant involved in the Predicate.
+        Build a Predicate instance.
+        Args: 
+            kwargs: You can pass:
+                - 3 args (left, operator, right)
+                    left: The left operand (it may be a String instance or a tuple)
+                    operator: See Predicate.operators, this is the binary operator
+                        involved in this Predicate. 
+                    right: The right value (it may be a String instance
+                        or a literal (String, numerical value, tuple...))
+                - 1 argument (list or tuple), containing three arguments
+                  (variable, operator, value)
         """
         if len(args) == 3:
             key, op, value = args
@@ -48,28 +72,90 @@ class Predicate:
         elif len(args) == 1 and isinstance(args[0], Predicate):
             key, op, value = args[0].get_tuple()
         else:
-            raise Exception, "Bad initializer for Predicate"
+            raise Exception, "Bad initializer for Predicate (args = %r)" % args
+
+        assert not isinstance(value, (frozenset, dict, set)), "Invalid value type (type = %r)" % type(value)
+        if isinstance(value, list):
+            value = tuple(value)
+
         self.key = key
+        if isinstance(op, StringTypes):
+            op = op.upper()
         if op in self.operators.keys():
             self.op = self.operators[op]
         elif op in self.operators_short.keys():
             self.op = self.operators_short[op]
         else:
             self.op = op
-        if isinstance(value, (list, set)):
+
+        if isinstance(value, list):
             self.value = tuple(value)
         else:
             self.value = value
 
+    @returns(StringTypes)
     def __str__(self):
-        return "Pred(%s, %s, %s)" % self.get_str_tuple()
+        """
+        Returns:
+            The '%s' representation of this Predicate.
+        """
+        key, op, value = self.get_str_tuple()
+        if isinstance(value, (tuple, list, set, frozenset)):
+            value = [repr(v) for v in value]
+            value = "[%s]" % ", ".join(value)
+        return "%s %s %r" % (key, op, value) 
 
+    @returns(StringTypes)
     def __repr__(self):
-        return self.__str__() 
+        """
+        Returns:
+            The '%r' representation of this Predicate.
+        """
+        return "Predicate<%s %s %r>" % self.get_str_tuple()
 
     def __hash__(self):
+        """
+        Returns:
+            The hash of this Predicate (this allows to define set of
+            Predicate instances).
+        """
         return hash(self.get_tuple())
 
+    @returns(bool)
+    def __eq__(self, predicate):
+        """
+        Returns:
+            True iif self == predicate.
+        """
+        if not predicate:
+            return False
+        return self.get_tuple() == predicate.get_tuple()
+
+    def get_key(self):
+        """
+        Returns:
+            The left operand of this Predicate. It may be a String
+            or a tuple of Strings.
+        """
+        return self.key
+    
+    def set_key(self, key):
+        """
+        Set the left operand of this Predicate.
+        Params:
+            key: The new left operand.
+        """
+        self.key = key
+
+    def get_op(self):
+        return self.op
+
+    def get_value(self):
+        return self.value
+
+    def set_value(self, value):
+        self.value = value
+
     def get_tuple(self):
         return (self.key, self.op, self.value)
 
@@ -80,6 +166,9 @@ class Predicate:
     def get_str_tuple(self):
         return (self.key, self.get_str_op(), self.value,)
 
+    def to_list(self):
+        return list(self.get_str_tuple())
+
     def match(self, dic, ignore_missing=False):
         if isinstance(self.key, tuple):
             print "PREDICATE MATCH", self.key
@@ -130,8 +219,10 @@ class Predicate:
         elif self.op == contains:
             method, subfield = self.key.split('.', 1)
             return not not [ x for x in dic[method] if x[subfield] == self.value] 
+        elif self.op == included:
+            return dic[self.key] in self.value
         else:
-            raise Exception, "Unexpected table format: %r", dic
+            raise Exception, "Unexpected table format: %r" % dic
 
     def filter(self, dic):
         """
@@ -171,3 +262,20 @@ class Predicate:
             print "----"
             return dic if self.match(dic) else None
 
+    def get_field_names(self):
+        if isinstance(self.key, (list, tuple, set, frozenset)):
+            return set(self.key)
+        else:
+            return set([self.key])
+
+    def get_value_names(self):
+        if isinstance(self.value, (list, tuple, set, frozenset)):
+            return set(self.value)
+        else:
+            return set([self.value])
+
+    def has_empty_value(self):
+        if isinstance(self.value, (list, tuple, set, frozenset)):
+            return not any(self.value)
+        else:
+            return not self.value