from collections.abc import Mapping from dataclasses import dataclass, field @dataclass class AttributeMap(Mapping): """ AttributeMap is a data class that is also a mapping, converting a dict into an object with attributes. Example: >>> amap = AttributeMap(attributes={'foo': True, 'bar': False}) >>> amap.foo True >>> amap.bar False Instantiating an AttributeMap using the from_dict() class method will recursively transform dictionary members sinto AttributeMaps: >>> nested_dict = {'foo': {'bar': {'baz': True}, 'boz': False}} >>> amap = AttributeMap.from_dict(nested_dict) >>> amap.foo.bar.baz True >>> amap.foo.boz False The dictionary can be accessed directly via 'attributes': >>> amap = AttributeMap(attributes={'foo': True, 'bar': False}) >>> list(amap.attributes.keys()): >>>['foo', 'bar'] Because AttributeMap is a mapping, you can use it anywhere you would use a regular mapping, like a dict: >>> amap = AttributeMap(attributes={'foo': True, 'bar': False}) >>> 'foo' in amap True >>> "{foo}, {bar}".format(**amap) True, False """ attributes: field(default_factory=dict) def __getattr__(self, attr): if attr in self.attributes: return self.attributes[attr] return self.__getattribute__(attr) def __len__(self): return len(self.attributes) def __getitem__(self, key): return self.attributes[key] def __iter__(self): return iter(self.attributes) @classmethod def from_dict(cls, kwargs: dict): """ Create a new AttributeMap object using keyword arguments. Dicts are recursively converted to AttributeMap objects; everything else is passed as-is. """ attrs = {} for k, v in sorted(kwargs.items()): attrs[k] = AttributeMap.from_dict(v) if type(v) is dict else v return cls(attributes=attrs)