aboutsummaryrefslogtreecommitdiffstats
path: root/netmodel/model/predicate.py
diff options
context:
space:
mode:
Diffstat (limited to 'netmodel/model/predicate.py')
-rw-r--r--netmodel/model/predicate.py306
1 files changed, 306 insertions, 0 deletions
diff --git a/netmodel/model/predicate.py b/netmodel/model/predicate.py
new file mode 100644
index 00000000..08ed956a
--- /dev/null
+++ b/netmodel/model/predicate.py
@@ -0,0 +1,306 @@
+#!/usr/bin/env python3
+# -*- coding: utf-8 -*-
+#
+# Copyright (c) 2017 Cisco and/or its affiliates.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at:
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+import copy
+
+from netmodel.model.field_names import FieldNames, FIELD_SEPARATOR
+
+from operator import (
+ and_, or_, inv, add, mul, sub, mod, truediv, lt, le, ne, gt, ge, eq, neg
+)
+
+# Define the inclusion operators
+class contains(type): pass
+class included(type): pass
+
+class Predicate:
+
+ operators = {
+ '==' : eq,
+ '!=' : ne,
+ '<' : lt,
+ '<=' : le,
+ '>' : gt,
+ '>=' : ge,
+ 'CONTAINS' : contains,
+ 'INCLUDED' : included
+ }
+
+ operators_short = {
+ '=' : eq,
+ '~' : ne,
+ '<' : lt,
+ '[' : le,
+ '>' : gt,
+ ']' : ge,
+ '}' : contains,
+ '{' : included
+ }
+
+ def __init__(self, *args, **kwargs):
+ """
+ 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
+ elif len(args) == 1 and isinstance(args[0], (tuple, list)) and \
+ len(args[0]) == 3:
+ key, op, value = args[0]
+ elif len(args) == 1 and isinstance(args[0], Predicate):
+ key, op, value = args[0].get_tuple()
+ else:
+ 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, str):
+ 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):
+ self.value = tuple(value)
+ else:
+ self.value = value
+
+ def __str__(self):
+ """
+ Returns:
+ The '%s' representation of this Predicate.
+ """
+ return repr(self)
+
+ def __repr__(self):
+ """
+ Returns:
+ The '%r' 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)
+
+ def __hash__(self):
+ """
+ Returns:
+ The hash of this Predicate (this allows to define set of
+ Predicate instances).
+ """
+ return hash(self.get_tuple())
+
+ def __eq__(self, predicate):
+ """
+ Returns:
+ True iif self == predicate.
+ """
+ if not predicate:
+ return False
+ return self.get_tuple() == predicate.get_tuple()
+
+ def copy(self):
+ return copy.deepcopy(self)
+
+ 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 update_key(self, function):
+ self.set_key(function(self.get_key()))
+
+ def get_op(self):
+ return self.op
+
+ def set_op(self, op):
+ self.op = 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)
+
+ def get_tuple_ext(self):
+ key, op, value = self.get_tuple()
+ key_field, _, key_subfield = key.partition(FIELD_SEPARATOR)
+ return (key_field, key_subfield, op, value)
+
+ def get_str_op(self):
+ op_str = [s for s, op in self.operators.items() if op == self.op]
+ return op_str[0]
+
+ 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):
+ # Can we match ?
+ if self.key not in dic:
+ return ignore_missing
+
+ if self.op == eq:
+ if isinstance(self.value, list):
+ return (dic[self.key] in self.value)
+ else:
+ return (dic[self.key] == self.value)
+ elif self.op == ne:
+ if isinstance(self.value, list):
+ return (dic[self.key] not in self.value)
+ else:
+ return (dic[self.key] != self.value)
+ elif self.op == lt:
+ if isinstance(self.value, str):
+ # prefix match
+ return dic[self.key].startswith('%s.' % self.value)
+ else:
+ return (dic[self.key] < self.value)
+ elif self.op == le:
+ if isinstance(self.value, str):
+ return dic[self.key] == self.value or \
+ dic[self.key].startswith('%s.' % self.value)
+ else:
+ return (dic[self.key] <= self.value)
+ elif self.op == gt:
+ if isinstance(self.value, str):
+ # prefix match
+ return self.value.startswith('%s.' % dic[self.key])
+ else:
+ return (dic[self.key] > self.value)
+ elif self.op == ge:
+ if isinstance(self.value, str):
+ # prefix match
+ return dic[self.key] == self.value or \
+ self.value.startswith('%s.' % dic[self.key])
+ else:
+ return (dic[self.key] >= self.value)
+ elif self.op == and_:
+ return (dic[self.key] & self.value)
+ elif self.op == or_:
+ return (dic[self.key] | self.value)
+ elif self.op == contains:
+ try:
+ method, subfield = self.key.split('.', 1)
+ return not not [ x for x in dic[method] \
+ if x[subfield] == self.value]
+ except ValueError: # split has failed
+ return self.value in dic[self.key]
+ elif self.op == included:
+ return dic[self.key] in self.value
+ else:
+ raise Exception("Unexpected table format: %r" % dic)
+
+ def filter(self, dic):
+ """
+ Filter dic according to the current predicate.
+ """
+
+ if '.' in self.key:
+ # users.hrn
+ method, subfield = self.key.split('.', 1)
+ if not method in dic:
+ return None
+
+ if isinstance(dic[method], dict):
+ subpred = Predicate(subfield, self.op, self.value)
+ match = subpred.match(dic[method])
+ return dic if match else None
+
+ elif isinstance(dic[method], (list, tuple)):
+ # 1..N relationships
+ match = False
+ if self.op == contains:
+ return dic if self.match(dic) else None
+ else:
+ subpred = Predicate(subfield, self.op, self.value)
+ dic[method] = subpred.filter(dic[method])
+ return dic
+ else:
+ raise Exception("Unexpected table format: %r", dic)
+
+
+ else:
+ # Individual field operations
+ return dic if self.match(dic) else None
+
+ def get_field_names(self):
+ if isinstance(self.key, (list, tuple, set, frozenset)):
+ return FieldNames(self.key)
+ else:
+ return FieldNames([self.key])
+
+ def get_value_names(self):
+ if isinstance(self.value, (list, tuple, set, frozenset)):
+ return FieldNames(self.value)
+ else:
+ return FieldNames([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
+
+ def is_composite(self):
+ """
+ Returns:
+ True iif this Predicate instance involves
+ a tuple key (and tuple value).
+ """
+ return isinstance(self.get_key(), tuple)
+
+ def rename(self, aliases):
+ if self.is_composite():
+ raise NotImplemented
+ if self.key in aliases:
+ self.key = aliases[self.key]