pola-rs/polars · error · TypeError

'suffix' must be a string; found

Error message

'suffix' must be a string; found {qualified_type_name(suffix)!r}

What it means

Type-guard on the `suffix` parameter of the categorical ends_with method. The Python layer validates the argument before handing it to the Rust cat_ends_with implementation; passing anything other than a str (e.g. None, bytes, or a column-like object) triggers this TypeError naming the actual type received.

Solutions

  1. Wrap the value with str(...) before calling
  2. Decode bytes to str if the suffix comes from binary input
  3. Guard with isinstance(suffix, str) and raise a clear error of your own
  4. Confirm the variable passed actually holds the suffix string

Example fix

// before
cat_col.cat.ends_with(version)  # version = 2 (int)
// after
cat_col.cat.ends_with(str(version))
Defensive patterns

Strategy: type-guard

Validate before calling

if not isinstance(suffix, str):
    raise TypeError(f'suffix must be str, got {type(suffix).__name__}')

Type guard

def is_str(x) -> bool:
    return isinstance(x, str)

Try / catch

try:
    e = col.cat.ends_with(suffix)
except TypeError as e:
    if 'must be a string' in str(e):
        e = col.cat.ends_with(suffix.decode() if isinstance(suffix, bytes) else str(suffix))
    else:
        raise

Prevention

When it happens

Trigger: Calling pl.col('c').cat.ends_with(42), ends_with(b'ngo'), ends_with(None), or passing an int/enum value instead of the string suffix.

Common situations: Suffix stored as a number in config (e.g. version suffix 1 instead of '1'); bytes from decoded data; variable mix-ups in generated expressions.

Understand the failure class

Background: "Must be a positive integer", "Invalid value", "Unsupported": the invalid-argument-value error family, when a library rejects the value you pass — this error's family across 35 libraries.

Related errors


AI-assisted analysis of pola-rs/polars@fe841f959e (2026-09-18). Data as JSON: /api/errors/a53d9de345e13b4f. Report an issue: GitHub.

Appendix: source

Thrown at py-polars/src/polars/expr/categorical.py:231

        │ mango  ┆ true       │
        │ null   ┆ null       │
        └────────┴────────────┘

        Using `ends_with` as a filter condition:

        >>> df.filter(pl.col("fruits").cat.ends_with("go"))
        shape: (1, 1)
        ┌────────┐
        │ fruits │
        │ ---    │
        │ cat    │
        ╞════════╡
        │ mango  │
        └────────┘
        """
        if not isinstance(suffix, str):
            msg = f"'suffix' must be a string; found {qualified_type_name(suffix)!r}"
            raise TypeError(msg)
        return wrap_expr(self._pyexpr.cat_ends_with(suffix))

    def slice(self, offset: int, length: int | None = None) -> Expr:
        """
        Extract a substring from the string representation of each value.

        .. engine-support:: in-memory, streaming, distributed

        Parameters
        ----------
        offset
            Start index. Negative indexing is supported.
        length
            Length of the slice. If set to `None` (default), the slice is taken to the
            end of the string.

        Returns
        -------

View on GitHub (pinned to fe841f959e)