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

  1. Give the constraint an explicit string name, e.g. batch_op.create_unique_constraint('uq_users_email', 'users', ['email']).
  2. If the constraint already exists in the DB with an auto-generated name, reflect it first and pass the reflected name.
  3. 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

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


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)