text: import weakref
  from collections import MutableMapping
  from functools import lru_cache
  from typing import TYPE_CHECKING, Iterator, Set
  
  import pandas as pd
  
  if TYPE_CHECKING:
      from ..models import Model
  
  
  class BaseAttr(MutableMapping):
      """
      Base class for Info and Results objects.
      """
  
      model: "Model"
      _method_namespace: str = None
      __slots__ = ("_model_ref",)
  
      @property
      def _cache(self):
          raise NotImplementedError
  
      @property
      def model(self):
          return self._model_ref()
  
      def __init__(self, obj):
          self._model_ref = weakref.ref(obj)
  
      def __len__(self) -> int:
          return sum(1 for _ in self)
  
      def __iter__(self) -> Iterator[str]:
          seen = set()
          prefix = f"get_{self._method_namespace}_keys_"
          n = len(prefix)
  
          for method in dir(self.model):
              if method.startswith(prefix) and "__" not in method:
                  key = method[n:]
                  if key not in seen:
                      yield key
                  seen.add(key)
  
          for key in self._cache:
              if key not in seen:
                  yield key
  
      def __getitem__(self, item):
          group, key = normalize_key(item)
          cache = self._cache
  
          namespace = self._method_namespace
          model = self.model
  
          if key:
              cache = cache[group]
              try:
                  return cache[key]
              except KeyError:
                  pass
              result = get_scalar_item(model, group, key, namespace)
  
              # Save in cache only explicit keys
              # This avoid problems with aliases
              if key in get_dict_keys(model, group, namespace):
                  cache[key] = result
              return result
  
          else:
              out = cache[group]
              try:
                  out.update(get_dict_item(model, group, namespace, exclude=set(out)))
              except KeyError:
                  if out:
                      return out
                  raise
              return out
  
      def __setitem__(self, item, value):
          group, key = normalize_key(item)
          if key:
              self._set_item(group, key, value)
          else:
              for k, v in value.items():
                  self._set_item(group, k, v)
  
      def __delitem__(self, item):
          group, key = normalize_key(item)
          if key:
              del self._cache[group][key]
          else:
              del self._cache[group]
  
      def _set_item(self, group, key, value):
          # Can be overridden by subclasses
          self._cache[group][key] = value
  
      def to_dict(self, flat=False):
          """
          Convert data to nested dict.
          """
          data = {}
          for k, v in self.items():
              if flat:
                  data.update({f"{k}.{ki}": vi for ki, vi in v.items()})
              else:
                  data[k] = v
          return data
  
      def to_frame(self) -> pd.DataFrame:
          """
          Expose information about object as a dataframe.
          """
          data = self.to_series()
          return pd.DataFrame({self._method_namespace: data})
  
      def to_series(self) -> pd.Series:
          """
          Expose information about object as a dataframe.
          """
          return pd.Series(self.to_dict(flat=True))
  
      def _html_repr_(self):
          return self.to_frame()._html_repr_()
  
  
  def normalize_key(key):
      """
      Return tuple of (group, key) from key.
      """
  
      if isinstance(key, str):
          group, _, key = key.partition(".")
      elif isinstance(key, tuple):
          group, key = key
      else:
          raise TypeError(f"invalid key type: {type(key).__class__}")
      return group, key or None
  
  
  def extra_keys(model, name, namespace):
      """
      Collect extra keys implemented as get_{namespace}_{group}__{name} methods.
      """
  
      prefix = f"get_{namespace}_value_{name}__"
      n = len(prefix)
  
      for attr in dir(model):
          if attr.startswith(prefix):
              yield attr[n:].replace("__", ".")
  
  
  def get_scalar_item(model: "Model", group, key, namespace):
      """
      Fetch scalar value from Model instance attribute.
      """
      assert isinstance(namespace, str)
  
      name = f"get_{namespace}_value_{group}"
      full_name = f"{name}__{key}"
  
      if hasattr(model, full_name):
          fn = getattr(model, full_name)
          return fn()
  
      try:
          fn = getattr(model, name)
      except AttributeError:
          cls = type(model).__name__
          raise KeyError(f"{cls} instance has no '{group}.{key}' {namespace} key.")
      else:
          return fn(key)
  
  
  def get_dict_item(model, group, namespace, exclude: Set[str] = frozenset()):
      """
      Fetch values from Model instance.
      """
  
      keys = get_dict_keys(model, group, namespace)
      prop = getattr(model, namespace)
      return {key: prop[group, key] for key in keys - exclude}
  
  
  @lru_cache(512)
  def get_dict_keys(model, group, namespace):
      """
      Get unique group keys for model attribute.
      """
  
      name = f"get_{namespace}_keys_{group}"
  
      fn = getattr(model, name, dict)
      keys = fn()
      extra = extra_keys(model, group, namespace)
      return {*keys, *extra}
  