Plain is headed towards 1.0! Subscribe for development updates →

  1import copy
  2from collections.abc import Mapping
  3
  4
  5class OrderedSet:
  6    """
  7    A set which keeps the ordering of the inserted items.
  8    """
  9
 10    def __init__(self, iterable=None):
 11        self.dict = dict.fromkeys(iterable or ())
 12
 13    def add(self, item):
 14        self.dict[item] = None
 15
 16    def remove(self, item):
 17        del self.dict[item]
 18
 19    def discard(self, item):
 20        try:
 21            self.remove(item)
 22        except KeyError:
 23            pass
 24
 25    def __iter__(self):
 26        return iter(self.dict)
 27
 28    def __reversed__(self):
 29        return reversed(self.dict)
 30
 31    def __contains__(self, item):
 32        return item in self.dict
 33
 34    def __bool__(self):
 35        return bool(self.dict)
 36
 37    def __len__(self):
 38        return len(self.dict)
 39
 40    def __repr__(self):
 41        data = repr(list(self.dict)) if self.dict else ""
 42        return f"{self.__class__.__qualname__}({data})"
 43
 44
 45class MultiValueDictKeyError(KeyError):
 46    pass
 47
 48
 49class MultiValueDict(dict):
 50    """
 51    A subclass of dictionary customized to handle multiple values for the
 52    same key.
 53
 54    >>> d = MultiValueDict({'name': ['Adrian', 'Simon'], 'position': ['Developer']})
 55    >>> d['name']
 56    'Simon'
 57    >>> d.getlist('name')
 58    ['Adrian', 'Simon']
 59    >>> d.getlist('doesnotexist')
 60    []
 61    >>> d.getlist('doesnotexist', ['Adrian', 'Simon'])
 62    ['Adrian', 'Simon']
 63    >>> d.get('lastname', 'nonexistent')
 64    'nonexistent'
 65    >>> d.setlist('lastname', ['Holovaty', 'Willison'])
 66
 67    This class exists to solve the irritating problem raised by cgi.parse_qs,
 68    which returns a list for every key, even though most web forms submit
 69    single name-value pairs.
 70    """
 71
 72    def __init__(self, key_to_list_mapping=()):
 73        super().__init__(key_to_list_mapping)
 74
 75    def __repr__(self):
 76        return f"<{self.__class__.__name__}: {super().__repr__()}>"
 77
 78    def __getitem__(self, key):
 79        """
 80        Return the last data value for this key, or [] if it's an empty list;
 81        raise KeyError if not found.
 82        """
 83        try:
 84            list_ = super().__getitem__(key)
 85        except KeyError:
 86            raise MultiValueDictKeyError(key)
 87        try:
 88            return list_[-1]
 89        except IndexError:
 90            return []
 91
 92    def __setitem__(self, key, value):
 93        super().__setitem__(key, [value])
 94
 95    def __copy__(self):
 96        return self.__class__([(k, v[:]) for k, v in self.lists()])
 97
 98    def __deepcopy__(self, memo):
 99        result = self.__class__()
