chore(deps): upgrade backend deps within their declared ranges

`uv lock --upgrade` plus the two code changes the new versions need.

firecrawl-anydoc 0.2.4 raises a dedicated `NeedsOcrError` where 0.2.3 raised
`UnsupportedError("... OCR is required")`, so the anydoc parser no longer
recognised a scanned PDF: the fallback still ran, but a near-empty result was
stored as an empty document instead of failing with the OCR_ENABLED hint.
`_needs_ocr` now accepts both spellings and looks the class up lazily, so an
older anydoc keeps working. 0.2.4 also refuses the CID-font NDA fixture
outright rather than dropping its Chinese column silently, so the PDF
trust-check tests stub that dropped output against the fixture's real bytes
(the check's own inputs) and a new test pins the refusal path.

ruff 0.16 widened its implicit default rule set, turning the dev-group bump
into 7131 findings across the tree. `.ruff.toml` now states the historical
selection (E4, E7, E9, F) explicitly and the CI pin moves to the locked
0.16.7, so lint no longer drifts with the version.
This commit is contained in:
Alex committed 2026-09-12 17:16:59 +01:00
1 parent 0d461d10a7
commit 7e00197678
9 files changed
+916 -810

No files matched your search

+25 -10
View File
@@ -9,11 +9,11 @@ of 2.3 GB, with equivalent output on clean office files, CSV and text-layer
PDFs.
anydoc never OCRs. It *detects* a scanned or image-only PDF and raises
``UnsupportedError`` ("OCR is required"), which is the routing point: a
parser given a ``fallback_parser`` (docling when installed, the native OCR
parser when OCR is on without docling, otherwise the legacy parser for the
suffix) delegates there; without one the file fails loudly as
``DocumentParseError`` rather than being stored empty.
``NeedsOcrError`` (``UnsupportedError`` "OCR is required" before 0.2.4),
which is the routing point: a parser given a ``fallback_parser`` (docling
when installed, the native OCR parser when OCR is on without docling,
otherwise the legacy parser for the suffix) delegates there; without one the
file fails loudly as ``DocumentParseError`` rather than being stored empty.
"""
import logging
from pathlib import Path
@@ -34,7 +34,7 @@ logger = logging.getLogger(__name__)
# not content.
_MIN_SCAN_FALLBACK_CHARS = 50
# Suffixes anydoc 0.2.3 converts, verified with ``anydoc.format_from_extension``.
# Suffixes anydoc 0.2.4 converts, verified with ``anydoc.format_from_extension``.
# ``.epub`` is deliberately absent: it stays on ``EpubParser``.
ANYDOC_SUFFIXES: Tuple[str, ...] = (
# Word processing
@@ -91,6 +91,22 @@ def _result_chars(result: Union[str, List[str]]) -> int:
return len(str(result).strip())
def _needs_ocr(exc: Exception) -> bool:
"""Whether an ``anydoc.ConvertError`` means "text exists but needs OCR".
anydoc 0.2.4 raises a dedicated ``NeedsOcrError`` for a PDF with no text
layer; 0.2.3 raised ``UnsupportedError`` with "OCR" in the message. Both
spellings route the same way, and the class is looked up lazily so an
anydoc without it still works.
"""
import anydoc
needs_ocr_error = getattr(anydoc, "NeedsOcrError", None)
if needs_ocr_error is not None and isinstance(exc, needs_ocr_error):
return True
return isinstance(exc, anydoc.UnsupportedError) and "OCR" in str(exc)
def _is_docling_backed(parser: Optional[BaseParser]) -> bool:
"""True when ``parser`` is a docling parser (worth a trust-check re-parse).
@@ -191,11 +207,10 @@ class AnydocParser(BaseParser):
) from exc
except anydoc.ConvertError as exc:
# Typed, per-document failures another engine may still handle:
# UnsupportedError (scanned PDF / unknown format), MalformedError,
# EncryptedError, MissingPartError.
ocr_needed = isinstance(exc, anydoc.UnsupportedError) and "OCR" in str(exc)
# NeedsOcrError (scanned PDF), UnsupportedError (unknown format),
# MalformedError, EncryptedError, MissingPartError.
return self._delegate(
path, errors, f"{type(exc).__name__}: {exc}", ocr_needed=ocr_needed
path, errors, f"{type(exc).__name__}: {exc}", ocr_needed=_needs_ocr(exc)
)
except OSError as exc:
raise DocumentParseError(