perf(document): lazy-import heavy document parsers

Move pypdf, python-docx, openpyxl, and python-pptx imports from module
level into the _extract_pdf / _extract_docx / _extract_xlsx /
_extract_pptx functions that actually use them. These four libraries
became core dependencies in v0.1.5.post2 (~25 MB combined) and were
paying the import cost on every nanobot startup even when no document
parsing was needed for the session.

The module-level SUPPORTED_EXTENSIONS set and the extract_text()
dispatch stay as-is; the "[error: <lib> not installed]" branches move
from the old module-level None sentinels into the corresponding
extractor's try/except ImportError block. Behavior for the error
message and for successful parses is identical.

All 20 tests in tests/test_document_parsing.py pass unchanged.

Fixes #3422
This commit is contained in:
Matt Van Horn
2026-04-25 02:10:30 +08:00
committed by Xubin Ren
parent 3441d5f89c
commit ee14e2df56
+19 -29
View File
@@ -7,26 +7,6 @@ from loguru import logger
from nanobot.utils.helpers import detect_image_mime
try:
from pypdf import PdfReader
except ImportError:
PdfReader = None # type: ignore
try:
from docx import Document as DocxDocument
except ImportError:
DocxDocument = None # type: ignore
try:
from openpyxl import load_workbook
except ImportError:
load_workbook = None # type: ignore
try:
from pptx import Presentation as PptxPresentation
except ImportError:
PptxPresentation = None # type: ignore
# Supported file extensions for text extraction
SUPPORTED_EXTENSIONS: set[str] = {
@@ -78,22 +58,16 @@ def extract_text(path: Path) -> str | None:
ext = path.suffix.lower()
# Document formats
# Document formats -- each branch lazily imports its parser so that
# startup does not pay the ~25 MB cost of loading openpyxl /
# python-docx / python-pptx / pypdf up front (see issue #3422).
if ext == ".pdf":
if PdfReader is None:
return "[error: pypdf not installed]"
return _extract_pdf(path)
elif ext == ".docx":
if DocxDocument is None:
return "[error: python-docx not installed]"
return _extract_docx(path)
elif ext == ".xlsx":
if load_workbook is None:
return "[error: openpyxl not installed]"
return _extract_xlsx(path)
elif ext == ".pptx":
if PptxPresentation is None:
return "[error: python-pptx not installed]"
return _extract_pptx(path)
elif _is_text_extension(ext):
return _extract_text_file(path)
@@ -107,6 +81,10 @@ def extract_text(path: Path) -> str | None:
def _extract_pdf(path: Path) -> str:
"""Extract text from PDF using pypdf."""
try:
from pypdf import PdfReader
except ImportError:
return "[error: pypdf not installed]"
try:
reader = PdfReader(path)
pages: list[str] = []
@@ -121,6 +99,10 @@ def _extract_pdf(path: Path) -> str:
def _extract_docx(path: Path) -> str:
"""Extract text from DOCX using python-docx."""
try:
from docx import Document as DocxDocument
except ImportError:
return "[error: python-docx not installed]"
try:
doc = DocxDocument(path)
paragraphs: list[str] = [p.text for p in doc.paragraphs if p.text.strip()]
@@ -132,6 +114,10 @@ def _extract_docx(path: Path) -> str:
def _extract_xlsx(path: Path) -> str:
"""Extract text from XLSX using openpyxl."""
try:
from openpyxl import load_workbook
except ImportError:
return "[error: openpyxl not installed]"
try:
wb = load_workbook(path, read_only=True, data_only=True)
try:
@@ -155,6 +141,10 @@ def _extract_xlsx(path: Path) -> str:
def _extract_pptx(path: Path) -> str:
"""Extract text from PPTX using python-pptx."""
try:
from pptx import Presentation as PptxPresentation
except ImportError:
return "[error: python-pptx not installed]"
try:
prs = PptxPresentation(path)
slides: list[str] = []