codekingpro/portable-devtools
114k
1"""
2"""
3
4# Created on 2014.08.23
5#
6# Author: Giovanni Cannata
7#
8# Copyright 2014 - 2020 Giovanni Cannata
9#
10# This file is part of ldap3.
11#
12# ldap3 is free software: you can redistribute it and/or modify
13# it under the terms of the GNU Lesser General Public License as published
14# by the Free Software Foundation, either version 3 of the License, or
15# (at your option) any later version.
16#
17# ldap3 is distributed in the hope that it will be useful,
18# but WITHOUT ANY WARRANTY; without even the implied warranty of
19# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20# GNU Lesser General Public License for more details.
21#
22# You should have received a copy of the GNU Lesser General Public License
23# along with ldap3 in the COPYING and COPYING.LESSER files.
24# If not, see <http://www.gnu.org/licenses/>.
25
26try:
27 from collections.abc import MutableMapping, Mapping
28except ImportError:
29 from collections import MutableMapping, Mapping
30
31from .. import SEQUENCE_TYPES
32
33
34class CaseInsensitiveDict(MutableMapping):
35 def __init__(self, other=None, **kwargs):
36 self._store = dict() # store use the original key
37 self._case_insensitive_keymap = dict() # is a mapping ci_key -> key
38 if other or kwargs:
39 if other is None:
40 other = dict()
41 self.update(other, **kwargs)
42
43 def __contains__(self, item):
44 try:
45 self.__getitem__(item)
46 return True
47 except KeyError:
48 return False
49
50 @staticmethod
51 def _ci_key(key):
52 return key.strip().lower() if hasattr(key, 'lower') else key
53
54 def __delitem__(self, key):
55 ci_key = self._ci_key(key)
56 del self._store[self._case_insensitive_keymap[ci_key]]
57 del self._case_insensitive_keymap[ci_key]
58
59 def __setitem__(self, key, item):
60 ci_key = self._ci_key(key)
61 if ci_key in self._case_insensitive_keymap: # updates existing value
62 self._store[self._case_insensitive_keymap[ci_key]] = item
63 else: # new key
64 self._store[key] = item
65 self._case_insensitive_keymap[ci_key] = key
66
67 def __getitem__(self, key):
68 return self._store[self._case_insensitive_keymap[self._ci_key(key)]]
69
70 def __iter__(self):
71 return self._store.__iter__()
72
73 def __len__(self): # if len is 0 then the cidict appears as False in IF statement
74 return len(self._store)
75
76 def __repr__(self):
77 return repr(self._store)
78
79 def __str__(self):
80 return str(self._store)
81
82 def keys(self):
83 return self._store.keys()
84
85 def values(self):
86 return self._store.values()
87
88 def items(self):
89 return self._store.items()
90
91 def __eq__(self, other):
92 if not isinstance(other, (Mapping, dict)):
93 return NotImplemented
94
95 if isinstance(other, CaseInsensitiveDict):
96 if len(self.items()) != len(other.items()):
97 return False
98 else:
99 for key, value in self.items():
100 if not (key in other and other[key] == value):
101 return False
102 return True
103
104 return self == CaseInsensitiveDict(other)
105
106 def copy(self):
107 return CaseInsensitiveDict(self._store)
108
109
110class CaseInsensitiveWithAliasDict(CaseInsensitiveDict):
111 def __init__(self, other=None, **kwargs):
112 self._aliases = dict()
113 self._alias_keymap = dict() # is a mapping key -> [alias1, alias2, ...]
114 CaseInsensitiveDict.__init__(self, other, **kwargs)
115
116 def aliases(self):
117 return self._aliases.keys()
118
119 def __setitem__(self, key, value):
120 if isinstance(key, SEQUENCE_TYPES):
121 ci_key = self._ci_key(key[0])
122 if ci_key not in self._aliases:
123 CaseInsensitiveDict.__setitem__(self, key[0], value)
124 self.set_alias(ci_key, key[1:])
125 else:
126 raise KeyError('\'' + str(key[0] + ' already used as alias'))
127 else:
128 ci_key = self._ci_key(key)
129 if ci_key not in self._aliases:
130 CaseInsensitiveDict.__setitem__(self, key, value)
131 else:
132 self[self._aliases[ci_key]] = value
133
134 def __delitem__(self, key):
135 ci_key = self._ci_key(key)
136 try:
137 CaseInsensitiveDict.__delitem__(self, ci_key)
138 if ci_key in self._alias_keymap:
139 for alias in self._alias_keymap[ci_key][:]: # removes aliases, uses a copy of _alias_keymap because iterator gets confused when aliases are removed from _alias_keymap
140 self.remove_alias(alias)
141 return
142 except KeyError: # try to remove alias
143 if ci_key in self._aliases:
144 self.remove_alias(ci_key)
145
146 def set_alias(self, key, alias, ignore_duplicates=False):
147 if not isinstance(alias, SEQUENCE_TYPES):
148 alias = [alias]
149 for alias_to_add in alias:
150 ci_key = self._ci_key(key)
151 if ci_key in self._case_insensitive_keymap:
152 ci_alias = self._ci_key(alias_to_add)
153 if ci_alias not in self._case_insensitive_keymap: # checks if alias is used a key
154 if ci_alias not in self._aliases: # checks if alias is used as another alias
155 self._aliases[ci_alias] = ci_key
156 if ci_key in self._alias_keymap: # extends alias keymap
157 self._alias_keymap[ci_key].append(self._ci_key(ci_alias))
158 else:
159 self._alias_keymap[ci_key] = list()
160 self._alias_keymap[ci_key].append(self._ci_key(ci_alias))
161 else:
162 if ci_key in self._alias_keymap and ci_alias in self._alias_keymap[ci_key]: # passes if alias is already defined to the same key
163 pass
164 elif not ignore_duplicates:
165 raise KeyError('\'' + str(alias_to_add) + '\' already used as alias')
166 else:
167 if ci_key == self._ci_key(self._case_insensitive_keymap[ci_alias]): # passes if alias is already defined to the same key
168 pass
169 elif not ignore_duplicates:
170 raise KeyError('\'' + str(alias_to_add) + '\' already used as key')
171 else:
172 for keymap in self._alias_keymap:
173 if ci_key in self._alias_keymap[keymap]: # kye is already aliased
174 self.set_alias(keymap, alias + [ci_key], ignore_duplicates=ignore_duplicates)
175 break
176 else:
177 raise KeyError('\'' + str(ci_key) + '\' is not an existing alias or key')
178
179 def remove_alias(self, alias):
180 if not isinstance(alias, SEQUENCE_TYPES):
181 alias = [alias]
182 for alias_to_remove in alias:
183 ci_alias = self._ci_key(alias_to_remove)
184 self._alias_keymap[self._aliases[ci_alias]].remove(ci_alias)
185 if not self._alias_keymap[self._aliases[ci_alias]]: # remove keymap if empty
186 del self._alias_keymap[self._aliases[ci_alias]]
187 del self._aliases[ci_alias]
188
189 def __getitem__(self, key):
190 try:
191 return CaseInsensitiveDict.__getitem__(self, key)
192 except KeyError:
193 return CaseInsensitiveDict.__getitem__(self, self._aliases[self._ci_key(key)])
194
195 def copy(self):
196 new = CaseInsensitiveWithAliasDict(self._store)
197 new._aliases = self._aliases.copy()
198 new._alias_keymap = self._alias_keymap
199 return new
200 