"""# Summary Pets route handlers — names must match openapi.yaml operationIds. Notes: ``parents`` is a list foreign key expanded to full objects on read and normalized from ids on write. ``upload_pet_photo`` stores the raw bytes under ``settings.uploads_dir``; ``stream_actions`` returns an SSE stream scoped to the pet's species. """ import os from collections.abc import AsyncIterator from fastapi import Request, Response, UploadFile from fastapi.responses import StreamingResponse from config import settings from infra.adapters import actions_stream from interfaces._common import ( delete_or_404, dump, expand_ref_list, paginate, read_or_404, svc, ) async def list_pets(request: Request, limit: int = 20, offset: int = 0) -> dict: """List pets (paginated). ``limit=0`` returns all remaining items. Args: request (Request): Incoming request. limit (int): Page size. Defaults to 20. offset (int): Items to skip. Defaults to 0. Returns: dict: ``{"total": int, "items": list[Pet]}``. """ docs = await svc(request, "pet").search({}) return paginate([dump(d) for d in docs], limit, offset) async def create_pet(request: Request, body: dict, response: Response) -> dict: """Create a pet; parents may be bare ids or expanded objects. Args: request (Request): Incoming request. body (dict): Pet fields (``id`` ignored; ``parents`` normalized). response (Response): Response object; set to 201 on success. Returns: dict: The created pet with parents expanded. Raises: HTTPException: 422 when a parent reference cannot be resolved. """ response.status_code = 201 body.pop("id", None) body["parents"] = await expand_ref_list( svc(request, "parent"), body.get("parents"), "parent" ) doc = await svc(request, "pet").create(body) return dump(doc) async def get_pet(request: Request, id: int) -> dict: """Fetch one pet by id. Args: request (Request): Incoming request. id (int): Pet identifier. Returns: dict: The pet document. Raises: HTTPException: 404 when the pet does not exist. """ return dump(await read_or_404(svc(request, "pet"), id, "pet")) async def update_pet(request: Request, id: int, body: dict) -> dict: """Replace a pet by id; parents may be bare ids or expanded objects. Args: request (Request): Incoming request. id (int): Pet identifier. body (dict): Full replacement payload. Returns: dict: The updated pet with parents expanded. Raises: HTTPException: 404 when the pet does not exist; 422 on a bad parent ref. """ await read_or_404(svc(request, "pet"), id, "pet") body.pop("id", None) body["parents"] = await expand_ref_list( svc(request, "parent"), body.get("parents"), "parent" ) doc = await svc(request, "pet").update(id, body) return dump(doc) async def delete_pet(request: Request, id: int, response: Response) -> None: """Delete a pet by id. Args: request (Request): Incoming request. id (int): Pet identifier. response (Response): Response object; set to 204 on success. Raises: HTTPException: 404 when the pet does not exist. """ await delete_or_404(svc(request, "pet"), id, "pet") response.status_code = 204 async def upload_pet_photo(request: Request, id: int, file: UploadFile) -> dict: """Store an uploaded photo for a pet and remember its path. Args: request (Request): Incoming request. id (int): Pet identifier. file (UploadFile): Multipart upload carrying the photo bytes. Returns: dict: ``{"id": int, "status": "photo_uploaded", "path": str}``. Raises: HTTPException: 404 when the pet does not exist. """ pet = await read_or_404(svc(request, "pet"), id, "pet") os.makedirs(settings.uploads_dir, exist_ok=True) safe_name = os.path.basename(file.filename or "photo") path = os.path.join(settings.uploads_dir, f"{id}_{safe_name}") content = await file.read() with open(path, "wb") as fh: fh.write(content) await svc(request, "pet").update(id, {"photo": path, "parents": pet.parents}) return {"id": id, "status": "photo_uploaded", "path": path} async def stream_actions(request: Request, id: int) -> StreamingResponse: """Stream random, species-appropriate pet actions over SSE. Args: request (Request): Incoming request. id (int): Pet identifier whose species scopes the stream. Returns: StreamingResponse: ``text/event-stream`` emitting ``data: {action}``. Raises: HTTPException: 404 when the pet does not exist. """ pet = await read_or_404(svc(request, "pet"), id, "pet") async def event_gen() -> AsyncIterator[str]: queue = await actions_stream.subscribe(id, pet.species) try: while True: if await request.is_disconnected(): break data = await queue.get() yield f"data: {data}\n\n" finally: actions_stream.unsubscribe(id, queue) return StreamingResponse(event_gen(), media_type="text/event-stream")