# api.py import asyncio import json # JSON 파싱을 위해 추가 from fastapi import APIRouter, FastAPI, File, HTTPException, UploadFile from fastapi.middleware.cors import CORSMiddleware from routers import google_docai from utils.config import ( CORS_ALLOW_CREDENTIALS, CORS_ALLOW_HEADERS, CORS_ALLOW_METHODS, CORS_ALLOW_ORIGINS, UPLOAD_DOCS_DIR, ) # 유틸리티 함수 임포트 (기존 코드 유지) from utils.file_utils import ( create_essential_directories, create_key, save_uploaded_file, ) app = FastAPI() # CORS 설정 (기존 코드 유지) app.add_middleware( CORSMiddleware, allow_origins=CORS_ALLOW_ORIGINS, allow_credentials=CORS_ALLOW_CREDENTIALS, allow_methods=CORS_ALLOW_METHODS, allow_headers=CORS_ALLOW_HEADERS, ) # 애플리케이션 시작 시 실행될 함수 (기존 코드 유지) @app.on_event("startup") async def startup_event(): print("Starting up...") create_essential_directories() print("Essential directories created.") # --- Document AI 라우터 --- doc_ai_router = APIRouter( prefix="/docai", tags=["DocumentAI"], ) # Document AI 관련 설정값 (프로덕션에서는 환경 변수나 설정 파일에서 로드 권장) DOCAI_PROJECT_ID = "drawingpdfocr-461103" DOCAI_LOCATION = "us" DOCAI_PROCESSOR_ID = "b838676d4e3b4758" # 실제 사용자의 프로세서 ID async def run_sync_in_threadpool(func, *args, **kwargs): """동기 함수를 별도의 스레드에서 실행하고 await 가능하게 만듭니다.""" loop = asyncio.get_event_loop() if hasattr(asyncio, "to_thread"): # Python 3.9+ return await asyncio.to_thread(func, *args, **kwargs) else: # Python < 3.9 return await loop.run_in_executor(None, lambda: func(*args, **kwargs)) @doc_ai_router.post("/process-document/") async def process_uploaded_document(file: UploadFile = File(...)): """ 업로드된 파일을 Document AI로 처리하고, 추출된 엔티티 정보를 JSON으로 반환합니다. """ if not file.content_type: raise HTTPException(status_code=400, detail="File content type is missing.") # 지원되는 MIME 타입 (예시, 필요에 따라 확장) allowed_mime_types = ["application/pdf", "image/jpeg", "image/png", "image/tiff"] if file.content_type not in allowed_mime_types: raise HTTPException( status_code=400, detail=f"Unsupported file type: '{file.content_type}'. Supported: {', '.join(allowed_mime_types)}", ) print(f"Received audio file for async processing: {file.filename}") file_id = str(create_key()) # 파일 저장 (유틸리티 함수 사용) try: file_path, file_content = save_uploaded_file(file, UPLOAD_DOCS_DIR, file_id) except HTTPException as e: raise e except Exception as e: raise HTTPException( status_code=500, detail=f"파일 저장 준비 중 오류 발생: {str(e)}" ) try: # Document AI 처리 (동기 함수를 비동기적으로 호출) document_result = await run_sync_in_threadpool( google_docai.process_document_from_content, # 수정된 함수 사용 project_id=DOCAI_PROJECT_ID, location=DOCAI_LOCATION, processor_id=DOCAI_PROCESSOR_ID, file_content=file_content, mime_type=file.content_type, field_mask="text,entities", # 필요한 필드 마스크 ) print(document_result) if not document_result: # 이 경우는 process_document_from_content 함수 내부에서 예외가 발생하지 않고 # None이나 빈 Document 객체를 반환했을 때를 대비 (일반적으론 예외 발생) raise HTTPException( status_code=500, detail="Failed to process document: No result from Document AI.", ) json_output_string = google_docai.extract_and_convert_to_json(document_result) return json.loads(json_output_string) except HTTPException as http_exc: # 이미 HTTPException으로 처리된 예외는 그대로 다시 발생시킴 raise http_exc except Exception as e: # 기타 예외 처리 (로깅 권장) # import traceback # print(f"Error processing file: {e}\n{traceback.format_exc()}") raise HTTPException( status_code=500, detail=f"An error occurred during document processing: {str(e)}", ) finally: await file.close() # 업로드된 파일 객체를 닫아 리소스 정리 # app에 라우터 등록 app.include_router(doc_ai_router) @app.get("/health/API") async def health_check(): """애플리케이션 상태 확인""" return {"status": "API ok"}