From ef1e12a1a4c6e8d02b04b8c9f45b54ff6b260a29 Mon Sep 17 00:00:00 2001 From: TenzDelek Date: Mon, 23 Feb 2026 16:38:43 +0530 Subject: [PATCH 1/6] search base --- api/config.py | 1 + api/constant.py | 3 +- api/http_message_utils.py | 21 +++++++++++++ api/search/search_response_model.py | 0 api/search/search_service.py | 49 +++++++++++++++++++++++++++++ api/search/search_view.py | 23 ++++++++++++++ 6 files changed, 96 insertions(+), 1 deletion(-) create mode 100644 api/http_message_utils.py create mode 100644 api/search/search_response_model.py create mode 100644 api/search/search_service.py create mode 100644 api/search/search_view.py diff --git a/api/config.py b/api/config.py index d0e80ae..2dba8b7 100644 --- a/api/config.py +++ b/api/config.py @@ -26,6 +26,7 @@ CACHE_CONNECTION_STRING="your-redis-connection-string", CACHE_PREFIX="agent_microservice:", CACHE_ASSISTANT_TIMEOUT=3600, + EXTERNAL_PECHA_API_URL="your-external-pecha-api-url", ) def get(key: str) -> str: diff --git a/api/constant.py b/api/constant.py index 1636e2a..b37e217 100644 --- a/api/constant.py +++ b/api/constant.py @@ -1,3 +1,4 @@ class Constant: CREATED_ASSISTANT_MESSAGE="Assistant created successfully" - IMAGE_UPLOAD_SUCCESS="Image uploaded successfully" \ No newline at end of file + IMAGE_UPLOAD_SUCCESS="Image uploaded successfully" + INSTANCE_TYPE="critical" \ No newline at end of file diff --git a/api/http_message_utils.py b/api/http_message_utils.py new file mode 100644 index 0000000..5395d3f --- /dev/null +++ b/api/http_message_utils.py @@ -0,0 +1,21 @@ +from fastapi import HTTPException +import logging +import httpx + +logger = logging.getLogger(__name__) + + +def handle_http_status_error(e: httpx.HTTPStatusError) -> None: + logger.error(f"External API error: {e.response.status_code} - {e.response.text}") + raise HTTPException( + status_code=e.response.status_code, + detail=f"External API error: {e.response.text}" + ) + + +def handle_request_error(e: httpx.RequestError) -> None: + logger.error(f"Request to external API failed: {str(e)}") + raise HTTPException( + status_code=500, + detail="Failed to connect to the service. Please try again later." + ) \ No newline at end of file diff --git a/api/search/search_response_model.py b/api/search/search_response_model.py new file mode 100644 index 0000000..e69de29 diff --git a/api/search/search_service.py b/api/search/search_service.py new file mode 100644 index 0000000..40018c1 --- /dev/null +++ b/api/search/search_service.py @@ -0,0 +1,49 @@ +import httpx +from api.http_message_utils import handle_http_status_error, handle_request_error +from api.config import get +from api.constant import Constant + + +client = httpx.AsyncClient(timeout=httpx.Timeout(30.0)) + +ACCEPT_JSON_HEADER = {"Accept": "application/json"} +EXTERNAL_PECHA_API_URL = get("EXTERNAL_PECHA_API_URL") + +async def get_search_texts_details(text_id: str) -> list[dict]: + instances = await call_external_pecha_api_instances(text_id) + if instances: + for instance in instances: + instance_id = instance.get("id") + if instance_id: + content = await call_external_pecha_api_instances_content(instance_id) + instance["content"] = content + + return instances + + +async def call_external_pecha_api_instances( + text_id: str +) -> list[dict]: + endpoint = f"{EXTERNAL_PECHA_API_URL}/texts/{text_id}/instances?instance_type={Constant.INSTANCE_TYPE}" + try: + response = await client.get(endpoint, headers=ACCEPT_JSON_HEADER) + response.raise_for_status() + data = response.json() + return data if isinstance(data, list) else [] + + except httpx.HTTPStatusError as e: + handle_http_status_error(e) + except httpx.RequestError as e: + handle_request_error(e) + +async def call_external_pecha_api_instances_content(instance_id: str) -> str: + endpoint = f"{EXTERNAL_PECHA_API_URL}/instances/{instance_id}?annotation=false&content=true" + try: + response = await client.get(endpoint, headers=ACCEPT_JSON_HEADER) + response.raise_for_status() + data = response.json() + return data.get("content", "") + except httpx.HTTPStatusError as e: + handle_http_status_error(e) + except httpx.RequestError as e: + handle_request_error(e) \ No newline at end of file diff --git a/api/search/search_view.py b/api/search/search_view.py new file mode 100644 index 0000000..02ef619 --- /dev/null +++ b/api/search/search_view.py @@ -0,0 +1,23 @@ +from __future__ import annotations + +from fastapi import APIRouter +from fastapi.security import HTTPBearer +from starlette import status +from api.search.search_service import get_search_texts_details +from api.search.search_response_model import SearchTextsDetailsResponse + + +oauth2_scheme = HTTPBearer() +search_router = APIRouter( + prefix="/search", + tags=["search"] +) + + +@search_router.get("/texts/{text_id}", status_code=status.HTTP_200_OK, response_model=SearchTextsDetailsResponse) +async def read_texts_details( + text_id: str +)->SearchTextsDetailsResponse: + return await get_search_texts_details( + text_id=text_id + ) \ No newline at end of file From 993ad34f31f2d1709c1fb2be9e2b142cfb3bdc6e Mon Sep 17 00:00:00 2001 From: TenzDelek Date: Mon, 23 Feb 2026 17:04:30 +0530 Subject: [PATCH 2/6] response strcture --- api/search/search_response_model.py | 9 +++++++++ api/search/search_service.py | 5 +++-- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/api/search/search_response_model.py b/api/search/search_response_model.py index e69de29..1948546 100644 --- a/api/search/search_response_model.py +++ b/api/search/search_response_model.py @@ -0,0 +1,9 @@ +from pydantic import BaseModel +from typing import Optional + +class SearchTextsDetailsResponse(BaseModel): + id: str + content: str + type: str + source: str + incipit_title: Optional[dict[str, str]] = None diff --git a/api/search/search_service.py b/api/search/search_service.py index 40018c1..4860ca5 100644 --- a/api/search/search_service.py +++ b/api/search/search_service.py @@ -2,6 +2,7 @@ from api.http_message_utils import handle_http_status_error, handle_request_error from api.config import get from api.constant import Constant +from api.search.search_response_model import SearchTextsDetailsResponse client = httpx.AsyncClient(timeout=httpx.Timeout(30.0)) @@ -9,7 +10,7 @@ ACCEPT_JSON_HEADER = {"Accept": "application/json"} EXTERNAL_PECHA_API_URL = get("EXTERNAL_PECHA_API_URL") -async def get_search_texts_details(text_id: str) -> list[dict]: +async def get_search_texts_details(text_id: str) -> list[SearchTextsDetailsResponse]: instances = await call_external_pecha_api_instances(text_id) if instances: for instance in instances: @@ -18,7 +19,7 @@ async def get_search_texts_details(text_id: str) -> list[dict]: content = await call_external_pecha_api_instances_content(instance_id) instance["content"] = content - return instances + return [SearchTextsDetailsResponse(**instance) for instance in instances] async def call_external_pecha_api_instances( From 6d70390c6544d671ca7cb1e2c97b610162248027 Mon Sep 17 00:00:00 2001 From: TenzDelek Date: Tue, 24 Feb 2026 10:02:25 +0530 Subject: [PATCH 3/6] add --- api/app.py | 2 ++ api/search/search_view.py | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/api/app.py b/api/app.py index 1fb739f..70229f7 100644 --- a/api/app.py +++ b/api/app.py @@ -5,6 +5,7 @@ from api.ai import ai_view from api.ui import ui_router from api.upload import media_view +from api.search import search_view import uvicorn api = FastAPI( @@ -30,6 +31,7 @@ async def health_check(): api.include_router(assistant_view.assistant_router) api.include_router(ai_view.ai_router) api.include_router(media_view.media_router) +api.include_router(search_view.search_router) if __name__ == "__main__": uvicorn.run("api.app:api", host="127.0.0.1", port=8000, reload=True) \ No newline at end of file diff --git a/api/search/search_view.py b/api/search/search_view.py index 02ef619..fd6bfbd 100644 --- a/api/search/search_view.py +++ b/api/search/search_view.py @@ -14,7 +14,7 @@ ) -@search_router.get("/texts/{text_id}", status_code=status.HTTP_200_OK, response_model=SearchTextsDetailsResponse) +@search_router.get("/{text_id}", status_code=status.HTTP_200_OK, response_model=SearchTextsDetailsResponse) async def read_texts_details( text_id: str )->SearchTextsDetailsResponse: From f1c665305abc2ebaefb04b512f2e67e0245138eb Mon Sep 17 00:00:00 2001 From: TenzDelek Date: Tue, 24 Feb 2026 10:14:08 +0530 Subject: [PATCH 4/6] clean up --- api/search/search_response_model.py | 4 +--- api/search/search_view.py | 4 ++-- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/api/search/search_response_model.py b/api/search/search_response_model.py index 1948546..410ac71 100644 --- a/api/search/search_response_model.py +++ b/api/search/search_response_model.py @@ -1,9 +1,7 @@ from pydantic import BaseModel -from typing import Optional class SearchTextsDetailsResponse(BaseModel): id: str content: str type: str - source: str - incipit_title: Optional[dict[str, str]] = None + source: str \ No newline at end of file diff --git a/api/search/search_view.py b/api/search/search_view.py index fd6bfbd..072db64 100644 --- a/api/search/search_view.py +++ b/api/search/search_view.py @@ -14,10 +14,10 @@ ) -@search_router.get("/{text_id}", status_code=status.HTTP_200_OK, response_model=SearchTextsDetailsResponse) +@search_router.get("/{text_id}", status_code=status.HTTP_200_OK, response_model=list[SearchTextsDetailsResponse]) async def read_texts_details( text_id: str -)->SearchTextsDetailsResponse: +)->list[SearchTextsDetailsResponse]: return await get_search_texts_details( text_id=text_id ) \ No newline at end of file From b43e445aa695a6015c522e0b5616ec50de309166 Mon Sep 17 00:00:00 2001 From: TenzDelek Date: Tue, 24 Feb 2026 10:15:09 +0530 Subject: [PATCH 5/6] remove auth --- api/search/search_view.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/api/search/search_view.py b/api/search/search_view.py index 072db64..24411b3 100644 --- a/api/search/search_view.py +++ b/api/search/search_view.py @@ -6,8 +6,6 @@ from api.search.search_service import get_search_texts_details from api.search.search_response_model import SearchTextsDetailsResponse - -oauth2_scheme = HTTPBearer() search_router = APIRouter( prefix="/search", tags=["search"] From c0d08c047ae370baca8fae0c7ea2f03e251658d2 Mon Sep 17 00:00:00 2001 From: TenzDelek Date: Tue, 24 Feb 2026 10:15:27 +0530 Subject: [PATCH 6/6] another one --- api/search/search_view.py | 1 - 1 file changed, 1 deletion(-) diff --git a/api/search/search_view.py b/api/search/search_view.py index 24411b3..0b39984 100644 --- a/api/search/search_view.py +++ b/api/search/search_view.py @@ -1,7 +1,6 @@ from __future__ import annotations from fastapi import APIRouter -from fastapi.security import HTTPBearer from starlette import status from api.search.search_service import get_search_texts_details from api.search.search_response_model import SearchTextsDetailsResponse