pola-rs/polars · error · ParameterCollisionError

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

Error message

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

What it means

Raised by polars.read_excel (engine='xlsx2csv') in _get_read_options when `has_header` is set in read_options AND its value fails the identity test `read_options['has_header'] is not has_header`. Because the check is `is not` (not !=), it fires both for genuinely conflicting values (True vs False) and for values that are merely equal-but-not-identical, e.g. the int 0/1 or numpy.bool_(False) versus the Python bool False. read_options['has_header'] is passed through to the internal read_csv call, so polars needs one unambiguous value.

Source

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

            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
        ):
            msg = 'the values of `has_header` and `read_options["has_header"]` are not compatible'
            raise ParameterCollisionError(msg)
        elif ("infer_schema_length" in read_options) and (
            infer_schema_length != N_INFER_DEFAULT
        ):
            msg = 'cannot specify both `infer_schema_length` and `read_options["infer_schema_length"]`'
            raise ParameterCollisionError(msg)

        read_options["infer_schema_length"] = infer_schema_length
        if "has_header" not in read_options:
            read_options["has_header"] = has_header
    else:
        read_options["infer_schema_length"] = infer_schema_length
        read_options["has_header"] = has_header

    return read_options


def _get_sheet_names(
    sheet_id: int | Sequence[int] | None,

View on GitHub (pinned to df599052da)

Solutions

  1. Set has_header in exactly one place: prefer the top-level pl.read_excel(..., has_header=...) parameter and remove it from read_options
  2. If it must live in read_options, coerce to a real Python bool: read_options={'has_header': bool(value)} and leave the top-level at default
  3. Never pass ints or numpy bools as has_header values

Example fix

# before (cfg.header is 0/1 from JSON)
pl.read_excel(src, engine='xlsx2csv', read_options={'has_header': cfg.header})

# after
pl.read_excel(src, engine='xlsx2csv', has_header=bool(cfg.header))
Defensive patterns

Strategy: validation

Validate before calling

has_header = bool(cfg.get('header', True))  # coerce ints/np.bool_ from config
opts = dict(read_options or {})
opts.pop('has_header', None)  # keep the value in exactly one place
df = pl.read_excel(src, engine='xlsx2csv', has_header=has_header, read_options=opts)

Type guard

def is_pure_bool(v) -> bool:
    """True only for real Python bools (the engine compares with `is`)."""
    return v is True or v is False

Try / catch

from polars.exceptions import ParameterCollisionError
try:
    df = pl.read_excel(src, engine='xlsx2csv', read_options=opts)
except ParameterCollisionError as e:
    if 'has_header' in str(e):
        opts = {k: v for k, v in opts.items() if k != 'has_header'}
        df = pl.read_excel(src, engine='xlsx2csv', read_options=opts)
    else:
        raise

Prevention

When it happens

Trigger: pl.read_excel(src, engine='xlsx2csv', read_options={'has_header': False}) with has_header left at its default True; or read_options={'has_header': 0}/np.bool_(False)/1 with a matching top-level value — `0 is not False` evaluates True, so it raises despite being semantically equal.

Common situations: Config-driven pipelines (YAML/JSON produce ints for booleans), pandas/numpy flag values passed through, and code that duplicates the header setting between read_options and the top-level parameter.

Related errors


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