{"id":"9fd781ecafb387ca","repo":"sqlalchemy/sqlalchemy","slug":"immutable-objects-do-not-support-copying","errorCode":null,"errorMessage":"Immutable objects do not support copying","messagePattern":"Immutable objects do not support copying","errorType":"exception","errorClass":"NotImplementedError","httpStatus":null,"severity":"error","filePath":"lib/sqlalchemy/sql/base.py","lineNumber":208,"sourceCode":"class Immutable:\n    \"\"\"mark a ClauseElement as 'immutable' when expressions are cloned.\n\n    \"immutable\" objects refers to the \"mutability\" of an object in the\n    context of SQL DQL and DML generation.   Such as, in DQL, one can\n    compose a SELECT or subquery of varied forms, but one cannot modify\n    the structure of a specific table or column within DQL.\n    :class:`.Immutable` is mostly intended to follow this concept, and as\n    such the primary \"immutable\" objects are :class:`.ColumnClause`,\n    :class:`.Column`, :class:`.TableClause`, :class:`.Table`.\n\n    \"\"\"\n\n    __slots__ = ()\n\n    _is_immutable: bool = True\n\n    def unique_params(self, *optionaldict: Any, **kwargs: Any) -> NoReturn:\n        raise NotImplementedError(\"Immutable objects do not support copying\")\n\n    def params(self, *optionaldict: Any, **kwargs: Any) -> NoReturn:\n        raise NotImplementedError(\"Immutable objects do not support copying\")\n\n    def _clone(self: _Self, **kw: Any) -> _Self:\n        return self\n\n    def _copy_internals(\n        self, *, omit_attrs: Iterable[str] = (), **kw: Any\n    ) -> None:\n        pass\n\n\nclass SingletonConstant(Immutable):\n    \"\"\"Represent SQL constants like NULL, TRUE, FALSE\"\"\"\n\n    _is_singleton_constant: bool = True\n","sourceCodeStart":190,"sourceCodeEnd":226,"githubUrl":"https://github.com/sqlalchemy/sqlalchemy/blob/d9b44cb731bd27e6e82c99a3c0fb598214e8e7ff/lib/sqlalchemy/sql/base.py#L190-L226","documentation":"Raised by Immutable.unique_params() on ClauseElement subclasses marked Immutable (notably Column, ColumnClause, Table, TableClause). These objects are structurally immutable in DQL/DML generation, so creating a copy with new bound-parameter values is disallowed by design. Calling .unique_params() therefore raises NotImplementedError instead of producing a new bound-parameter copy.","triggerScenarios":"Calling `.unique_params(...)` directly on a Table or Column instance, or on a column-like element that is detected as immutable, e.g. `my_table.unique_params({'x': 1})` or `my_column.unique_params(x=1)`. Also triggered indirectly when generic code blindly calls unique_params() on any ClauseElement without checking mutability.","commonSituations":"Generic helper code that iterates over SQL elements and calls unique_params() for bind-parameter substitution; refactoring a query builder that previously used mutable TextClause/Select elements onto raw Table/Column references; migrating from older SQLAlchemy where mutability semantics differed.","solutions":["Do not call unique_params() on Table/Column/immutable elements; instead apply .params()/.unique_params() on the Select or other mutable parent statement that references them.","If you need per-execution bind values, pass them to connection.execute(stmt, params=...) rather than mutating the immutable element.","Guard generic traversal code with a check for the `_is_immutable` attribute (or `isinstance(el, Immutable)`) before calling unique_params()."],"exampleFix":"// before\nstmt = my_table.unique_params({'id': 5})\n\n// after\nstmt = select(my_table).where(my_table.c.id == bindparam('id'))\nconn.execute(stmt, {'id': 5})","handlingStrategy":"type-guard","validationCode":"def can_call_unique_params(obj) -> bool:\n    \"\"\"Call before obj.unique_params(...) to avoid NotImplementedError on\n    immutable objects (Column, ColumnClause, Table, TableClause).\"\"\"\n    return not getattr(obj, \"_is_immutable\", False)","typeGuard":"from typing import Protocol\n\ncclass SupportsParams(Protocol):\n    def params(self, *a, **k): ...\n    def unique_params(self, *a, **k): ...\n\ndef is_mutable_clause(obj) -> bool:\n    \"\"\"Narrower: True when obj supports params()/unique_params() (not Immutable).\"\"\"\n    return not bool(getattr(obj, \"_is_immutable\", False))","tryCatchPattern":"try:\n    bound = clause.unique_params(value=1)\nexcept NotImplementedError as e:\n    if \"Immutable objects do not support copying\" in str(e):\n        # bind params on a mutable clone instead, or use a fresh literal bind\n        bound = clause._clone()  # immutable _clone returns self; rebuild instead\n        raise TypeError(\n            f\"Cannot bind params on immutable {type(clause).__name__}; \"\n            f\"wrap in a subquery or use literal_binds\"\n        ) from e\n    raise","preventionTips":["Never call .params()/.unique_params() on Table, Column, ColumnClause, or TableClause instances; they are Immutable.","If you need bound parameters on such an object, wrap it in select() or a subquery first so the outer construct is mutable.","Check the `_is_immutable` attribute (or use the type-guard) before parameter binding in generic helpers."],"tags":["immutable","bind-params","clauseelement","core"],"analyzedSha":"d9b44cb731bd27e6e82c99a3c0fb598214e8e7ff","analyzedAt":"2026-08-01T17:20:52.075Z","schemaVersion":2}