pola-rs/polars · error · OSError

file must be opened in binary mode

Error message

file {source.name!r} must be opened in binary mode

What it means

When a file-handle source is passed to `read_excel` with the fastexcel engine, it must be opened in binary mode ('b'); text-mode handles raise `OSError` because fastexcel needs raw bytes to parse the xlsx container.

Solutions

  1. Open the file with mode 'rb': `open(path, 'rb')`
  2. Or simply pass the path string/Path directly instead of a handle
  3. If using a text handle by mistake, read bytes from a separate binary handle

Example fix

// before
with open('data.xlsx') as f:
    pl.read_excel(f)
// after
with open('data.xlsx', 'rb') as f:
    pl.read_excel(f)
Defensive patterns

Strategy: validation

Validate before calling

if hasattr(src, 'mode'):
    assert 'b' in src.mode, 'Excel file handles must be opened in binary mode'

typeGuard: def is_binary_handle(src) -> bool:
    return hasattr(src, 'mode') and 'b' in src.mode

Type guard

def is_binary_handle(src) -> bool:
    return hasattr(src, 'mode') and 'b' in src.mode

Try / catch

try:
    df = pl.read_excel(handle)
except OSError as e:
    if 'binary mode' in str(e):
        df = pl.read_excel(handle.name)
    else:
        raise

Prevention

When it happens

Trigger: `pl.read_excel(open('f.xlsx'))` (default text mode) or `open('f.xlsx', 'r')` with engine='fastexcel'.

Common situations: Forgetting mode='rb' when opening workbooks; wrapping an Excel file in a text pipeline; code refactored from csv.read (text) to Excel reading.

Understand the failure class

Background: "is not a compatible type" / "cannot merge" errors: when a value's type doesn't match what the library requires — this error's family across 65 libraries.

Related errors


AI-assisted analysis of pola-rs/polars@fe841f959e (2026-09-18). Data as JSON: /api/errors/f0adfc8a615416cd. Report an issue: GitHub.

Appendix: source

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

    elif engine == "calamine":
        fastexcel = import_optional("fastexcel", min_version="0.7.0")
        reading_bytesio, reading_bytes = (
            isinstance(source, BytesIO),
            isinstance(source, bytes),
        )
        if (reading_bytesio or reading_bytes) and parse_version(
            module_version := fastexcel.__version__
        ) < (0, 10):
            msg = f"`fastexcel` >= 0.10 is required to read bytes; found {module_version})"
            raise ModuleUpgradeRequiredError(msg)

        if reading_bytesio:
            source = source.getvalue()  # type: ignore[union-attr]
        elif isinstance(source, (BufferedReader, TextIOWrapper)):
            if "b" not in source.mode:
                msg = f"file {source.name!r} must be opened in binary mode"
                raise OSError(msg)
            elif (filename := source.name) and Path(filename).exists():
                source = filename
            else:
                source = source.read()

        parser = fastexcel.read_excel(source, **engine_options)
        sheets = [
            {"index": i + 1, "name": nm} for i, nm in enumerate(parser.sheet_names)
        ]
        return _read_spreadsheet_calamine, parser, sheets

    msg = f"unrecognized engine: {engine!r}"
    raise NotImplementedError(msg)


def _csv_buffer_to_frame(
    csv: StringIO,
    *,

View on GitHub (pinned to fe841f959e)