{"record":{"id":"e00935e3dff53ff8","repo":"django/django","slug":"lexeme-value-must-be-a-string-got-value-class","errorCode":null,"errorMessage":"Lexeme value must be a string, got {value.__class__.__name__}.","messagePattern":"Lexeme value must be a string, got (.+?)\\.","errorType":"exception","errorClass":"TypeError","httpStatus":null,"severity":"error","filePath":"django/contrib/postgres/search.py","lineNumber":468,"sourceCode":"\n    def __and__(self, other):\n        return self._combine(other, self.BITAND, False)\n\n    def __rand__(self, other):\n        return self._combine(other, self.BITAND, True)\n\n\nclass Lexeme(LexemeCombinable, Value):\n    _output_field = SearchQueryField()\n\n    def __init__(\n        self, value, output_field=None, *, invert=False, prefix=False, weight=None\n    ):\n        if value == \"\":\n            raise ValueError(\"Lexeme value cannot be empty.\")\n\n        if not isinstance(value, str):\n            raise TypeError(\n                f\"Lexeme value must be a string, got {value.__class__.__name__}.\"\n            )\n\n        if weight is not None and (\n            not isinstance(weight, str) or weight.lower() not in {\"a\", \"b\", \"c\", \"d\"}\n        ):\n            raise ValueError(\n                f\"Weight must be one of 'A', 'B', 'C', and 'D', got {weight!r}.\"\n            )\n\n        self.prefix = prefix\n        self.invert = invert\n        self.weight = weight\n        super().__init__(value, output_field=output_field)\n\n    def as_sql(self, compiler, connection):\n        param = quote_lexeme(self.value)\n        label = \"\"","sourceCodeStart":450,"sourceCodeEnd":486,"githubUrl":"https://github.com/django/django/blob/b5388a3a80cafcce2e34196d8e81cf5b48eb33bb/django/contrib/postgres/search.py#L450-L486","documentation":"After the empty-string check, `Lexeme.__init__` (search.py:467-470) verifies `isinstance(value, str)` and raises `TypeError` if not, naming the actual class. This catches non-string scalars (int, None, bytes) that would otherwise fail during SQL quoting. The ordering of checks means a non-string non-empty value (e.g. 0, None, an int) reaches this branch.","triggerScenarios":"Calling `Lexeme(123)`, `Lexeme(None)`, `Lexeme(b'bytes')`, or `Lexeme(some_object)` where the value is not a string. Note: `Lexeme('')` raises the empty error, not this one.","commonSituations":"Passing an integer ID instead of text; passing None from an optional field; bytes vs str confusion (Python 3); passing a model instance instead of one of its string attributes.","solutions":["Pass a string: `Lexeme(str(value))` only after confirming the value is non-empty.","Pull the right attribute: `Lexeme(doc.title)` instead of `Lexeme(doc)`.","Decode bytes to str before passing."],"exampleFix":"// before\nLexeme(product.id)\n// after\nLexeme(str(product.name))","handlingStrategy":"type-guard","validationCode":"if not isinstance(value, str):\n    raise TypeError(f'Lexeme value must be str, got {type(value).__name__}')\nlex = Lexeme(value)","typeGuard":"def is_str(v) -> bool:\n    return isinstance(v, str)","tryCatchPattern":"try:\n    lex = Lexeme(value)\nexcept TypeError as e:\n    if 'must be a string' in str(e):\n        lex = Lexeme(str(value))\n    else:\n        raise","preventionTips":["Pull the string attribute off objects before passing (e.g. doc.title, not doc).","Decode bytes to str upstream.","Validate API payloads that feed into Lexeme construction."],"tags":["postgres","django","search","type-error"],"backgroundTag":null,"analyzedSha":"b5388a3a80cafcce2e34196d8e81cf5b48eb33bb","analyzedAt":"2026-08-10T17:37:52.993Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}