class Expando: """ Readonly dynamic class that eases the access to attributes and sub attributes It is initialized with a dict You can then access the property using dot '.' (ex. obj.prop1.prop2) """ def __init__(self, props): self._props = props def __getattr__(self, item): if item not in self._props: raise AttributeError(item) current = self._props[item] return Expando(current) if isinstance(current, dict) else current def __setitem__(self, key, value): self._props[key] = value def get(self, path): """ returns the value, from a string with represents the path :param path: :return: """ current = self._props for attr in path.split("."): if isinstance(current, list): temp = [] for value in current: if value and attr in value: temp.append(value[attr]) current = temp else: if current is None or attr not in current: return None current = current[attr] return current def as_dict(self): """ Return the information as a dictionary :return: """ return self._props.copy() def to_dict(self, mappings: dict) -> dict: return {prop_name: self.get(path) for path, prop_name in mappings.items() if prop_name is not None} def __hasattr__(self, item): return item in self._props def __repr__(self): if "key" in self._props: return f"Expando(key={self._props["key"]})" props_as_str = str(self._props) if len(props_as_str) > 50: props_as_str = props_as_str[:50] + "..." return f"Expando({props_as_str})" def __eq__(self, other): if not isinstance(other, Expando): return False return self._props == other._props def __hash__(self): return hash(tuple(sorted(self._props.items())))