{"record":{"id":"6d8c9b9388468f6a","repo":"pola-rs/polars","slug":"non-contiguous-buffer-must-be-made-contiguous","errorCode":null,"errorMessage":"non-contiguous buffer must be made contiguous","messagePattern":"non-contiguous buffer must be made contiguous","errorType":"exception","errorClass":"CopyNotAllowedError","httpStatus":null,"severity":"error","filePath":"py-polars/src/polars/interchange/buffer.py","lineNumber":36,"sourceCode":"\nclass PolarsBuffer(Buffer):\n    \"\"\"\n    A buffer object backed by a Polars Series consisting of a single chunk.\n\n    Parameters\n    ----------\n    data\n        The Polars Series backing the buffer object.\n    allow_copy\n        Allow data to be copied during operations on this column. If set to `False`,\n        a RuntimeError will be raised if data would be copied.\n    \"\"\"\n\n    def __init__(self, data: Series, *, allow_copy: bool = True) -> None:\n        if data.n_chunks() > 1:\n            if not allow_copy:\n                msg = \"non-contiguous buffer must be made contiguous\"\n                raise CopyNotAllowedError(msg)\n            data = data.rechunk()\n\n        self._data = data\n\n    @property\n    def bufsize(self) -> int:\n        \"\"\"Buffer size in bytes.\"\"\"\n        dtype = polars_dtype_to_dtype(self._data.dtype)\n\n        if dtype[0] == DtypeKind.BOOL:\n            _, offset, length = self._data._get_buffer_info()\n            n_bits = offset + length\n            n_bytes, rest = divmod(n_bits, 8)\n            # Round up to the nearest byte\n            if rest == 0:\n                return n_bytes\n            else:\n                return n_bytes + 1","sourceCodeStart":18,"sourceCodeEnd":54,"githubUrl":"https://github.com/pola-rs/polars/blob/df599052daf96e7a9cc30a3b0c6bd25d6947e3c0/py-polars/src/polars/interchange/buffer.py#L18-L54","documentation":"Raised by the dataframe interchange protocol (PolarsBuffer.__init__) when the backing Series has more than one chunk and allow_copy=False. Serving a contiguous buffer view requires rechunking, which copies memory, so zero-copy consumers get a CopyNotAllowedError (a RuntimeError subclass).","triggerScenarios":"Calling __dataframe__(allow_copy=False) on a DataFrame/Series whose columns have n_chunks() > 1; interchange consumers like query compilers (pandas via protocol, ibis) that request zero copy; building a DataFrame from concatenated parts and handing it to the interchange API.","commonSituations":"Data assembled from scans/concat retains chunking; libraries that strictly pass allow_copy=False per the interchange spec; performance-sensitive pipelines that forbid hidden copies.","solutions":["Call df.rechunk() (or series.rechunk()) before handing data to the interchange consumer","Pass allow_copy=True if a copy is acceptable","Reduce chunk fragmentation at the source (e.g. avoid repeated concat/vstack of small frames)"],"exampleFix":"// before\nexchange_df = df.__dataframe__(allow_copy=False)\n// after\ndf = df.rechunk()\nexchange_df = df.__dataframe__(allow_copy=False)","handlingStrategy":"validation","validationCode":"if df.get_column(df.columns[0]).n_chunks() > 1:\n    df = df.rechunk()  # before requesting allow_copy=False interchange","typeGuard":"def is_chunk_contiguous(df) -> bool:\n    return all(n == 1 for n in df.n_chunks('all'))","tryCatchPattern":"from polars.interchange.protocol import CopyNotAllowedError\ntry:\n    dfi = df.__dataframe__(allow_copy=False)\nexcept CopyNotAllowedError:\n    dfi = df.rechunk().__dataframe__(allow_copy=False)","preventionTips":["Rechunk after concat-heavy construction","Test zero-copy paths with multi-chunk fixtures","Treat CopyNotAllowedError as a signal of chunk fragmentation, not a polars bug"],"tags":["polars","interchange-protocol","zero-copy","chunking","memory"],"backgroundTag":null,"analyzedSha":"df599052daf96e7a9cc30a3b0c6bd25d6947e3c0","analyzedAt":"2026-08-16T12:10:03.978Z","schemaVersion":2},"datasetVersion":"2026-08-16T13:17:31.715Z"}