{"record":{"id":"e9387cfe330bec2a","repo":"opendataloader-project/opendataloader-pdf","slug":"ocr-engine-ocr-engine-is-not-supported-in-hybr","errorCode":null,"errorMessage":"OCR engine '{ocr_engine}' is not supported in hybrid local mode (filtered by _OCR_ENGINE_DENYLIST). Available engines: {available}","messagePattern":"OCR engine '(.+?)' is not supported in hybrid local mode \\(filtered by _OCR_ENGINE_DENYLIST\\)\\. Available engines: (.+?)","errorType":"validation","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"python/opendataloader-pdf/src/opendataloader_pdf/hybrid_server.py","lineNumber":449,"sourceCode":"        TableFormerMode,\n        TableStructureOptions,\n        TesseractCliOcrOptions,\n        TesseractOcrOptions,\n    )\n    from docling.document_converter import DocumentConverter, PdfFormatOption\n    from docling.models.factories import get_ocr_factory\n\n    # Delegate engine selection to docling's factory. We block external plugins for\n    # security/reproducibility; the module-level _OCR_ENGINE_DENYLIST filters\n    # engines unsuitable for hybrid local mode (e.g., remote inference servers).\n    ocr_factory = get_ocr_factory(allow_external_plugins=False)\n    if ocr_engine in _OCR_ENGINE_DENYLIST:\n        # Programmatic callers (importing this module) bypass argparse `choices`,\n        # so enforce the denylist here too. Without this, the module-level claim\n        # that `_OCR_ENGINE_DENYLIST` is shared across CLI and create_converter\n        # would only be true at the CLI layer.\n        available = sorted(set(ocr_factory.registered_kind) - _OCR_ENGINE_DENYLIST)\n        raise ValueError(\n            f\"OCR engine '{ocr_engine}' is not supported in hybrid local mode \"\n            f\"(filtered by _OCR_ENGINE_DENYLIST). Available engines: {available}\"\n        )\n    try:\n        ocr_options = ocr_factory.create_options(\n            kind=ocr_engine,\n            force_full_page_ocr=force_full_page_ocr,\n        )\n    except RuntimeError as e:\n        # Library-friendly error type so programmatic callers can catch and retry\n        # with a different engine. main() relies on argparse `choices` to gate\n        # invalid CLI input, so this branch is reached only via direct calls.\n        available = sorted(set(ocr_factory.registered_kind) - _OCR_ENGINE_DENYLIST)\n        raise ValueError(\n            f\"Unknown ocr_engine '{ocr_engine}': {e}\\nAvailable engines: {available}\"\n        ) from e\n\n    if ocr_lang:","sourceCodeStart":431,"sourceCodeEnd":467,"githubUrl":"https://github.com/opendataloader-project/opendataloader-pdf/blob/a7789b8e77dd05e2b8659eb3ea12fc458f80bfb8/python/opendataloader-pdf/src/opendataloader_pdf/hybrid_server.py#L431-L467","documentation":"ValueError raised when the requested ocr_engine is in the module-level _OCR_ENGINE_DENYLIST (currently frozenset({'kserve_v2_ocr'})), which blocks remote-inference-server OCR engines in hybrid local mode for security and reproducibility. This guard is enforced in create_converter too, not just the CLI argparse choices, because programmatic callers bypass argparse. The message lists the engines that remain available (registered kinds minus the denylist).","triggerScenarios":"Calling create_converter (or the hybrid server path) with ocr_engine='kserve_v2_ocr' (or any future value added to _OCR_ENGINE_DENYLIST). Programmatic import-and-call bypasses the CLI's argparse choices validation, so this in-function check is what catches it.","commonSituations":"A caller copies an engine name from docling docs that includes a remote inference server engine blocked here. A config defaults to a denied engine. An upgrade added a new engine to the denylist that the caller's saved config still references.","solutions":["Switch to an allowed engine: use one of the values printed in the 'Available engines' list (e.g. easyocr, tesseract, or auto).","If you genuinely need remote inference, do NOT use hybrid local mode — run the engine as a separate service and adjust your architecture, since the denylist is intentional for local mode.","Update any persisted config/defaults that reference a denied engine name.","Check the current denylist via from opendataloader_pdf.hybrid_server import _OCR_ENGINE_DENYLIST before choosing an engine programmatically."],"exampleFix":"# before: denied remote-inference engine in local mode\ncreate_converter(ocr_engine='kserve_v2_ocr')  # -> ValueError\n# after: use a locally-available engine\ncreate_converter(ocr_engine='easyocr')","handlingStrategy":"validation","validationCode":"from opendataloader_pdf.hybrid_server import _OCR_ENGINE_DENYLIST\ndef is_allowed_engine(engine: str) -> bool:\n    return engine not in _OCR_ENGINE_DENYLIST\n","typeGuard":"def is_denied_engine_error(exc: ValueError) -> bool:\n    return isinstance(exc, ValueError) and \"_OCR_ENGINE_DENYLIST\" in str(exc)","tryCatchPattern":"try:\n    create_converter(ocr_engine=engine)\nexcept ValueError as e:\n    if \"_OCR_ENGINE_DENYLIST\" in str(e):\n        # pick a locally-available engine instead\n        engine = \"easyocr\"\n        create_converter(ocr_engine=engine)\n    raise","preventionTips":["Never hardcode a denied engine name; read _OCR_ENGINE_DENYLIST at runtime.","Prefer 'auto' or a locally-installed engine (easyocr, tesseract).","Do not attempt remote-inference engines in hybrid local mode — the denylist is intentional."],"tags":["hybrid","ocr","denylist","config","security","valueerror","python"],"backgroundTag":null,"analyzedSha":"a7789b8e77dd05e2b8659eb3ea12fc458f80bfb8","analyzedAt":"2026-08-14T05:22:03.953Z","schemaVersion":2},"datasetVersion":"2026-08-14T10:17:34.591Z"}