Source code for metadsl.dict_tools

import collections.abc
import dataclasses
import typing

__all__ = ["safe_merge", "Item", "UnhashableMapping", "HashableMapping"]

T = typing.TypeVar("T")
V = typing.TypeVar("V")


[docs]@dataclasses.dataclass class Item(typing.Generic[T, V]): key: T value: V
[docs]@dataclasses.dataclass(init=False, frozen=True) class HashableMapping(collections.abc.Mapping, typing.Generic[T, V]): """ Like a dictionary, but immutable and hashable. """ _items: typing.Tuple[typing.Tuple[T, V], ...] def __init__(self, mapping: typing.Mapping[T, V]): object.__setattr__(self, "_items", tuple((k, v) for k, v in mapping.items())) def __hash__(self): return hash(self._items) def __getitem__(self, key: T) -> V: for item in self._items: if item[0] == key: return item[1] raise KeyError() def __iter__(self): for item in self._items: yield item[0] def __len__(self): return len(self._items) def __str__(self): return "{" + ", ".join(f"{k}={v}" for k, v in self._items) + "}" def __repr__(self): return f"HashableMapping({str(self)})"
[docs]@dataclasses.dataclass(init=False) class UnhashableMapping(collections.abc.MutableMapping, typing.Generic[T, V]): """ Like a dictionary, but can have unhashable keys. """ _items: typing.List[Item[T, V]] def __init__(self, *items: Item[T, V]): self._items = list(items) def __getitem__(self, key: T) -> V: item = self._find_item(key) return item.value def __setitem__(self, key: T, value: V) -> None: try: item = self._find_item(key) except KeyError: item = Item(key, value) self._items.append(item) else: item.value = value def __delitem__(self, key: T) -> None: self._items.remove(self._find_item(key)) def _find_item(self, key: T) -> Item[T, V]: for item in self._items: if item.key == key: return item raise KeyError() def __iter__(self): for item in self._items: yield item.key def __len__(self): return len(self._items) def __str__(self): return "{" + ", ".join(f"{item.key}={item.value}" for item in self._items) + "}" def __repr__(self): return f"UnhashableMapping({str(self)})"
[docs]def safe_merge( *mappings: typing.Mapping[T, V], dict_constructor: typing.Type[typing.MutableMapping] = dict, ) -> typing.Mapping[T, V]: """ Combing mappings by merging the dictionaries. It raises a ValueError if there are duplicate keys and they are not equal. """ res: typing.MutableMapping[T, V] = dict_constructor() for mapping in mappings: for key, value in mapping.items(): if key in res: if res[key] != value: raise ValueError( f"for key {key}, first value {res[key]} not equal to second value {value} when merging {mappings}" ) else: res[key] = value return res