psycopg/psycopg2 · error · ValueError
invalid name
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.
Solutions
- Sanitize placeholder names: allow only [A-Za-z0-9_] and reject/replace everything else.
- Generate placeholder names from a controlled allowlist or sequential counter rather than user input.
- 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
- Generate placeholder names from a controlled counter or allowlist, never raw user input.
- Restrict placeholder names to [A-Za-z0-9_] and require a non-digit first char.
- Treat ')' in a name as an injection red flag and reject loudly.
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
- cannot switch from automatic field numbering to manual
- cannot switch from manual field numbering to automatic
- expected string or None as name, got
- no format conversion supported by SQL
- no format specification supported by SQL
AI-assisted analysis of psycopg/psycopg2@3a6d9d6ddc (2026-08-04).
Data as JSON: /api/errors/33090e04f38334b4.
Report an issue: GitHub.
Appendix: 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)