psycopg/psycopg2 · error · TypeError

expected string or None as name, got {name!r}

Error message

expected string or None as name, got {name!r}

What it means

Raised by Placeholder.__init__() (lib/sql.py:431) when the name argument is neither a str nor None. A str becomes a named placeholder (%(name)s); None (or omitted) becomes a positional %s. Any other type (int, bytes, list, etc.) has no defined rendering and is rejected with TypeError. Note in particular that an int like 0 does NOT mean 'positional index 0' - positional placeholders take None.

Source

Thrown at lib/sql.py:431

        ...     sql.SQL(', ').join(sql.Placeholder() * len(names)))
        >>> print(q1.as_string(conn))
        insert into table ("foo", "bar", "baz") values (%s, %s, %s)

        >>> q2 = sql.SQL("insert into table ({}) values ({})").format(
        ...     sql.SQL(', ').join(map(sql.Identifier, names)),
        ...     sql.SQL(', ').join(map(sql.Placeholder, names)))
        >>> print(q2.as_string(conn))
        insert into table ("foo", "bar", "baz") values (%(foo)s, %(bar)s, %(baz)s)

    """

    def __init__(self, name=None):
        if isinstance(name, str):
            if ')' in name:
                raise ValueError(f"invalid name: {name!r}")

        elif name is not None:
            raise TypeError(f"expected string or None as name, got {name!r}")

        super().__init__(name)

    @property
    def name(self):
        """The name of the `!Placeholder`."""
        return self._wrapped

    def __repr__(self):
        if self._wrapped is None:
            return f"{self.__class__.__name__}()"
        else:
            return f"{self.__class__.__name__}({self._wrapped!r})"

    def as_string(self, context):
        if self._wrapped is not None:
            return f"%({self._wrapped})s"
        else:

View on GitHub (pinned to 3a6d9d6ddc)

Solutions

  1. For a positional placeholder, pass None or omit the argument: sql.Placeholder() or sql.Placeholder(None).
  2. For a named placeholder, pass a str.
  3. Add a type check upstream so only str/None reach Placeholder.

Example fix

// before
ph = sql.Placeholder(0)
// after
ph = sql.Placeholder()        # positional %s
# or
ph = sql.Placeholder('name')  # named %(name)s
Defensive patterns

Strategy: type-guard

Validate before calling

def make_placeholder(name=None):
    if name is not None and not isinstance(name, str):
        raise TypeError(f'Placeholder name must be str or None, got {type(name).__name__}')
    return sql.Placeholder(name)

Type guard

def is_valid_placeholder_arg(name) -> bool:
    return name is None or isinstance(name, str)

Prevention

When it happens

Trigger: sql.Placeholder(0) (mistakenly treating it as a positional index), sql.Placeholder(b'x'), sql.Placeholder(['x']), sql.Placeholder(123).

Common situations: Confusing Placeholder(0) with positional-by-index (it is not); passing bytes names; passing a list/tuple accidentally; copy-paste from code that numbered placeholders.

Related errors


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