diff --git a/.dockerignore b/.dockerignore index 9e00d76..f9a33a6 100644 --- a/.dockerignore +++ b/.dockerignore @@ -16,6 +16,7 @@ **/.idea **/coverage **/tmp +corpus/ **/.pytest_cache **/.mypy_cache **/.ruff_cache diff --git a/gateway/Dockerfile b/gateway/Dockerfile index a55b9fd..9145155 100644 --- a/gateway/Dockerfile +++ b/gateway/Dockerfile @@ -55,7 +55,6 @@ COPY cmake/ ./cmake/ COPY engine/ ./engine/ COPY bindings/ ./bindings/ COPY gateway/ ./gateway/ -COPY corpus/ ./corpus/ # Configure and build in the same layer. Keeping these operations together # avoids Docker/overlayfs timestamp skew causing Ninja to regenerate @@ -65,7 +64,7 @@ COPY corpus/ ./corpus/ RUN --mount=type=cache,target=/root/.cache \ --mount=type=cache,target=/opt/vcpkg/downloads \ cmake --preset linux-release \ - -DPDFENGINE_BUILD_TESTS=ON \ + -DPDFENGINE_BUILD_TESTS=OFF \ -DPDFENGINE_WITH_PDFIUM=ON \ -DPDFENGINE_WITH_SKIA=OFF \ -DPDFENGINE_WITH_QPDF=ON \ @@ -73,11 +72,7 @@ RUN --mount=type=cache,target=/root/.cache \ -DCMAKE_SUPPRESS_REGENERATION=ON \ && cmake --build out/build/linux-release -# Stage 2: Tester -FROM builder AS tester -RUN ctest --test-dir out/build/linux-release --output-on-failure - -# Stage 3: Runtime +# Stage 2: Runtime FROM python:3.11-slim ENV PYTHONDONTWRITEBYTECODE=1 \ @@ -92,10 +87,15 @@ RUN apt-get update \ zlib1g \ libpng16-16 \ fonts-liberation \ + libgl1 \ + libglib2.0-0 \ + libgomp1 \ + libsm6 \ + libxext6 \ && rm -rf /var/lib/apt/lists/* # Install the extension globally so it's not shadowed by the volume mount ./gateway:/home/app -COPY --from=tester /build/gateway/pdfengine*.so /usr/local/lib/python3.11/site-packages/ +COPY --from=builder /build/gateway/pdfengine*.so /usr/local/lib/python3.11/site-packages/ RUN groupadd --system app \ && useradd --system --gid app --create-home --home-dir /home/app app diff --git a/gateway/app/routers/ocr.py b/gateway/app/routers/ocr.py index 370c90f..f30cf04 100644 --- a/gateway/app/routers/ocr.py +++ b/gateway/app/routers/ocr.py @@ -77,9 +77,15 @@ def perform_ocr_on_page(document_id: str, page_index: int) -> OCRPageResponse: raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail="Page index out of bounds") if not ocr_service.is_ocr_available(): + init_err = ocr_service.get_ocr_init_error() + err_detail = ( + f"RapidOCR engine is not installed or available ({init_err})." + if init_err + else "RapidOCR engine is not installed or available." + ) raise HTTPException( status_code=status.HTTP_503_SERVICE_UNAVAILABLE, - detail="RapidOCR engine is not installed or available.", + detail=err_detail, ) try: diff --git a/gateway/app/services/ocr.py b/gateway/app/services/ocr.py index 9c3f73f..9ab0926 100644 --- a/gateway/app/services/ocr.py +++ b/gateway/app/services/ocr.py @@ -8,11 +8,12 @@ logger = logging.getLogger(__name__) _ocr_engine = None _ocr_available = False +_ocr_init_error: Optional[str] = None def load_ocr() -> bool: """Explicitly initializes the RapidOCR engine once during application startup.""" - global _ocr_engine, _ocr_available + global _ocr_engine, _ocr_available, _ocr_init_error if _ocr_engine is not None: return _ocr_available @@ -20,10 +21,14 @@ def load_ocr() -> bool: from rapidocr_onnxruntime import RapidOCR _ocr_engine = RapidOCR() _ocr_available = True + _ocr_init_error = None + logger.info("[Startup] RapidOCR engine loaded successfully into memory.") print("[Startup] RapidOCR engine loaded successfully into memory.") except Exception as e: _ocr_engine = None _ocr_available = False + _ocr_init_error = str(e) + logger.error(f"[Startup] Warning: RapidOCR engine initialization failed: {e}", exc_info=True) print(f"[Startup] Warning: RapidOCR engine not available: {e}") return _ocr_available @@ -37,6 +42,10 @@ def is_ocr_available() -> bool: return _ocr_available and _ocr_engine is not None +def get_ocr_init_error() -> Optional[str]: + return _ocr_init_error + + def recognize_image_bytes(image_bytes: bytes) -> Dict[str, Any]: if not is_ocr_available():