{"record":{"id":"c8e5618bc04c04cc","repo":"pola-rs/polars","slug":"bytemask-must-be-converted-into-a-bitmask","errorCode":null,"errorMessage":"bytemask must be converted into a bitmask","messagePattern":"bytemask must be converted into a bitmask","errorType":"exception","errorClass":"CopyNotAllowedError","httpStatus":null,"severity":"error","filePath":"py-polars/src/polars/interchange/from_dataframe.py","lineNumber":326,"sourceCode":"\n    if null_value != 0:\n        if not allow_copy:\n            msg = \"bitmask must be inverted\"\n            raise CopyNotAllowedError(msg)\n        s = ~s\n\n    return s\n\n\ndef _construct_validity_buffer_from_bytemask(\n    buffer: Buffer,\n    null_value: int,\n    *,\n    allow_copy: bool,\n) -> Series:\n    if not allow_copy:\n        msg = \"bytemask must be converted into a bitmask\"\n        raise CopyNotAllowedError(msg)\n\n    buffer_info = (buffer.ptr, 0, buffer.bufsize)\n    s = pl.Series._from_buffer(UInt8, buffer_info, owner=buffer)\n    s = s.cast(Boolean)\n\n    if null_value != 0:\n        s = ~s\n\n    return s\n","sourceCodeStart":308,"sourceCodeEnd":336,"githubUrl":"https://github.com/pola-rs/polars/blob/df599052daf96e7a9cc30a3b0c6bd25d6947e3c0/py-polars/src/polars/interchange/from_dataframe.py#L308-L336","documentation":"USE_BYTEMASK validity stores one byte per value. Polars validity is bit-packed, so a bytemask must be read as a UInt8 Series and cast to Boolean - always a copy, regardless of null_value. Under allow_copy=False Polars raises CopyNotAllowedError before touching the buffer.","triggerScenarios":"allow_copy=False conversion of any column whose producer declares USE_BYTEMASK validity in describe_null.","commonSituations":"Simple producer implementations that find bytemasks easier to emit; performance-sensitive zero-copy consumers; together with errors 301/306/307/312, one of several copy-forcing representation mismatches.","solutions":["Retry with allow_copy=True","Have the producer emit a packed bitmask (USE_BITMASK) instead of a bytemask","Clean nulls upstream so no validity buffer is needed at all"],"exampleFix":"// before\ndf = pl.from_dataframe(producer_df, allow_copy=False)  # bytemask validity -> error\n\n// after\ntry:\n    df = pl.from_dataframe(producer_df, allow_copy=False)\nexcept pl.exceptions.CopyNotAllowedError:\n    df = pl.from_dataframe(producer_df, allow_copy=True)","handlingStrategy":"try-catch","validationCode":"from polars.interchange.protocol import ColumnNullType\n\ndef columns_use_bytemask(df) -> bool:\n    proto = df.__dataframe__(allow_copy=False)\n    return any(\n        col.describe_null()[0] == ColumnNullType.USE_BYTEMASK\n        for col in proto.get_columns()\n    )","typeGuard":null,"tryCatchPattern":"try:\n    out = pl.from_dataframe(df, allow_copy=False)\nexcept pl.exceptions.CopyNotAllowedError:\n    # bytemask must be cast to bit-packed validity; allow the copy\n    out = pl.from_dataframe(df, allow_copy=True)","preventionTips":["Producers: emit packed bitmasks instead of bytemasks for polars consumers","Remember bytemask validity always copies - never promise zero-copy for such producers","Use one shared retry-with-copy helper for every CopyNotAllowedError in the ingest path"],"tags":["polars","interchange-protocol","null-handling","bytemask","zero-copy"],"backgroundTag":null,"analyzedSha":"df599052daf96e7a9cc30a3b0c6bd25d6947e3c0","analyzedAt":"2026-08-16T12:10:03.978Z","schemaVersion":2},"datasetVersion":"2026-08-16T13:17:31.715Z"}