from __future__ import annotations from io import BytesIO from pathlib import Path MAX_DOCUMENT_BYTES = 5 * 1024 * 1024 class DocumentError(ValueError): pass def read_document(path: Path) -> str: path = path.expanduser().resolve() if not path.is_file(): raise DocumentError(f"Document does not exist: {path}") if path.stat().st_size > MAX_DOCUMENT_BYTES: raise DocumentError("Document is larger than the 5 MB safety limit.") return read_document_bytes(path.name, path.read_bytes()) def read_document_bytes(filename: str, data: bytes) -> str: if len(data) > MAX_DOCUMENT_BYTES: raise DocumentError("Document is larger than the 5 MB safety limit.") suffix = Path(filename).suffix.lower() if suffix in {".txt", ".md", ".json"}: try: text = data.decode("utf-8") except UnicodeDecodeError as exc: raise DocumentError(f"{filename} is not valid UTF-8 text.") from exc elif suffix == ".pdf": from pypdf import PdfReader text = "\n".join(page.extract_text() or "" for page in PdfReader(BytesIO(data)).pages) elif suffix == ".docx": from docx import Document document = Document(BytesIO(data)) parts = [paragraph.text for paragraph in document.paragraphs] for table in document.tables: for row in table.rows: parts.append(" | ".join(cell.text for cell in row.cells)) text = "\n".join(parts) else: raise DocumentError("Supported resume formats: .pdf, .docx, .txt, .md, and .json") text = text.strip() if not text: raise DocumentError(f"No readable text was found in {filename}.") return text