sqlalchemy/sqlalchemy · error · NotImplementedError
Immutable objects do not support copying
Error message
Immutable objects do not support copying
What it means
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.
Source
Thrown at lib/sqlalchemy/sql/base.py:208
class Immutable:
"""mark a ClauseElement as 'immutable' when expressions are cloned.
"immutable" objects refers to the "mutability" of an object in the
context of SQL DQL and DML generation. Such as, in DQL, one can
compose a SELECT or subquery of varied forms, but one cannot modify
the structure of a specific table or column within DQL.
:class:`.Immutable` is mostly intended to follow this concept, and as
such the primary "immutable" objects are :class:`.ColumnClause`,
:class:`.Column`, :class:`.TableClause`, :class:`.Table`.
"""
__slots__ = ()
_is_immutable: bool = True
def unique_params(self, *optionaldict: Any, **kwargs: Any) -> NoReturn:
raise NotImplementedError("Immutable objects do not support copying")
def params(self, *optionaldict: Any, **kwargs: Any) -> NoReturn:
raise NotImplementedError("Immutable objects do not support copying")
def _clone(self: _Self, **kw: Any) -> _Self:
return self
def _copy_internals(
self, *, omit_attrs: Iterable[str] = (), **kw: Any
) -> None:
pass
class SingletonConstant(Immutable):
"""Represent SQL constants like NULL, TRUE, FALSE"""
_is_singleton_constant: bool = True
View on GitHub (pinned to d9b44cb731)
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().
Example fix
// before
stmt = my_table.unique_params({'id': 5})
// after
stmt = select(my_table).where(my_table.c.id == bindparam('id'))
conn.execute(stmt, {'id': 5}) Defensive patterns
Strategy: type-guard
Validate before calling
def can_call_unique_params(obj) -> bool:
"""Call before obj.unique_params(...) to avoid NotImplementedError on
immutable objects (Column, ColumnClause, Table, TableClause)."""
return not getattr(obj, "_is_immutable", False) Type guard
from typing import Protocol
cclass SupportsParams(Protocol):
def params(self, *a, **k): ...
def unique_params(self, *a, **k): ...
def is_mutable_clause(obj) -> bool:
"""Narrower: True when obj supports params()/unique_params() (not Immutable)."""
return not bool(getattr(obj, "_is_immutable", False)) Try / catch
try:
bound = clause.unique_params(value=1)
except NotImplementedError as e:
if "Immutable objects do not support copying" in str(e):
# bind params on a mutable clone instead, or use a fresh literal bind
bound = clause._clone() # immutable _clone returns self; rebuild instead
raise TypeError(
f"Cannot bind params on immutable {type(clause).__name__}; "
f"wrap in a subquery or use literal_binds"
) from e
raise Prevention
- 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.
When it happens
Trigger: 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.
Common situations: 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.
Related errors
- can't set attribute
- can't delete attribute
- Additional arguments should be named <dialectname>_<argument
AI-assisted analysis of sqlalchemy/sqlalchemy@d9b44cb731 (2026-08-01).
Data as JSON: /data/errors/9fd781ecafb387ca.json.
Report an issue: GitHub.