# Path: src/bananas/models.py
# class ModelDict(Dict[str, Any]):
# 
#     _nested: Optional[Dict[str, "ModelDict"]] = None
# 
#     def __getattr__(self, item: str) -> Any:
#         """
#         Try to to get attribute as key item.
#         Fallback on prefixed nested keys.
#         Finally fallback on real attribute lookup.
#         """
#         try:
#             return self.__getitem__(item)
#         except KeyError:
#             try:
#                 return self.__getnested__(item)
#             except KeyError:
#                 return self.__getattribute__(item)
# 
#     def __getnested__(self, item: str) -> "ModelDict":
#         """
#         Find existing items prefixed with given item
#         and return a new ModelDict containing matched keys,
#         stripped from prefix.
# 
#         :param str item: Item prefix key to find
#         :return ModelDict:
#         """
#         # Ensure _nested cache
#         if self._nested is None:
#             self._nested: Dict[str, ModelDict] = {}
# 
#         # Try to get previously accessed/cached nested item
#         value = self._nested.get(item, MISSING)
# 
#         if not isinstance(value, Missing):
#             # Return previously accessed nested item
#             return value
# 
#         else:
#             # Find any keys matching nested prefix
#             prefix = item + "__"
#             keys = [key for key in self.keys() if key.startswith(prefix)]
# 
#             if keys:
#                 # Construct nested dict of matched keys, stripped from prefix
#                 n = ModelDict({key[len(item) + 2 :]: self[key] for key in keys})
# 
#                 # Cache and return
#                 self._nested[item] = n
#                 return n
# 
#         # Item not a nested key, raise
#         raise KeyError(item)
# 
#     def expand(self) -> "ModelDict":
#         keys = list(self)
#         for key in keys:
#             field, __, nested_key = key.partition("__")
#             if nested_key:
#                 if field not in keys:
#                     nested = self.__getnested__(field)
#                     if isinstance(nested, self.__class__):
#                         nested = nested.expand()
#                     self[field] = nested
#                 del self[key]
#         return ModelDict(self)
# 
#     @classmethod
#     # Ignore types until no longer work-in-progress.
#     def from_model(cls, model, *fields, **named_fields):  # type: ignore[no-untyped-def]
#         """
#         Work-in-progress constructor,
#         consuming fields and values from django model instance.
#         """
#         d = ModelDict()
# 
#         if not (fields or named_fields):
#             # Default to all fields
#             fields = [  # type: ignore[assignment]
#                 f.attname for f in model._meta.concrete_fields
#             ]
# 
#         not_found = object()
# 
#         for name, field in chain(zip(fields, fields), named_fields.items()):
#             _fields = field.split("__")
#             value = model
#             for i, _field in enumerate(_fields, start=1):
#                 # NOTE: we don't want to rely on hasattr here
#                 previous_value = value
#                 value = getattr(previous_value, _field, not_found)
# 
#                 if value is not_found:
#                     if _field in dir(previous_value):
#                         raise ValueError(
#                             "{!r}.{} had an AttributeError exception".format(
#                                 previous_value, _field
#                             )
#                         )
#                     else:
#                         raise AttributeError(
#                             "{!r} does not have {!r} attribute".format(
#                                 previous_value, _field
#                             )
#                         )
# 
#                 elif value is None:
#                     if name not in named_fields:
#                         name = "__".join(_fields[:i])
#                     break
# 
#             d[name] = value
# 
#         return d


import logging
from typing import (
    TYPE_CHECKING,
    Any,
    Dict,
    Iterable,
    Iterator,
    List,
    Mapping,
    Optional,
    Type,
    TypeVar,
    Union,
)
from django.db.models import Model
from django.db.models.expressions import Combinable
from django.db.models.query import QuerySet
from typing_extensions import Protocol
from .models import ModelDict
    from django.db.models.query import _QuerySet



if TYPE_CHECKING:

_log = logging.getLogger(__name__)

T = TypeVar("T", bound="QuerySet[Model]")


class ModelDictIterable:
    def __init__(self, queryset: T) -> None:
        self.queryset = queryset
        self.named_fields: Mapping[str, str] = self.queryset._hints.get("_named_fields")  # type: ignore[attr-defined]


