40 lines
1.3 KiB
Python
40 lines
1.3 KiB
Python
|
|
"""The contract every search backend implements."""
|
||
|
|
|
||
|
|
from __future__ import annotations
|
||
|
|
|
||
|
|
from abc import ABC, abstractmethod
|
||
|
|
|
||
|
|
from apps.search.types import SearchRequest, SearchResult
|
||
|
|
|
||
|
|
|
||
|
|
class SearchBackend(ABC):
|
||
|
|
"""
|
||
|
|
Strategy interface for keyword search.
|
||
|
|
|
||
|
|
Two implementations ship with the project:
|
||
|
|
|
||
|
|
* :class:`apps.search.backends.elastic.ElasticsearchBackend` - the default,
|
||
|
|
used in every environment that runs the ELK stack.
|
||
|
|
* :class:`apps.search.backends.postgres.PostgresBackend` - a full-text
|
||
|
|
fallback that keeps the API (and the test suite) working without any
|
||
|
|
external search service.
|
||
|
|
|
||
|
|
Keeping the surface this small is deliberate: the views, the serializers and
|
||
|
|
the frontend are completely unaware of which engine answered the query.
|
||
|
|
"""
|
||
|
|
|
||
|
|
#: Human readable identifier, surfaced in the API response and health check.
|
||
|
|
name: str = "base"
|
||
|
|
|
||
|
|
@abstractmethod
|
||
|
|
def search(self, request: SearchRequest) -> SearchResult:
|
||
|
|
"""Execute a search and return ranked hits plus facet counts."""
|
||
|
|
|
||
|
|
@abstractmethod
|
||
|
|
def suggest(self, prefix: str, limit: int = 8) -> list[str]:
|
||
|
|
"""Return type-ahead completions for a partial name or skill."""
|
||
|
|
|
||
|
|
@abstractmethod
|
||
|
|
def ping(self) -> bool:
|
||
|
|
"""Raise or return ``False`` when the backend is not usable."""
|