Spaces:
Running
Running
| """Modality detection: map a document file to its parse path (pure, no I/O). | |
| The first stage of the core pipeline (architecture section 3, step 1) is intake | |
| and type detection: decide whether a file is a *native PDF* -- which Docling | |
| parses into text/layout directly -- or an *image* (a scan or a phone photo), | |
| which goes through the vision-direct or OCR strategy. That single choice, | |
| ``"native_pdf"`` vs ``"image"``, is the only thing this module produces; the | |
| acquisition stage downstream uses it to pick a parser. | |
| Classification is by file extension first, with a standard-library MIME-type | |
| fallback for files whose extension is not enumerated explicitly. No bytes are | |
| read -- the result is a pure function of the path string -- so detection stays | |
| fast, deterministic for the supported extensions, and testable offline. An | |
| unrecognised type raises :class:`UnsupportedModalityError` with an actionable | |
| message; the runner catches it per document and routes to review (CLAUDE.md | |
| rule 6), so a stray file never halts the loop. | |
| """ | |
| from __future__ import annotations | |
| import mimetypes | |
| from pathlib import Path | |
| from typing import Literal | |
| # The two parse paths the pipeline supports. ``image`` covers both flatbed scans | |
| # and phone photos -- they share the same downstream handling (architecture | |
| # section 7), so the distinction is not made here. | |
| Modality = Literal["native_pdf", "image"] | |
| # Extensions parsed as native PDFs (the Docling text/layout path). | |
| NATIVE_PDF_EXTENSIONS: frozenset[str] = frozenset({".pdf"}) | |
| # Raster image extensions treated as scans/photos (vision-direct or OCR path). | |
| IMAGE_EXTENSIONS: frozenset[str] = frozenset( | |
| {".png", ".jpg", ".jpeg", ".tif", ".tiff", ".bmp", ".gif", ".webp"} | |
| ) | |
| class UnsupportedModalityError(ValueError): | |
| """Raised when a file's type maps to none of the supported modalities.""" | |
| def _modality_from_mime(mime_type: str | None) -> Modality | None: | |
| """Classify a MIME type into a modality, or ``None`` if it maps to neither. | |
| Args: | |
| mime_type: A MIME type such as ``"application/pdf"`` or ``"image/png"``, | |
| or ``None`` when the type could not be guessed. | |
| Returns: | |
| ``"native_pdf"`` for PDF, ``"image"`` for any ``image/*`` type, or | |
| ``None`` when the MIME type is unknown or unsupported. | |
| """ | |
| if mime_type == "application/pdf": | |
| return "native_pdf" | |
| if mime_type is not None and mime_type.startswith("image/"): | |
| return "image" | |
| return None | |
| def detect_modality(path: str | Path) -> Modality: | |
| """Determine the parse modality of a document from its filename. | |
| Extension matching (case-insensitively) is the authoritative signal for the | |
| supported types; for any other extension the standard library's MIME guess | |
| is consulted as a fallback before giving up. The file is never opened, so | |
| ``path`` need not exist. | |
| Args: | |
| path: The document path. Only the filename and its extension are | |
| inspected; directories and the file's contents are ignored. | |
| Returns: | |
| ``"native_pdf"`` for PDFs, ``"image"`` for supported raster images. | |
| Raises: | |
| UnsupportedModalityError: If the extension and MIME guess both fail to | |
| resolve to a supported modality. | |
| """ | |
| name = Path(path).name | |
| suffix = Path(name).suffix.lower() | |
| if suffix in NATIVE_PDF_EXTENSIONS: | |
| return "native_pdf" | |
| if suffix in IMAGE_EXTENSIONS: | |
| return "image" | |
| guessed = _modality_from_mime(mimetypes.guess_type(name)[0]) | |
| if guessed is not None: | |
| return guessed | |
| shown_suffix = suffix or "(none)" | |
| raise UnsupportedModalityError( | |
| f"Cannot determine modality for {name!r}: extension {shown_suffix} is not " | |
| f"a supported document type. Supported: PDF " | |
| f"({', '.join(sorted(NATIVE_PDF_EXTENSIONS))}) or image " | |
| f"({', '.join(sorted(IMAGE_EXTENSIONS))})." | |
| ) | |
| def is_supported(path: str | Path) -> bool: | |
| """Report whether ``path`` resolves to a supported modality. | |
| A convenience predicate over :func:`detect_modality` for callers (e.g. the | |
| watcher) that want to skip unsupported files without handling an exception. | |
| Args: | |
| path: The document path to test. | |
| Returns: | |
| ``True`` if :func:`detect_modality` would succeed, ``False`` otherwise. | |
| """ | |
| try: | |
| detect_modality(path) | |
| except UnsupportedModalityError: | |
| return False | |
| return True | |