Open cpoppema opened 6 months ago
Hi,
Can you also post the previous status of your models before you updated them?
The issue here seems that alembic does some assumptions regarding what's a serial and what is not. That on itself should be fine, but not that the behaviour changes randomly
Of course, that should've been this:
@as_declarative()
class BaseModel:
pk = Column(Integer, primary_key=True, index=True)
class IdColumnMixin:
@declared_attr
def id(cls):
return Column(UUID(as_uuid=True), unique=True, nullable=False, index=True)
class AssociationModel(BaseModel):
__tablename__ = "association_table"
__table_args__ = (UniqueConstraint("parent_id", "child_id"),)
parent_id = Column(ForeignKey("parent.id", ondelete="CASCADE"))
child_id = Column(ForeignKey("child.id", ondelete="CASCADE"))
class ParentModel(IdColumnMixin, BaseModel):
__tablename__ = "parent"
child_set = relationship(
"ChildModel",
secondary="association_table",
back_populates="parent_set",
cascade="all, delete",
)
class ChildModel(IdColumnMixin, BaseModel):
__tablename__ = "child"
parent_set = relationship(
"ParentModel",
secondary="association_table",
back_populates="child_set",
)
Describe the bug
I have
compare_server_default=True
and am runningalembic revision --autogenerate
. I am changing primary key column from int to uuid and havecompare_server_default
enabled to help withserver_default=func.gen_random_uuid()
. Sometimes autogenerate recognizes the existing pk sequences, sometimes it does not, leading to a new migration file that's empty, or sometimes contains a change applyingserver_default=None
. Using postgres 15.xExpected behavior I'm still new to postgres/alembic/sqlalchemy, so I don't know what the desired behavior is. However, I would expect a deterministic result.
To Reproduce
When I am running autogenerate this is basically my models.py:
And the database has these defaults for the tables:
Error
autogenerate creates a file with upgrade/downgrade function that contain either 0, 1, or 2 alter_columns:
For no operations, the log looks like this:
For 1 operation it can look like this:
Versions.
Additional context
The order in which the tables are reflected seems to matter and is random. The
alter_column
operations are added whenassociation_table
is reflected before the other(s). I played around with theresolve_fks: bool = True
-argument in the functionreflect_table
: with this boolean set to False, the output is consistent no matter the other the tables are reflected in. The log output can end up being:Although
association_table
is reflected beforeparent
here, it does not result in analter_column
operation in the migration file.When I looked at locals() inside
autogenerate/compare.py::_compare_server_default
, whenchild
and/orparent
was reflected thanks toresolve_fks=True
, the variableconn_col_default
had a value ofDefaultClause(<sqlalchemy.sql.elements.TextClause object at 0x..........>, for_update=False)
which holds the stringnextval('child_pk_seq'::regclass)
(or parent_pk_seq of course). When association_table is reflected last, or withresolve_fks=False
, the variableconn_col_default
is alwaysNone
.