psycopg/psycopg2 · error · TypeError
Composed elements must be Composable, got {i!r} instead
Error message
Composed elements must be Composable, got {i!r} instead What it means
Raised by Composed.__init__ (lib/sql.py:107-109) when any element in the sequence passed to Composed(...) is not an instance of Composable. Composed is a composition of Composable parts (SQL, Identifier, Literal, Placeholder, or another Composed); raw strings, bytes, numbers, or None are rejected because they would bypass proper escaping and produce unsafe or malformed SQL.
Source
Thrown at lib/sql.py:108
The object is usually created using `!Composable` operators and methods.
However it is possible to create a `!Composed` directly specifying a
sequence of `!Composable` as arguments.
Example::
>>> comp = sql.Composed(
... [sql.SQL("insert into "), sql.Identifier("table")])
>>> print(comp.as_string(conn))
insert into "table"
`!Composed` objects are iterable (so they can be used in `SQL.join` for
instance).
"""
def __init__(self, seq):
wrapped = []
for i in seq:
if not isinstance(i, Composable):
raise TypeError(
f"Composed elements must be Composable, got {i!r} instead")
wrapped.append(i)
super().__init__(wrapped)
@property
def seq(self):
"""The list of the content of the `!Composed`."""
return list(self._wrapped)
def as_string(self, context):
rv = []
for i in self._wrapped:
rv.append(i.as_string(context))
return ''.join(rv)
def __iter__(self):
return iter(self._wrapped)View on GitHub (pinned to 3a6d9d6ddc)
Solutions
- Wrap every element in the appropriate Composable: sql.SQL for constant snippets, sql.Identifier for names, sql.Literal for values, sql.Placeholder for parameters.
- Use sql.SQL(', ').join([...]) which accepts Composables and is the idiomatic way to build lists.
- Never put raw user input directly into a Composed; always go through Literal or a placeholder.
Example fix
// before
comp = sql.Composed(["SELECT * FROM ", sql.Identifier('t')])
// after
comp = sql.Composed([sql.SQL("SELECT * FROM "), sql.Identifier('t')]) Defensive patterns
Strategy: type-guard
Validate before calling
for el in seq:
assert isinstance(el, Composable), f'element {el!r} is not Composable'
comp = sql.Composed(seq) Type guard
from psycopg2.sql import Composable
def all_composable(seq) -> bool:
return all(isinstance(x, Composable) for x in seq) Try / catch
try:
comp = sql.Composed(seq)
except TypeError as e:
if 'must be Composable' in str(e):
seq = [sql.SQL(x) if isinstance(x, str) else x for x in seq]
comp = sql.Composed(seq)
else: raise Prevention
- Never put raw strings/bytes/numbers into a Composed list.
- Use sql.SQL(', ').join(...) which enforces Composable inputs idiomatically.
- Wrap user values with sql.Literal and names with sql.Identifier.
When it happens
Trigger: Constructing sql.Composed(["SELECT", sql.Identifier('t')]) or sql.Composed([sql.SQL('a'), 'b', 1]). Also triggered indirectly via Composable.__add__ (which builds Composed) and SQL.format() which assembles a Composed from template parts.
Common situations: Developers mix raw strings into Composed lists expecting automatic wrapping, or pass user-supplied values directly instead of wrapping them with sql.Literal. Confusion between sql.SQL (constant template, no escaping) and sql.Literal (escaped value).
Related errors
- SQL values must be strings
- Composed.join() argument must be a string or an SQL
- pgrange must be a string or a RangeAdapter strict subclass
- pyrange must be a type or a Range strict subclass
- you can't specify both 'database' and 'dbname' arguments
AI-assisted analysis of psycopg/psycopg2@3a6d9d6ddc (2026-08-04).
Data as JSON: /data/errors/263d3ee154dbaff3.json.
Report an issue: GitHub.