from __future__ import annotations from collections import defaultdict from functools import lru_cache from typing import Any, Dict, List, Optional from botocore import xform_name from moto.core.utils import get_service_model class XFormedAttributeAccessMixin: """Mixin allowing access to "xformed" attributes: obj.DBInstanceIdentifier will retrieve the value of obj.db_instance_identifier """ BOTOCORE_MODEL: Optional[str] = None _model_attribute_aliases: Dict[str, List[str]] = {} _xform_cache: Dict[str, str] = {} def __getattr__(self, name: str) -> Any: if name in self.model_attributes: return self.get_modeled_attribute(name) raise AttributeError(f"Attribute '{name}' not found!") def get_modeled_attribute(self, attr_name: str) -> Any: for attr_alias in self.model_attribute_aliases[attr_name]: try: return super().__getattribute__(attr_alias) except AttributeError: pass else: raise AttributeError @property def model_attributes(self) -> List[str]: return list(self.model_attribute_aliases.keys()) @property def model_attribute_aliases(self) -> Dict[str, List[str]]: if not self._model_attribute_aliases: self._model_attribute_aliases = self.get_model_attributes_info() return self._model_attribute_aliases @classmethod @lru_cache() def get_model_attributes_info(cls) -> Dict[str, List[str]]: service_name = cls.__module__.split(".")[1] model_name = cls.BOTOCORE_MODEL or cls.__name__ service_model = get_service_model(service_name) model_shape = service_model.shape_for(model_name) valid_attributes: Dict[str, List[str]] = defaultdict(list) for member_name, member_shape in model_shape.members.items(): # type: ignore[attr-defined] aliases = valid_attributes[member_name] if member_shape.type_name == "list": if member_name != member_shape.name: xformed_name = cls._xform_name(member_shape.name) aliases.append(xformed_name) xformed_member_name = cls._xform_name(member_name) aliases.append(xformed_member_name) if member_name.startswith(model_name): short_name = member_name[len(model_name) :] xformed_short_name = cls._xform_name(short_name) aliases.append(xformed_short_name) return valid_attributes @classmethod def _xform_name(cls, name: str) -> str: return xform_name(name, _xform_cache=cls._xform_cache)
Memory