pola-rs/polars · error · CopyNotAllowedError

string buffers must be converted

Error message

string buffers must be converted

What it means

Raised by PolarsColumn.get_buffers in the interchange protocol when the column is a String (utf8) dtype and allow_copy=False. Polars' string view layout must be converted to the interchange spec's offsets representation, which requires copying, so the zero-copy contract cannot be honored for strings.

Source

Thrown at py-polars/src/polars/interchange/column.py:157

        else:
            subchunks_per_chunk = n_chunks // total_n_chunks
            for chunk in chunks:
                size = len(chunk)
                step = size // subchunks_per_chunk
                if size % subchunks_per_chunk != 0:
                    step += 1
                for start in range(0, step * subchunks_per_chunk, step):
                    yield PolarsColumn(
                        chunk[start : start + step], allow_copy=self._allow_copy
                    )

    def get_buffers(self) -> ColumnBuffers:
        """Return a dictionary containing the underlying buffers."""
        dtype = self._col.dtype

        if dtype == String and not self._allow_copy:
            msg = "string buffers must be converted"
            raise CopyNotAllowedError(msg)

        buffers = self._col._get_buffers()

        return {
            "data": self._wrap_data_buffer(buffers["values"]),
            "validity": self._wrap_validity_buffer(buffers["validity"]),
            "offsets": self._wrap_offsets_buffer(buffers["offsets"]),
        }

    def _wrap_data_buffer(self, buffer: Series) -> tuple[PolarsBuffer, Dtype]:
        interchange_buffer = PolarsBuffer(buffer, allow_copy=self._allow_copy)
        dtype = polars_dtype_to_dtype(buffer.dtype)
        return interchange_buffer, dtype

    def _wrap_validity_buffer(
        self, buffer: Series | None
    ) -> tuple[PolarsBuffer, Dtype] | None:
        if buffer is None:

View on GitHub (pinned to df599052da)

Solutions

  1. Pass allow_copy=True whenever the frame contains String columns
  2. Drop or transform string columns before interchange (e.g. cast to Categorical is not zero-copy either — better to exclude them from the strict path)
  3. If the consumer only needs values, read the interchange column's data via its own iteration APIs rather than raw buffers

Example fix

// before
df.__dataframe__(allow_copy=False).column(0).get_buffers()  # column 0 is String
// after
df.__dataframe__(allow_copy=True).column(0).get_buffers()
Defensive patterns

Strategy: validation

Validate before calling

string_cols = [name for name, dt in df.schema.items() if dt == pl.String]
allow_copy = False if not string_cols else True

Type guard

def frame_is_zero_copy_safe(df) -> bool:
    return not any(dt == pl.String for dt in df.schema.values())

Try / catch

from polars.interchange.protocol import CopyNotAllowedError
try:
    buffers = col.get_buffers()
except CopyNotAllowedError:
    buffers = PolarsColumn(col._col, allow_copy=True).get_buffers()

Prevention

When it happens

Trigger: Calling __dataframe__(allow_copy=False) and then get_buffers() on a String column; strict zero-copy consumers (query compilers, arrow-free bridges) walking buffers of a frame that contains string columns; benchmark harnesses asserting no-copy interchange.

Common situations: Frames with string keys/labels passed to allow_copy=False consumers; migrating from older polars where string interchange had different copy behavior; performance contracts that forbid copies on hot paths containing text data.

Related errors


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