psycopg/psycopg2 · error · TypeError

SQL identifier parts must be strings

Error message

SQL identifier parts must be strings

What it means

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.

Source

Thrown at lib/sql.py:327

    Multiple strings can be passed to the object to represent a qualified name,
    i.e. a dot-separated sequence of identifiers.

    Example::

        >>> query = sql.SQL("select {} from {}").format(
        ...     sql.Identifier("table", "field"),
        ...     sql.Identifier("schema", "table"))
        >>> print(query.as_string(conn))
        select "table"."field" from "schema"."table"

    """
    def __init__(self, *strings):
        if not strings:
            raise TypeError("Identifier cannot be empty")

        for s in strings:
            if not isinstance(s, str):
                raise TypeError("SQL identifier parts must be strings")

        super().__init__(strings)

    @property
    def strings(self):
        """A tuple with the strings wrapped by the `Identifier`."""
        return self._wrapped

    @property
    def string(self):
        """The string wrapped by the `Identifier`.
        """
        if len(self._wrapped) == 1:
            return self._wrapped[0]
        else:
            raise AttributeError(
                "the Identifier wraps more than one than one string")

View on GitHub (pinned to 3a6d9d6ddc)

Solutions

  1. Coerce each part to str before passing: sql.Identifier(str(part)).
  2. Validate upstream that all parts are strings and surface a clearer error if not.
  3. Filter out None / non-string entries before constructing the Identifier.

Example fix

// before
ident = sql.Identifier(table_idx)
// after
ident = sql.Identifier(str(table_idx))
Defensive patterns

Strategy: type-guard

Validate before calling

def build_identifier(parts):
    parts = [str(p) for p in parts]   # or: reject non-str explicitly
    return sql.Identifier(*parts)

Type guard

from typing import Iterable

def all_parts_are_str(parts: Iterable) -> bool:
    return all(isinstance(p, str) for p in parts)

Prevention

When it happens

Trigger: 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().

Common situations: 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.

Related errors


AI-assisted analysis of psycopg/psycopg2@3a6d9d6ddc (2026-08-04). Data as JSON: /data/errors/6aee06922d535582.json. Report an issue: GitHub.