"""The two HTTP contracts exposed by the supplied n8n workflows.""" from __future__ import annotations import httpx from config import Settings, require_webhook_url class WebhookError(RuntimeError): """An n8n request failed or returned an unusable response.""" class N8NClient: def __init__( self, settings: Settings, transport: httpx.AsyncBaseTransport | None = None, ) -> None: self.settings = settings self._transport = transport async def ask(self, message: str, collection_name: str, session_id: str) -> str: message = message.strip() collection_name = collection_name.strip() if not message or not collection_name or not session_id: raise ValueError("Укажите вопрос и коллекцию.") url = require_webhook_url(self.settings.chat_webhook_url, "вопросов") try: async with httpx.AsyncClient( transport=self._transport, timeout=self.settings.chat_timeout_seconds, ) as client: response = await client.post( url, json={ "message": message, "collection_name": collection_name, "session_id": session_id, }, ) response.raise_for_status() except httpx.TimeoutException as exc: raise WebhookError("Время ожидания ответа истекло. Попробуйте ещё раз.") from exc except httpx.HTTPStatusError as exc: raise WebhookError( f"Webhook вопросов вернул HTTP {exc.response.status_code}." ) from exc except httpx.RequestError as exc: raise WebhookError("Не удалось связаться с webhook вопросов.") from exc answer = response.text.strip() if not answer: raise WebhookError("Webhook вопросов вернул пустой ответ.") return answer async def upload(self, filename: str, contents: bytes, collection_name: str) -> None: collection_name = collection_name.strip() if not collection_name: raise ValueError("Укажите коллекцию.") if not filename.lower().endswith(".md"): raise ValueError("Можно загружать только файлы .md.") if not contents: raise ValueError("Выбранный файл пуст.") if len(contents) > self.settings.max_file_bytes: raise ValueError("Размер файла превышает допустимый предел.") url = require_webhook_url(self.settings.upload_webhook_url, "загрузки") try: async with httpx.AsyncClient( transport=self._transport, timeout=self.settings.upload_timeout_seconds, ) as client: response = await client.post( url, data={"collection_name": collection_name}, files={"file": (filename, contents, "text/markdown")}, ) response.raise_for_status() except httpx.TimeoutException as exc: raise WebhookError("Время ожидания загрузки истекло. Проверьте n8n перед повтором.") from exc except httpx.HTTPStatusError as exc: raise WebhookError( f"Webhook загрузки вернул HTTP {exc.response.status_code}." ) from exc except httpx.RequestError as exc: raise WebhookError("Не удалось связаться с webhook загрузки.") from exc