"""Template (Ontology) CRUD endpoints.""" import logging from typing import List from fastapi import APIRouter, Depends, HTTPException, status from sqlalchemy.orm import Session from database import get_db from models import Template from schemas import TemplateCreate, TemplateOut, TemplateUpdate logger = logging.getLogger(__name__) router = APIRouter(prefix="/api/templates", tags=["Templates"]) def _pack_mapping_rules(data: dict) -> dict: """Pack classes/rules into mapping_rules for DB storage.""" mapping = data.get("mapping_rules") or {} if "classes" in data and data["classes"] is not None: mapping["classes"] = data.pop("classes") if "rules" in data and data["rules"] is not None: mapping["rules"] = data.pop("rules") data["mapping_rules"] = mapping return data def _unpack_template(template: Template) -> Template: """Unpack mapping_rules into classes/rules for response.""" mapping = template.mapping_rules or {} # Set as attributes so Pydantic from_attributes can pick them up template.classes = mapping.get("classes", []) template.rules = mapping.get("rules", []) return template @router.post( "", response_model=TemplateOut, status_code=status.HTTP_201_CREATED, summary="Create a new template", ) def create_template(payload: TemplateCreate, db: Session = Depends(get_db)) -> Template: """Create a new ontology template / segmentation class.""" data = payload.model_dump() data = _pack_mapping_rules(data) template = Template(**data) db.add(template) db.commit() db.refresh(template) _unpack_template(template) logger.info("Created template id=%s name=%s", template.id, template.name) return template @router.get( "", response_model=List[TemplateOut], summary="List all templates", ) def list_templates( skip: int = 0, limit: int = 100, db: Session = Depends(get_db), ) -> List[Template]: """Retrieve all ontology templates.""" templates = db.query(Template).offset(skip).limit(limit).all() for t in templates: _unpack_template(t) return templates @router.get( "/{template_id}", response_model=TemplateOut, summary="Get a single template", ) def get_template(template_id: int, db: Session = Depends(get_db)) -> Template: """Retrieve a template by its ID.""" template = db.query(Template).filter(Template.id == template_id).first() if not template: raise HTTPException(status_code=404, detail="Template not found") _unpack_template(template) return template @router.patch( "/{template_id}", response_model=TemplateOut, summary="Update a template", ) def update_template( template_id: int, payload: TemplateUpdate, db: Session = Depends(get_db), ) -> Template: """Update template fields partially.""" template = db.query(Template).filter(Template.id == template_id).first() if not template: raise HTTPException(status_code=404, detail="Template not found") data = payload.model_dump(exclude_unset=True) if "classes" in data or "rules" in data: data = _pack_mapping_rules(data) for key, value in data.items(): setattr(template, key, value) db.commit() db.refresh(template) _unpack_template(template) logger.info("Updated template id=%s", template_id) return template @router.delete( "/{template_id}", status_code=status.HTTP_204_NO_CONTENT, summary="Delete a template", ) def delete_template(template_id: int, db: Session = Depends(get_db)) -> None: """Delete a template. Associated annotations will have template_id set to NULL.""" template = db.query(Template).filter(Template.id == template_id).first() if not template: raise HTTPException(status_code=404, detail="Template not found") db.delete(template) db.commit() logger.info("Deleted template id=%s", template_id)