{"record":{"id":"f1b5ffe8290fec7b","repo":"pola-rs/polars","slug":"the-values-of-has-header-and-read-options-has","errorCode":null,"errorMessage":"the values of `has_header` and `read_options[\"has_header\"]` are not compatible","messagePattern":"the values of `has_header` and `read_options\\[\"has_header\"\\]` are not compatible","errorType":"exception","errorClass":"ParameterCollisionError","httpStatus":null,"severity":"error","filePath":"py-polars/src/polars/io/spreadsheet/functions.py","lineNumber":763,"sourceCode":"            infer_schema_length != N_INFER_DEFAULT\n        ):\n            msg = 'cannot specify both `infer_schema_length` and `read_options[\"schema_sample_rows\"]`'\n            raise ParameterCollisionError(msg)\n\n        read_options[\"schema_sample_rows\"] = infer_schema_length\n        if has_header is False and \"header_row\" not in read_options:\n            read_options[\"header_row\"] = None\n\n    elif engine == \"xlsx2csv\":\n        if (\"columns\" in read_options) and columns:\n            msg = 'cannot specify both `columns` and `read_options[\"columns\"]`'\n            raise ParameterCollisionError(msg)\n        elif (\n            \"has_header\" in read_options\n            and read_options[\"has_header\"] is not has_header\n        ):\n            msg = 'the values of `has_header` and `read_options[\"has_header\"]` are not compatible'\n            raise ParameterCollisionError(msg)\n        elif (\"infer_schema_length\" in read_options) and (\n            infer_schema_length != N_INFER_DEFAULT\n        ):\n            msg = 'cannot specify both `infer_schema_length` and `read_options[\"infer_schema_length\"]`'\n            raise ParameterCollisionError(msg)\n\n        read_options[\"infer_schema_length\"] = infer_schema_length\n        if \"has_header\" not in read_options:\n            read_options[\"has_header\"] = has_header\n    else:\n        read_options[\"infer_schema_length\"] = infer_schema_length\n        read_options[\"has_header\"] = has_header\n\n    return read_options\n\n\ndef _get_sheet_names(\n    sheet_id: int | Sequence[int] | None,","sourceCodeStart":745,"sourceCodeEnd":781,"githubUrl":"https://github.com/pola-rs/polars/blob/df599052daf96e7a9cc30a3b0c6bd25d6947e3c0/py-polars/src/polars/io/spreadsheet/functions.py#L745-L781","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["Set has_header in exactly one place: prefer the top-level pl.read_excel(..., has_header=...) parameter and remove it from read_options","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","Never pass ints or numpy bools as has_header values"],"exampleFix":"# before (cfg.header is 0/1 from JSON)\npl.read_excel(src, engine='xlsx2csv', read_options={'has_header': cfg.header})\n\n# after\npl.read_excel(src, engine='xlsx2csv', has_header=bool(cfg.header))","handlingStrategy":"validation","validationCode":"has_header = bool(cfg.get('header', True))  # coerce ints/np.bool_ from config\nopts = dict(read_options or {})\nopts.pop('has_header', None)  # keep the value in exactly one place\ndf = pl.read_excel(src, engine='xlsx2csv', has_header=has_header, read_options=opts)","typeGuard":"def is_pure_bool(v) -> bool:\n    \"\"\"True only for real Python bools (the engine compares with `is`).\"\"\"\n    return v is True or v is False","tryCatchPattern":"from polars.exceptions import ParameterCollisionError\ntry:\n    df = pl.read_excel(src, engine='xlsx2csv', read_options=opts)\nexcept ParameterCollisionError as e:\n    if 'has_header' in str(e):\n        opts = {k: v for k, v in opts.items() if k != 'has_header'}\n        df = pl.read_excel(src, engine='xlsx2csv', read_options=opts)\n    else:\n        raise","preventionTips":["Never duplicate has_header between the top-level parameter and read_options","Coerce config-sourced booleans with bool(...) — YAML/JSON give ints, numpy gives np.bool_, and the engine's `is not` check rejects both","When refactoring csv code, strip read_csv kwargs that polars already exposes as parameters"],"tags":["polars","excel","xlsx2csv","parameter-collision","has-header","identity-comparison","booleans"],"backgroundTag":null,"analyzedSha":"df599052daf96e7a9cc30a3b0c6bd25d6947e3c0","analyzedAt":"2026-08-16T12:10:03.978Z","schemaVersion":2},"datasetVersion":"2026-08-16T13:17:31.715Z"}