psycopg/psycopg2 · error · TypeError

SQL values must be strings

Error message

SQL values must be strings

What it means

Raised by SQL.__init__ (lib/sql.py:182-183) when the argument to sql.SQL(...) is not a str instance (bytes, int, None, or any other type). SQL wraps a constant snippet of SQL that is inserted verbatim (no escaping), so the input must be text; non-string input would be ambiguous to serialize and could break query encoding.

Source

Thrown at lib/sql.py:183

    names).

    The *string* doesn't undergo any form of escaping, so it is not suitable to
    represent variable identifiers or values: you should only use it to pass
    constant strings representing templates or snippets of SQL statements; use
    other objects such as `Identifier` or `Literal` to represent variable
    parts.

    Example::

        >>> query = sql.SQL("select {0} from {1}").format(
        ...    sql.SQL(', ').join([sql.Identifier('foo'), sql.Identifier('bar')]),
        ...    sql.Identifier('table'))
        >>> print(query.as_string(conn))
        select "foo", "bar" from "table"
    """
    def __init__(self, string):
        if not isinstance(string, str):
            raise TypeError("SQL values must be strings")
        super().__init__(string)

    @property
    def string(self):
        """The string wrapped by the `!SQL` object."""
        return self._wrapped

    def as_string(self, context):
        return self._wrapped

    def format(self, *args, **kwargs):
        """
        Merge `Composable` objects into a template.

        :param `Composable` args: parameters to replace to numbered
            (``{0}``, ``{1}``) or auto-numbered (``{}``) placeholders
        :param `Composable` kwargs: parameters to replace to named (``{name}``)
            placeholders

View on GitHub (pinned to 3a6d9d6ddc)

Solutions

  1. Ensure the argument to sql.SQL is a str; decode bytes first: sql.SQL(fragment.decode('utf8')).
  2. If the value is user-supplied data (not a constant SQL snippet), use sql.Literal(value) instead of sql.SQL.
  3. For identifiers (table/column names) use sql.Identifier, never sql.SQL with a raw name.

Example fix

// before
q = sql.SQL(b"SELECT 1")  # bytes
// after
q = sql.SQL("SELECT 1")
Defensive patterns

Strategy: type-guard

Validate before calling

if not isinstance(s, str):
    raise TypeError('sql.SQL requires a str')
q = sql.SQL(s)

Type guard

def is_sql_string(s) -> bool:
    return isinstance(s, str)

Try / catch

try:
    q = sql.SQL(s)
except TypeError:
    if isinstance(s, bytes):
        q = sql.SQL(s.decode('utf8'))
    else: raise

Prevention

When it happens

Trigger: Calling sql.SQL(b'SELECT 1'), sql.SQL(42), sql.SQL(None), or sql.SQL(some_object). Because SQL content is emitted unescaped, the library enforces str to prevent accidental injection of untrusted bytes/objects.

Common situations: Reading a query fragment from a file in binary mode and passing bytes. Passing a value that should be a Literal (escaped) but wrapping it in SQL (unescaped). Forgetting to decode a bytes value from mogrify or an external source.

Related errors


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