| 1 | """ |
|---|
| 2 | Tools to mess with dicts. |
|---|
| 3 | """ |
|---|
| 4 | |
|---|
| 5 | from __future__ import annotations |
|---|
| 6 | from typing import Callable, TypeVar |
|---|
| 7 | |
|---|
| 8 | K = TypeVar("K") |
|---|
| 9 | V = TypeVar("V") |
|---|
| 10 | |
|---|
| 11 | def filter(pred: Callable[[V], bool], orig: dict[K, V]) -> dict[K, V]: |
|---|
| 12 | """ |
|---|
| 13 | Filter out key/value pairs whose value fails to match a predicate. |
|---|
| 14 | """ |
|---|
| 15 | return { |
|---|
| 16 | k: v |
|---|
| 17 | for (k, v) |
|---|
| 18 | in orig.items() |
|---|
| 19 | if pred(v) |
|---|
| 20 | } |
|---|
| 21 | |
|---|
| 22 | class DictOfSets(dict): |
|---|
| 23 | def add(self, key, value): |
|---|
| 24 | if key in self: |
|---|
| 25 | self[key].add(value) |
|---|
| 26 | else: |
|---|
| 27 | self[key] = set([value]) |
|---|
| 28 | |
|---|
| 29 | def update(self, otherdictofsets): |
|---|
| 30 | for key, values in list(otherdictofsets.items()): |
|---|
| 31 | if key in self: |
|---|
| 32 | self[key].update(values) |
|---|
| 33 | else: |
|---|
| 34 | self[key] = set(values) |
|---|
| 35 | |
|---|
| 36 | def discard(self, key, value): |
|---|
| 37 | if not key in self: |
|---|
| 38 | return |
|---|
| 39 | self[key].discard(value) |
|---|
| 40 | if not self[key]: |
|---|
| 41 | del self[key] |
|---|
| 42 | |
|---|
| 43 | class AuxValueDict(dict): |
|---|
| 44 | """I behave like a regular dict, but each key is associated with two |
|---|
| 45 | values: the main value, and an auxilliary one. Setting the main value |
|---|
| 46 | (with the usual d[key]=value) clears the auxvalue. You can set both main |
|---|
| 47 | and auxvalue at the same time, and can retrieve the values separately. |
|---|
| 48 | |
|---|
| 49 | The main use case is a dictionary that represents unpacked child values |
|---|
| 50 | for a directory node, where a common pattern is to modify one or more |
|---|
| 51 | children and then pass the dict back to a packing function. The original |
|---|
| 52 | packed representation can be cached in the auxvalue, and the packing |
|---|
| 53 | function can use it directly on all unmodified children. On large |
|---|
| 54 | directories with a complex packing function, this can save considerable |
|---|
| 55 | time.""" |
|---|
| 56 | |
|---|
| 57 | def __init__(self, *args, **kwargs): |
|---|
| 58 | super(AuxValueDict, self).__init__(*args, **kwargs) |
|---|
| 59 | self.auxilliary = {} |
|---|
| 60 | |
|---|
| 61 | def __setitem__(self, key, value): |
|---|
| 62 | super(AuxValueDict, self).__setitem__(key, value) |
|---|
| 63 | self.auxilliary[key] = None # clear the auxvalue |
|---|
| 64 | |
|---|
| 65 | def __delitem__(self, key): |
|---|
| 66 | super(AuxValueDict, self).__delitem__(key) |
|---|
| 67 | self.auxilliary.pop(key) |
|---|
| 68 | |
|---|
| 69 | def get_aux(self, key, default=None): |
|---|
| 70 | """Retrieve the auxilliary value. There is no way to distinguish |
|---|
| 71 | between an auxvalue of 'None' and a key that does not have an |
|---|
| 72 | auxvalue, and get_aux() will not raise KeyError when called with a |
|---|
| 73 | missing key.""" |
|---|
| 74 | return self.auxilliary.get(key, default) |
|---|
| 75 | |
|---|
| 76 | def set_with_aux(self, key, value, auxilliary): |
|---|
| 77 | """Set both the main value and the auxilliary value. There is no way |
|---|
| 78 | to distinguish between an auxvalue of 'None' and a key that does not |
|---|
| 79 | have an auxvalue.""" |
|---|
| 80 | super(AuxValueDict, self).__setitem__(key, value) |
|---|
| 81 | self.auxilliary[key] = auxilliary |
|---|
| 82 | |
|---|
| 83 | |
|---|
| 84 | class _TypedKeyDict(dict): |
|---|
| 85 | """Dictionary that enforces key type. |
|---|
| 86 | |
|---|
| 87 | Doesn't override everything, but probably good enough to catch most |
|---|
| 88 | problems. |
|---|
| 89 | |
|---|
| 90 | Subclass and override KEY_TYPE. |
|---|
| 91 | """ |
|---|
| 92 | |
|---|
| 93 | KEY_TYPE = object |
|---|
| 94 | |
|---|
| 95 | def __init__(self, *args, **kwargs): |
|---|
| 96 | dict.__init__(self, *args, **kwargs) |
|---|
| 97 | for key in self: |
|---|
| 98 | if not isinstance(key, self.KEY_TYPE): |
|---|
| 99 | raise TypeError("{} must be of type {}".format( |
|---|
| 100 | repr(key), self.KEY_TYPE)) |
|---|
| 101 | |
|---|
| 102 | |
|---|
| 103 | def _make_enforcing_override(K, method_name): |
|---|
| 104 | def f(self, key, *args, **kwargs): |
|---|
| 105 | if not isinstance(key, self.KEY_TYPE): |
|---|
| 106 | raise TypeError("{} must be of type {}".format( |
|---|
| 107 | repr(key), self.KEY_TYPE)) |
|---|
| 108 | return getattr(dict, method_name)(self, key, *args, **kwargs) |
|---|
| 109 | f.__name__ = method_name |
|---|
| 110 | setattr(K, method_name, f) |
|---|
| 111 | |
|---|
| 112 | for _method_name in ["__setitem__", "__getitem__", "setdefault", "get", |
|---|
| 113 | "__delitem__"]: |
|---|
| 114 | _make_enforcing_override(_TypedKeyDict, _method_name) |
|---|
| 115 | del _method_name |
|---|
| 116 | |
|---|
| 117 | |
|---|
| 118 | class BytesKeyDict(_TypedKeyDict): |
|---|
| 119 | """Keys should be bytes.""" |
|---|
| 120 | |
|---|
| 121 | KEY_TYPE = bytes |
|---|
| 122 | |
|---|
| 123 | |
|---|
| 124 | class UnicodeKeyDict(_TypedKeyDict): |
|---|
| 125 | """Keys should be unicode strings.""" |
|---|
| 126 | |
|---|
| 127 | KEY_TYPE = str |
|---|