{"record":{"id":"cfe7f490cd693219","repo":"headroomlabs-ai/headroom","slug":"reading-xlsx-files-requires-openpyxl-install-it","errorCode":null,"errorMessage":"Reading .xlsx files requires openpyxl. Install it with: pip install headroom-ai[spreadsheet]","messagePattern":"Reading \\.xlsx files requires openpyxl\\. Install it with: pip install headroom-ai\\[spreadsheet\\]","errorType":"exception","errorClass":"ImportError","httpStatus":null,"severity":"error","filePath":"headroom/transforms/spreadsheet_ingest.py","lineNumber":34,"sourceCode":"from pathlib import Path\n\n__all__ = [\"load_spreadsheet\"]\n\n\ndef _rows_to_csv(rows: list[list[object]]) -> str:\n    \"\"\"Render rows to CSV text, dropping fully empty trailing rows.\"\"\"\n    buf = io.StringIO()\n    writer = csv.writer(buf)\n    for row in rows:\n        writer.writerow([\"\" if cell is None else cell for cell in row])\n    return buf.getvalue().strip(\"\\n\")\n\n\ndef _load_xlsx(path: Path) -> dict[str, str]:\n    try:\n        import openpyxl\n    except ImportError as e:  # pragma: no cover - openpyxl ships in [dev]; defensive guard\n        raise ImportError(\n            \"Reading .xlsx files requires openpyxl. \"\n            \"Install it with: pip install headroom-ai[spreadsheet]\"\n        ) from e\n\n    wb = openpyxl.load_workbook(path, read_only=True, data_only=True)\n    sheets: dict[str, str] = {}\n    try:\n        for ws in wb.worksheets:\n            rows = [list(r) for r in ws.iter_rows(values_only=True)]\n            text = _rows_to_csv(rows)\n            if text.strip():\n                sheets[ws.title] = text\n    finally:\n        wb.close()\n    return sheets\n\n\ndef _load_xls(","sourceCodeStart":16,"sourceCodeEnd":52,"githubUrl":"https://github.com/headroomlabs-ai/headroom/blob/322425c43bffde1ed0b64fecf3cf5951565dd82b/headroom/transforms/spreadsheet_ingest.py#L16-L52","documentation":"Spreadsheet ingest needs the optional `openpyxl` package to read `.xlsx` workbooks, and it is not installed in the current environment. The library keeps spreadsheet parsers as an optional dependency to keep the base install lean, so this ImportError is the documented pointer to install the `spreadsheet` extra.","triggerScenarios":"Calling the ingest API with a `.xlsx` path (which routes to `_load_xlsx`) in an environment where `pip install headroom-ai` was run without the `[spreadsheet]` extra, so `import openpyxl` fails.","commonSituations":"New deployment or CI image built from a minimal requirements list; a Docker image that copied only the base package; upgrading headroom in a venv created before spreadsheet support was needed; local dev machine never given the extra.","solutions":["Install the extra: `pip install headroom-ai[spreadsheet]`","Add the extra to your requirements/pyproject so environments are built with it: `headroom-ai[spreadsheet]` in dependencies, or the extra in a Dockerfile/CI requirements file","If openpyxl is intentionally absent, pre-check the file type and skip or reject `.xlsx` inputs with your own message before calling ingest"],"exampleFix":"# before\n$ pip install headroom-ai\nsheets = ingest_spreadsheet(Path(\"report.xlsx\"))\n\n# after\n$ pip install \"headroom-ai[spreadsheet]\"\nsheets = ingest_spreadsheet(Path(\"report.xlsx\"))","handlingStrategy":"validation","validationCode":"def xlsx_supported() -> bool:\n    try:\n        import openpyxl  # noqa: F401\n        return True\n    except ImportError:\n        return False\n\nif path.suffix.lower() == \".xlsx\" and not xlsx_supported():\n    raise RuntimeError(\"this deployment cannot ingest .xlsx; install headroom-ai[spreadsheet]\")","typeGuard":"def can_read_xlsx() -> bool:\n    return importlib.util.find_spec(\"openpyxl\") is not None","tryCatchPattern":"try:\n    sheets = ingest_spreadsheet(path)\nexcept ImportError as e:\n    if \"openpyxl\" in str(e):\n        raise RuntimeError(\"deploy fix: pip install headroom-ai[spreadsheet]\") from e\n    raise","preventionTips":["Put headroom-ai[spreadsheet] in the base requirements for any service touching spreadsheets","Fail fast at startup with an importlib availability probe if the extra is missing","Bake the extra into Docker/CI images, not ad-hoc installs"],"tags":["dependencies","optional-extras","spreadsheet","importerror"],"backgroundTag":null,"analyzedSha":"322425c43bffde1ed0b64fecf3cf5951565dd82b","analyzedAt":"2026-08-15T01:03:05.481Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}