summaryrefslogtreecommitdiff
path: root/dns/immutable.py
blob: dc48fe852af628dcc7b7780c5fa17d1462f38afb (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
# Copyright (C) Dnspython Contributors, see LICENSE for text of ISC license

import collections.abc
import sys

if sys.version_info >= (3, 7):
    odict = dict
else:
    from collections import OrderedDict as odict  # pragma: no cover


class ImmutableDict(collections.abc.Mapping):
    def __init__(self, dictionary, no_copy=False):
        """Make an immutable dictionary from the specified dictionary.

        If *no_copy* is `True`, then *dictionary* will be wrapped instead
        of copied.  Only set this if you are sure there will be no external
        references to the dictionary.
        """
        if no_copy and isinstance(dictionary, odict):
            self._odict = dictionary
        else:
            self._odict = odict(dictionary)
        self._hash = None

    def __getitem__(self, key):
        return self._odict.__getitem__(key)

    def __hash__(self):
        if self._hash is None:
            self._hash = 0
            for key in sorted(self._odict.keys()):
                self._hash ^= hash(key)
        return self._hash

    def __len__(self):
        return len(self._odict)

    def __iter__(self):
        return iter(self._odict)


def constify(o):
    """
    Convert mutable types to immutable types.
    """
    if isinstance(o, bytearray):
        return bytes(o)
    if isinstance(o, tuple):
        try:
            hash(o)
            return o
        except Exception:
            return tuple(constify(elt) for elt in o)
    if isinstance(o, list):
        return tuple(constify(elt) for elt in o)
    if isinstance(o, dict):
        cdict = odict()
        for k, v in o.items():
            cdict[k] = constify(v)
        return ImmutableDict(cdict, True)
    return o