from typing import Any, Callable, Dict, Iterable, List, Tuple, Type, TypeVar, Generic, Optional, Protocol, runtime_checkable, cast from sqlalchemy import and_, func, inspect, or_, text from sqlalchemy.engine import Engine, Connection from sqlalchemy.orm import Load, Session, raiseload, selectinload, with_polymorphic, Mapper, RelationshipProperty, class_mapper, ColumnProperty from sqlalchemy.orm.attributes import InstrumentedAttribute from sqlalchemy.orm.base import NO_VALUE from sqlalchemy.orm.util import AliasedClass from sqlalchemy.sql import operators from sqlalchemy.sql.elements import UnaryExpression from crudkit.core.base import Version from crudkit.core.spec import CRUDSpec from crudkit.core.types import OrderSpec, SeekWindow from crudkit.backend import BackendInfo, make_backend_info def _expand_requires(model_cls, fields): out, seen = [], set() def add(f): if f not in seen: seen.add(f); out.append(f) for f in fields: add(f) parts = f.split(".") cur_cls = model_cls prefix = [] for p in parts[:-1]: rel = getattr(cur_cls.__mapper__.relationships, 'get', lambda _: None)(p) if not rel: cur_cls = None break cur_cls = rel.mapper.class_ prefix.append(p) if cur_cls is None: continue leaf = parts[-1] deps = (getattr(cur_cls, "__crudkit_field_requires__", {}) or {}).get(leaf) if not deps: continue pre = ".".join(prefix) for dep in deps: add(f"{pre + '.' if pre else ''}{dep}") return out def _is_rel(model_cls, name: str) -> bool: try: prop = model_cls.__mapper__.relationships.get(name) return isinstance(prop, RelationshipProperty) except Exception: return False def _is_instrumented_column(attr) -> bool: try: return hasattr(attr, "property") and isinstance(attr.property, ColumnProperty) except Exception: return False def _loader_options_for_fields(root_alias, model_cls, fields: list[str]) -> list[Load]: """ For bare MANYTOONE names in fields (e.g. "location"), selectinload the relationship and only fetch the related PK. This is enough for preselecting