sqlalchemy/alembic · error · ValueError
Can only return single object for DowngradeOps traverse
Error message
Can only return single object for DowngradeOps traverse
What it means
The rewriter raises ValueError when a custom rewrite registered for a DowngradeOps directive returns zero or more than one directive during traversal. As with UpgradeOps, the traversal contract requires each DowngradeOps container to map to exactly one DowngradeOps to preserve the migration script structure.
Solutions
- Ensure your @writer.rewrites handler for DowngradeOps always returns exactly one DowngradeOps.
- Mutate op.ops in place to add or remove downgrade steps, then return the same op.
- Return an empty DowngradeOps(ops=[]) if you genuinely want a no-op downgrade, never None or a multi-element list.
Example fix
# before
@writer.rewrites(DowngradeOps)
def rewrite_down(context, revision, op):
if not op.ops:
return None # error
return op
# after
@writer.rewrites(DowngradeOps)
def rewrite_down(context, revision, op):
return op # always exactly one, even if ops is empty Defensive patterns
Strategy: validation
Validate before calling
from alembic.operations import ops
def safe_rewrite_downgrade(handler):
def wrapper(context, revision, op):
result = handler(context, revision, op)
assert isinstance(result, ops.DowngradeOps), 'must return one DowngradeOps'
return result
return wrapper Type guard
from alembic.operations import ops
def is_single_downgrade_ops(value) -> bool:
return isinstance(value, ops.DowngradeOps) Prevention
- Always return exactly one DowngradeOps from @writer.rewrites(DowngradeOps) handlers.
- Never return None; return DowngradeOps(ops=[]) for a no-op downgrade.
- Unit-test the rewrite against empty and non-empty op lists.
When it happens
Trigger: Registering @writer.rewrites(DowngradeOps) and returning a list of multiple DowngradeOps objects, None, or an empty list.
Common situations: Custom downgrade rewriting that attempts to fork a downgrade into multiple branches; a rewrite that returns [] when it has 'nothing to do'.
Related errors
- Can only return single object for UpgradeOps traverse
- Autogenerate rendering of SQL Expression language…
- can't return inspector as this AutogenContext has no…
- Duplicate table keys across multiple MetaData objects
- no dispatch function for object
AI-assisted analysis of sqlalchemy/alembic@5551b5d35f (2026-08-11).
Data as JSON: /api/errors/cf693b67d0b5d223.
Report an issue: GitHub.
Appendix: source
Thrown at alembic/autogenerate/rewriter.py:179
revision: _GetRevArg,
directive: MigrationScript,
) -> None:
upgrade_ops_list: list[UpgradeOps] = []
for upgrade_ops in directive.upgrade_ops_list:
ret = self._traverse_for(context, revision, upgrade_ops)
if len(ret) != 1:
raise ValueError(
"Can only return single object for UpgradeOps traverse"
)
upgrade_ops_list.append(ret[0])
directive.upgrade_ops = upgrade_ops_list
downgrade_ops_list: list[DowngradeOps] = []
for downgrade_ops in directive.downgrade_ops_list:
ret = self._traverse_for(context, revision, downgrade_ops)
if len(ret) != 1:
raise ValueError(
"Can only return single object for DowngradeOps traverse"
)
downgrade_ops_list.append(ret[0])
directive.downgrade_ops = downgrade_ops_list
@_traverse.dispatch_for(ops.OpContainer)
def _traverse_op_container(
self,
context: MigrationContext,
revision: _GetRevArg,
directive: OpContainer,
) -> None:
self._traverse_list(context, revision, directive.ops)
@_traverse.dispatch_for(ops.MigrateOperation)
def _traverse_any_directive(
self,
context: MigrationContext,View on GitHub (pinned to 5551b5d35f)