67 lines
2.2 KiB
Python
67 lines
2.2 KiB
Python
"""
|
|
Extension → extractor dispatch.
|
|
|
|
Maps a file extension to an extractor "family" module under doocus/extractors/.
|
|
Families are imported lazily so an app that only handles text files doesn't need
|
|
the office/pdf/image dependencies installed. Unknown extensions (and families
|
|
whose optional deps are missing) are reported as "unhandled" — never fatal.
|
|
"""
|
|
import importlib
|
|
import logging
|
|
from pathlib import Path
|
|
from typing import Dict, Optional
|
|
|
|
from .extractors.base import ExtractionResult
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
# extension (no dot, lowercase) → family module name under doocus.extractors
|
|
EXT_FAMILY: Dict[str, str] = {
|
|
"md": "text", "markdown": "text", "txt": "text",
|
|
"yaml": "text", "yml": "text", "json": "text",
|
|
"csv": "tabular",
|
|
"docx": "office", "pptx": "office", "xlsx": "office",
|
|
"pdf": "pdf",
|
|
"html": "web", "htm": "web",
|
|
"jpg": "image", "jpeg": "image", "png": "image",
|
|
"mp4": "media",
|
|
}
|
|
|
|
|
|
def ext_of(source: Path) -> str:
|
|
return source.suffix.lower().lstrip(".")
|
|
|
|
|
|
def family_for(source: Path) -> Optional[str]:
|
|
return EXT_FAMILY.get(ext_of(source))
|
|
|
|
|
|
def extract(source: Path, options: dict) -> ExtractionResult:
|
|
"""Dispatch to the right family extractor.
|
|
|
|
Returns an ExtractionResult in all cases — unhandled types and import/extract
|
|
failures come back as an empty result carrying a warning, so a batch keeps
|
|
going.
|
|
"""
|
|
family = family_for(source)
|
|
if family is None:
|
|
return ExtractionResult(
|
|
extractor="unhandled",
|
|
warnings=[f"no extractor for extension '.{ext_of(source)}'"],
|
|
)
|
|
try:
|
|
module = importlib.import_module(f".extractors.{family}", package=__package__)
|
|
except ImportError as e:
|
|
return ExtractionResult(
|
|
extractor=f"{family}/unavailable",
|
|
warnings=[f"extractor '{family}' unavailable (missing dependency): {e}"],
|
|
)
|
|
try:
|
|
return module.extract(source, options)
|
|
except Exception as e: # isolate: one bad file must not abort the batch
|
|
logger.warning("extractor '%s' failed on %s: %s", family, source.name, e)
|
|
return ExtractionResult(
|
|
extractor=f"{family}/error",
|
|
warnings=[f"extraction failed: {e}"],
|
|
)
|