summaryrefslogtreecommitdiffstats
path: root/netmodel/model/field_names.py
diff options
context:
space:
mode:
Diffstat (limited to 'netmodel/model/field_names.py')
-rw-r--r--netmodel/model/field_names.py396
1 files changed, 396 insertions, 0 deletions
diff --git a/netmodel/model/field_names.py b/netmodel/model/field_names.py
new file mode 100644
index 00000000..82881998
--- /dev/null
+++ b/netmodel/model/field_names.py
@@ -0,0 +1,396 @@
+#!/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.
+#
+
+FIELD_SEPARATOR = '.'
+DEFAULT_IS_STAR = False
+
+class FieldNames(list):
+ """
+ A FieldNames instance gather a set of field_names or represents *.
+ THIS IS NOT a set(Field).
+
+ The distinction between parent and children fields is based on the
+ FieldNames.FIELD_SEPARATOR character.
+ """
+
+ #--------------------------------------------------------------------------
+ # Constructor
+ #--------------------------------------------------------------------------
+
+ def __init__(self, *args, **kwargs):
+ """
+ Constructor.
+ """
+ star = kwargs.pop('star', DEFAULT_IS_STAR)
+ list.__init__(self, *args, **kwargs)
+ size = len(self)
+ if star and size != 0:
+ raise ValueError("Inconsistent parameter (star = %s size = %s)" % \
+ (star, size))
+ # self._star == False and len(self) == 0 occurs when we create
+ # FieldNames() (to use later |=) and must behaves as FieldNames(star =
+ # False)
+ self._star = star
+
+ def __repr__(self):
+ """
+ Returns:
+ The %r representation of this FieldNames instance.
+ """
+ if self.is_star():
+ return "<FieldNames *>"
+ else:
+ return "<FieldNames %r>" % [x for x in self]
+
+ def __hash__(self):
+ return hash((self._star,) + tuple(self))
+
+ def is_star(self):
+ """
+ Returns:
+ True iif this FieldNames instance correspond to "*".
+ Example : SELECT * FROM foo
+ """
+ try:
+ return self._star
+ except:
+ # This is due to a bug in early versions of Python 2.7 which are
+ # present on PlanetLab. During copy.deepcopy(), the object is
+ # reconstructed using append before the state (self.__dict__ is
+ # reconstructed). Hence the object has no _star when append is
+ # called and this raises a crazy Exception:
+ # I could not reproduce in a smaller example
+ # http://pastie.org/private/5nf15jg0qcvd05pbmnrp8g
+ return False
+
+ def set_star(self):
+ """
+ Update this FieldNames instance to make it corresponds to "*"
+ """
+ self._star = True
+ self.clear()
+
+ def unset_star(self, field_names):
+ """
+ Update this FieldNames instance to make it corresponds to a set of
+ FieldNames
+
+ Args:
+ field_names: A FieldNames instance or a set of String instances
+ (field names)
+ """
+ assert len(field_names) > 0
+ self._star = False
+ if field_names:
+ self |= field_names
+
+ def is_empty(self):
+ """
+ Returns:
+ True iif FieldNames instance designates contains least one field
+ name.
+ """
+ return not self.is_star() and len(self) == 0
+
+ def copy(self):
+ """
+ Returns:
+ A copy of this FieldNames instance.
+ """
+ return FieldNames(self[:])
+
+ #--------------------------------------------------------------------------
+ # Iterators
+ #--------------------------------------------------------------------------
+
+ def iter_field_subfield(self):
+ for f in self:
+ field, _, subfield = f.partition(FIELD_SEPARATOR)
+ yield (field, subfield)
+
+ #--------------------------------------------------------------------------
+ # Overloaded set internal functions
+ #--------------------------------------------------------------------------
+
+ def __or__(self, fields):
+ """
+ Compute the union of two FieldNames instances.
+ Args:
+ fields: a set of String (corresponding to field names) or a
+ FieldNames instance.
+ Returns:
+ The union of the both FieldNames instance.
+ """
+ if self.is_star() or fields.is_star():
+ return FieldNames(star = True)
+ else:
+ l = self[:]
+ l.extend([x for x in fields if x not in l])
+ return FieldNames(l)
+
+ def __ior__(self, fields):
+ """
+ Compute the union of two FieldNames instances.
+ Args:
+ fields: a set of Field instances or a FieldNames instance.
+ Returns:
+ The updated FieldNames instance.
+ """
+ if fields.is_star():
+ self.set_star()
+ return self
+ else:
+ self.extend([x for x in fields if x not in self])
+ return self
+
+ def __and__(self, fields):
+ """
+ Compute the intersection of two FieldNames instances.
+ Args:
+ fields: a set of Field instances or a FieldNames instance.
+ Returns:
+ The intersection of the both FieldNames instances.
+ """
+ if self.is_star():
+ return fields.copy()
+ elif isinstance(fields, FieldNames) and fields.is_star():
+ return self.copy()
+ else:
+ return FieldNames([x for x in self if x in fields])
+
+ def __iand__(self, fields):
+ """
+ Compute the intersection of two FieldNames instances.
+ Args:
+ fields: a set of Field instances or a FieldNames instance.
+ Returns:
+ The updated FieldNames instance.
+ """
+ if self.is_star():
+ self.unset_star(fields)
+ elif fields.is_star():
+ pass
+ else:
+ self[:] = [x for x in self if x in fields]
+ return self
+
+ def __nonzero__(self):
+ return self.is_star() or bool(list(self))
+
+ # Python>=3
+ __bool__ = __nonzero__
+
+ __add__ = __or__
+
+ def __sub__(self, fields):
+ if fields.is_star():
+ return FieldNames(star = False)
+ else:
+ if self.is_star():
+ # * - x,y,z = ???
+ return FieldNames(star = True)
+ else:
+ return FieldNames([x for x in self if x not in fields])
+
+ def __isub__(self, fields):
+ raise NotImplemented
+
+ def __iadd__(self, fields):
+ raise NotImplemented
+
+ #--------------------------------------------------------------------------
+ # Overloaded set comparison functions
+ #--------------------------------------------------------------------------
+
+ def __eq__(self, other):
+ """
+ Test whether this FieldNames instance corresponds to another one.
+ Args:
+ other: The FieldNames instance compared to self.
+ Returns:
+ True if the both FieldNames instance matches.
+ """
+ return self.is_star() and other.is_star() or set(self) == set(other)
+
+ def __le__(self, other):
+ """
+ Test whether this FieldNames instance in included in
+ (or equal to) another one.
+ Args:
+ other: The FieldNames instance compared to self or
+ Returns:
+ True if the both FieldNames instance matches.
+ """
+ assert isinstance(other, FieldNames),\
+ "Invalid other = %s (%s)" % (other, type(other))
+
+ return (self.is_star() and other.is_star())\
+ or (not self.is_star() and other.is_star())\
+ or (set(self) <= set(other)) # list.__le__(self, other)
+
+ # Defined with respect of previous functions
+
+ def __ne__(self, other):
+ """
+ Test whether this FieldNames instance differs to another one.
+ Args:
+ other: The FieldNames instance compared to self.
+ Returns:
+ True if the both FieldNames instance differs.
+ """
+ return not self == other
+
+ def __lt__(self, other):
+ """
+ Test whether this FieldNames instance in strictly included in
+ another one.
+ Args:
+ other: The FieldNames instance compared to self.
+ Returns:
+ True if self is strictly included in other.
+ """
+ return self <= other and self != other
+
+ def __ge__(self, other):
+ return other.__le__(self)
+
+ def __gt__(self, other):
+ return other.__lt__(self)
+
+ #--------------------------------------------------------------------------
+ # Overloaded set functions
+ #--------------------------------------------------------------------------
+
+ def add(self, field_name):
+ # DEPRECATED
+ assert isinstance(field_name, str)
+ self.append(field_name)
+
+ def set(self, field_names):
+ assert isinstance(field_names, FieldNames)
+ if field_names.is_star():
+ self.set_star()
+ return
+ assert len(field_names) > 0
+ self._star = False
+ self.clear()
+ self |= field_names
+
+ def append(self, field_name):
+ if not isinstance(field_name, str):
+ raise TypeError("Invalid field_name %s (string expected, got %s)" \
+ % (field_name, type(field_name)))
+
+ if not self.is_star():
+ list.append(self, field_name)
+
+ def clear(self):
+ self._star = True
+ del self[:]
+
+ def rename(self, aliases):
+ """
+ Rename all the field names involved in self according to a dict.
+ Args:
+ aliases: A {String : String} mapping the old field name and
+ the new field name.
+ Returns:
+ The updated FieldNames instance.
+ """
+ s = self.copy()
+ for element in s:
+ if element in aliases:
+ s.remove(element)
+ s.add(aliases[element])
+ self.clear()
+ self |= s
+ return self
+
+ @staticmethod
+ def join(field, subfield):
+ return "%s%s%s" % (field, FIELD_SEPARATOR, subfield)
+
+ @staticmethod
+ def after_path(field, path, allow_shortcuts = True):
+ """
+ Returns the part of the field after path
+
+ Args:
+ path (list):
+ allow_shortcuts (bool): Default to True.
+ """
+ if not path:
+ return (field, None)
+ last = None
+ field_parts = field.split(FIELD_SEPARATOR)
+ for path_element in path[1:]:
+ if path_element == field_parts[0]:
+ field_parts.pop(0)
+ last = None
+ else:
+ last = path_element
+ return (FIELD_SEPARATOR.join(field_parts), last)
+
+ def split_subfields(self, include_parent = True, current_path = None,
+ allow_shortcuts = True):
+ """
+ Args:
+ include_parent (bool): is the parent field included in the list of
+ returned FieldNames (1st part of the tuple).
+ current_path (list): the path of fields that will be skipped at the
+ beginning
+ path_shortcuts (bool): do we allow shortcuts in the path
+
+ Returns: A tuple made of 4 operands:
+ fields:
+ map_method_subfields:
+ map_original_field:
+ rename:
+
+ Example path = ROOT.A.B
+ split_subfields(A.B.C.D, A.B.C.D', current_path=[ROOT,A,B]) =>
+ (FieldNames(), { C: [D, D'] })
+ split_subfields(A.E.B.C.D, A.E.B.C.D', current_path=[ROOT,A,B]) =>
+ (FieldNames(), { C: [D, D'] })
+ """
+ field_names = FieldNames()
+ map_method_subfields = dict()
+ map_original_field = dict()
+ rename = dict()
+
+ for original_field in self:
+ # The current_path can be seen as a set of fields that have to be
+ # passed through before we can consider a field
+ field, last = FieldNames.after_path(original_field, current_path,
+ allow_shortcuts)
+
+ field_name, _, subfield = field.partition(FIELD_SEPARATOR)
+
+ if not subfield:
+ field_names.add(field_name)
+ else:
+ if include_parent:
+ field_names.add(field_name)
+ if not field_name in map_method_subfields:
+ map_method_subfields[field_name] = FieldNames()
+ map_method_subfields[field_name].add(subfield)
+
+ map_original_field[field_name] = original_field
+ rename[field_name] = last
+
+ return (field_names, map_method_subfields, map_original_field, rename)