41 lines
1.2 KiB
Python
41 lines
1.2 KiB
Python
"""Search provider abstraction (section 16).
|
|||
|
|
|
||
|
|
API clients and the rest of the service layer talk to `SearchProvider`, not
|
||
|
|
to PostgreSQL directly, so the underlying engine can be swapped later (e.g.
|
||
|
|
for an `OpenSearchProvider`) without touching callers.
|
||
|
|
"""
|
||
|
|
|
||
|
|
from __future__ import annotations
|
||
|
|
|
||
|
|
import uuid
|
||
|
|
from abc import ABC, abstractmethod
|
||
|
|
from dataclasses import dataclass, field
|
||
|
|
|
||
|
|
from sqlalchemy.ext.asyncio import AsyncSession
|
||
|
|
|
||
|
|
|
||
|
|
@dataclass
|
||
|
|
class SearchMatch:
|
||
|
|
entity_id: uuid.UUID
|
||
|
|
score: float
|
||
|
|
|
||
|
|
|
||
|
|
@dataclass
|
||
|
|
class SearchResultIDs:
|
||
|
|
movies: list[SearchMatch] = field(default_factory=list)
|
||
|
|
series: list[SearchMatch] = field(default_factory=list)
|
||
|
|
people: list[SearchMatch] = field(default_factory=list)
|
||
|
|
collections: list[SearchMatch] = field(default_factory=list)
|
||
|
|
companies: list[SearchMatch] = field(default_factory=list)
|
||
|
|
|
||
|
|
|
||
|
|
class SearchProvider(ABC):
|
||
|
|
"""Returns ranked Aurora entity IDs matching a query. Never returns
|
||
|
|
fully-hydrated entities — that's the SearchService's job, so the
|
||
|
|
provider stays a thin, swappable ranking layer."""
|
||
|
|
|
||
|
|
@abstractmethod
|
||
|
|
async def search(
|
||
|
|
self, session: AsyncSession, query: str, *, limit_per_type: int = 10
|
||
|
|
) -> SearchResultIDs: ...
|