{"record":{"id":"1bc2ea98f4fb5f5e","repo":"sqlalchemy/alembic","slug":"can-only-return-single-object-for-upgradeops-trave","errorCode":null,"errorMessage":"Can only return single object for UpgradeOps traverse","messagePattern":"Can only return single object for UpgradeOps traverse","errorType":"exception","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"alembic/autogenerate/rewriter.py","lineNumber":168,"sourceCode":"        revision: _GetRevArg,\n        directives: list[MigrationScript],\n    ) -> None:\n        self.process_revision_directives(context, revision, directives)\n        for process_revision_directives in self._chained:\n            process_revision_directives(context, revision, directives)\n\n    @_traverse.dispatch_for(ops.MigrationScript)\n    def _traverse_script(\n        self,\n        context: MigrationContext,\n        revision: _GetRevArg,\n        directive: MigrationScript,\n    ) -> None:\n        upgrade_ops_list: list[UpgradeOps] = []\n        for upgrade_ops in directive.upgrade_ops_list:\n            ret = self._traverse_for(context, revision, upgrade_ops)\n            if len(ret) != 1:\n                raise ValueError(\n                    \"Can only return single object for UpgradeOps traverse\"\n                )\n            upgrade_ops_list.append(ret[0])\n\n        directive.upgrade_ops = upgrade_ops_list\n\n        downgrade_ops_list: list[DowngradeOps] = []\n        for downgrade_ops in directive.downgrade_ops_list:\n            ret = self._traverse_for(context, revision, downgrade_ops)\n            if len(ret) != 1:\n                raise ValueError(\n                    \"Can only return single object for DowngradeOps traverse\"\n                )\n            downgrade_ops_list.append(ret[0])\n        directive.downgrade_ops = downgrade_ops_list\n\n    @_traverse.dispatch_for(ops.OpContainer)\n    def _traverse_op_container(","sourceCodeStart":150,"sourceCodeEnd":186,"githubUrl":"https://github.com/sqlalchemy/alembic/blob/5551b5d35f985c99cb8f1af2b3c526b050e4c059/alembic/autogenerate/rewriter.py#L150-L186","documentation":"The rewriter raises ValueError when a custom rewrite function registered for an UpgradeOps directive (via @writer.rewrites) returns zero or more than one directive during traversal. The MigrationScript traversal expects each UpgradeOps container to rewrite to exactly one UpgradeOps so the upgrade script stays structurally valid.","triggerScenarios":"Registering @writer.rewrites(UpgradeOps) and returning a list of multiple UpgradeOps, or returning None, or returning an empty list from the handler.","commonSituations":"Custom directive rewriting that tries to split a single UpgradeOps into several scripts; a rewrite function that conditionally returns the op or None depending on a flag.","solutions":["Ensure your @writer.rewrites handler for UpgradeOps always returns exactly one UpgradeOps object.","To add/remove operations, mutate the UpgradeOps.ops list in place and return the same object.","If you need multiple upgrade scripts, emit them via process_revision_directives at the MigrationScript level instead of the UpgradeOps level."],"exampleFix":"# before\n@writer.rewrites(UpgradeOps)\ndef rewrite_upgrade(context, revision, op):\n    return [op, UpgradeOps(ops=[])]  # two -> error\n\n# after\n@writer.rewrites(UpgradeOps)\ndef rewrite_upgrade(context, revision, op):\n    op.ops.insert(0, my_extra_op)\n    return op  # exactly one","handlingStrategy":"validation","validationCode":"from alembic.operations import ops\n\ndef safe_rewrite_upgrade(handler):\n    def wrapper(context, revision, op):\n        result = handler(context, revision, op)\n        assert isinstance(result, ops.UpgradeOps), 'must return one UpgradeOps'\n        return result\n    return wrapper","typeGuard":"from alembic.operations import ops\n\ndef is_single_upgrade_ops(value) -> bool:\n    return isinstance(value, ops.UpgradeOps)","tryCatchPattern":null,"preventionTips":["Always return exactly one UpgradeOps from @writer.rewrites(UpgradeOps) handlers.","Mutate op.ops in place rather than returning a new list.","Add a unit test asserting the rewrite returns a single UpgradeOps for representative inputs."],"tags":["rewriter","autogenerate","upgrade-ops","directive"],"backgroundTag":null,"analyzedSha":"5551b5d35f985c99cb8f1af2b3c526b050e4c059","analyzedAt":"2026-08-11T01:38:46.612Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}