pola-rs/polars · error · TypeError

'suffix' must be a string; found {qualified_type_name(suffix

Error message

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

What it means

Expr.cat.ends_with is the suffix counterpart of starts_with: the suffix must be a Python str, and any other type raises TypeError with the found type's qualified name. Comparison happens on string representations of categorical values, so a concrete string suffix is required.

Source

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

        │ 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 df599052da)

Solutions

  1. Decode bytes to str first: suffix.decode() if isinstance(suffix, bytes) else suffix
  2. Coerce at the boundary: .cat.ends_with(str(suffix))
  3. For per-row dynamic suffixes, cast to String and use .str.ends_with(other_expr)

Example fix

# before
pl.col('cat_col').cat.ends_with(b'go')  # TypeError

# after
pl.col('cat_col').cat.ends_with(b'go'.decode())
# per-row dynamic: pl.col('cat_col').cast(pl.String).str.ends_with(pl.col('suffix_col'))
Defensive patterns

Strategy: type-guard

Validate before calling

if isinstance(suffix, bytes):
    suffix = suffix.decode()
assert isinstance(suffix, str), 'cat.ends_with requires a str suffix'
expr = pl.col('c').cat.ends_with(suffix)

Type guard

from typing import TypeGuard

def is_str_suffix(s) -> TypeGuard[str]:
    return isinstance(s, str)

Try / catch

try:
    e = pl.col('c').cat.ends_with(suffix)
except TypeError:
    e = pl.col('c').cast(pl.String).str.ends_with(str(suffix))

Prevention

When it happens

Trigger: pl.col('c').cat.ends_with(b'go') (bytes), .cat.ends_with(42), or .cat.ends_with(pl.lit('go')).

Common situations: Suffixes arriving as bytes from binary pipelines; passing expressions out of habit; programmatic suffix values from untyped config dicts.

Related errors


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