# Path: src/bananas/models.py
# class ModelDict(Dict[str, Any]):
# 
#     _nested: Optional[Dict[str, "ModelDict"]] = None
# 
#     def __getattr__(self, item: str) -> Any:
#         """
#         Try to to get attribute as key item.
#         Fallback on prefixed nested keys.
#         Finally fallback on real attribute lookup.
#         """
#         try:
#             return self.__getitem__(item)
#         except KeyError:
#             try:
#                 return self.__getnested__(item)
#             except KeyError:
#                 return self.__getattribute__(item)
# 
#     def __getnested__(self, item: str) -> "ModelDict":
#         """
#         Find existing items prefixed with given item
#         and return a new ModelDict containing matched keys,
#         stripped from prefix.
# 
#         :param str item: Item prefix key to find
#         :return ModelDict:
#         """
#         # Ensure _nested cache
#         if self._nested is None:
#             self._nested: Dict[str, ModelDict] = {}
# 
#         # Try to get previously accessed/cached nested item
#         value = self._nested.get(item, MISSING)
# 
#         if not isinstance(value, Missing):
#             # Return previously accessed nested item
#             return value
# 
#         else:
#             # Find any keys matching nested prefix
#             prefix = item + "__"
#             keys = [key for key in self.keys() if key.startswith(prefix)]
# 
#             if keys:
#                 # Construct nested dict of matched keys, stripped from prefix
#                 n = ModelDict({key[len(item) + 2 :]: self[key] for key in keys})
# 
#                 # Cache and return
#                 self._nested[item] = n
#                 return n
# 
#         # Item not a nested key, raise
#         raise KeyError(item)
# 
#     def expand(self) -> "ModelDict":
#         keys = list(self)
#         for key in keys:
#             field, __, nested_key = key.partition("__")
#             if nested_key:
#                 if field not in keys:
#                     nested = self.__getnested__(field)
#                     if isinstance(nested, self.__class__):
#                         nested = nested.expand()
#                     self[field] = nested
#                 del self[key]
#         return ModelDict(self)
# 
#     @classmethod
#     # Ignore types until no longer work-in-progress.
#     def from_model(cls, model, *fields, **named_fields):  # type: ignore[no-untyped-def]
#         """
#         Work-in-progress constructor,
#         consuming fields and values from django model instance.
#         """
#         d = ModelDict()
# 
#         if not (fields or named_fields):
#             # Default to all fields
#             fields = [  # type: ignore[assignment]
#                 f.attname for f in model._meta.concrete_fields
#             ]
# 
#         not_found = object()
# 
#         for name, field in chain(zip(fields, fields), named_fields.items()):
#             _fields = field.split("__")
#             value = model
#             for i, _field in enumerate(_fields, start=1):
#                 # NOTE: we don't want to rely on hasattr here
#                 previous_value = value
#                 value = getattr(previous_value, _field, not_found)
# 
#                 if value is not_found:
#                     if _field in dir(previous_value):
#                         raise ValueError(
#                             "{!r}.{} had an AttributeError exception".format(
#                                 previous_value, _field
#                             )
#                         )
#                     else:
#                         raise AttributeError(
#                             "{!r} does not have {!r} attribute".format(
#                                 previous_value, _field
#                             )
#                         )
# 
#                 elif value is None:
#                     if name not in named_fields:
#                         name = "__".join(_fields[:i])
#                     break
# 
#             d[name] = value
# 
#         return d


# Path: src/bananas/query.py
import logging
from typing import (
    TYPE_CHECKING,
    Any,
    Dict,
    Iterable,
    Iterator,
    List,
    Mapping,
    Optional,
    Type,
    TypeVar,
    Union,
)
from django.db.models import Model
from django.db.models.expressions import Combinable
from django.db.models.query import QuerySet
from typing_extensions import Protocol
from .models import ModelDict
    from django.db.models.query import _QuerySet




if TYPE_CHECKING:

_log = logging.getLogger(__name__)

T = TypeVar("T", bound="QuerySet[Model]")


class ModelDictIterable:
    def __init__(self, queryset: T) -> None:
        self.queryset = queryset
        self.named_fields: Mapping[str, str] = self.queryset._hints.get("_named_fields")  # type: ignore[attr-defined]


    def __iter__(self) -> Iterator[ModelDict]: