Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions src/file_manipulator.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import os
from src.filler import Filler
from src.llm import LLM
from commonforms import prepare_form
from src.pdf_template import prepare_form_safe


class FileManipulator:
Expand All @@ -14,7 +14,7 @@ def create_template(self, pdf_path: str):
By using commonforms, we create an editable .pdf template and we store it.
"""
template_path = pdf_path[:-4] + "_template.pdf"
prepare_form(pdf_path, template_path)
prepare_form_safe(pdf_path, template_path)
return template_path

def fill_form(self, user_input: str, fields: list, pdf_form_path: str):
Expand Down
46 changes: 46 additions & 0 deletions src/pdf_template.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
"""PDF template preparation abstraction.

Why this exists:
- `commonforms` pulls in heavy runtime deps (ultralytics -> cv2 -> numpy).
- In minimal/server environments this can hard-crash the interpreter during import.
- FireForm only needs `commonforms.prepare_form()` for template creation.

This module provides a safe wrapper that:
- imports `commonforms` lazily (only when template creation is requested)
- raises a clear Python exception instead of crashing at app import time
"""

from __future__ import annotations

from typing import Protocol


class TemplatePreparer(Protocol):
def __call__(self, input_pdf_path: str, output_pdf_path: str) -> None:
...


def prepare_form_safe(input_pdf_path: str, output_pdf_path: str) -> None:
"""Prepare a PDF template using `commonforms` via a lazy import.

Raises:
RuntimeError: if `commonforms` cannot be imported or fails at runtime.
"""

try:
from commonforms import prepare_form as _prepare_form # type: ignore
except Exception as e: # pragma: no cover
# Catch broad exceptions because some environments segfault or raise
# low-level import errors when image dependencies are missing.
raise RuntimeError(
"Failed to import `commonforms`. Template creation requires the optional "
"commonforms + OpenCV runtime dependencies. "
"If running in Docker, ensure libgl1/libglib2.0-0 are installed."
) from e

try:
_prepare_form(input_pdf_path, output_pdf_path)
except Exception as e:
raise RuntimeError(
f"Template preparation failed for '{input_pdf_path}'."
) from e
20 changes: 20 additions & 0 deletions tests/test_commonforms_lazy_import.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import importlib


def test_importing_api_main_does_not_eagerly_import_commonforms(monkeypatch):
"""Regression test for environments where commonforms (cv2/numpy) crashes.

The FastAPI app should be importable without importing `commonforms`.
Template creation should be the only place that tries to import it.
"""

def _fail_import(name, globals=None, locals=None, fromlist=(), level=0):
if name == "commonforms":
raise ImportError("commonforms should not be imported at app import time")
return original_import(name, globals, locals, fromlist, level)

original_import = __builtins__["__import__"]
monkeypatch.setitem(__builtins__, "__import__", _fail_import)

# Should not raise
importlib.import_module("api.main")
12 changes: 11 additions & 1 deletion tests/test_templates.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,14 @@
def test_create_template(client):
import pytest


def test_create_template(client, monkeypatch):
# Patch at the usage site: api.routes.templates imports Controller
monkeypatch.setattr(
"api.routes.templates.Controller.create_template",
lambda _self, _pdf_path: "src/inputs/file_template.pdf",
raising=True,
)

payload = {
"name": "Template 1",
"pdf_path": "src/inputs/file.pdf",
Expand Down