{"record":{"id":"b6643d8f53aa7bec","repo":"sqlalchemy/alembic","slug":"operation-is-not-reversible-original-column-is-no","errorCode":null,"errorMessage":"operation is not reversible; original column is not present","messagePattern":"operation is not reversible; original column is not present","errorType":"exception","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"alembic/operations/ops.py","lineNumber":2310,"sourceCode":"        super().__init__(table_name, schema=schema)\n        self.column_name = column_name\n        self.kw = kw\n        self.if_exists = if_exists\n        self._reverse = _reverse\n\n    def to_diff_tuple(\n        self,\n    ) -> tuple[str, str | None, str, Column[Any]]:\n        return (\n            \"remove_column\",\n            self.schema,\n            self.table_name,\n            self.to_column(),\n        )\n\n    def reverse(self) -> AddColumnOp:\n        if self._reverse is None:\n            raise ValueError(\n                \"operation is not reversible; \"\n                \"original column is not present\"\n            )\n\n        op = AddColumnOp.from_column_and_tablename(\n            self.schema, self.table_name, self._reverse.column\n        )\n        op.if_not_exists = self.if_exists\n        return op\n\n    @classmethod\n    def from_column_and_tablename(\n        cls,\n        schema: str | None,\n        tname: str,\n        col: Column[Any],\n    ) -> DropColumnOp:\n        return cls(","sourceCodeStart":2292,"sourceCodeEnd":2328,"githubUrl":"https://github.com/sqlalchemy/alembic/blob/5551b5d35f985c99cb8f1af2b3c526b050e4c059/alembic/operations/ops.py#L2292-L2328","documentation":"Raised as ValueError by DropColumnOp.reverse() (ops.py:2308-2313) when _reverse is None. To reverse a column drop, Alembic must re-add the original column, which requires the original Column object captured at construction time via from_column_and_tablename(). A DropColumnOp built without that reverse reference cannot produce an AddColumnOp.","triggerScenarios":"Constructing ops.DropColumnOp('t', 'c') directly (no _reverse) and then calling .reverse() on it — e.g. when building a downgrade from an upgrade op list programmatically. from_column_and_tablename(schema, tname, col) sets _reverse, but the bare constructor does not.","commonSituations":"Custom autogenerate renderers or rewriters that drop columns by constructing the op manually; calling .reverse() during downgrade generation before the original column metadata was attached.","solutions":["Create the op with the column info: DropColumnOp.from_column_and_tablename(schema, 't', column_obj) so _reverse is populated.","When reversing a list of ops, ensure each DropColumnOp originated from from_column_and_tablename() or carried an AddColumnOp as _reverse.","Guard with `if op._reverse is not None:` before calling .reverse() in generic loop code."],"exampleFix":"// before\nop = ops.DropColumnOp('users', 'email')\ndowngrade = op.reverse()  # raises\n// after\nop = ops.DropColumnOp.from_column_and_tablename(\n    None, 'users', sa.Column('email', sa.String(50))\n)\ndowngrade = op.reverse()  # returns AddColumnOp","handlingStrategy":"validation","validationCode":"# Build DropColumnOp with the original column so .reverse() works.\nfrom alembic.operations.ops import DropColumnOp\nimport sqlalchemy as sa\n\ndef reversible_drop(schema, table, col):\n    return DropColumnOp.from_column_and_tablename(schema, table, col)\n\nop = reversible_drop(None, 'users', sa.Column('email', sa.String(50)))\ndowngrade = op.reverse()  # safe, returns AddColumnOp","typeGuard":"from alembic.operations.ops import DropColumnOp\n\ndef is_reversible(op: DropColumnOp) -> bool:\n    return getattr(op, '_reverse', None) is not None","tryCatchPattern":null,"preventionTips":["Create DropColumnOp via from_column_and_tablename() so the original column is captured.","In autogenerate rewriters, only call .reverse() when _reverse is set.","Keep the original Column object available when building reversible ops."],"tags":["alembic","columns","operations-api","database-migration"],"backgroundTag":null,"analyzedSha":"5551b5d35f985c99cb8f1af2b3c526b050e4c059","analyzedAt":"2026-08-11T01:38:46.612Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}