{"record":{"id":"a53d9de345e13b4f","repo":"pola-rs/polars","slug":"suffix-must-be-a-string-found-qualified-type-n","errorCode":null,"errorMessage":"'suffix' must be a string; found {qualified_type_name(suffix)!r}","messagePattern":"'suffix' must be a string; found (.+?)","errorType":"exception","errorClass":"TypeError","httpStatus":null,"severity":"error","filePath":"py-polars/src/polars/expr/categorical.py","lineNumber":259,"sourceCode":"        │ mango  ┆ true       │\n        │ null   ┆ null       │\n        └────────┴────────────┘\n\n        Using `ends_with` as a filter condition:\n\n        >>> df.filter(pl.col(\"fruits\").cat.ends_with(\"go\"))\n        shape: (1, 1)\n        ┌────────┐\n        │ fruits │\n        │ ---    │\n        │ cat    │\n        ╞════════╡\n        │ mango  │\n        └────────┘\n        \"\"\"\n        if not isinstance(suffix, str):\n            msg = f\"'suffix' must be a string; found {qualified_type_name(suffix)!r}\"\n            raise TypeError(msg)\n        return wrap_expr(self._pyexpr.cat_ends_with(suffix))\n\n    def slice(self, offset: int, length: int | None = None) -> Expr:\n        \"\"\"\n        Extract a substring from the string representation of each value.\n\n        .. engine-support:: in-memory, streaming, distributed\n\n        Parameters\n        ----------\n        offset\n            Start index. Negative indexing is supported.\n        length\n            Length of the slice. If set to `None` (default), the slice is taken to the\n            end of the string.\n\n        Returns\n        -------","sourceCodeStart":241,"sourceCodeEnd":277,"githubUrl":"https://github.com/pola-rs/polars/blob/df599052daf96e7a9cc30a3b0c6bd25d6947e3c0/py-polars/src/polars/expr/categorical.py#L241-L277","documentation":"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.","triggerScenarios":"pl.col('c').cat.ends_with(b'go') (bytes), .cat.ends_with(42), or .cat.ends_with(pl.lit('go')).","commonSituations":"Suffixes arriving as bytes from binary pipelines; passing expressions out of habit; programmatic suffix values from untyped config dicts.","solutions":["Decode bytes to str first: suffix.decode() if isinstance(suffix, bytes) else suffix","Coerce at the boundary: .cat.ends_with(str(suffix))","For per-row dynamic suffixes, cast to String and use .str.ends_with(other_expr)"],"exampleFix":"# before\npl.col('cat_col').cat.ends_with(b'go')  # TypeError\n\n# after\npl.col('cat_col').cat.ends_with(b'go'.decode())\n# per-row dynamic: pl.col('cat_col').cast(pl.String).str.ends_with(pl.col('suffix_col'))","handlingStrategy":"type-guard","validationCode":"if isinstance(suffix, bytes):\n    suffix = suffix.decode()\nassert isinstance(suffix, str), 'cat.ends_with requires a str suffix'\nexpr = pl.col('c').cat.ends_with(suffix)","typeGuard":"from typing import TypeGuard\n\ndef is_str_suffix(s) -> TypeGuard[str]:\n    return isinstance(s, str)","tryCatchPattern":"try:\n    e = pl.col('c').cat.ends_with(suffix)\nexcept TypeError:\n    e = pl.col('c').cast(pl.String).str.ends_with(str(suffix))","preventionTips":["Decode bytes suffixes at ingestion boundaries","For per-row dynamic suffixes use cast(pl.String).str.ends_with(other_expr)"],"tags":["categorical","type-guard","string","suffix"],"backgroundTag":null,"analyzedSha":"df599052daf96e7a9cc30a3b0c6bd25d6947e3c0","analyzedAt":"2026-08-16T12:10:03.978Z","schemaVersion":2},"datasetVersion":"2026-08-16T13:17:31.715Z"}