{"id":"6aee06922d535582","repo":"psycopg/psycopg2","slug":"sql-identifier-parts-must-be-strings","errorCode":null,"errorMessage":"SQL identifier parts must be strings","messagePattern":"SQL identifier parts must be strings","errorType":"exception","errorClass":"TypeError","httpStatus":null,"severity":"error","filePath":"lib/sql.py","lineNumber":327,"sourceCode":"    Multiple strings can be passed to the object to represent a qualified name,\n    i.e. a dot-separated sequence of identifiers.\n\n    Example::\n\n        >>> query = sql.SQL(\"select {} from {}\").format(\n        ...     sql.Identifier(\"table\", \"field\"),\n        ...     sql.Identifier(\"schema\", \"table\"))\n        >>> print(query.as_string(conn))\n        select \"table\".\"field\" from \"schema\".\"table\"\n\n    \"\"\"\n    def __init__(self, *strings):\n        if not strings:\n            raise TypeError(\"Identifier cannot be empty\")\n\n        for s in strings:\n            if not isinstance(s, str):\n                raise TypeError(\"SQL identifier parts must be strings\")\n\n        super().__init__(strings)\n\n    @property\n    def strings(self):\n        \"\"\"A tuple with the strings wrapped by the `Identifier`.\"\"\"\n        return self._wrapped\n\n    @property\n    def string(self):\n        \"\"\"The string wrapped by the `Identifier`.\n        \"\"\"\n        if len(self._wrapped) == 1:\n            return self._wrapped[0]\n        else:\n            raise AttributeError(\n                \"the Identifier wraps more than one than one string\")\n","sourceCodeStart":309,"sourceCodeEnd":345,"githubUrl":"https://github.com/psycopg/psycopg2/blob/3a6d9d6ddc6b53eaa80b712f5fa6b23abbdc38db/lib/sql.py#L309-L345","documentation":"Raised by Identifier.__init__() (lib/sql.py:327) during the per-part isinstance(s, str) loop. PostgreSQL identifiers are textual names; non-string parts (int, None, bytes, etc.) have no defined quoting rule, so the constructor rejects them with TypeError rather than silently coercing. Every argument passed to Identifier must be a str.","triggerScenarios":"sql.Identifier(123), sql.Identifier(None), sql.Identifier('schema', 0), sql.Identifier(b'table'), or sql.Identifier(*col_indices) where the list contains ints from enumerate().","commonSituations":"Passing an integer index instead of a column name; receiving None from a missing config/dict key; using bytes names from a binary protocol; DB metadata that returns ints.","solutions":["Coerce each part to str before passing: sql.Identifier(str(part)).","Validate upstream that all parts are strings and surface a clearer error if not.","Filter out None / non-string entries before constructing the Identifier."],"exampleFix":"// before\nident = sql.Identifier(table_idx)\n// after\nident = sql.Identifier(str(table_idx))","handlingStrategy":"type-guard","validationCode":"def build_identifier(parts):\n    parts = [str(p) for p in parts]   # or: reject non-str explicitly\n    return sql.Identifier(*parts)","typeGuard":"from typing import Iterable\n\ndef all_parts_are_str(parts: Iterable) -> bool:\n    return all(isinstance(p, str) for p in parts)","tryCatchPattern":null,"preventionTips":["Coerce DB-metadata ints/indices to str before wrapping.","Filter None out of name lists coming from dicts/config that may have missing keys.","Never pass bytes names - decode first."],"tags":["psycopg2","sql-composition","identifier","typeerror"],"analyzedSha":"3a6d9d6ddc6b53eaa80b712f5fa6b23abbdc38db","analyzedAt":"2026-08-04T19:56:51.958Z","schemaVersion":2}