{"record":{"id":"fa4617ff275535a9","repo":"headroomlabs-ai/headroom","slug":"reading-legacy-xls-files-requires-xlrd-install-i","errorCode":null,"errorMessage":"Reading legacy .xls files requires xlrd. Install it with: pip install headroom-ai[spreadsheet]","messagePattern":"Reading legacy \\.xls files requires xlrd\\. Install it with: pip install headroom-ai\\[spreadsheet\\]","errorType":"exception","errorClass":"ImportError","httpStatus":null,"severity":"error","filePath":"headroom/transforms/spreadsheet_ingest.py","lineNumber":58,"sourceCode":"    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(\n    path: Path,\n) -> dict[str, str]:  # pragma: no cover - legacy .xls; needs optional xlrd + binary fixture\n    try:\n        import xlrd\n    except ImportError as e:\n        raise ImportError(\n            \"Reading legacy .xls files requires xlrd. \"\n            \"Install it with: pip install headroom-ai[spreadsheet]\"\n        ) from e\n\n    book = xlrd.open_workbook(str(path))\n    sheets: dict[str, str] = {}\n    for sheet in book.sheets():\n        rows = [sheet.row_values(i) for i in range(sheet.nrows)]\n        text = _rows_to_csv(rows)\n        if text.strip():\n            sheets[sheet.name] = text\n    return sheets\n\n\ndef load_spreadsheet(path: str | Path) -> dict[str, str]:\n    \"\"\"Load a spreadsheet file into ``{sheet_name: csv_text}``.\n\n    Args:","sourceCodeStart":40,"sourceCodeEnd":76,"githubUrl":"https://github.com/headroomlabs-ai/headroom/blob/322425c43bffde1ed0b64fecf3cf5951565dd82b/headroom/transforms/spreadsheet_ingest.py#L40-L76","documentation":"Reading legacy binary `.xls` (Excel 97-2003) workbooks requires the optional `xlrd` package, which is not installed. `xlrd` is kept separate from the base install (and from openpyxl, which only handles `.xlsx`), so legacy-format support is opt-in via the `spreadsheet` extra. Note the loader itself is marked as needing the optional dep plus a binary fixture, so it is exercised only lightly by the test suite.","triggerScenarios":"Calling the ingest API with a path whose suffix is `.xls`, routing to `_load_xls`, in an environment where `import xlrd` fails.","commonSituations":"Ingesting exports from old enterprise systems that still emit `.xls`; installing only `openpyxl` (which since v2.x explicitly dropped `.xls` support) and assuming it covers all Excel formats; minimal CI images without the extra.","solutions":["Install the extra: `pip install headroom-ai[spreadsheet]` (it includes xlrd)","Convert the workbook to `.xlsx` upstream (Excel/LibreOffice `--convert-to xlsx`, or pandas `read_excel` round-trip) if adding a dependency is undesirable","Reject or quarantine `.xls` inputs with a clear pre-check if legacy format support is out of scope for your pipeline"],"exampleFix":"# before\n$ pip install openpyxl\nsheets = ingest_spreadsheet(Path(\"legacy_export.xls\"))  # ImportError\n\n# after\n$ pip install \"headroom-ai[spreadsheet]\"\nsheets = ingest_spreadsheet(Path(\"legacy_export.xls\"))","handlingStrategy":"validation","validationCode":"def xls_supported() -> bool:\n    try:\n        import xlrd  # noqa: F401\n        return True\n    except ImportError:\n        return False\n\nif path.suffix.lower() == \".xls\" and not xls_supported():\n    raise RuntimeError(\"legacy .xls ingestion requires headroom-ai[spreadsheet]\")","typeGuard":"def can_read_xls() -> bool:\n    return importlib.util.find_spec(\"xlrd\") is not None","tryCatchPattern":"try:\n    sheets = ingest_spreadsheet(path)\nexcept ImportError as e:\n    if \"xlrd\" in str(e):\n        # convert instead of installing: libreoffice --headless --convert-to xlsx\n        raise RuntimeError(\".xls needs the spreadsheet extra or pre-conversion\") from e\n    raise","preventionTips":["Install the [spreadsheet] extra wherever legacy Excel files may arrive","Convert .xls to .xlsx at intake if you cannot add the dependency","Reject .xls uploads with a clear message if legacy support is out of scope"],"tags":["dependencies","optional-extras","spreadsheet","legacy-formats","importerror"],"backgroundTag":null,"analyzedSha":"322425c43bffde1ed0b64fecf3cf5951565dd82b","analyzedAt":"2026-08-15T01:03:05.481Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}