"""
Assembly Specifications API router for the julee CEAP system.
This module provides the API endpoints for assembly specifications,
which define how to assemble documents of specific types including
JSON schemas and knowledge service query configurations.
Routes defined at root level:
- GET / - List assembly specifications (paginated)
- GET /{id} - Get a specific assembly specification by ID
These routes are mounted at /assembly_specifications in the main app.
"""
import logging
from typing import cast
from fastapi import APIRouter, Depends, HTTPException, Path
from fastapi_pagination import Page, paginate
from julee.domain.models import AssemblySpecification
from julee.domain.repositories.assembly_specification import (
AssemblySpecificationRepository,
)
from julee.api.dependencies import (
get_assembly_specification_repository,
)
from julee.api.requests import CreateAssemblySpecificationRequest
[docs]
logger = logging.getLogger(__name__)
# Create the router for assembly specifications
@router.get("/", response_model=Page[AssemblySpecification])
[docs]
async def get_assembly_specifications(
repository: AssemblySpecificationRepository = Depends( # type: ignore[misc]
get_assembly_specification_repository
),
) -> Page[AssemblySpecification]:
"""
Get a paginated list of assembly specifications.
This endpoint returns all assembly specifications in the system
with pagination support. Each specification contains the configuration
needed to define how to assemble documents of specific types.
Returns:
Page[AssemblySpecification]: Paginated list of specifications
"""
logger.info("Assembly specifications requested")
try:
# Get all assembly specifications from the repository
specifications = await repository.list_all()
logger.info(
"Assembly specifications retrieved successfully",
extra={"count": len(specifications)},
)
# Use fastapi-pagination to paginate the results
return cast(Page[AssemblySpecification], paginate(specifications))
except Exception as e:
logger.error(
"Failed to retrieve assembly specifications",
exc_info=True,
extra={"error_type": type(e).__name__, "error_message": str(e)},
)
raise HTTPException(
status_code=500,
detail="Failed to retrieve specifications due to an internal " "error.",
)
@router.get("/{assembly_specification_id}", response_model=AssemblySpecification)
[docs]
async def get_assembly_specification(
assembly_specification_id: str = Path(
description="The ID of the assembly specification to retrieve"
),
repository: AssemblySpecificationRepository = Depends( # type: ignore[misc]
get_assembly_specification_repository
),
) -> AssemblySpecification:
"""
Get a specific assembly specification by ID.
This endpoint retrieves a single assembly specification by its unique
identifier. The specification contains the JSON schema and knowledge
service query configurations needed for document assembly.
Args:
assembly_specification_id: The unique ID of the specification
Returns:
AssemblySpecification: The requested specification
Raises:
HTTPException: 404 if specification not found, 500 for other errors
"""
logger.info(
"Assembly specification requested",
extra={"assembly_specification_id": assembly_specification_id},
)
try:
# Get the specific assembly specification from the repository
specification = await repository.get(assembly_specification_id)
if specification is None:
logger.warning(
"Assembly specification not found",
extra={"assembly_specification_id": assembly_specification_id},
)
raise HTTPException(
status_code=404,
detail=f"Assembly specification with ID "
f"'{assembly_specification_id}' not found.",
)
logger.info(
"Assembly specification retrieved successfully",
extra={
"assembly_specification_id": assembly_specification_id,
"specification_name": specification.name,
},
)
return specification
except HTTPException:
# Re-raise HTTP exceptions (like 404)
raise
except Exception as e:
logger.error(
"Failed to retrieve assembly specification",
exc_info=True,
extra={
"error_type": type(e).__name__,
"error_message": str(e),
"assembly_specification_id": assembly_specification_id,
},
)
raise HTTPException(
status_code=500,
detail="Failed to retrieve specification due to an internal " "error.",
)
@router.post("/", response_model=AssemblySpecification)
[docs]
async def create_assembly_specification(
request: CreateAssemblySpecificationRequest,
repository: AssemblySpecificationRepository = Depends( # type: ignore[misc]
get_assembly_specification_repository
),
) -> AssemblySpecification:
"""
Create a new assembly specification.
This endpoint creates a new assembly specification that defines how to
assemble documents of specific types, including JSON schemas and
knowledge service query configurations.
Args:
request: The assembly specification creation request
repository: Injected repository for persistence
Returns:
AssemblySpecification: The created specification with generated ID and
timestamps
"""
logger.info(
"Assembly specification creation requested",
extra={"specification_name": request.name},
)
try:
# Generate unique ID for the new specification
specification_id = await repository.generate_id()
# Convert request to domain model with generated ID
specification = request.to_domain_model(specification_id)
# Save the specification via repository
await repository.save(specification)
logger.info(
"Assembly specification created successfully",
extra={
"assembly_specification_id": (specification.assembly_specification_id),
"specification_name": specification.name,
"version": specification.version,
},
)
return specification
except Exception as e:
logger.error(
"Failed to create assembly specification",
exc_info=True,
extra={
"error_type": type(e).__name__,
"error_message": str(e),
"specification_name": request.name,
},
)
raise HTTPException(
status_code=500,
detail="Failed to create specification due to an internal error.",
)