+++ /dev/null
-#!/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 logging
-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.5'
-
-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=self.key, cls=self.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(self.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, CaseInsensitiveDict)):
- res[key] = value.as_dict(short=short)
- else:
- res[key] = copy.copy(value)
-
- return res
-
- # -------------------------------------------------------------------------
- def __copy__(self):
-
- new_dict = self.__class__()
- for key in self._map.keys():
- new_dict[key] = copy.copy(self._map[key])
-
- return new_dict
-
- # -------------------------------------------------------------------------
- def get_key(self, key, strict=True):
- """Getting the saved value of the key in correct form."""
-
- if not isinstance(key, str):
- raise WrongKeyTypeError(key)
-
- for okey in self._map.keys():
- if okey.lower() == key.lower():
- return okey
-
- if strict:
- raise CaseInsensitiveKeyError(key)
-
- return None
-
- # -------------------------------------------------------------------------
- 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
+++ /dev/null
-#!/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 mutable set with case insensitive sting items.
-"""
-from __future__ import absolute_import
-
-# Standard module
-import logging
-
-try:
- from collections.abc import MutableSet
-except ImportError:
- from collections import MutableSet
-
-# Third party modules
-
-# Own modules
-from fb_tools.errors import FbError
-
-__version__ = '0.1.1'
-
-LOG = logging.getLogger(__name__)
-
-# =============================================================================
-class WrongItemTypeError(TypeError, FbError):
- """Exeception class for the case, that a given parameter ist not of type str."""
-
- # -------------------------------------------------------------------------
- def __init__(self, item):
-
- self.item = item
- super(WrongItemTypeError, self).__init__()
-
- # -------------------------------------------------------------------------
- def __str__(self):
-
- msg = "Key {item!r} must be of type 'str', but is of type {cls!r} instead."
- return msg.format(item=self.item, cls=self.item.__class__.__name__)
-
-
-# =============================================================================
-class WrongCompareSetClassError(TypeError, FbError):
- """Exeception class for the case, that a given class ist not of an
- instance of CaseInsensitiveStringSet."""
-
- # -------------------------------------------------------------------------
- def __init__(self, other):
-
- self.other_class = other.__class__.__name__
- super(WrongCompareSetClassError, self).__init__()
-
- # -------------------------------------------------------------------------
- def __str__(self):
-
- msg = "Object {!r} is not a CaseInsensitiveStringSet object."
- return msg.format(self.other_class)
-
-
-# =============================================================================
-class CaseInsensitiveStringSet(MutableSet):
- """
- A mutable set, where the strings are insensitive strings.
- The items MUST be of type string!
- It works like a set.
- """
-
- wrong_type_msg = "Item {item!r} must be of type 'str', but is of type {cls!r} instead."
-
- # -------------------------------------------------------------------------
- def __init__(self, iterable):
-
- self._items = {}
-
- for item in iterable:
-
- if not isinstance(item, str):
- raise WrongItemTypeError(item)
-
- self.add(item)
-
- # -------------------------------------------------------------------------
- # Mandatory methods (ABC methods)
-
- # -------------------------------------------------------------------------
- def __iter__(self):
-
- for key in sorted(self._items.keys()):
- yield self._items[key]
-
- # -------------------------------------------------------------------------
- def __contains__(self, value):
- """ The 'in' operator."""
-
- if not isinstance(value, str):
- raise WrongItemTypeError(value)
-
- ival = value.lower()
- if ival in self._items:
- return True
- return False
-
- # -------------------------------------------------------------------------
- def __len__(self):
- return len(self._items)
-
- # -------------------------------------------------------------------------
- def add(self, value):
-
- if not isinstance(value, str):
- raise WrongItemTypeError(value)
-
- ival = value.lower()
- self._items[ival] = value
-
- # -------------------------------------------------------------------------
- def discard(self, value):
-
- ival = value.lower()
- if ival in self._items:
- del self._items[ival]
-
- # -------------------------------------------------------------------------
- # Nice to have methods
-
- # -------------------------------------------------------------------------
- def issubset(self, other):
-
- if not isinstance(other, CaseInsensitiveStringSet):
- raise WrongCompareSetClassError(other)
-
- for item in self._items:
- if item not in other:
- return False
-
- return True
-
- # -------------------------------------------------------------------------
- def __le__(self, other):
- """The '<=' operator."""
-
- if not isinstance(other, CaseInsensitiveStringSet):
- raise WrongCompareSetClassError(other)
-
- return self.issubset(other)
-
- # -------------------------------------------------------------------------
- def __eq__(self, other):
- """The '==' operator."""
-
- if not isinstance(other, CaseInsensitiveStringSet):
- raise WrongCompareSetClassError(other)
-
- if len(self) != len(other):
- return False
-
- for item in self._items:
- if item not in other:
- return False
-
- return True
-
- # -------------------------------------------------------------------------
- def __ne__(self, other):
- """The '!=' operator."""
-
- if not isinstance(other, CaseInsensitiveStringSet):
- raise WrongCompareSetClassError(other)
-
- if self == other:
- return False
- return True
-
- # -------------------------------------------------------------------------
- def __lt__(self, other):
- """The '<' operator."""
-
- if not isinstance(other, CaseInsensitiveStringSet):
- raise WrongCompareSetClassError(other)
-
- ret = True
- for item in self._items:
- if item not in other:
- ret = False
- if ret:
- if len(self) != len(other):
- return True
- return False
-
- # -------------------------------------------------------------------------
- def __gt__(self, other):
- """The '>' operator."""
-
- if not isinstance(other, CaseInsensitiveStringSet):
- raise WrongCompareSetClassError(other)
-
- if self.__le__(other):
- return False
- return True
-
- # -------------------------------------------------------------------------
- def __ge__(self, other):
- """The '>=' operator."""
-
- if not isinstance(other, CaseInsensitiveStringSet):
- raise WrongCompareSetClassError(other)
-
- if self.__lt__(other):
- return False
- return True
-
- # -------------------------------------------------------------------------
- def __copy__(self):
-
- empty = []
- new_set = self.__class__(empty)
- for item in self:
- new_set.add(item)
-
- return new_set
-
- # -------------------------------------------------------------------------
- def copy(self):
- return self.__copy__()
-
- # -------------------------------------------------------------------------
- def values(self):
-
- ret = []
- for item in self:
- ret.append(item)
- return ret
-
- # -------------------------------------------------------------------------
- def __str__(self):
-
- if len(self) == 0:
- return "{}()".format(self.__class__.__name__)
-
- ret = "{}(".format(self.__class__.__name__)
- ret += ', '.join(map(lambda x: "{!r}".format(x), self.values()))
- ret += ')'
-
- return ret
-
- # -------------------------------------------------------------------------
- def __repr__(self):
- return str(self)
-
- # -------------------------------------------------------------------------
- def union(self, *others):
-
- for other in others:
- if not isinstance(other, CaseInsensitiveStringSet):
- raise WrongCompareSetClassError(other)
-
- new_set = self.__copy__()
- for other in others:
- for item in other:
- if item not in new_set:
- new_set.add(item)
-
- return new_set
-
- # -------------------------------------------------------------------------
- def __or__(self, *others):
- """The '|' operator."""
-
- for other in others:
- if not isinstance(other, CaseInsensitiveStringSet):
- raise WrongCompareSetClassError(other)
-
- return self.union(*others)
-
- # -------------------------------------------------------------------------
- def intersection(self, *others):
-
- for other in others:
- if not isinstance(other, CaseInsensitiveStringSet):
- raise WrongCompareSetClassError(other)
-
- empty = []
- new_set = self.__class__(empty)
- for item in self:
- do_add = True
- for other in others:
- if item not in other:
- do_add = False
- if do_add:
- new_set.add(item)
-
- return new_set
-
- # -------------------------------------------------------------------------
- def __and__(self, *others):
- """The '&' operator."""
-
- for other in others:
- if not isinstance(other, CaseInsensitiveStringSet):
- raise WrongCompareSetClassError(other)
-
- return self.intersection(*others)
-
- # -------------------------------------------------------------------------
- def difference(self, *others):
-
- for other in others:
- if not isinstance(other, CaseInsensitiveStringSet):
- raise WrongCompareSetClassError(other)
-
- empty = []
- new_set = self.__class__(empty)
- for item in self:
- do_add = True
- for other in others:
- if item in other:
- do_add = False
- if do_add:
- new_set.add(item)
-
- return new_set
-
- # -------------------------------------------------------------------------
- def __sub__(self, *others):
- """The '-' operator."""
-
- for other in others:
- if not isinstance(other, CaseInsensitiveStringSet):
- raise WrongCompareSetClassError(other)
-
- return self.difference(*others)
-
- # -------------------------------------------------------------------------
- def symmetric_difference(self, other):
-
- if not isinstance(other, CaseInsensitiveStringSet):
- raise WrongCompareSetClassError(other)
-
- empty = []
- new_set = self.__class__(empty)
-
- for item in self:
- if item not in other:
- new_set.add(item)
-
- for item in other:
- if item not in self:
- new_set.add(item)
-
- return new_set
-
- # -------------------------------------------------------------------------
- def __xor__(self, other):
- """The '^' operator."""
-
- if not isinstance(other, CaseInsensitiveStringSet):
- raise WrongCompareSetClassError(other)
-
- return self.symmetric_difference(other)
-
- # -------------------------------------------------------------------------
- def isdisjoint(self, other):
-
- if not isinstance(other, CaseInsensitiveStringSet):
- raise WrongCompareSetClassError(other)
-
- for item in self:
- if item in other:
- return False
-
- for item in other:
- if item in self:
- return False
-
- return True
-
- # -------------------------------------------------------------------------
- def update(self, *others):
-
- for other in others:
- if not isinstance(other, CaseInsensitiveStringSet):
- raise WrongCompareSetClassError(other)
-
- for other in others:
- for item in other:
- if item not in self:
- self.add(item)
-
- # -------------------------------------------------------------------------
- def __ior__(self, *others):
- """The '|=' operator."""
-
- for other in others:
- if not isinstance(other, CaseInsensitiveStringSet):
- raise WrongCompareSetClassError(other)
-
- self.update(*others)
-
- # -------------------------------------------------------------------------
- def intersection_update(self, *others):
-
- for other in others:
- if not isinstance(other, CaseInsensitiveStringSet):
- raise WrongCompareSetClassError(other)
-
- for item in self:
- for other in others:
- if item not in other:
- self.discard(item)
- break
-
- # -------------------------------------------------------------------------
- def __iand__(self, *others):
- """The '&=' operator."""
-
- for other in others:
- if not isinstance(other, CaseInsensitiveStringSet):
- raise WrongCompareSetClassError(other)
-
- self.intersection_update(*others)
-
- # -------------------------------------------------------------------------
- def difference_update(self, *others):
-
- for other in others:
- if not isinstance(other, CaseInsensitiveStringSet):
- raise WrongCompareSetClassError(other)
-
- for item in self:
- for other in others:
- if item in other:
- self.discard(item)
- break
-
- # -------------------------------------------------------------------------
- def __isub__(self, *others):
- """The '-=' operator."""
-
- for other in others:
- if not isinstance(other, CaseInsensitiveStringSet):
- raise WrongCompareSetClassError(other)
-
- self.difference_update(*others)
-
- # -------------------------------------------------------------------------
- def symmetric_difference_update(self, other):
-
- if not isinstance(other, CaseInsensitiveStringSet):
- raise WrongCompareSetClassError(other)
-
- for item in self:
- if item in other:
- self.discard(item)
-
- for item in other:
- if item not in self:
- self.add(item)
-
- # -------------------------------------------------------------------------
- def __ixor__(self, other):
- """The '|=' operator."""
-
- if not isinstance(other, CaseInsensitiveStringSet):
- raise WrongCompareSetClassError(other)
-
- self.symmetric_difference_update(other)
-
- # -------------------------------------------------------------------------
- def remove(self, value):
-
- ival = value.lower()
- if ival in self._items:
- del self._items[ival]
- return
-
- raise KeyError(value)
-
- # -------------------------------------------------------------------------
- def pop(self):
-
- if len(self) == 0:
- raise IndexError("pop() from empty list")
-
- key = self._items.keys()[0]
- value = self._items[key]
- del self._items[key]
-
- return value
-
- # -------------------------------------------------------------------------
- def clear(self):
-
- self._items = {}
-
- # -------------------------------------------------------------------------
- def as_list(self):
-
- ret = []
- for item in self:
- ret.append(item)
-
- return ret
-
-
-# =============================================================================
-if __name__ == "__main__":
-
- pass
-
-# vim: tabstop=4 expandtab shiftwidth=4 softtabstop=4 list