sqlalchemy/alembic · error · ValueError
Constraint must have a name
Error message
Constraint must have a name
What it means
Raised by ApplyBatchImpl.add_constraint() when the constraint passed to a batch add_constraint/create_*_constraint call has no defined name (constraint_name_defined() returns False). Batch mode rebuilds the table by name, so every constraint must be addressable by name. Without a name Alembic cannot reliably re-create the constraint on the shadow table.
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 44fb345033)
Solutions
- Pass an explicit, non-None constraint_name to the batch create_*_constraint call.
- If relying on an automated naming scheme, pass naming_convention=... to op.batch_alter_table() so names are resolved during batch rebuild.
- Build the constraint object with a concrete name before add_constraint().
Example fix
// before
with op.batch_alter_table('user') as batch_op:
batch_op.create_unique_constraint(None, ['email']) # raises ValueError
// after
with op.batch_alter_table('user') as batch_op:
batch_op.create_unique_constraint('uq_user_email', ['email']) Defensive patterns
Strategy: validation
Validate before calling
def ensure_constraint_name(constraint_name):
if not constraint_name:
raise ValueError('batch add_constraint requires an explicit name')
return constraint_name
# before batch_op.create_unique_constraint(name, cols):
ensure_constraint_name(name) Type guard
from sqlalchemy.sql.schema import Constraint
from alembic.util.sqla_compat import constraint_name_defined
def constraint_has_name(const) -> bool:
return constraint_name_defined(getattr(const, 'name', None)) Try / catch
try:
batch_op.create_unique_constraint(name, cols)
except ValueError as e:
if 'Constraint must have a name' in str(e):
batch_op.create_unique_constraint(_generated_name, cols)
else:
raise Prevention
- Always pass an explicit constraint name in batch contexts.
- Pass naming_convention= to batch_alter_table when relying on auto-naming.
- Add a pre-flight assertion in migration helpers that names are set.
When it happens
Trigger: Inside batch_alter_table, calling batch_op.create_unique_constraint(None, ['col']), batch_op.create_check_constraint(None, 'cond'), batch_op.create_primary_key(None, ['col']) or batch_op.add_constraint(Constraint(...)) with a constraint whose .name is None and is not resolved by a naming_convention.
Common situations: Passing constraint_name=None because the non-batch API tolerates it under a naming convention, but the batch context has no naming_convention set; copying an online-mode constraint creation into a batch block; reflected constraints that lost their name.
Related errors
- No such constraint: '%s'
- Can't drop table in batch mode
- No such index: '%s'
- constraint cannot be produced; original constraint is not pr
- 'type' can be one of %s
AI-assisted analysis of sqlalchemy/alembic@44fb345033 (2026-08-04).
Data as JSON: /data/errors/ef170fd981920ddb.json.
Report an issue: GitHub.