Source code for qcodes.utils.attribute_helpers

from contextlib import contextmanager
from typing import TYPE_CHECKING, Any, ClassVar, Union

if TYPE_CHECKING:
    from collections.abc import Iterator, Sequence


[docs] class DelegateAttributes: """ Mixin class to create attributes of this object by delegating them to one or more dictionaries and/or objects. Also fixes ``__dir__`` so the delegated attributes will show up in ``dir()`` and ``autocomplete``. Attribute resolution order: 1. Real attributes of this object. 2. Keys of each dictionary in ``delegate_attr_dicts`` (in order). 3. Attributes of each object in ``delegate_attr_objects`` (in order). """ delegate_attr_dicts: ClassVar[list[str]] = [] """ A list of names (strings) of dictionaries which are (or will be) attributes of ``self``, whose keys should be treated as attributes of ``self``. """ delegate_attr_objects: ClassVar[list[str]] = [] """ A list of names (strings) of objects which are (or will be) attributes of ``self``, whose attributes should be passed through to ``self``. """ omit_delegate_attrs: ClassVar[list[str]] = [] """ A list of attribute names (strings) to *not* delegate to any other dictionary or object. """ def __getattr__(self, key: str) -> Any: if key in self.omit_delegate_attrs: raise AttributeError( f"'{self.__class__.__name__}' does not delegate attribute {key}" ) for name in self.delegate_attr_dicts: if key == name: # needed to prevent infinite loops! raise AttributeError( f"dict '{key}' has not been created in object '{self.__class__.__name__}'" ) try: d = getattr(self, name, None) if d is not None: return d[key] except KeyError: pass for name in self.delegate_attr_objects: if key == name: raise AttributeError( f"object '{key}' has not been created in object '{self.__class__.__name__}'" ) try: obj = getattr(self, name, None) if obj is not None: return getattr(obj, key) except AttributeError: pass raise AttributeError( f"'{self.__class__.__name__}' object and its delegates have no attribute '{key}'" ) def __dir__(self) -> list[str]: names = list(super().__dir__()) for name in self.delegate_attr_dicts: d = getattr(self, name, None) if d is not None: names += [k for k in d.keys() if k not in self.omit_delegate_attrs] for name in self.delegate_attr_objects: obj = getattr(self, name, None) if obj is not None: names += [k for k in dir(obj) if k not in self.omit_delegate_attrs] return sorted(set(names))
[docs] def strip_attrs(obj: object, whitelist: "Sequence[str]" = ()) -> None: """ Irreversibly remove all direct instance attributes of object, to help with disposal, breaking circular references. Args: obj: Object to be stripped. whitelist: List of names that are not stripped from the object. """ try: lst = set(list(obj.__dict__.keys())) - set(whitelist) for key in lst: try: del obj.__dict__[key] except Exception: pass except Exception: pass
[docs] def checked_getattr( instance: Any, attribute: str, expected_type: Union[type, tuple[type, ...]] ) -> Any: """ Like ``getattr`` but raises type error if not of expected type. """ attr: Any = getattr(instance, attribute) if not isinstance(attr, expected_type): raise TypeError() return attr
[docs] def getattr_indexed(instance: Any, attribute: str) -> Any: """ Similar to ``getattr`` but allows indexing the returned attribute. Returning a default value is _not_ supported. The indices are decimal digits surrounded by square brackets. Chained indexing is supported, but the string should not contain any whitespace between consecutive indices. Example: `getattr_indexed(some_object, "list_of_lists_field[1][2]")` """ if not attribute.endswith("]"): return getattr(instance, attribute) end: int = len(attribute) - 1 start: int = attribute.find("[", 0, end) attr: Any = getattr(instance, attribute[0:start]) start += 1 while (pos := attribute.find("][", start, end)) != -1: index = int(attribute[start:pos]) attr = attr[index] start = pos + 2 index = int(attribute[start:end]) attr = attr[index] return attr
[docs] def checked_getattr_indexed( instance: Any, attribute: str, expected_type: Union[type, tuple[type, ...]] ) -> Any: """ Like ``getattr_indexed`` but raises type error if not of expected type. """ attr: Any = getattr_indexed(instance, attribute) if not isinstance(attr, expected_type): raise TypeError() return attr
[docs] @contextmanager def attribute_set_to( object_: object, attribute_name: str, new_value: Any ) -> "Iterator[None]": """ This context manager allows to change a given attribute of a given object to a new value, and the original value is reverted upon exit of the context manager. Args: object_: The object which attribute value is to be changed. attribute_name: The name of the attribute that is to be changed. new_value: The new value to which the attribute of the object is to be changed. """ old_value = getattr(object_, attribute_name) setattr(object_, attribute_name, new_value) try: yield finally: setattr(object_, attribute_name, old_value)