From: Frank Brehm Date: Mon, 16 Nov 2020 12:13:09 +0000 (+0100) Subject: Adding lib/ldap_migration/idict.py for the CaseInsensitiveDict class X-Git-Url: https://git.uhu-banane.de/?a=commitdiff_plain;h=3b8bd34042de66d5308dcd34fadec54bbeb3d375;p=pixelpark%2Fldap-migration.git Adding lib/ldap_migration/idict.py for the CaseInsensitiveDict class --- diff --git a/lib/ldap_migration/config.py b/lib/ldap_migration/config.py index a00fd44..6f6c1fe 100644 --- a/lib/ldap_migration/config.py +++ b/lib/ldap_migration/config.py @@ -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 index 0000000..fa7a8eb --- /dev/null +++ b/lib/ldap_migration/idict.py @@ -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