pola-rs/polars · error · OSError
file {source.name!r} must be opened in binary mode
Error message
file {source.name!r} must be opened in binary mode What it means
Raised by pl.read_excel (engine='calamine', the default) in _initialise_spreadsheet_parser as an OSError when source is a file handle (BufferedReader or TextIOWrapper) whose mode lacks 'b' — i.e. a file opened in text mode. fastexcel needs bytes or a filename, so polars validates the handle's mode before handing it over.
Source
Thrown at py-polars/src/polars/io/spreadsheet/functions.py:884
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 df599052da)
Solutions
- Open the file in binary mode: open(path, 'rb')
- Simplest: pass the path string itself and let polars open the file
- If the handle comes from elsewhere, read it as bytes (fh.read() in binary) or use its .name if it points at a real file
Example fix
# before
with open('report.xlsx') as f: # text mode -> OSError
df = pl.read_excel(f)
# after
with open('report.xlsx', 'rb') as f:
df = pl.read_excel(f)
# or simply: df = pl.read_excel('report.xlsx') Defensive patterns
Strategy: validation
Validate before calling
def ensure_binary_handle(fh):
mode = getattr(fh, 'mode', 'b')
if 'b' not in mode:
fh.close()
return open(fh.name, 'rb')
return fh
with ensure_binary_handle(open('report.xlsx')) as fh:
df = pl.read_excel(fh, engine='calamine') Type guard
def is_binary_fileobj(fh) -> bool:
return hasattr(fh, 'mode') and 'b' in fh.mode Try / catch
try:
df = pl.read_excel(fh, engine='calamine')
except OSError as e:
if 'binary mode' in str(e):
df = pl.read_excel(fh.name, engine='calamine') # pass the path instead
else:
raise Prevention
- Always open workbooks with open(path, 'rb') — or pass the path string and skip handle management
- Audit helpers that vend file handles for text defaults
- This check is calamine-only; if you rely on text handles elsewhere, isolate the calamine call
When it happens
Trigger: with open('f.xlsx') as f: pl.read_excel(f) — Python's open() defaults to text mode ('r'), producing a TextIOWrapper that fails the 'b' in mode check. Also open(path, 'rt'), or handles obtained from text-oriented helpers.
Common situations: Code copied from csv workflows (text mode is fine there); refactoring from open(path) context managers; passing a handle returned by a generic 'download to file' utility that opened it for text.
Related errors
- `fastexcel` >= 0.10 is required to read bytes; found {module
- a more recent version of `fastexcel` is required for 'schema
- a more recent version of `fastexcel` is required for 'use_co
- a more recent version of `fastexcel` is required for 'table_
- table named {table_name!r} not found in sheet {sheet_name!r}
AI-assisted analysis of pola-rs/polars@df599052da (2026-08-16).
Data as JSON: /api/errors/f0adfc8a615416cd.
Report an issue: GitHub.