class ProxyObject: def __init__(self, obj, mappings): self._obj = obj self._mappings = mappings self._props = {} self._create_props() def __getattr__(self, item): if item not in self._props: raise AttributeError(item) return self._props[item] def __hasattr__(self, item): return item in self._props def as_dict(self): return self._props.copy() def __repr__(self): if "key" in self._props: return f"ProxyObject(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"ProxyObject({props_as_str})" def __eq__(self, other): if not isinstance(other, ProxyObject): return False return self._props == other._props def __hash__(self): return hash(tuple(sorted(self._props.items()))) def _create_props(self): for prop_name, path in self._mappings.items(): attrs = path.split(".") current = self._obj # Check if path ends with wildcard has_wildcard_in_path = attrs[-1] == "*" # Navigate to the target object for attr in attrs: if attr == "*": break if hasattr(current, attr): current = getattr(current, attr) else: break # Handle wildcard cases if prop_name == "*" or has_wildcard_in_path: # Copy all properties from current object if hasattr(current, '__dict__'): for attr_name, attr_value in current.__dict__.items(): if not attr_name.startswith('_'): # If attr_value is an object with properties, expand them if has_wildcard_in_path and hasattr(attr_value, '__dict__'): for sub_attr_name, sub_attr_value in attr_value.__dict__.items(): if not sub_attr_name.startswith('_'): self._props[sub_attr_name] = sub_attr_value else: # Simple value or prop_name == "*" self._props[attr_name] = attr_value else: # Normal mapping (no wildcard) self._props[prop_name] = current