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

  1. Pass an explicit, non-None constraint_name to the batch create_*_constraint call.
  2. If relying on an automated naming scheme, pass naming_convention=... to op.batch_alter_table() so names are resolved during batch rebuild.
  3. 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

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


AI-assisted analysis of sqlalchemy/alembic@44fb345033 (2026-08-04). Data as JSON: /data/errors/ef170fd981920ddb.json. Report an issue: GitHub.