pola-rs/polars · error · RuntimeError

no named tables found in sheet {sheet_name!r} (looking for {

Error message

no named tables found in sheet {sheet_name!r} (looking for {table_name!r})

What it means

The else-branch of the same raise in _read_spreadsheet_openpyxl as error 436: it fires only when n_tables == 0, i.e. the loop over parser.worksheets never ran — the workbook contains zero worksheets. Since sheet_name stays None, the message renders as "no named tables found in sheet None (looking for 'X')". A workbook with no sheets at all is unusual (Excel itself always keeps one) and typically indicates a corrupt, empty, or mis-generated xlsx.

Source

Thrown at py-polars/src/polars/io/spreadsheet/functions.py:1219

    has_header = read_options.pop("has_header", True)
    schema_overrides = schema_overrides or {}
    no_inference = infer_schema_length == 0
    header: list[str | None] = []

    if table_name and not sheet_name:
        ws, sheet_name, n_tables = None, None, 0
        for sheet in parser.worksheets:
            n_tables += 1
            if table_name in sheet.tables:
                ws, sheet_name = sheet, sheet.title
                break
        if ws is None:
            msg = (
                f"table named {table_name!r} not found in sheet {sheet_name!r}"
                if n_tables
                else f"no named tables found in sheet {sheet_name!r} (looking for {table_name!r})"
            )
            raise RuntimeError(msg)
    else:
        ws = parser[sheet_name]

    # prefer detection of actual table objects; otherwise read
    # data in the used worksheet range, dropping null columns
    if tables := getattr(ws, "tables", None):
        table = tables[table_name] if table_name else next(iter(tables.values()))
        rows = list(ws[table.ref])
        if not rows:
            return _empty_frame(raise_if_empty)
        if has_header:
            header.extend(cell.value for cell in rows.pop(0))
        else:
            header.extend(f"column_{n}" for n in range(1, len(rows[0]) + 1))
        if table.totalsRowCount:
            rows = rows[: -table.totalsRowCount]
        rows_iter = rows
    elif table_name:

View on GitHub (pinned to df599052da)

Solutions

  1. Open the file in Excel/LibreOffice — if it will not open, the file is corrupt: re-download or regenerate it
  2. Verify with openpyxl first: wb = openpyxl.load_workbook(path, read_only=True); assert wb.worksheets
  3. Guard ingestion pipelines with a file-integrity check (size, zip validity) before parsing

Example fix

# before
pl.read_excel(src, table_name='Sales', engine='openpyxl')

# after (validate the workbook has sheets first)
import openpyxl
wb = openpyxl.load_workbook(src, read_only=True)
if not wb.worksheets:
    raise ValueError(f'{src}: workbook has no worksheets (corrupt or empty file)')
pl.read_excel(src, table_name='Sales', engine='openpyxl')
Defensive patterns

Strategy: validation

Validate before calling

import openpyxl, zipfile

def workbook_is_readable(path) -> bool:
    if not zipfile.is_zipfile(path):
        return False
    with openpyxl.load_workbook(path, read_only=True) as wb:
        return bool(wb.worksheets)  # zero worksheets -> this error (message shows 'sheet None')

if not workbook_is_readable(src):
    raise ValueError(f'{src}: corrupt/empty workbook — regenerate or re-download it')
df = pl.read_excel(src, table_name=tbl, engine='openpyxl')

Try / catch

try:
    df = pl.read_excel(src, table_name=tbl, engine='openpyxl')
except RuntimeError as e:
    if 'no named tables found' in str(e) and 'sheet None' in str(e):
        raise ValueError(f'{src}: workbook has no worksheets (corrupt file)') from e
    raise

Prevention

When it happens

Trigger: pl.read_excel(src, table_name='X', engine='openpyxl') on an xlsx whose workbook.xml declares no sheets; a truncated download (HTTP response cut short but parseable); a workbook skeleton written by a broken generator.

Common situations: Interrupted file transfers; test fixtures that write empty workbooks; files produced by failing upstream jobs that still emit a zip container.

Related errors


AI-assisted analysis of pola-rs/polars@df599052da (2026-08-16). Data as JSON: /api/errors/905b03a0740588bd. Report an issue: GitHub.