sqlalchemy/alembic · error · ValueError
Constraint must have a name
Error message
Constraint must have a name
What it means
Raised as ValueError by ApplyBatchImpl.add_constraint (batch.py:663-665) when a constraint added inside a batch_alter_table block has no usable name. Batch mode recreates the table by copying it and must track constraints in named_constraints / unnamed_constraints dictionaries, so it calls constraint_name_defined() (sqla_compat.py:138) which requires the name to be a real string or the SQLAlchemy 'none-name' symbol; a bare None fails. Without a name the constraint cannot be re-created deterministically on the new copy of the table.
Solutions
- Give the constraint an explicit string name, e.g. batch_op.create_unique_constraint('uq_users_email', 'users', ['email']).
- If the constraint already exists in the DB with an auto-generated name, reflect it first and pass the reflected name.
- For naming conventions, set a SQLAlchemy naming_convention in your MetaData so constraints get deterministic names before entering batch mode.
Example fix
// before
with op.batch_alter_table('users') as batch_op:
batch_op.create_unique_constraint(None, 'users', ['email'])
// after
with op.batch_alter_table('users') as batch_op:
batch_op.create_unique_constraint('uq_users_email', 'users', ['email']) Defensive patterns
Strategy: validation
Validate before calling
# Ensure a name exists before adding a constraint in batch mode.
def ensure_constraint_name(name):
if name is None:
raise ValueError('Batch add_constraint requires an explicit constraint name')
return name
with op.batch_alter_table('t') as batch_op:
batch_op.create_unique_constraint(
ensure_constraint_name('uq_t_col'), 't', ['col']
) Type guard
from sqlalchemy.sql.schema import Constraint
def has_real_name(c: Constraint) -> bool:
return isinstance(c.name, str) and len(c.name) > 0 Prevention
- Always pass an explicit string name when creating constraints inside batch_alter_table.
- Configure a SQLAlchemy naming_convention on MetaData so reflected constraints carry names.
- Reflect the existing schema to learn auto-generated names before batch alters.
When it happens
Trigger: Inside a batch block calling batch_op.create_unique_constraint(None, 'users', ['email']) or batch_op.create_check_constraint(None, 'ck', 'x > 0') with the name argument set to None. Also triggered by create_foreign_key(None, 'src', 'dst', ['a'], ['b']) inside batch mode.
Common situations: Developers who normally rely on unnamed constraints with non-batch op.create_unique_constraint(..., name=None) hit SQLite-style batch mode (where batch recreation is mandatory for most alters) and the name requirement surfaces. Auto-generated migrations that omit explicit constraint names also fail under batch.
Related errors
- No such constraint
- Can't drop table in batch mode
- constraint cannot be produced; original constraint is not…
- No such index
- TODO
AI-assisted analysis of sqlalchemy/alembic@5551b5d35f (2026-08-11).
Data as JSON: /api/errors/ef170fd981920ddb.
Report an issue: GitHub.
Appendix: source
Thrown at alembic/operations/batch.py:665
the CREATE TABLE and doesn't need an extra step here.
"""
def create_table_comment(self, table):
"""the batch table creation function will issue create_table_comment
on the real "impl" as part of the create table process.
"""
def drop_table_comment(self, table):
"""the batch table creation function will issue drop_table_comment
on the real "impl" as part of the create table process.
"""
def add_constraint(self, const: Constraint) -> None:
if not constraint_name_defined(const.name):
raise ValueError("Constraint must have a name")
if isinstance(const, sql_schema.PrimaryKeyConstraint):
if self.table.primary_key in self.unnamed_constraints:
self.unnamed_constraints.remove(self.table.primary_key)
if constraint_name_string(const.name):
self.named_constraints[const.name] = const
else:
self.unnamed_constraints.append(const)
def drop_constraint(self, const: Constraint) -> None:
if not const.name:
raise ValueError("Constraint must have a name")
try:
if const.name in self.col_named_constraints:
col, const = self.col_named_constraints.pop(const.name)
for col_const in list(self.columns[col.name].constraints):
if col_const.name == const.name:View on GitHub (pinned to 5551b5d35f)