[Schema] Adopt LifecycleTimestampsMixin on the account manager keypairs
- Dominant language
- Python
- Stars
- 670
- Forks
- 183
- Avg merge
- 15h 13m
- Merged PRs (30d)
- 368
Description
## Objective
Give the account manager's keypairs the audit-timestamp columns the manager's tables already carry: created_at and updated_at from a LifecycleTimestampsMixin, instead of hand-rolled created_at plus modified_at.
## Background
The manager settled on models/mixins/timestamp.py (CreatedAtMixin, UpdatedAtMixin, LifecycleTimestampsMixin), and users, keypairs, groups and vfolder_invitations moved onto it in BA-7207; domains follows in BA-7352. The account manager was never part of that sweep, so the same fact is spelled two ways across the two components.
KeypairRow (account_manager/models/keypair.py) declares created_at and modified_at inline, as nullable sa.Column with server_default=now() and onupdate=current_timestamp(). Both are always populated in practice - the server default fires on every insert - yet every reader still has to treat them as optional.
ai.backend.common carries no SQLAlchemy code, so the manager's mixin module cannot be imported from here and cannot be lifted into common without dragging SQLAlchemy into it. The account manager gets its own mixin module mirroring the manager's column declarations.
Nothing outside the row declaration and the initial 1449797cc931_create_tables migration reads modified_at in this component - no service, no API handler, no DTO - so the rename has no call-site fallout and no response shape changes.
## Acceptance Criteria
- account_manager/models/mixins/timestamp.py declares CreatedAtMixin, UpdatedAtMixin and LifecycleTimestampsMixin with the same column shape the manager uses: NOT NULL, server_default=now(), onupdate=now() on updated_at, and sort_order keeping the pair last and ordered created then updated.
- KeypairRow inherits LifecycleTimestampsMixin and drops its own created_at and modified_at declarations.
- An account manager alembic migration renames keypairs.modified_at to updated_at, backfills NULLs in created_at and updated_at from the sibling timestamp or now(), and sets both columns NOT NULL so the mixin's declaration holds.
- The migration round-trips: upgrade then downgrade on a populated account manager database restores the modified_at name and the nullable declaration.
- pants check passes with the Mapped[datetime] typing the mixin introduces on the row.
## Notes
- user_profiles is the other account manager table that hand-rolls the same pair; it reuses the mixin this issue adds and moves in a separate change.
- The account manager exposes neither timestamp on its API, so there is no deprecation window to open here - unlike BA-7208, which keeps modified_at alive on the manager's user and keypair responses.
- Sharing one mixin module between the manager and the account manager would mean putting SQLAlchemy into ai.backend.common; out of scope, worth revisiting if a third component needs it.
- Roughly 100 lines.
JIRA Issue: BA-7354
Contributor guide
Research direction
Start with account_manager/models/keypair.py and compare its timestamp declarations with the manager's models/mixins/timestamp.py; then inspect the account manager migration history, including 1449797cc931_create_tables. Verify the mixin, keypair schema migration, populated upgrade/downgrade round-trip, and pants check against the stated null backfill and NOT NULL requirements.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- python, sqlalchemy
- Domain
- backend, databases
- Issue type
- Refactor
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Quiet
- Clarity
- Clearly specified
- Newbie friendliness
- 68/100