psycopg/psycopg2 · error · ValueError

invalid name: {name!r}

Error message

invalid name: {name!r}

What it means

Raised by Placeholder.__init__() (lib/sql.py:428) when a named placeholder's name string contains ')'. Named placeholders render as %(name)s, so a ')' would prematurely close the placeholder and break the query syntax (and could enable injection). The constructor scans the name and rejects any containing ')' with ValueError before storing it.

Source

Thrown at lib/sql.py:428

        >>> q1 = sql.SQL("insert into table ({}) values ({})").format(
        ...     sql.SQL(', ').join(map(sql.Identifier, names)),
        ...     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):

View on GitHub (pinned to 3a6d9d6ddc)

Solutions

  1. Sanitize placeholder names: allow only [A-Za-z0-9_] and reject/replace everything else.
  2. Generate placeholder names from a controlled allowlist or sequential counter rather than user input.
  3. Validate the name does not contain ')' (and ideally any non-identifier char) before passing it in.

Example fix

// before
ph = sql.Placeholder(user_input)
// after
import re
safe = re.fullmatch(r'[A-Za-z_][A-Za-z0-9_]*', user_input)
if not safe:
    raise ValueError('bad placeholder name')
ph = sql.Placeholder(user_input)
Defensive patterns

Strategy: validation

Validate before calling

import re

_SAFE_NAME = re.compile(r'[A-Za-z_][A-Za-z0-9_]*')

def safe_placeholder(name):
    if not (isinstance(name, str) and _SAFE_NAME.fullmatch(name)):
        raise ValueError(f'unsafe placeholder name: {name!r}')
    return sql.Placeholder(name)

Type guard

import re

def is_valid_placeholder_name(name) -> bool:
    return (
        isinstance(name, str)
        and ')' not in name
        and re.fullmatch(r'[A-Za-z_][A-Za-z0-9_]*', name) is not None
    )

Prevention

When it happens

Trigger: sql.Placeholder('foo)bar'), sql.Placeholder('a); drop'), or any sql.Placeholder(name) where name contains a ')' character.

Common situations: Building placeholder names from untrusted/unsanitized user input; concatenating user-provided identifiers into placeholder names; copy-paste that includes a closing paren.

Related errors


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