-
Notifications
You must be signed in to change notification settings - Fork 33
Refactor: Change the DeclarativeBase sqlalchemy style #748
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Refactor: Change the DeclarativeBase sqlalchemy style #748
Conversation
|
All the steps under https://docs.sqlalchemy.org/en/20/changelog/whatsnew_20.html#migrating-an-existing-mapping should be followed before closing #612 substituting |
ryuwd
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for working on this! Next steps:
Columntomapped_column(see diff comment example injob_logging/schema.py)- Add
Mapped[...]annotations for all schema files - Remove
cast(__table__, Table)everywhere
| """Used to insert a new job with original JDL. Returns inserted job id.""" | ||
| result = await self.conn.execute( | ||
| JobJDLs.__table__.insert().values( | ||
| cast(Table, JobJDLs.__table__) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The cast shouldn't be necessary here once the schamas have been fully migrated
| JobJDLs.__table__.update().where( | ||
| JobJDLs.__table__.c.JobID == bindparam("b_JobID") | ||
| ), | ||
| cast(Table, JobJDLs.__table__) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Same here and everywhere else
| class LoggingInfo(JobLoggingDBBase): | ||
| __tablename__ = "LoggingInfo" | ||
| job_id = Column("JobID", Integer) | ||
| seq_num = Column("SeqNum", Integer) | ||
| status = Column("Status", String(32), default="") | ||
| minor_status = Column("MinorStatus", String(128), default="") | ||
| application_status = Column("ApplicationStatus", String(255), default="") | ||
| status_time = DateNowColumn("StatusTime") | ||
| status_time_order = Column("StatusTimeOrder", MagicEpochDateTime, default=0) | ||
| source = Column("StatusSource", String(32), default="Unknown") | ||
| __table_args__ = (PrimaryKeyConstraint("JobID", "SeqNum"),) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
To migrate this class:
- first change the factories in diracx-db/src/diracx/db/sql/utils.py
-from sqlalchemy import Column as RawColumn
+from sqlalchemy.orm import mapped_column
-Column: partial[RawColumn] = partial(RawColumn, nullable=False)
-NullColumn: partial[RawColumn] = partial(RawColumn, nullable=True)
-DateNowColumn = partial(Column, type_=DateTime(timezone=True), server_default=utcnow())
+Column: partial[mapped_column] = partial(mapped_column, nullable=False)
+NullColumn: partial[mapped_column] = partial(mapped_column, nullable=True)
+DateNowColumn = partial(mapped_column, type_=DateTime(timezone=True), server_default=utcnow())N.B. for any use of NullColumn you can use Optional inside the type hint
id_token: Mapped[Optional[dict]] = NullColumn("IDToken", JSON())- add
Mappedtype annotations to this class
+from sqlalchemy.orm import Mapped
+
class LoggingInfo(JobLoggingDBBase):
__tablename__ = "LoggingInfo"
- job_id = Column("JobID", Integer)
+ job_id: Mapped[int] = Column("JobID", Integer)
- seq_num = Column("SeqNum", Integer)
+ seq_num: Mapped[int] = Column("SeqNum", Integer)
- status = Column("Status", String(32), default="")
+ status: Mapped[str] = Column("Status", String(32), default="")
- minor_status = Column("MinorStatus", String(128), default="")
+ minor_status: Mapped[str] = Column("MinorStatus", String(128), default="")
- application_status = Column("ApplicationStatus", String(255), default="")
+ application_status: Mapped[str] = Column("ApplicationStatus", String(255), default="")
- status_time = DateNowColumn("StatusTime")
+ status_time: Mapped[datetime] = DateNowColumn("StatusTime")
- status_time_order = Column("StatusTimeOrder", MagicEpochDateTime, default=0)
+ status_time_order: Mapped[int] = Column("StatusTimeOrder", MagicEpochDateTime, default=0)
- source = Column("StatusSource", String(32), default="Unknown")
+ source: Mapped[str] = Column("StatusSource", String(32), default="Unknown")
__table_args__ = (PrimaryKeyConstraint("JobID", "SeqNum"),)There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, I started like this, but with step 5 "make use of pep-593 Annotated to package common directives into types", doesn't it change the approach?
For example, shouldn't we do something like:
# In utils.py
datetime_now = Annotated[datetime, mapped_column(DateTime(timezone=True), server_default=utcnow())]Instead of keeping DateNowColumn as partial[mapped_column] ?
Then use it like this :
status_time: Mapped[datetime_now] = mapped_column("StatusTime")What do you think about it ?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes! For the type hints on the schema attributes I'd encourage to make aliases for the more complicated annotated types likely to be used more than once
As for the mapped_column objects we can reuse the (refactored) partial factories in utils.py, if that is not ideal we can think of a cleaner approach
Therefore we end up with
datetime_now = Annotated[datetime, DateNowColumn("StatusTime")]
...
class JobLoggingDB(JobLoggingDBBase):
...
status_time: Mapped[datetime_now]
...
closed: #612
Changes:
Questions:
There is other new styles in SQLAlchemy, for example the declarative use of Column is replaced by mapped_column.
Should this be applied as well?
In the Bookkeeping schema, this has been done, but the other changes haven’t been applied. Is there a reason for that?