pola-rs/polars · error · CopyNotAllowedError

non-contiguous buffer must be made contiguous

Error message

non-contiguous buffer must be made contiguous

What it means

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).

Source

Thrown at py-polars/src/polars/interchange/buffer.py:36

class PolarsBuffer(Buffer):
    """
    A buffer object backed by a Polars Series consisting of a single chunk.

    Parameters
    ----------
    data
        The Polars Series backing the buffer object.
    allow_copy
        Allow data to be copied during operations on this column. If set to `False`,
        a RuntimeError will be raised if data would be copied.
    """

    def __init__(self, data: Series, *, allow_copy: bool = True) -> None:
        if data.n_chunks() > 1:
            if not allow_copy:
                msg = "non-contiguous buffer must be made contiguous"
                raise CopyNotAllowedError(msg)
            data = data.rechunk()

        self._data = data

    @property
    def bufsize(self) -> int:
        """Buffer size in bytes."""
        dtype = polars_dtype_to_dtype(self._data.dtype)

        if dtype[0] == DtypeKind.BOOL:
            _, offset, length = self._data._get_buffer_info()
            n_bits = offset + length
            n_bytes, rest = divmod(n_bits, 8)
            # Round up to the nearest byte
            if rest == 0:
                return n_bytes
            else:
                return n_bytes + 1

View on GitHub (pinned to df599052da)

Solutions

  1. Call df.rechunk() (or series.rechunk()) before handing data to the interchange consumer
  2. Pass allow_copy=True if a copy is acceptable
  3. Reduce chunk fragmentation at the source (e.g. avoid repeated concat/vstack of small frames)

Example fix

// before
exchange_df = df.__dataframe__(allow_copy=False)
// after
df = df.rechunk()
exchange_df = df.__dataframe__(allow_copy=False)
Defensive patterns

Strategy: validation

Validate before calling

if df.get_column(df.columns[0]).n_chunks() > 1:
    df = df.rechunk()  # before requesting allow_copy=False interchange

Type guard

def is_chunk_contiguous(df) -> bool:
    return all(n == 1 for n in df.n_chunks('all'))

Try / catch

from polars.interchange.protocol import CopyNotAllowedError
try:
    dfi = df.__dataframe__(allow_copy=False)
except CopyNotAllowedError:
    dfi = df.rechunk().__dataframe__(allow_copy=False)

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Related errors


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