"""Клиент qa-docs-service.""" from __future__ import annotations import os import httpx class QaServiceConfigError(RuntimeError): """Не заданы обязательные переменные окружения для qa-docs-service.""" def _config() -> tuple[str, str]: url = os.environ.get("QA_SERVICE_URL") api_key = os.environ.get("QA_SERVICE_API_KEY") if not url or not api_key: raise QaServiceConfigError( "QA_SERVICE_URL / QA_SERVICE_API_KEY не заданы (проверь .env)" ) return url.rstrip("/"), api_key def slug_from(slug_or_url: str) -> str: """Принимает и slug, и полную ссылку вида .../docs/.""" return slug_or_url.strip().rstrip("/").rsplit("/", 1)[-1] async def publish_doc( title: str, markdown: str, card_url: str | None = None, problem: str | None = None, ) -> dict: """Публикует новый QA-документ, возвращает {"slug": ..., "url": ...}.""" base_url, api_key = _config() async with httpx.AsyncClient() as client: response = await client.post( f"{base_url}/docs", headers={"X-API-Key": api_key}, json={ "title": title, "markdown": markdown, "card_url": card_url, "problem": problem, }, ) response.raise_for_status() return response.json() async def update_doc( slug: str, title: str, markdown: str, card_url: str | None = None, problem: str | None = None, ) -> dict: """Перезаписывает уже опубликованный документ по slug (та же ссылка). Возвращает {"slug": ..., "url": ...}; 404 (через raise_for_status), если под этим slug ничего не опубликовано. """ base_url, api_key = _config() async with httpx.AsyncClient() as client: response = await client.put( f"{base_url}/docs/{slug_from(slug)}", headers={"X-API-Key": api_key}, json={ "title": title, "markdown": markdown, "card_url": card_url, "problem": problem, }, ) response.raise_for_status() return response.json() async def get_doc(slug: str) -> dict: """Скачивает опубликованный документ: исходный markdown, метаданные и отметки тестировщиков по сценариям (поле scenarios). 404 (через raise_for_status), если под этим slug ничего не опубликовано. """ base_url, api_key = _config() async with httpx.AsyncClient() as client: response = await client.get( f"{base_url}/docs/{slug_from(slug)}/raw", headers={"X-API-Key": api_key}, ) response.raise_for_status() return response.json() def _tasks_config() -> tuple[str, dict]: """URL сервиса и заголовки для /tasks. Подходит и основной ключ (у тебя), и QA_TASKS_TOKEN (у Claude на фронте/бэке, которому доступны только задания). """ url = os.environ.get("QA_SERVICE_URL") token = os.environ.get("QA_SERVICE_API_KEY") or os.environ.get("QA_TASKS_TOKEN") if not url or not token: raise QaServiceConfigError( "QA_SERVICE_URL и QA_SERVICE_API_KEY или QA_TASKS_TOKEN не заданы (проверь .env)" ) return url.rstrip("/"), {"Authorization": f"Bearer {token}"} async def publish_task( target: str, title: str, markdown: str, card_url: str | None = None ) -> dict: """Публикует задание для фронта/бэка, возвращает {slug, url, target, ...}.""" base_url, headers = _tasks_config() async with httpx.AsyncClient() as client: response = await client.post( f"{base_url}/tasks", headers=headers, json={"title": title, "markdown": markdown, "target": target, "card_url": card_url}, ) response.raise_for_status() return response.json() async def update_task( slug: str, target: str, title: str, markdown: str, card_url: str | None = None ) -> dict: """Перезаписывает задание по slug (та же ссылка).""" base_url, headers = _tasks_config() async with httpx.AsyncClient() as client: response = await client.put( f"{base_url}/tasks/{slug_from(slug)}", headers=headers, json={"title": title, "markdown": markdown, "target": target, "card_url": card_url}, ) response.raise_for_status() return response.json() async def get_task(slug: str) -> dict: """Задание целиком: {slug, url, title, target, card_url, markdown, ...}.""" base_url, headers = _tasks_config() async with httpx.AsyncClient() as client: response = await client.get( f"{base_url}/tasks/{slug_from(slug)}/raw", headers=headers ) response.raise_for_status() return response.json() async def list_tasks(card_url: str | None = None, target: str | None = None) -> list[dict]: """Задания без текста, новые первыми; фильтры по карточке и адресату.""" base_url, headers = _tasks_config() params = {key: value for key, value in (("card", card_url), ("target", target)) if value} async with httpx.AsyncClient() as client: response = await client.get(f"{base_url}/tasks", headers=headers, params=params) response.raise_for_status() return response.json()