]> Frank Brehm's Git Trees - pixelpark/ldap-migration.git/commitdiff
Adding lib/ldap_migration/idict.py for the CaseInsensitiveDict class
authorFrank Brehm <frank.brehm@pixelpark.com>
Mon, 16 Nov 2020 12:13:09 +0000 (13:13 +0100)
committerFrank Brehm <frank.brehm@pixelpark.com>
Mon, 16 Nov 2020 12:13:09 +0000 (13:13 +0100)
lib/ldap_migration/config.py
lib/ldap_migration/idict.py [new file with mode: 0644]

index a00fd448b8b4ccfb9cf4175a9fe67c9668c1c90e..6f6c1fe8dedf2f8880d1a9724f9277b0b5155021 100644 (file)
@@ -3,7 +3,7 @@
 """
 @author: Frank Brehm
 @contact: frank@brehm-online.com
-@copyright: © 2019 by Frank Brehm, Berlin
+@copyright: © 2020 by Frank Brehm, Berlin
 @summary: A module for providing a configuration for the ldap_migration application
 """
 from __future__ import absolute_import
diff --git a/lib/ldap_migration/idict.py b/lib/ldap_migration/idict.py
new file mode 100644 (file)
index 0000000..fa7a8eb
--- /dev/null
@@ -0,0 +1,361 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+"""
+@author: Frank Brehm
+@contact: frank@brehm-online.com
+@copyright: © 2020 by Frank Brehm, Berlin
+@summary: A module for providing a dict with case insensitive keys.
+"""
+from __future__ import absolute_import
+
+# Standard module
+import copy
+
+try:
+    from collections.abc import MutableMapping
+except ImportError:
+    from collections import MutableMapping
+
+# Third party modules
+
+# Own modules
+from fb_tools.common import pp
+from fb_tools.errors import FbError
+from fb_tools.obj import FbBaseObject
+
+__version__ = '0.1.0'
+
+LOG = logging.getLogger(__name__)
+
+
+# =============================================================================
+class WrongKeyTypeError(TypeError, FbError):
+
+    # -------------------------------------------------------------------------
+    def __init__(self, key):
+
+        self.key = key
+
+    # -------------------------------------------------------------------------
+    def __str__(self):
+
+        msg = "Key {key!r} must be of type 'str', but is of type {cls!r} instead."
+        return msg.format(key=key, cls=key.__class__.__name__)
+
+
+# =============================================================================
+class WrongCompareClassError(TypeError, FbError):
+
+    # -------------------------------------------------------------------------
+    def __init__(self, other):
+
+        self.other_class = other.__class__.__name__
+
+    # -------------------------------------------------------------------------
+    def __str__(self):
+
+        msg = "Object {!r} is not a CaseInsensitiveDict object."
+        return msg.format(self.other_class)
+
+
+# =============================================================================
+class CaseInsensitiveKeyError(KeyError, FbError):
+
+    # -------------------------------------------------------------------------
+    def __init__(self, key):
+
+        self.key = key
+
+    # -------------------------------------------------------------------------
+    def __str__(self):
+
+        msg = "Key {!r} not existing in CaseInsensitiveDict."
+        return msg.format(key)
+
+
+# =============================================================================
+class CaseInsensitiveDict(MutableMapping):
+    """
+    A dictionary, where the keys are insensitive strings.
+    The keys MUST be of type string!
+    It works like a dict.
+    """
+
+    wrong_type_msg = "Key {key!r} must be of type 'str', but is of type {cls!r} instead."
+
+    # -------------------------------------------------------------------------
+    def __init__(self, **kwargs):
+        '''Use the object dict'''
+        self._map = dict()
+
+        for key in kwargs:
+            self._set_item(key, kwargs[key])
+
+    # -------------------------------------------------------------------------
+    def _set_item(self, key, value):
+
+        if not isinstance(key, str):
+            raise WrongKeyTypeError(key)
+
+        for okey in self._map.keys():
+            if okey.lower() == key.lower():
+                key = okey
+                break
+
+        self._map[key] = value
+
+    # -------------------------------------------------------------------------
+    def _get_item(self, key):
+
+        if not isinstance(key, str):
+            raise WrongKeyTypeError(key)
+
+        for okey in self._map.keys():
+            if okey.lower() == key.lower():
+                return self._map[okey]
+
+        raise CaseInsensitiveKeyError(key)
+
+    # -------------------------------------------------------------------------
+    def get(self, key):
+        return self._get_item(key)
+
+    # -------------------------------------------------------------------------
+    def _del_item(self, key, strict=True):
+
+        if not isinstance(key, str):
+            raise WrongKeyTypeError(key)
+
+        for okey in self._map.keys():
+            if okey.lower() == key.lower():
+                del self._map[okey]
+                return
+
+        if strict:
+            raise CaseInsensitiveKeyError(key)
+
+        return
+
+    # -------------------------------------------------------------------------
+    # The next five methods are requirements of the ABC.
+    def __setitem__(self, key, value):
+        self._set_item(key, value)
+
+    # -------------------------------------------------------------------------
+    def __getitem__(self, key):
+        return self._get_item(key)
+
+    # -------------------------------------------------------------------------
+    def __delitem__(self, key):
+        self._del_item(key)
+
+    # -------------------------------------------------------------------------
+    def __iter__(self):
+
+        for key in self.keys():
+            yield key
+
+    # -------------------------------------------------------------------------
+    def __len__(self):
+        return len(self._map)
+
+    # -------------------------------------------------------------------------
+    # The next methods aren't required, but nice for different purposes:
+    def __str__(self):
+        '''returns simple dict representation of the mapping'''
+        return str(self._map)
+
+    # -------------------------------------------------------------------------
+    def __repr__(self):
+        '''echoes class, id, & reproducible representation in the REPL'''
+        return '{}, {}({})'.format(
+            super(CaseInsensitiveDict, self).__repr__(),
+            self.__class__.__name__,
+            self._map)
+
+    # -------------------------------------------------------------------------
+    def __contains__(self, key):
+
+        if not isinstance(key, str):
+            raise WrongKeyTypeError(key)
+
+        for okey in self._map.keys():
+            if okey.lower() == key.lower():
+                return True
+
+        return False
+
+    # -------------------------------------------------------------------------
+    def keys(self):
+
+        return sorted(self._map.keys(), key=str.lower)
+
+    # -------------------------------------------------------------------------
+    def items(self):
+
+        item_list = []
+
+        for key in sorted(self._map.keys(), key=str.lower):
+            value = self._map[key]
+            item_list.append((key, value))
+
+        return item_list
+
+    # -------------------------------------------------------------------------
+    def values(self):
+
+        value_list = []
+
+        for key in sorted(self._map.keys(), key=str.lower):
+            value_list.append(self._map[key])
+
+        return value_list
+
+    # -------------------------------------------------------------------------
+    def __eq__(self, other):
+
+        if not isinstance(other, CaseInsensitiveDict):
+            raise WrongCompareClassError(other)
+
+        if len(self) != len(other):
+            return False
+
+        # First compare keys
+        my_keys = []
+        other_keys = []
+
+        for key in self.keys():
+            my_keys.append(key)
+
+        for key in other.keys():
+            other_keys.append(key)
+
+        if my_keys != other_keys:
+            return False
+
+        # Now compare values
+        for key in self.keys():
+            if self[key] != other[key]:
+                return False
+
+        return True
+
+    # -------------------------------------------------------------------------
+    def __ne__(self, other):
+
+        if not isinstance(other, CaseInsensitiveDict):
+            raise WrongCompareClassError(other)
+
+        if self.__eq__(other):
+            return True
+
+        return False
+
+    # -------------------------------------------------------------------------
+    def pop(self, key, *args):
+
+        if not isinstance(key, str):
+            raise WrongKeyTypeError(key)
+
+        for okey in self._map.keys():
+            if okey.lower() == key.lower():
+                key = okey
+                break
+
+        return self._map.pop(key, *args)
+
+    # -------------------------------------------------------------------------
+    def popitem(self):
+
+        if not len(self._map):
+            return None
+
+        key = self.keys()[0]
+        value = self._map[key]
+        del self._map[key]
+        return (key, value)
+
+    # -------------------------------------------------------------------------
+    def clear(self):
+        self._map = dict()
+
+    # -------------------------------------------------------------------------
+    def setdefault(self, key, default):
+
+        if not isinstance(key, str):
+            raise WrongKeyTypeError(key)
+
+        for okey in self._map.keys():
+            if okey.lower() == key.lower():
+                return self._map[okey]
+
+        self._set_item(key, default)
+        return default
+
+    # -------------------------------------------------------------------------
+    def update(self, other):
+
+        if isinstance(other, CaseInsensitiveDict) or isinstance(other, dict):
+            for key in other.keys():
+                self._set_item(key, other[key])
+            return
+
+        for tokens in other:
+            key = tokens[0]
+            value = tokens[1]
+            self._set_item(key, value)
+
+    # -------------------------------------------------------------------------
+    def as_dict(self, short=True):
+
+        res = {}
+        for key in self._map.keys():
+            value = self._map[key]
+            if isinstance(value, FbBaseObject):
+                res[key] = value.as_dict(short=short)
+            else:
+                res[key] = copy.copy(value)
+
+        return res
+
+    # -------------------------------------------------------------------------
+    def set_key(self, key, *args):
+
+        if not isinstance(key, str):
+            raise WrongKeyTypeError(key)
+
+        value = None
+
+        if len(args) > 1:
+            msg = "Wrong arguments {!r} in calling set_key(), at most one optional argument "
+            msg += "may be given."
+            raise AttributeError(msg.format(args))
+        elif len(args) == 1:
+            value = args[0]
+
+        for okey in self._map.keys():
+            if okey.lower() == key.lower():
+                if okey == key:
+                    if len(args) == 1:
+                        self._map[key] = value
+                else:
+                    if len(args) < 1:
+                        # Taking the old value
+                        value = self._map[okey]
+                    del self._map[okey]
+                    self._map[key] = value
+                return
+
+        # Given key not found
+        if len(args) == 1:
+            self._map[key] = value
+        else:
+            raise CaseInsensitiveKeyError(key)
+
+
+# =============================================================================
+if __name__ == "__main__":
+
+    pass
+
+# vim: tabstop=4 expandtab shiftwidth=4 softtabstop=4 list