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:`adaptationView on GitHub (pinned to 3a6d9d6ddc)
Solutions
- Use the .strings property (tuple) to read all parts regardless of count.
- If you need the dotted rendered form, call .as_string(context) which always works.
- 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
- Default to reading .strings (tuple) instead of .string.
- Need the rendered form? Use .as_string(ctx) which works for any part count.
- Branch on len(id.strings) before touching .string.
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
- Identifier cannot be empty
- SQL identifier parts must be strings
- no format specification supported by SQL
- no format conversion supported by SQL
- cannot switch from automatic field numbering to manual
AI-assisted analysis of psycopg/psycopg2@3a6d9d6ddc (2026-08-04).
Data as JSON: /data/errors/73955abb61f3f753.json.
Report an issue: GitHub.