{"id":"ef170fd981920ddb","repo":"sqlalchemy/alembic","slug":"constraint-must-have-a-name","errorCode":null,"errorMessage":"Constraint must have a name","messagePattern":"Constraint must have a name","errorType":"validation","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"alembic/operations/batch.py","lineNumber":665,"sourceCode":"        the CREATE TABLE and doesn't need an extra step here.\n\n        \"\"\"\n\n    def create_table_comment(self, table):\n        \"\"\"the batch table creation function will issue create_table_comment\n        on the real \"impl\" as part of the create table process.\n\n        \"\"\"\n\n    def drop_table_comment(self, table):\n        \"\"\"the batch table creation function will issue drop_table_comment\n        on the real \"impl\" as part of the create table process.\n\n        \"\"\"\n\n    def add_constraint(self, const: Constraint) -> None:\n        if not constraint_name_defined(const.name):\n            raise ValueError(\"Constraint must have a name\")\n        if isinstance(const, sql_schema.PrimaryKeyConstraint):\n            if self.table.primary_key in self.unnamed_constraints:\n                self.unnamed_constraints.remove(self.table.primary_key)\n\n        if constraint_name_string(const.name):\n            self.named_constraints[const.name] = const\n        else:\n            self.unnamed_constraints.append(const)\n\n    def drop_constraint(self, const: Constraint) -> None:\n        if not const.name:\n            raise ValueError(\"Constraint must have a name\")\n        try:\n            if const.name in self.col_named_constraints:\n                col, const = self.col_named_constraints.pop(const.name)\n\n                for col_const in list(self.columns[col.name].constraints):\n                    if col_const.name == const.name:","sourceCodeStart":647,"sourceCodeEnd":683,"githubUrl":"https://github.com/sqlalchemy/alembic/blob/44fb3450330204b222ff05135e1fbbbdb28c44db/alembic/operations/batch.py#L647-L683","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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()."],"exampleFix":"// before\nwith op.batch_alter_table('user') as batch_op:\n    batch_op.create_unique_constraint(None, ['email'])  # raises ValueError\n\n// after\nwith op.batch_alter_table('user') as batch_op:\n    batch_op.create_unique_constraint('uq_user_email', ['email'])","handlingStrategy":"validation","validationCode":"def ensure_constraint_name(constraint_name):\n    if not constraint_name:\n        raise ValueError('batch add_constraint requires an explicit name')\n    return constraint_name\n\n# before batch_op.create_unique_constraint(name, cols):\nensure_constraint_name(name)","typeGuard":"from sqlalchemy.sql.schema import Constraint\nfrom alembic.util.sqla_compat import constraint_name_defined\n\ndef constraint_has_name(const) -> bool:\n    return constraint_name_defined(getattr(const, 'name', None))","tryCatchPattern":"try:\n    batch_op.create_unique_constraint(name, cols)\nexcept ValueError as e:\n    if 'Constraint must have a name' in str(e):\n        batch_op.create_unique_constraint(_generated_name, cols)\n    else:\n        raise","preventionTips":["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."],"tags":["alembic","batch-mode","constraints","migrations"],"analyzedSha":"44fb3450330204b222ff05135e1fbbbdb28c44db","analyzedAt":"2026-08-04T19:57:10.248Z","schemaVersion":2}