100        memo[id(self)] = result
101        for key, value in dict.items(self):
102            dict.__setitem__(
103                result, copy.deepcopy(key, memo), copy.deepcopy(value, memo)
104            )
105        return result
106
107    def __getstate__(self):
108        return {**self.__dict__, "_data": {k: self._getlist(k) for k in self}}
109
110    def __setstate__(self, obj_dict):
111        data = obj_dict.pop("_data", {})
112        for k, v in data.items():
113            self.setlist(k, v)
114        self.__dict__.update(obj_dict)
115
116    def get(self, key, default=None):
117        """
118        Return the last data value for the passed key. If key doesn't exist
119        or value is an empty list, return `default`.
120        """
121        try:
122            val = self[key]
123        except KeyError:
124            return default
125        if val == []:
126            return default
127        return val
128
129    def _getlist(self, key, default=None, force_list=False):
130        """
131        Return a list of values for the key.
132
133        Used internally to manipulate values list. If force_list is True,
134        return a new copy of values.
135        """
136        try:
137            values = super().__getitem__(key)
138        except KeyError:
139            if default is None:
140                return []
141            return default
142        else:
143            if force_list:
144                values = list(values) if values is not None else None
145            return values
146
147    def getlist(self, key, default=None):
148        """
149        Return the list of values for the key. If key doesn't exist, return a
150        default value.
151        """
152        return self._getlist(key, default, force_list=True)
153
154    def setlist(self, key, list_):
155        super().__setitem__(key, list_)
156
157    def setdefault(self, key, default=None):
158        if key not in self:
159            self[key] = default
160            # Do not return default here because __setitem__() may store
161            # another value -- QueryDict.__setitem__() does. Look it up.
162        return self[key]
163
164    def setlistdefault(self, key, default_list=None):
165        if key not in self:
166            if default_list is None:
167                default_list = []
168            self.setlist(key, default_list)
169            # Do not return default_list here because setlist() may store
170            # another value -- QueryDict.setlist() does. Look it up.
171        return self._getlist(key)
172
173    def appendlist(self, key, value):
174        """Append an item to the internal list associated with key."""
175        self.setlistdefault(key).append(value)
176
177    def items(self):
178        """
179        Yield (key, value) pairs, where value is the last item in the list
180        associated with the key.
181        """
182        for key in self:
183            yield key, self[key]
184
185    def lists(self):
186        """Yield (key, list) pairs."""
187        return iter(super().items())
188
189    def values(self):
190        """Yield the last value on every key list."""
191        for key in self:
192            yield self[key]
193
194    def copy(self):
195        """Return a shallow copy of this object."""
196        return copy.copy(self)
197
198    def update(self, *args, **kwargs):
199        """Extend rather than replace existing key lists."""
200        if len(args) > 1:
201            raise TypeError("update expected at most 1 argument, got %d" % len(args))  # noqa: UP031
202        if args:
203            arg = args[0]
204            if isinstance(arg, MultiValueDict):
205                for key, value_list in arg.lists():
206                    self.setlistdefault(key).extend(value_list)
207            else:
208                if isinstance(arg, Mapping):
209                    arg = arg.items()
210                for key, value in arg:
211                    self.setlistdefault(key).append(value)
212        for key, value in kwargs.items():
213            self.setlistdefault(key).append(value)
214
215    def dict(self):
216        """Return current object as a dict with singular values."""
217        return {key: self[key] for key in self}
218
219
220class ImmutableList(tuple):
221    """
222    A tuple-like object that raises useful errors when it is asked to mutate.
223
224    Example::
225
226        >>> a = ImmutableList(range(5), warning="You cannot mutate this.")
227        >>> a[3] = '4'
228        Traceback (most recent call last):
229            ...
230        AttributeError: You cannot mutate this.
231    """
232
233    def __new__(cls, *args, warning="ImmutableList object is immutable.", **kwargs):
234        self = tuple.__new__(cls, *args, **kwargs)
235        self.warning = warning
236        return self
237
238    def complain(self, *args, **kwargs):
239        raise AttributeError(self.warning)
240
241    # All list mutation functions complain.
242    __delitem__ = complain
243    __delslice__ = complain
244    __iadd__ = complain
245    __imul__ = complain
246    __setitem__ = complain
247    __setslice__ = complain
248    append = complain
249    extend = complain
250    insert = complain
251    pop = complain
252    remove = complain
253    sort = complain
254    reverse = complain
255
256
257class DictWrapper(dict):
258    """
259    Wrap accesses to a dictionary so that certain values (those starting with
260    the specified prefix) are passed through a function before being returned.
261    The prefix is removed before looking up the real value.
262
263    Used by the SQL construction code to ensure that values are correctly
264    quoted before being used.
265    """
266
267    def __init__(self, data, func, prefix):
268        super().__init__(data)
269        self.func = func
270        self.prefix = prefix
271
272    def __getitem__(self, key):
273        """
274        Retrieve the real value after stripping the prefix string (if
275        present). If the prefix is present, pass the value through self.func
276        before returning, otherwise return the raw value.
277        """
278        use_func = key.startswith(self.prefix)
279        key = key.removeprefix(self.prefix)
280        value = super().__getitem__(key)
281        if use_func:
282            return self.func(value)
283        return value
284
285
286class CaseInsensitiveMapping(Mapping):
287    """
288    Mapping allowing case-insensitive key lookups. Original case of keys is
289    preserved for iteration and string representation.
290
291    Example::
292
293        >>> ci_map = CaseInsensitiveMapping({'name': 'Jane'})
294        >>> ci_map['Name']
295        Jane
296        >>> ci_map['NAME']
297        Jane
298        >>> ci_map['name']
299        Jane
300        >>> ci_map  # original case preserved
301        {'name': 'Jane'}
302    """
303
304    def __init__(self, data):
305        self._store = {k.lower(): (k, v) for k, v in self._unpack_items(data)}
306
307    def __getitem__(self, key):
308        return self._store[key.lower()][1]
309
310    def __len__(self):
311        return len(self._store)
312
313    def __eq__(self, other):
314        return isinstance(other, Mapping) and {
315            k.lower(): v for k, v in self.items()
316        } == {k.lower(): v for k, v in other.items()}
317
318    def __iter__(self):
319        return (original_key for original_key, value in self._store.values())
320
321    def __repr__(self):
322        return repr(dict(self._store.values()))
323
324    def copy(self):
325        return self
326
327    @staticmethod
328    def _unpack_items(data):
329        # Explicitly test for dict first as the common case for performance,
330        # avoiding abc's __instancecheck__ and _abc_instancecheck for the
331        # general Mapping case.
332        if isinstance(data, dict | Mapping):
333            yield from data.items()
334            return
335        for i, elem in enumerate(data):
336            if len(elem) != 2:
337                raise ValueError(
338                    f"dictionary update sequence element #{i} has length {len(elem)}; "
339                    "2 is required."
340                )
341            if not isinstance(elem[0], str):
342                raise ValueError(
343                    f"Element key {elem[0]!r} invalid, only strings are allowed"
344                )
345            yield elem