"""
Knowledge Service Queries API router for the julee CEAP system.
This module provides the API endpoints for knowledge service queries,
which define how to extract specific data using external knowledge services
during the assembly process.
Routes defined at root level:
- GET / - List knowledge service queries (paginated)
- GET /{query_id} - Get individual query details
- POST / - Create new knowledge service query
These routes are mounted at /knowledge_service_queries in the main app.
"""
import logging
from typing import cast
from fastapi import APIRouter, Depends, HTTPException, Query
from fastapi_pagination import Page, paginate
from julee.api.dependencies import (
get_knowledge_service_query_repository,
)
from julee.api.requests import CreateKnowledgeServiceQueryRequest
from julee.domain.models import KnowledgeServiceQuery
from julee.domain.repositories.knowledge_service_query import (
KnowledgeServiceQueryRepository,
)
[docs]
logger = logging.getLogger(__name__)
# Create the router for knowledge service queries
@router.get("/", response_model=Page[KnowledgeServiceQuery])
[docs]
async def get_knowledge_service_queries(
ids: str | None = Query(
None,
description="Comma-separated list of query IDs for bulk retrieval",
openapi_examples={
"bulk_query": {
"summary": "Bulk retrieval example",
"value": "query-123,query-456,query-789",
}
},
),
repository: KnowledgeServiceQueryRepository = Depends( # type: ignore[misc]
get_knowledge_service_query_repository
),
) -> Page[KnowledgeServiceQuery]:
"""
Get knowledge service queries by IDs or list all with pagination.
This endpoint supports two modes:
1. Bulk retrieval: Pass comma-separated IDs to get specific queries
2. List all: Without IDs parameter, returns paginated list of all queries
Each query contains the configuration needed to extract specific data
using external knowledge services.
Args:
ids: Optional comma-separated list of query IDs for bulk retrieval
Returns:
Page[KnowledgeServiceQuery]: List of queries (bulk) or paginated
list (all)
"""
if ids is not None:
# Check for empty or whitespace-only parameter
if not ids.strip():
raise HTTPException(
status_code=400,
detail="Invalid ids parameter: must contain at least one " "valid ID",
)
# Bulk retrieval mode
logger.info(
"Bulk knowledge service queries requested",
extra={"ids_param": ids},
)
try:
# Parse and validate IDs
id_list = [id.strip() for id in ids.split(",") if id.strip()]
if not id_list:
raise HTTPException(
status_code=400,
detail="Invalid ids parameter: must contain at least "
"one valid ID",
)
if len(id_list) > 100: # Reasonable limit
raise HTTPException(
status_code=400,
detail="Too many IDs requested: maximum 100 IDs per " "request",
)
# Use repository's get_many method
results = await repository.get_many(id_list)
# Filter out None results and preserve found queries
found_queries = [query for query in results.values() if query is not None]
logger.info(
"Bulk knowledge service queries retrieved successfully",
extra={
"requested_count": len(id_list),
"found_count": len(found_queries),
"missing_count": len(id_list) - len(found_queries),
},
)
# Return as paginated result for consistent API response format
return cast(Page[KnowledgeServiceQuery], paginate(found_queries))
except HTTPException:
# Re-raise HTTP exceptions (like 400 Bad Request)
raise
except Exception as e:
logger.error(
"Failed to retrieve bulk knowledge service queries",
exc_info=True,
extra={
"error_type": type(e).__name__,
"error_message": str(e),
"ids_param": ids,
},
)
raise HTTPException(
status_code=500,
detail="Failed to retrieve queries due to an internal error.",
)
else:
# List all mode (existing functionality)
logger.info("All knowledge service queries requested")
try:
# Get all knowledge service queries from the repository
queries = await repository.list_all()
logger.info(
"Knowledge service queries retrieved successfully",
extra={"count": len(queries)},
)
# Use fastapi-pagination to paginate the results
return cast(Page[KnowledgeServiceQuery], paginate(queries))
except Exception as e:
logger.error(
"Failed to retrieve knowledge service queries",
exc_info=True,
extra={
"error_type": type(e).__name__,
"error_message": str(e),
},
)
raise HTTPException(
status_code=500,
detail="Failed to retrieve queries due to an internal error.",
)
@router.post("/", response_model=KnowledgeServiceQuery)
[docs]
async def create_knowledge_service_query(
request: CreateKnowledgeServiceQueryRequest,
repository: KnowledgeServiceQueryRepository = Depends( # type: ignore[misc]
get_knowledge_service_query_repository
),
) -> KnowledgeServiceQuery:
"""
Create a new knowledge service query.
This endpoint creates a new knowledge service query configuration that
defines how to extract specific data using external knowledge services
during the assembly process.
Args:
request: The knowledge service query creation request
repository: Injected repository for persistence
Returns:
KnowledgeServiceQuery: The created query with generated ID and
timestamps
"""
logger.info(
"Knowledge service query creation requested",
extra={"query_name": request.name},
)
try:
# Generate unique ID for the new query
query_id = await repository.generate_id()
# Convert request to domain model with generated ID
query = request.to_domain_model(query_id)
# Save the query via repository
await repository.save(query)
logger.info(
"Knowledge service query created successfully",
extra={
"query_id": query.query_id,
"query_name": query.name,
"knowledge_service_id": query.knowledge_service_id,
},
)
return query
except Exception as e:
logger.error(
"Failed to create knowledge service query",
exc_info=True,
extra={
"error_type": type(e).__name__,
"error_message": str(e),
"query_name": request.name,
},
)
raise HTTPException(
status_code=500,
detail="Failed to create query due to an internal error.",
)
@router.get("/{query_id}", response_model=KnowledgeServiceQuery)
[docs]
async def get_knowledge_service_query(
query_id: str,
repository: KnowledgeServiceQueryRepository = Depends( # type: ignore[misc]
get_knowledge_service_query_repository
),
) -> KnowledgeServiceQuery:
"""
Get a specific knowledge service query by ID.
Args:
query_id: The ID of the query to retrieve
repository: Injected repository for data access
Returns:
KnowledgeServiceQuery: The requested query
Raises:
HTTPException: 404 if query not found, 500 for internal errors
"""
logger.info(
"Knowledge service query detail requested",
extra={"query_id": query_id},
)
try:
query = await repository.get(query_id)
if query is None:
logger.warning(
"Knowledge service query not found",
extra={"query_id": query_id},
)
raise HTTPException(
status_code=404,
detail=f"Knowledge service query with ID '{query_id}' " "not found",
)
logger.info(
"Knowledge service query retrieved successfully",
extra={
"query_id": query.query_id,
"query_name": query.name,
},
)
return query
except HTTPException:
# Re-raise HTTP exceptions (like 404 Not Found)
raise
except Exception as e:
logger.error(
"Failed to retrieve knowledge service query",
exc_info=True,
extra={
"error_type": type(e).__name__,
"error_message": str(e),
"query_id": query_id,
},
)
raise HTTPException(
status_code=500,
detail="Failed to retrieve query due to an internal error.",
)