pola-rs/polars · error · ParameterCollisionError

the values of `has_header` and `read_options["header_row"]`

Error message

the values of `has_header` and `read_options["header_row"]` are not compatible

What it means

polars.exceptions.ParameterCollisionError raised while normalizing read_excel options for the calamine engine: has_header=False contradicts read_options['header_row'] being set. header_row tells calamine which row holds the headers; declaring has_header=False says there is no header at all — the two cannot both hold, so the pair is rejected.

Source

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

def _get_read_options(
    read_options: dict[str, Any] | None,
    *,
    engine: ExcelSpreadsheetEngine,
    columns: Sequence[int] | Sequence[str] | None,
    infer_schema_length: int | None,
    has_header: bool,
) -> dict[str, Any]:
    """Normalise top-level parameters to engine-specific 'read_options' dict."""
    read_options = (read_options or {}).copy()

    if engine == "calamine":
        if ("use_columns" in read_options) and columns:
            msg = 'cannot specify both `columns` and `read_options["use_columns"]`'
            raise ParameterCollisionError(msg)
        elif read_options.get("header_row") is not None and has_header is False:
            msg = 'the values of `has_header` and `read_options["header_row"]` are not compatible'
            raise ParameterCollisionError(msg)
        elif ("schema_sample_rows" in read_options) and (
            infer_schema_length != N_INFER_DEFAULT
        ):
            msg = 'cannot specify both `infer_schema_length` and `read_options["schema_sample_rows"]`'
            raise ParameterCollisionError(msg)

        read_options["schema_sample_rows"] = infer_schema_length
        if has_header is False and "header_row" not in read_options:
            read_options["header_row"] = None

    elif engine == "xlsx2csv":
        if ("columns" in read_options) and columns:
            msg = 'cannot specify both `columns` and `read_options["columns"]`'
            raise ParameterCollisionError(msg)
        elif (
            "has_header" in read_options
            and read_options["has_header"] is not has_header
        ):

View on GitHub (pinned to df599052da)

Solutions

  1. For headerless data, pass has_header=False and remove 'header_row' from read_options.
  2. To relocate the header, keep read_options={'header_row': N} and leave has_header at its default (True).
  3. Build engine read_options per file shape instead of sharing one dict across headered and headerless inputs.

Example fix

# before
pl.read_excel('f.xlsx', has_header=False, read_options={'header_row': 0})

# after
pl.read_excel('f.xlsx', has_header=False)
Defensive patterns

Strategy: validation

Validate before calling

ro = dict(read_options or {})
if has_header is False:
    ro.pop('header_row', None)  # contradictory with has_header=False
pl.read_excel(path, has_header=has_header, read_options=ro)

Try / catch

try:
    pl.read_excel(path, has_header=has_header, read_options=read_options)
except pl.exceptions.ParameterCollisionError as e:
    if 'header_row' in str(e):
        ro = {k: v for k, v in (read_options or {}).items() if k != 'header_row'}
        pl.read_excel(path, has_header=has_header, read_options=ro)
    else:
        raise

Prevention

When it happens

Trigger: pl.read_excel('f.xlsx', engine='calamine', has_header=False, read_options={'header_row': 0}). Any non-None header_row together with has_header=False.

Common situations: Reading headerless exports while reusing a read_options dict built for headered files; porting calamine snippets that always set header_row=0 into polars calls that also set has_header.

Related errors


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