1+ from typing import Tuple , Sequence , Any , Mapping , Optional
2+
3+ _RaiseKeyError = object () # singleton for no-default behavior
4+
5+
16class CaseInsensitiveDict (dict ):
27 """
38 A dictionary in which the keys are case-insensitive.
@@ -17,16 +22,16 @@ class CaseInsensitiveDict(dict):
1722
1823 """
1924
20- def __init__ (self , seq = None , ** kwargs ):
25+ def __init__ (self , seq : Sequence = None , ** kwargs ) -> None :
2126 super ().__init__ (seq or {}, ** kwargs )
2227 self .lowercase_dict = {}
2328 for key in self :
2429 self ._register_key (key )
2530
26- def __getitem__ (self , key : str ):
31+ def __getitem__ (self , key : str ) -> Any :
2732 return super ().__getitem__ (self .lowercase_dict [key .lower ()])
2833
29- def get (self , key : str , default = None ):
34+ def get (self , key : str , default : Any = None ) -> Any :
3035 """
3136 Get the value for a given case-insensitive key.
3237
@@ -50,14 +55,126 @@ def get(self, key: str, default=None):
5055 else :
5156 return default
5257
53- def __setitem__ (self , key : str , value ) :
58+ def __setitem__ (self , key : str , value : Any ) -> None :
5459 self ._register_key (key )
5560 super ().__setitem__ (key , value )
5661
57- def __contains__ (self , key : str ):
62+ def __contains__ (self , key : str ) -> bool :
5863 return self .lowercase_dict .__contains__ (key .lower ())
5964
60- def _register_key (self , key : str ):
65+ def __delitem__ (self , key ) -> None :
66+ key = self .lowercase_dict .get (key .lower (), key )
67+ super ().__delitem__ (key )
68+ del self .lowercase_dict [key .lower ()]
69+
70+ def clear (self ) -> None :
71+ """Remove all items from the dictionary."""
72+ super ().clear ()
73+ self .lowercase_dict .clear ()
74+
75+ def pop (self , key : str , default = _RaiseKeyError ) -> Any :
76+ """
77+ Remove and return the value for a given key from the dictionary.
78+
79+ If key is in the dictionary, remove it and return its value, else return default.
80+ If default is not given and key is not in the dictionary, a KeyError is raised.
81+
82+ Parameters
83+ ----------
84+ key: str
85+ The key to look up (possibly with a different casing).
86+
87+ default: Any
88+ The result to return if the key is not present.
89+
90+ Returns
91+ -------
92+ Any
93+ The value associated with the case-insensitive version of `key`, or None
94+ if `key` is not present.
95+
96+ """
97+ if default is _RaiseKeyError :
98+ if key not in self :
99+ raise KeyError (key )
100+ val = super ().pop (self .lowercase_dict [key .lower ()])
101+ else :
102+ val = super ().pop (self .lowercase_dict .get (key .lower ()), default )
103+ if key in self :
104+ del self .lowercase_dict [key .lower ()]
105+ return val
106+
107+ def popitem (self ) -> Tuple :
108+ """
109+ Remove and return a (key, value) pair from the dictionary.
110+
111+ popitem() is useful to destructively iterate over a dictionary, as often used
112+ in set algorithms. If the dictionary is empty, calling popitem() raises a
113+ KeyError.
114+
115+ Changed in version 3.7: LIFO order is now guaranteed. In prior versions,
116+ popitem() would return an arbitrary key/value pair.
117+
118+ Returns
119+ -------
120+ Tuple(str, Any)
121+ The key-value pair
122+
123+ """
124+ result = super ().popitem ()
125+ del self .lowercase_dict [result [0 ].lower ()]
126+ return result
127+
128+ def copy (self ) -> 'CaseInsensitiveDict' :
129+ """
130+ Return a shallow copy of the dictionary.
131+
132+ Returns
133+ -------
134+ CaseInsensitiveDict
135+ A duplicate of the dictionary
136+
137+ """
138+ return CaseInsensitiveDict (super ().copy ())
139+
140+ def update (self , mapping : Optional [Mapping [str , Any ]] = None , ** kwargs ) -> None :
141+ """
142+ Update the dictionary with the key/value pairs from other, overwriting existing keys.
143+
144+ update() accepts either another dictionary object or an iterable of
145+ key/value pairs (as tuples or other iterables of length two). If keyword
146+ arguments are specified, the dictionary is then updated with those
147+ key/value pairs: d.update(red=1, blue=2).
148+
149+ Parameters
150+ ----------
151+ mapping: Mapping
152+ The set of (key, value) pairs to store
153+
154+ kwargs: (str, Any)
155+ Alternatively, the set of keyword arguments
156+
157+ """
158+ if mapping is None :
159+ mapping = dict ()
160+ no_mapping = True
161+ else :
162+ no_mapping = False
163+ for key in list (mapping .keys ()) + list (kwargs .keys ()):
164+ if key .lower () in self .lowercase_dict :
165+ prev = self .lowercase_dict [key .lower ()]
166+ if prev != key :
167+ raise ValueError (
168+ "Key '{}' already exists in dict with different case: "
169+ "'{}'" .format (key , prev ))
170+ if no_mapping :
171+ super ().update (** kwargs )
172+ else :
173+ super ().update (mapping , ** kwargs )
174+ for key in list (mapping .keys ()) + list (kwargs .keys ()):
175+ self ._register_key (key )
176+
177+ def _register_key (self , key : str ) -> None :
61178 """
62179 Register a key to the dictionary.
63180
0 commit comments