pola-rs/polars · error · CopyNotAllowedError

string buffers must be converted

Error message

string buffers must be converted

What it means

Ingesting a string column through the interchange protocol always materializes new data+offsets buffers into a Polars String Series — it is never zero-copy — so _string_column_to_series raises CopyNotAllowedError when allow_copy=False. Only empty string columns (size 0) are exempt. Dictionary-encoded columns with string categories hit this too, because categories are converted via the same path.

Solutions

  1. Call pl.from_dataframe(obj, allow_copy=True) (or drop the argument — the default allows copies).
  2. If zero-copy is a hard requirement, exclude or re-encode string columns on the producer side before conversion.
  3. Catch CopyNotAllowedError and retry with allow_copy=True only for the frames that need it.

Example fix

# before
df = pl.from_dataframe(obj, allow_copy=False)  # string column -> CopyNotAllowedError

# after
df = pl.from_dataframe(obj, allow_copy=True)
Defensive patterns

Strategy: fallback

Validate before calling

from polars.interchange.protocol import DtypeKind

def contains_strings(icdf) -> bool:
    return any(
        icdf.get_column(i).dtype[0] == DtypeKind.STRING and icdf.get_column(i).size() > 0
        for i in range(icdf.num_columns())
    )

allow_copy = not contains_strings(obj.__dataframe__())
df = pl.from_dataframe(obj, allow_copy=allow_copy)

Try / catch

from polars.interchange.protocol import CopyNotAllowedError

try:
    df = pl.from_dataframe(obj, allow_copy=False)
except CopyNotAllowedError:
    df = pl.from_dataframe(obj, allow_copy=True)  # strings always require one copy

Prevention

When it happens

Trigger: pl.from_dataframe(obj, allow_copy=False) where obj contains at least one non-empty string column (DtypeKind.STRING), or a dictionary/categorical column whose categories are strings; also reached via _categorical_column_to_series for the categories buffer.

Common situations: Zero-copy ingestion pipelines that pass allow_copy=False to enforce no allocation, then hit text columns; ETL from pandas/pyarrow-backed producers with object/string data; assuming string data can be imported without copying.

Related errors


AI-assisted analysis of pola-rs/polars@9b5d73fd00 (2026-08-19). Data as JSON: /api/errors/269634cca801ae5e. Report an issue: GitHub.

Appendix: source

Thrown at py-polars/src/polars/interchange/from_dataframe.py:111

) -> Series:
    buffers = column.get_buffers()
    offset = column.offset

    data_buffer = _construct_data_buffer(
        *buffers["data"], column.size(), offset, allow_copy=allow_copy
    )
    validity_buffer = _construct_validity_buffer(
        buffers["validity"], column, dtype, data_buffer, offset, allow_copy=allow_copy
    )
    return pl.Series._from_buffers(dtype, data=data_buffer, validity=validity_buffer)


def _string_column_to_series(column: Column, *, allow_copy: bool) -> Series:
    if column.size() == 0:
        return pl.Series(dtype=String)
    elif not allow_copy:
        msg = "string buffers must be converted"
        raise CopyNotAllowedError(msg)

    buffers = column.get_buffers()
    offset = column.offset

    offsets_buffer_info = buffers["offsets"]
    if offsets_buffer_info is None:
        msg = "cannot create String column without an offsets buffer"
        raise RuntimeError(msg)
    offsets_buffer = _construct_offsets_buffer(
        *offsets_buffer_info, offset, allow_copy=allow_copy
    )

    buffer, dtype = buffers["data"]
    data_buffer = _construct_data_buffer(
        buffer, dtype, buffer.bufsize, offset=0, allow_copy=allow_copy
    )

    # First construct a Series without a validity buffer

View on GitHub (pinned to 9b5d73fd00)