psycopg/psycopg2 · error · AttributeError

the Identifier wraps more than one than one string

Error message

the Identifier wraps more than one than one string

What it means

Raised by the Identifier.string property (lib/sql.py:343) when accessed on a multi-part Identifier. The .string accessor is a convenience that returns the single wrapped string; for qualified names like Identifier('schema','table') there is no single string, so it raises AttributeError. Use the .strings property (plural) to get the tuple of all parts. (The message contains the original library typo 'more than one than one'; it is upstream text, not something to fix locally.)

Source

Thrown at lib/sql.py:343

        for s in strings:
            if not isinstance(s, str):
                raise TypeError("SQL identifier parts must be strings")

        super().__init__(strings)

    @property
    def strings(self):
        """A tuple with the strings wrapped by the `Identifier`."""
        return self._wrapped

    @property
    def string(self):
        """The string wrapped by the `Identifier`.
        """
        if len(self._wrapped) == 1:
            return self._wrapped[0]
        else:
            raise AttributeError(
                "the Identifier wraps more than one than one string")

    def __repr__(self):
        return f"{self.__class__.__name__}({', '.join(map(repr, self._wrapped))})"

    def as_string(self, context):
        return '.'.join(ext.quote_ident(s, context) for s in self._wrapped)


class Literal(Composable):
    """
    A `Composable` representing an SQL value to include in a query.

    Usually you will want to include placeholders in the query and pass values
    as `~cursor.execute()` arguments. If however you really really need to
    include a literal value in the query you can use this object.

    The string returned by `!as_string()` follows the normal :ref:`adaptation

View on GitHub (pinned to 3a6d9d6ddc)

Solutions

  1. Use the .strings property (tuple) to read all parts regardless of count.
  2. If you need the dotted rendered form, call .as_string(context) which always works.
  3. Branch on len(id.strings) == 1 before using .string.

Example fix

// before
name = identifier.string        # AttributeError if qualified
// after
name = '.'.join(identifier.strings)   # works for 1 or many parts
Defensive patterns

Strategy: type-guard

Validate before calling

def identifier_name(identifier) -> str:
    """Safe single-string accessor for 1- or many-part Identifiers."""
    parts = identifier.strings
    return parts[0] if len(parts) == 1 else '.'.join(parts)

Type guard

def is_single_part_identifier(identifier) -> bool:
    return len(identifier.strings) == 1

Prevention

When it happens

Trigger: Accessing .string on sql.Identifier('schema','table') or any Identifier built with more than one argument. Generic helper code that assumes id.string works for every Identifier will trip on qualified names.

Common situations: A helper that reads .string and is later handed a schema-qualified name from config; refactoring a single-part name to a dotted name without updating consumers.

Related errors


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