{"id":"73955abb61f3f753","repo":"psycopg/psycopg2","slug":"the-identifier-wraps-more-than-one-than-one-string","errorCode":null,"errorMessage":"the Identifier wraps more than one than one string","messagePattern":"the Identifier wraps more than one than one string","errorType":"exception","errorClass":"AttributeError","httpStatus":null,"severity":"error","filePath":"lib/sql.py","lineNumber":343,"sourceCode":"        for s in strings:\n            if not isinstance(s, str):\n                raise TypeError(\"SQL identifier parts must be strings\")\n\n        super().__init__(strings)\n\n    @property\n    def strings(self):\n        \"\"\"A tuple with the strings wrapped by the `Identifier`.\"\"\"\n        return self._wrapped\n\n    @property\n    def string(self):\n        \"\"\"The string wrapped by the `Identifier`.\n        \"\"\"\n        if len(self._wrapped) == 1:\n            return self._wrapped[0]\n        else:\n            raise AttributeError(\n                \"the Identifier wraps more than one than one string\")\n\n    def __repr__(self):\n        return f\"{self.__class__.__name__}({', '.join(map(repr, self._wrapped))})\"\n\n    def as_string(self, context):\n        return '.'.join(ext.quote_ident(s, context) for s in self._wrapped)\n\n\nclass Literal(Composable):\n    \"\"\"\n    A `Composable` representing an SQL value to include in a query.\n\n    Usually you will want to include placeholders in the query and pass values\n    as `~cursor.execute()` arguments. If however you really really need to\n    include a literal value in the query you can use this object.\n\n    The string returned by `!as_string()` follows the normal :ref:`adaptation","sourceCodeStart":325,"sourceCodeEnd":361,"githubUrl":"https://github.com/psycopg/psycopg2/blob/3a6d9d6ddc6b53eaa80b712f5fa6b23abbdc38db/lib/sql.py#L325-L361","documentation":"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.)","triggerScenarios":"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.","commonSituations":"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.","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."],"exampleFix":"// before\nname = identifier.string        # AttributeError if qualified\n// after\nname = '.'.join(identifier.strings)   # works for 1 or many parts","handlingStrategy":"type-guard","validationCode":"def identifier_name(identifier) -> str:\n    \"\"\"Safe single-string accessor for 1- or many-part Identifiers.\"\"\"\n    parts = identifier.strings\n    return parts[0] if len(parts) == 1 else '.'.join(parts)","typeGuard":"def is_single_part_identifier(identifier) -> bool:\n    return len(identifier.strings) == 1","tryCatchPattern":null,"preventionTips":["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."],"tags":["psycopg2","sql-composition","identifier","attributeerror"],"analyzedSha":"3a6d9d6ddc6b53eaa80b712f5fa6b23abbdc38db","analyzedAt":"2026-08-04T19:56:51.958Z","schemaVersion":2}