headroomlabs-ai/headroom · error · ImportError

Reading .xlsx files requires openpyxl. Install it with: pip

Error message

Reading .xlsx files requires openpyxl. Install it with: pip install headroom-ai[spreadsheet]

What it means

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.

Source

Thrown at headroom/transforms/spreadsheet_ingest.py:34

from pathlib import Path

__all__ = ["load_spreadsheet"]


def _rows_to_csv(rows: list[list[object]]) -> str:
    """Render rows to CSV text, dropping fully empty trailing rows."""
    buf = io.StringIO()
    writer = csv.writer(buf)
    for row in rows:
        writer.writerow(["" if cell is None else cell for cell in row])
    return buf.getvalue().strip("\n")


def _load_xlsx(path: Path) -> dict[str, str]:
    try:
        import openpyxl
    except ImportError as e:  # pragma: no cover - openpyxl ships in [dev]; defensive guard
        raise ImportError(
            "Reading .xlsx files requires openpyxl. "
            "Install it with: pip install headroom-ai[spreadsheet]"
        ) from e

    wb = openpyxl.load_workbook(path, read_only=True, data_only=True)
    sheets: dict[str, str] = {}
    try:
        for ws in wb.worksheets:
            rows = [list(r) for r in ws.iter_rows(values_only=True)]
            text = _rows_to_csv(rows)
            if text.strip():
                sheets[ws.title] = text
    finally:
        wb.close()
    return sheets


def _load_xls(

View on GitHub (pinned to 322425c43b)

Solutions

  1. Install the extra: `pip install headroom-ai[spreadsheet]`
  2. 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
  3. If openpyxl is intentionally absent, pre-check the file type and skip or reject `.xlsx` inputs with your own message before calling ingest

Example fix

# before
$ pip install headroom-ai
sheets = ingest_spreadsheet(Path("report.xlsx"))

# after
$ pip install "headroom-ai[spreadsheet]"
sheets = ingest_spreadsheet(Path("report.xlsx"))
Defensive patterns

Strategy: validation

Validate before calling

def xlsx_supported() -> bool:
    try:
        import openpyxl  # noqa: F401
        return True
    except ImportError:
        return False

if path.suffix.lower() == ".xlsx" and not xlsx_supported():
    raise RuntimeError("this deployment cannot ingest .xlsx; install headroom-ai[spreadsheet]")

Type guard

def can_read_xlsx() -> bool:
    return importlib.util.find_spec("openpyxl") is not None

Try / catch

try:
    sheets = ingest_spreadsheet(path)
except ImportError as e:
    if "openpyxl" in str(e):
        raise RuntimeError("deploy fix: pip install headroom-ai[spreadsheet]") from e
    raise

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Related errors


AI-assisted analysis of headroomlabs-ai/headroom@322425c43b (2026-08-15). Data as JSON: /api/errors/cfe7f490cd693219. Report an issue: GitHub.