This repository has been archived on 2026-10-08. You can view files and clone it. You cannot open issues or pull requests or push a commit.
Files

91 lines
3.5 KiB
Python

import json
import os
import subprocess
from dataclasses import asdict
from datetime import datetime
from pathlib import Path
from typing import cast
import pytest
from recoll_modern.engine.protocol import Engine, EngineHealth, EngineHit, EnginePage, SearchRequest
from recoll_modern.engine.recoll import RecollEngine
from .engine_contract import EngineContract
_CONTAINER_SCRIPT = """
import json, sys
from dataclasses import asdict
from pathlib import Path
from recoll_modern.engine.protocol import SearchRequest
from recoll_modern.engine.recoll import RecollEngine
engine = RecollEngine(Path('/var/lib/recoll-modern/recoll-config'))
method, payload = sys.argv[1], json.loads(sys.argv[2])
if method == 'search':
result = engine.search(SearchRequest(**payload))
elif method == 'health':
result = engine.health()
elif method == 'extracted_text':
result = engine.extracted_text(payload['path'])
else:
raise ValueError(method)
print(json.dumps(asdict(result) if method != 'extracted_text' else result, default=str))
"""
class ContainerRecollEngine:
def _call(self, method: str, payload: dict[str, object]) -> object:
result = subprocess.run(
["docker", "compose", "exec", "-T", "app", "python", "-c", _CONTAINER_SCRIPT,
method, json.dumps(payload)],
capture_output=True, text=True, check=False, timeout=30,
)
assert result.returncode == 0, result.stderr
return json.loads(result.stdout)
def search(self, request: SearchRequest) -> EnginePage:
value = cast(dict[str, object], self._call("search", asdict(request)))
hits = []
for raw in cast(list[dict[str, object]], value["hits"]):
date = raw["date"]
raw["date"] = datetime.fromisoformat(date) if isinstance(date, str) else None
raw["collection_ids"] = tuple(cast(list[str], raw["collection_ids"]))
raw["snippets"] = tuple(cast(list[str], raw["snippets"]))
hits.append(EngineHit(**raw)) # type: ignore[arg-type]
return EnginePage(
tuple(hits), int(cast(int, value["total"])), bool(value["total_is_approximate"])
)
def health(self) -> EngineHealth:
value = cast(dict[str, object], self._call("health", {}))
return EngineHealth(bool(value["available"]), cast(str | None, value["message"]))
def extracted_text(self, path: str) -> str:
return cast(str, self._call("extracted_text", {"path": path}))
def force_reindex(self, paths: tuple[str, ...]) -> None:
raise NotImplementedError
class TestRecollEngine(EngineContract):
@pytest.fixture(autouse=True)
def available_engine(self) -> None:
config = os.environ.get("RECOLL_MODERN_TEST_RECOLL_CONFIG_DIR")
if config is None:
if os.environ.get("RECOLL_MODERN_REQUIRE_DOCKER") != "1":
pytest.skip("Recoll test container is not running")
bootstrap = subprocess.run(
["docker", "compose", "exec", "-T", "app", "recollindex", "-c",
"/var/lib/recoll-modern/recoll-config", "-i", "-Z",
"/documents"],
capture_output=True, text=True, check=False, timeout=30,
)
assert bootstrap.returncode == 0, bootstrap.stderr
self.engine = cast(Engine, ContainerRecollEngine())
assert self.engine.health().available
return
engine = RecollEngine(Path(config))
if not engine.health().available:
pytest.skip("Recoll test container is not running")
self.engine = engine