sqlalchemy/alembic · error · ValueError

This MigrationScript instance has a multiple-entry list for…

Error message

This MigrationScript instance has a multiple-entry list for UpgradeOps; please use the upgrade_ops_list attribute.

What it means

Raised as ValueError by the MigrationScript.upgrade_ops getter (ops.py:2822-2835) when self._upgrade_ops contains more than one entry. A MigrationScript normally holds a single UpgradeOps; but in a multi-database ('multidb') autogenerate setup multiple UpgradeOps (one per database) are stored, and the singular accessor is disabled on purpose, directing callers to the upgrade_ops_list attribute.

Solutions

  1. Use migration_script.upgrade_ops_list, which always returns a list (single- or multi-entry), and iterate it.
  2. In custom hooks, pick the right entry: `for uo in script.upgrade_ops_list: ...` rather than `script.upgrade_ops`.
  3. If you truly expect a single DB, ensure your env.py does not register multiple UpgradeOps.

Example fix

// before
def process_revision_directives(context, revision, directives):
    script = directives[0]
    up = script.upgrade_ops  # raises in multidb
// after
def process_revision_directives(context, revision, directives):
    script = directives[0]
    for up in script.upgrade_ops_list:
        up.ops.append(my_custom_op)
Defensive patterns

Strategy: validation

Validate before calling

# In process_revision_directives, always iterate the list form.
def process_revision_directives(context, revision, directives):
    script = directives[0]
    for up_ops in script.upgrade_ops_list:  # never script.upgrade_ops
        up_ops.ops.extend(my_extra_ops)

Type guard

from alembic.operations.ops import MigrationScript

def is_multidb(script: MigrationScript) -> bool:
    return len(script._upgrade_ops) > 1

Try / catch

try:
    up = script.upgrade_ops
except ValueError:
    up = None
ops_list = script.upgrade_ops_list  # always safe

Prevention

When it happens

Trigger: Accessing migration_script.upgrade_ops on a script produced by a multi-database (multidb) env.py / template where configure() was called per-DB, yielding a list of >1 UpgradeOps. Common in `alembic revision --autogenerate` against multiple engines.

Common situations: Switching a project from single-DB to the multidb template, or writing custom autogenerate hooks (e.g. in a `process_revision_directives` function) that assume a single UpgradeOps but receive a list.

Related errors


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

Appendix: source

Thrown at alembic/operations/ops.py:2831

        self.splice = splice
        self.branch_label = branch_label
        self.version_path = (
            pathlib.Path(version_path).as_posix() if version_path else None
        )
        self.depends_on = depends_on
        self.upgrade_ops = upgrade_ops
        self.downgrade_ops = downgrade_ops

    @property
    def upgrade_ops(self) -> UpgradeOps | None:
        """An instance of :class:`.UpgradeOps`.

        .. seealso::

            :attr:`.MigrationScript.upgrade_ops_list`
        """
        if len(self._upgrade_ops) > 1:
            raise ValueError(
                "This MigrationScript instance has a multiple-entry "
                "list for UpgradeOps; please use the "
                "upgrade_ops_list attribute."
            )
        elif not self._upgrade_ops:
            return None
        else:
            return self._upgrade_ops[0]

    @upgrade_ops.setter
    def upgrade_ops(self, upgrade_ops: UpgradeOps | list[UpgradeOps]) -> None:
        self._upgrade_ops = util.to_list(upgrade_ops)
        for elem in self._upgrade_ops:
            assert isinstance(elem, UpgradeOps)

    @property
    def downgrade_ops(self) -> DowngradeOps | None:
        """An instance of :class:`.DowngradeOps`.

View on GitHub (pinned to 5551b5d35f)