pola-rs/polars · error · ModuleUpgradeRequiredError
`fastexcel` >= 0.10 is required to read bytes; found {module
Error message
`fastexcel` >= 0.10 is required to read bytes; found {module_version}) What it means
Raised by pl.read_excel (engine='calamine', the default) in _initialise_spreadsheet_parser when source is bytes or BytesIO and the installed fastexcel is older than 0.10.0. polars imports fastexcel with a floor of only 0.7.0, then refuses to hand raw bytes to older versions that cannot accept them (ModuleUpgradeRequiredError, a ModuleNotFoundError subclass; note the message text carries a stray trailing parenthesis — cosmetic only). This also affects reads of remote URLs, since polars downloads them to a BytesIO before parsing.
Source
Thrown at py-polars/src/polars/io/spreadsheet/functions.py:877
openpyxl = import_optional("openpyxl")
if isinstance(source, bytes):
source = BytesIO(source)
parser = openpyxl.load_workbook(source, data_only=True, **engine_options)
sheets = [{"index": i + 1, "name": ws.title} for i, ws in enumerate(parser)]
return _read_spreadsheet_openpyxl, parser, sheets
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
View on GitHub (pinned to df599052da)
Solutions
- Upgrade: pip install -U 'fastexcel>=0.10' (latest is fine and also unlocks table_name/use_columns)
- If you cannot upgrade, write the bytes to a temporary file and pass the path instead (old fastexcel reads paths fine)
- Alternatively switch to engine='openpyxl', which accepts BytesIO directly
Example fix
# before (fastexcel 0.7.x)
pl.read_excel(requests.get(url).content)
# after (no upgrade possible: spool to a temp file)
import tempfile, pathlib
with tempfile.NamedTemporaryFile(suffix='.xlsx', delete=False) as f:
f.write(workbook_bytes)
pl.read_excel(f.name, engine='openpyxl') # or upgrade fastexcel>=0.10 and pass bytes directly Defensive patterns
Strategy: validation
Validate before calling
def supports_bytes_source() -> bool:
try:
import fastexcel
return tuple(int(p) for p in fastexcel.__version__.split('.')[:2]) >= (0, 10)
except ImportError:
return False
if isinstance(source, (bytes, io.BytesIO)) and not supports_bytes_source():
import tempfile
with tempfile.NamedTemporaryFile(suffix='.xlsx', delete=False) as f:
f.write(source if isinstance(source, bytes) else source.getvalue())
source = f.name # hand old fastexcel a path instead of bytes
df = pl.read_excel(source, engine='calamine') Try / catch
from polars.exceptions import ModuleUpgradeRequiredError
try:
df = pl.read_excel(workbook_bytes)
except ModuleUpgradeRequiredError as e:
if 'fastexcel' in str(e):
df = pl.read_excel(workbook_bytes, engine='openpyxl') # fallback engine takes bytes
else:
raise Prevention
- Pin fastexcel >= 0.10 (ideally latest) in requirements next to polars
- Remember URL sources become BytesIO internally, so remote reads need the same version floor
- Catch ModuleUpgradeRequiredError (a ModuleNotFoundError subclass) in deployment smoke tests
When it happens
Trigger: pl.read_excel(workbook_bytes, engine='calamine') or pl.read_excel('https://host/report.xlsx') with fastexcel 0.7.x–0.9.x installed; BytesIO sources from HTTP downloads, S3 clients, or in-memory generation.
Common situations: Stale fastexcel pin in requirements.txt/lockfile; CI caches that keep the old wheel; environments where only the import floor (0.7) was ever checked; datasets fetched over the network.
Related errors
- 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_
- file {source.name!r} must be opened in binary mode
- 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/92087a67579a9bd3.
Report an issue: GitHub.