search_hooks.py 1.81 KB
"""Helpers for preserving app-module search monkeypatch hooks."""

from __future__ import annotations

from dataclasses import dataclass
from typing import Any, Callable, MutableMapping, Protocol, runtime_checkable

SearchDispatcher = Callable[..., dict[str, Any]]
SearchQueryResolver = Callable[..., tuple[str, str]]
ModuleNamespace = MutableMapping[str, Any]


@runtime_checkable
class SearchHookSource(Protocol):
    """Mutable object that exposes search hook callables."""

    resolve_search_query: SearchQueryResolver
    dispatch_search_request: SearchDispatcher


@dataclass(frozen=True)
class SearchHookBindings:
    """Wrappers that resolve search callables from the app module namespace."""

    resolve_search_query: SearchQueryResolver
    dispatch_search_request: SearchDispatcher


def _resolve_hook_callable(
    hook_source: SearchHookSource | ModuleNamespace,
    name: str,
) -> Callable[..., Any]:
    if isinstance(hook_source, MutableMapping):
        return hook_source[name]
    return getattr(hook_source, name)


def _bind_hook_callable(
    hook_source: SearchHookSource | ModuleNamespace,
    name: str,
) -> Callable[..., Any]:
    """Resolve the latest callable from the hook source at call time."""

    def _call(**kwargs):
        target = _resolve_hook_callable(hook_source, name)
        return target(**kwargs)

    return _call


def build_search_hook_bindings(
    hook_source: SearchHookSource | ModuleNamespace,
) -> SearchHookBindings:
    """Build monkeypatch-compatible search wrappers for the app entrypoint."""

    return SearchHookBindings(
        resolve_search_query=_bind_hook_callable(hook_source, "resolve_search_query"),
        dispatch_search_request=_bind_hook_callable(hook_source, "dispatch_search_request"),
    )


__all__ = [
    "SearchHookBindings",
    "build_search_hook_bindings",
]