sqlalchemy/alembic · error · TypeError

'type' can be one of

Error message

'type' can be one of %s

What it means

Raised as TypeError by Operations.schema_obj.generic_constraint (schemaobj.py:176-182) when the type_ argument is not a key in the allowed types dict {foreignkey, primary, unique, check, None}. The KeyError is re-raised as a TypeError listing the valid values, telling the caller they passed an unrecognised constraint type.

Solutions

  1. Use one of the exact tokens: 'foreignkey', 'primary', 'unique', or 'check' (case-sensitive).
  2. For drop_constraint, pick type_ matching the constraint kind: foreign key -> 'foreignkey', primary key -> 'primary', unique -> 'unique', check -> 'check'.
  3. If unsure, pass type_=None to drop a generic constraint by name.

Example fix

// before
op.drop_constraint('fk_orders_user', 'orders', type_='foreign')  # wrong
// after
op.drop_constraint('fk_orders_user', 'orders', type_='foreignkey')
Defensive patterns

Strategy: type-guard

Validate before calling

# Validate the constraint type token before passing it to op.drop_constraint.
VALID_TYPES = {'foreignkey', 'primary', 'unique', 'check', None}

def drop_constraint_safe(name, table, type_):
    if type_ not in VALID_TYPES:
        raise ValueError(
            f'Invalid constraint type {type_!r}; expected one of {sorted(t for t in VALID_TYPES if t)}'
        )
    op.drop_constraint(name, table, type_=type_)

Type guard

from typing import Literal, get_args

ConstraintType = Literal['foreignkey', 'primary', 'unique', 'check']

def is_valid_constraint_type(t: str | None) -> bool:
    return t is None or t in get_args(ConstraintType)

Prevention

When it happens

Trigger: Calling op.drop_constraint('name', 'table', type_='fk') or type_='foreign' / type_='pk' / type_='index' (misspelled or wrong). The valid strings are exactly 'foreignkey','primary','unique','check' (and None). Anything else hits the KeyError branch.

Common situations: Typos and abbreviations in hand-written migrations; passing the SQLAlchemy constraint class name instead of the alembic short token; copy-paste from docs that use a different vocabulary.

Related errors


AI-assisted analysis of sqlalchemy/alembic@5551b5d35f (2026-08-11). Data as JSON: /api/errors/4808b48ab6281f33. Report an issue: GitHub.

Appendix: source

Thrown at alembic/operations/schemaobj.py:179

        table_name: str,
        type_: str | None,
        schema: str | None = None,
        **kw,
    ) -> Any:
        t = self.table(table_name, schema=schema)
        types: dict[str | None, Any] = {
            "foreignkey": lambda name: sa_schema.ForeignKeyConstraint(
                [], [], name=name
            ),
            "primary": sa_schema.PrimaryKeyConstraint,
            "unique": sa_schema.UniqueConstraint,
            "check": lambda name: sa_schema.CheckConstraint("", name=name),
            None: sa_schema.Constraint,
        }
        try:
            const = types[type_]
        except KeyError as ke:
            raise TypeError(
                "'type' can be one of %s"
                % ", ".join(sorted(repr(x) for x in types))
            ) from ke
        else:
            const = const(name=name)
            t.append_constraint(const)
            return const

    def metadata(self) -> MetaData:
        kw = {}
        if (
            self.migration_context is not None
            and "target_metadata" in self.migration_context.opts
        ):
            mt = self.migration_context.opts["target_metadata"]
            if hasattr(mt, "naming_convention"):
                kw["naming_convention"] = mt.naming_convention
        return sa_schema.MetaData(**kw)

View on GitHub (pinned to 5551b5d35f)