From ebc1846649d139450e3aed1430c33015ed17e753 Mon Sep 17 00:00:00 2001 From: Sohel2309 Date: Tue, 11 Aug 2026 23:27:58 +0000 Subject: [PATCH] fix(pdf): raise clear error when fast strategy can't handle complex PDF partition_pdf() skips pdfminer text extraction for PDFs flagged by is_pdf_too_complex() as mostly vector graphics (e.g. CAD/engineering drawings), since pdfminer is slow and unreliable on them. For strategy="auto" this correctly falls back to another strategy. But an explicitly-requested strategy="fast" has no fallback available, so it silently returned an empty element list with no indication anything went wrong. This raises a ValueError in that case instead, explaining why and suggesting strategy="hi_res" or strategy="auto". strategy="auto" is unaffected and continues to fall back gracefully, and normal (non- complex) PDFs with strategy="fast" are unaffected. Fixes #4260 --- CHANGELOG.md | 2 ++ .../partition/pdf_image/test_pdf.py | 22 ++++++++++++++++ unstructured/partition/pdf.py | 25 ++++++++++++------- 3 files changed, 40 insertions(+), 9 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 44718af363..48125e0ffb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,8 @@ ### Fixes +- **Raise a clear error instead of silently returning no elements when `strategy="fast"` can't be honored**: `partition_pdf()` skips pdfminer text extraction for PDFs flagged by `is_pdf_too_complex()` as mostly vector graphics, since pdfminer is slow and unreliable on them. For `strategy="auto"` this correctly falls back to another strategy, but an explicitly-requested `strategy="fast"` had no fallback available and silently returned `[]` with no indication anything went wrong. It now raises a `ValueError` explaining why and suggesting `"hi_res"` or `"auto"` instead. `strategy="auto"` is unaffected and continues to fall back gracefully. + - **Stop the `GLOBAL_WORKING_DIR` tests from disturbing other pytest-xdist workers**: test-only change, no library behavior changes. The two tests exercising `GLOBAL_WORKING_DIR_ENABLED` now redirect the working dir to a private `tmp_path` and restore `tempfile.tempdir` unconditionally, rather than moving the shared pgid-keyed directory aside mid-run and leaving the worker's `tempfile.tempdir` pointed at it. That shared path made `test_dockerfile` fail intermittently, with an unrelated test dying inside `tempfile`. ## 0.25.2 diff --git a/test_unstructured/partition/pdf_image/test_pdf.py b/test_unstructured/partition/pdf_image/test_pdf.py index bde2f4245c..4481d09583 100644 --- a/test_unstructured/partition/pdf_image/test_pdf.py +++ b/test_unstructured/partition/pdf_image/test_pdf.py @@ -1767,6 +1767,28 @@ def test_is_pdf_too_complex_returns_false_for_normal_pdf(): assert not pdf.is_pdf_too_complex(filename=example_doc_path("pdf/layout-parser-paper.pdf")) +def test_partition_pdf_raises_for_explicit_fast_strategy_on_complex_pdf(): + """strategy="fast" cannot honor a PDF flagged as too complex for pdfminer text + extraction (see is_pdf_too_complex). Previously this silently returned an empty + element list; it should now raise a clear, actionable error instead. See #4260.""" + filename = example_doc_path("pdf/layout-parser-paper.pdf") + + with mock.patch.object(pdf, "is_pdf_too_complex", return_value=True): + with pytest.raises(ValueError, match="too complex"): + pdf.partition_pdf(filename=filename, strategy=PartitionStrategy.FAST) + + +def test_partition_pdf_auto_strategy_still_falls_back_on_complex_pdf(): + """strategy="auto" should keep degrading gracefully (no exception) when a PDF is + flagged as too complex -- only an explicitly-requested "fast" strategy should raise.""" + filename = example_doc_path("pdf/layout-parser-paper.pdf") + + with mock.patch.object(pdf, "is_pdf_too_complex", return_value=True): + elements = pdf.partition_pdf(filename=filename, strategy=PartitionStrategy.AUTO) + + assert len(elements) > 0 + + def test_document_to_element_list_omits_coord_system_when_coord_points_absent(): # TODO (yao): investigate why we need this test. The LayoutElement definition suggests bbox # can't be None and it has to be a Rectangle object that has x1, y1, x2, y2 attributes. diff --git a/unstructured/partition/pdf.py b/unstructured/partition/pdf.py index 2eb2597a24..7edd69bbd0 100644 --- a/unstructured/partition/pdf.py +++ b/unstructured/partition/pdf.py @@ -300,14 +300,21 @@ def partition_pdf_or_image( pdf_text_extractable = False if not is_image: - try: - if is_pdf_too_complex(filename=filename, file=file): - logger.info( - "PDF is too complex for text extraction based on heuristic checks. " - "Falling back to hi_res strategy without text extraction." + if is_pdf_too_complex(filename=filename, file=file): + if strategy == PartitionStrategy.FAST: + raise ValueError( + "PDF is too complex for text extraction based on heuristic checks " + "(high ratio of vector graphics to text), so the fast strategy " + "cannot reliably extract text from it. Use strategy='hi_res' or " + "strategy='auto' instead." ) + logger.info( + "PDF is too complex for text extraction based on heuristic checks. " + "Falling back to hi_res strategy without text extraction." + ) - else: + else: + try: extracted_elements = extractable_elements( filename=filename, file=spooled_to_bytes_io_if_needed(file), @@ -323,9 +330,9 @@ def partition_pdf_or_image( for page_elements in extracted_elements for el in page_elements ) - except Exception as e: - logger.debug(e) - logger.info("PDF text extraction failed, skip text extraction...") + except Exception as e: + logger.debug(e) + logger.info("PDF text extraction failed, skip text extraction...") strategy = determine_pdf_or_image_strategy( strategy,