{"record":{"id":"ba422490df2577b4","repo":"pola-rs/polars","slug":"bitmask-must-be-inverted","errorCode":null,"errorMessage":"bitmask must be inverted","messagePattern":"bitmask must be inverted","errorType":"exception","errorClass":"CopyNotAllowedError","httpStatus":null,"severity":"error","filePath":"py-polars/src/polars/interchange/from_dataframe.py","lineNumber":312,"sourceCode":"        msg = f\"unsupported null type: {null_type!r}\"\n        raise NotImplementedError(msg)\n\n\ndef _construct_validity_buffer_from_bitmask(\n    buffer: Buffer,\n    null_value: int,\n    length: int,\n    offset: int = 0,\n    *,\n    allow_copy: bool,\n) -> Series:\n    buffer_info = (buffer.ptr, offset, length)\n    s = pl.Series._from_buffer(Boolean, buffer_info, buffer)\n\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)","sourceCodeStart":294,"sourceCodeEnd":330,"githubUrl":"https://github.com/pola-rs/polars/blob/df599052daf96e7a9cc30a3b0c6bd25d6947e3c0/py-polars/src/polars/interchange/from_dataframe.py#L294-L330","documentation":"For bitmask validity, Polars reads the buffer as a Boolean Series and, when the producer reports null_value != 0 (bits mark nulls instead of valids), must invert it with ~s to obtain a validity mask. The inversion allocates, so under allow_copy=False Polars raises CopyNotAllowedError('bitmask must be inverted').","triggerScenarios":"allow_copy=False conversion of a column with USE_BITMASK validity where describe_null reports null_value=1 (null-indicator mask instead of validity mask).","commonSituations":"Producers that store null-indicator masks (1 = null) rather than validity masks (1 = valid); zero-copy consumers that assume bitmask validity is always free.","solutions":["Retry with allow_copy=True","Have the producer emit a standard validity mask with null_value=0 (bit set = valid)","Invert the mask upstream in the producer"],"exampleFix":"// before\ndf = pl.from_dataframe(producer_df, allow_copy=False)  # inverted bitmask -> 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 bitmasks_need_inversion(df) -> bool:\n    proto = df.__dataframe__(allow_copy=False)\n    return any(\n        col.describe_null()[:2] == (ColumnNullType.USE_BITMASK, 1)\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    # inverted bitmask must be flipped; allow the copy\n    out = pl.from_dataframe(df, allow_copy=True)","preventionTips":["Producers: emit validity masks (1 = valid, null_value = 0) rather than null-indicator masks","Verify the reported null_value in describe_null() when zero-copy matters","Centralize the CopyNotAllowedError retry in one conversion helper"],"tags":["polars","interchange-protocol","null-handling","bitmask","zero-copy"],"backgroundTag":null,"analyzedSha":"df599052daf96e7a9cc30a3b0c6bd25d6947e3c0","analyzedAt":"2026-08-16T12:10:03.978Z","schemaVersion":2},"datasetVersion":"2026-08-16T13:17:31.715Z"}