docling-project/docling · error · NotImplementedError

ThreadedDoclingParseDocumentBackend only supports iter_pages

Error message

ThreadedDoclingParseDocumentBackend only supports iter_pages().

What it means

ThreadedDoclingParseDocumentBackend implements the lazily-threaded PDF parsing strategy where pages are produced as the parser completes them via iter_pages(). Its load_page(page_no) intentionally raises NotImplementedError because random-access page loading is incompatible with the threaded, streaming design. Hitting it is an API contract violation, not an environmental problem.

Source

Thrown at docling/backend/docling_parse_backend.py:555

        docling-parse document is loaded purely to read the (cheap, structure-only) outline.
        """
        password = (
            self.options.password.get_secret_value() if self.options.password else None
        )
        if isinstance(self.path_or_stream, BytesIO):
            self.path_or_stream.seek(0)
        dp_doc = DoclingPdfParser(loglevel="fatal").load(
            path_or_stream=self.path_or_stream, lazy=True, password=password
        )
        if dp_doc is None:
            return []
        try:
            return extract_outline_from_docling_parse(dp_doc)
        finally:
            dp_doc.unload()

    def load_page(self, page_no: int) -> PdfPageBackend:
        raise NotImplementedError(
            "ThreadedDoclingParseDocumentBackend only supports iter_pages()."
        )

    def iter_pages(self) -> Iterator[ThreadedDoclingParsePageBackend]:
        for result in self.parser.iterate_results():
            yield ThreadedDoclingParsePageBackend(result)

    def unload(self) -> None:
        if self._closed:
            return
        self._closed = True
        self.parser.unload(self.doc_key)
        super().unload()

View on GitHub (pinned to 61d76f1ff3)

Solutions

  1. Use iter_pages() to consume pages from the threaded backend instead of load_page().
  2. If you need random access, switch the pipeline back to the default parse backend (drop the threaded/parse-threads pipeline mode).
  3. Materialize pages first when both patterns are needed: pages = list(backend.iter_pages()), then index into the list.
  4. Guard generic helpers with hasattr/use a capability check or isinstance test against the threaded backend before calling load_page.

Example fix

# before
page = backend.load_page(3)  # NotImplementedError on threaded backend

# after
pages = list(backend.iter_pages())
page = pages[3] if len(pages) > 3 else None
Defensive patterns

Strategy: validation

Validate before calling

# Feature-detect before random page access:
if hasattr(backend, "load_page") and type(backend).load_page is not object.__getattribute__:
    pass  # too loose; prefer explicit type check below

Type guard

from docling.backend.docling_parse_backend import ThreadedDoclingParseDocumentBackend

def supports_random_page_access(backend) -> bool:
    return not isinstance(backend, ThreadedDoclingParseDocumentBackend)

Try / catch

try:
    page = backend.load_page(n)
except NotImplementedError:
    pages = list(backend.iter_pages())  # fallback: materialize streaming pages
    page = pages[n] if n < len(pages) else None

Prevention

When it happens

Trigger: Calling load_page(n) on ThreadedDoclingParseDocumentBackend — directly in custom code, or through generic code that assumes every AbstractPdfDocumentBackend supports random page access (e.g. OCR utilities or page-render helpers that fetch pages by number).

Common situations: Custom pipelines written against the non-threaded DoclingParseDocumentBackend (where load_page works) later switched to PdfPipelineMode.PARSE_THREADS; shared helper code iterating pages by index; third-party code unaware of the threaded backend's contract.

Related errors


AI-assisted analysis of docling-project/docling@61d76f1ff3 (2026-08-14). Data as JSON: /api/errors/6f759d92eafd71b7. Report an issue: GitHub.