diff --git a/admyral/db/alembic/versions/7985f1c159a3_switch_to_timezone_aware_datetime_.py b/admyral/db/alembic/versions/7985f1c159a3_switch_to_timezone_aware_datetime_.py new file mode 100644 index 00000000..a80a4e34 --- /dev/null +++ b/admyral/db/alembic/versions/7985f1c159a3_switch_to_timezone_aware_datetime_.py @@ -0,0 +1,516 @@ +"""switch to timezone aware datetime objects + +Revision ID: 7985f1c159a3 +Revises: 45c7ea136fad +Create Date: 2024-11-13 16:27:08.719491 + +""" + +from typing import Sequence, Union + +from alembic import op +import sqlalchemy as sa +import sqlmodel # noqa F401 +from sqlalchemy.dialects import postgresql + +# revision identifiers, used by Alembic. +revision: str = "7985f1c159a3" +down_revision: Union[str, None] = "45c7ea136fad" +branch_labels: Union[str, Sequence[str], None] = None +depends_on: Union[str, Sequence[str], None] = None + + +def upgrade() -> None: + # ### commands auto generated by Alembic - please adjust! ### + op.alter_column( + "Account", + "created_at", + existing_type=postgresql.TIMESTAMP(), + type_=sa.TIMESTAMP(timezone=True), + existing_nullable=False, + existing_server_default=sa.text("now()"), + ) + op.alter_column( + "Account", + "updated_at", + existing_type=postgresql.TIMESTAMP(), + type_=sa.TIMESTAMP(timezone=True), + existing_nullable=False, + existing_server_default=sa.text("now()"), + ) + op.alter_column( + "Session", + "created_at", + existing_type=postgresql.TIMESTAMP(), + type_=sa.TIMESTAMP(timezone=True), + existing_nullable=False, + existing_server_default=sa.text("now()"), + ) + op.alter_column( + "Session", + "updated_at", + existing_type=postgresql.TIMESTAMP(), + type_=sa.TIMESTAMP(timezone=True), + existing_nullable=False, + existing_server_default=sa.text("now()"), + ) + op.alter_column( + "User", + "created_at", + existing_type=postgresql.TIMESTAMP(), + type_=sa.TIMESTAMP(timezone=True), + existing_nullable=False, + existing_server_default=sa.text("now()"), + ) + op.alter_column( + "User", + "updated_at", + existing_type=postgresql.TIMESTAMP(), + type_=sa.TIMESTAMP(timezone=True), + existing_nullable=False, + existing_server_default=sa.text("now()"), + ) + op.alter_column( + "User", + "email_verified", + existing_type=postgresql.TIMESTAMP(), + type_=sa.TIMESTAMP(timezone=True), + existing_nullable=True, + ) + op.alter_column( + "api_keys", + "created_at", + existing_type=postgresql.TIMESTAMP(), + type_=sa.TIMESTAMP(timezone=True), + existing_nullable=False, + existing_server_default=sa.text("now()"), + ) + op.alter_column( + "api_keys", + "updated_at", + existing_type=postgresql.TIMESTAMP(), + type_=sa.TIMESTAMP(timezone=True), + existing_nullable=False, + existing_server_default=sa.text("now()"), + ) + op.alter_column( + "pip_lockfile_cache", + "created_at", + existing_type=postgresql.TIMESTAMP(), + type_=sa.TIMESTAMP(timezone=True), + existing_nullable=False, + existing_server_default=sa.text("CURRENT_TIMESTAMP"), + ) + op.alter_column( + "pip_lockfile_cache", + "updated_at", + existing_type=postgresql.TIMESTAMP(), + type_=sa.TIMESTAMP(timezone=True), + existing_nullable=False, + existing_server_default=sa.text("CURRENT_TIMESTAMP"), + ) + op.alter_column( + "pip_lockfile_cache", + "expiration_time", + existing_type=postgresql.TIMESTAMP(), + type_=sa.TIMESTAMP(timezone=True), + existing_nullable=False, + ) + op.alter_column( + "python_actions", + "created_at", + existing_type=postgresql.TIMESTAMP(), + type_=sa.TIMESTAMP(timezone=True), + existing_nullable=False, + existing_server_default=sa.text("CURRENT_TIMESTAMP"), + ) + op.alter_column( + "python_actions", + "updated_at", + existing_type=postgresql.TIMESTAMP(), + type_=sa.TIMESTAMP(timezone=True), + existing_nullable=False, + existing_server_default=sa.text("CURRENT_TIMESTAMP"), + ) + op.alter_column( + "secrets", + "created_at", + existing_type=postgresql.TIMESTAMP(), + type_=sa.TIMESTAMP(timezone=True), + existing_nullable=False, + existing_server_default=sa.text("CURRENT_TIMESTAMP"), + ) + op.alter_column( + "secrets", + "updated_at", + existing_type=postgresql.TIMESTAMP(), + type_=sa.TIMESTAMP(timezone=True), + existing_nullable=False, + existing_server_default=sa.text("CURRENT_TIMESTAMP"), + ) + op.alter_column( + "workflow_controls_results", + "created_at", + existing_type=postgresql.TIMESTAMP(), + type_=sa.TIMESTAMP(timezone=True), + existing_nullable=False, + existing_server_default=sa.text("now()"), + ) + op.alter_column( + "workflow_controls_results", + "updated_at", + existing_type=postgresql.TIMESTAMP(), + type_=sa.TIMESTAMP(timezone=True), + existing_nullable=False, + existing_server_default=sa.text("now()"), + ) + op.alter_column( + "workflow_run_steps", + "created_at", + existing_type=postgresql.TIMESTAMP(), + type_=sa.TIMESTAMP(timezone=True), + existing_nullable=False, + existing_server_default=sa.text("CURRENT_TIMESTAMP"), + ) + op.alter_column( + "workflow_run_steps", + "updated_at", + existing_type=postgresql.TIMESTAMP(), + type_=sa.TIMESTAMP(timezone=True), + existing_nullable=False, + existing_server_default=sa.text("CURRENT_TIMESTAMP"), + ) + op.alter_column( + "workflow_runs", + "created_at", + existing_type=postgresql.TIMESTAMP(), + type_=sa.TIMESTAMP(timezone=True), + existing_nullable=False, + existing_server_default=sa.text("CURRENT_TIMESTAMP"), + ) + op.alter_column( + "workflow_runs", + "updated_at", + existing_type=postgresql.TIMESTAMP(), + type_=sa.TIMESTAMP(timezone=True), + existing_nullable=False, + existing_server_default=sa.text("CURRENT_TIMESTAMP"), + ) + op.alter_column( + "workflow_runs", + "completed_at", + existing_type=postgresql.TIMESTAMP(), + type_=sa.TIMESTAMP(timezone=True), + existing_nullable=True, + ) + op.alter_column( + "workflow_runs", + "failed_at", + existing_type=postgresql.TIMESTAMP(), + type_=sa.TIMESTAMP(timezone=True), + existing_nullable=True, + ) + op.alter_column( + "workflow_runs", + "canceled_at", + existing_type=postgresql.TIMESTAMP(), + type_=sa.TIMESTAMP(timezone=True), + existing_nullable=True, + ) + op.alter_column( + "workflow_schedules", + "created_at", + existing_type=postgresql.TIMESTAMP(), + type_=sa.TIMESTAMP(timezone=True), + existing_nullable=False, + existing_server_default=sa.text("CURRENT_TIMESTAMP"), + ) + op.alter_column( + "workflow_schedules", + "updated_at", + existing_type=postgresql.TIMESTAMP(), + type_=sa.TIMESTAMP(timezone=True), + existing_nullable=False, + existing_server_default=sa.text("CURRENT_TIMESTAMP"), + ) + op.alter_column( + "workflow_webhooks", + "created_at", + existing_type=postgresql.TIMESTAMP(), + type_=sa.TIMESTAMP(timezone=True), + existing_nullable=False, + existing_server_default=sa.text("CURRENT_TIMESTAMP"), + ) + op.alter_column( + "workflow_webhooks", + "updated_at", + existing_type=postgresql.TIMESTAMP(), + type_=sa.TIMESTAMP(timezone=True), + existing_nullable=False, + existing_server_default=sa.text("CURRENT_TIMESTAMP"), + ) + op.alter_column( + "workflows", + "created_at", + existing_type=postgresql.TIMESTAMP(), + type_=sa.TIMESTAMP(timezone=True), + existing_nullable=False, + existing_server_default=sa.text("CURRENT_TIMESTAMP"), + ) + op.alter_column( + "workflows", + "updated_at", + existing_type=postgresql.TIMESTAMP(), + type_=sa.TIMESTAMP(timezone=True), + existing_nullable=False, + existing_server_default=sa.text("CURRENT_TIMESTAMP"), + ) + # ### end Alembic commands ### + + +def downgrade() -> None: + # ### commands auto generated by Alembic - please adjust! ### + op.alter_column( + "workflows", + "updated_at", + existing_type=sa.TIMESTAMP(timezone=True), + type_=postgresql.TIMESTAMP(), + existing_nullable=False, + existing_server_default=sa.text("CURRENT_TIMESTAMP"), + ) + op.alter_column( + "workflows", + "created_at", + existing_type=sa.TIMESTAMP(timezone=True), + type_=postgresql.TIMESTAMP(), + existing_nullable=False, + existing_server_default=sa.text("CURRENT_TIMESTAMP"), + ) + op.alter_column( + "workflow_webhooks", + "updated_at", + existing_type=sa.TIMESTAMP(timezone=True), + type_=postgresql.TIMESTAMP(), + existing_nullable=False, + existing_server_default=sa.text("CURRENT_TIMESTAMP"), + ) + op.alter_column( + "workflow_webhooks", + "created_at", + existing_type=sa.TIMESTAMP(timezone=True), + type_=postgresql.TIMESTAMP(), + existing_nullable=False, + existing_server_default=sa.text("CURRENT_TIMESTAMP"), + ) + op.alter_column( + "workflow_schedules", + "updated_at", + existing_type=sa.TIMESTAMP(timezone=True), + type_=postgresql.TIMESTAMP(), + existing_nullable=False, + existing_server_default=sa.text("CURRENT_TIMESTAMP"), + ) + op.alter_column( + "workflow_schedules", + "created_at", + existing_type=sa.TIMESTAMP(timezone=True), + type_=postgresql.TIMESTAMP(), + existing_nullable=False, + existing_server_default=sa.text("CURRENT_TIMESTAMP"), + ) + op.alter_column( + "workflow_runs", + "canceled_at", + existing_type=sa.TIMESTAMP(timezone=True), + type_=postgresql.TIMESTAMP(), + existing_nullable=True, + ) + op.alter_column( + "workflow_runs", + "failed_at", + existing_type=sa.TIMESTAMP(timezone=True), + type_=postgresql.TIMESTAMP(), + existing_nullable=True, + ) + op.alter_column( + "workflow_runs", + "completed_at", + existing_type=sa.TIMESTAMP(timezone=True), + type_=postgresql.TIMESTAMP(), + existing_nullable=True, + ) + op.alter_column( + "workflow_runs", + "updated_at", + existing_type=sa.TIMESTAMP(timezone=True), + type_=postgresql.TIMESTAMP(), + existing_nullable=False, + existing_server_default=sa.text("CURRENT_TIMESTAMP"), + ) + op.alter_column( + "workflow_runs", + "created_at", + existing_type=sa.TIMESTAMP(timezone=True), + type_=postgresql.TIMESTAMP(), + existing_nullable=False, + existing_server_default=sa.text("CURRENT_TIMESTAMP"), + ) + op.alter_column( + "workflow_run_steps", + "updated_at", + existing_type=sa.TIMESTAMP(timezone=True), + type_=postgresql.TIMESTAMP(), + existing_nullable=False, + existing_server_default=sa.text("CURRENT_TIMESTAMP"), + ) + op.alter_column( + "workflow_run_steps", + "created_at", + existing_type=sa.TIMESTAMP(timezone=True), + type_=postgresql.TIMESTAMP(), + existing_nullable=False, + existing_server_default=sa.text("CURRENT_TIMESTAMP"), + ) + op.alter_column( + "workflow_controls_results", + "updated_at", + existing_type=sa.TIMESTAMP(timezone=True), + type_=postgresql.TIMESTAMP(), + existing_nullable=False, + existing_server_default=sa.text("now()"), + ) + op.alter_column( + "workflow_controls_results", + "created_at", + existing_type=sa.TIMESTAMP(timezone=True), + type_=postgresql.TIMESTAMP(), + existing_nullable=False, + existing_server_default=sa.text("now()"), + ) + op.alter_column( + "secrets", + "updated_at", + existing_type=sa.TIMESTAMP(timezone=True), + type_=postgresql.TIMESTAMP(), + existing_nullable=False, + existing_server_default=sa.text("CURRENT_TIMESTAMP"), + ) + op.alter_column( + "secrets", + "created_at", + existing_type=sa.TIMESTAMP(timezone=True), + type_=postgresql.TIMESTAMP(), + existing_nullable=False, + existing_server_default=sa.text("CURRENT_TIMESTAMP"), + ) + op.alter_column( + "python_actions", + "updated_at", + existing_type=sa.TIMESTAMP(timezone=True), + type_=postgresql.TIMESTAMP(), + existing_nullable=False, + existing_server_default=sa.text("CURRENT_TIMESTAMP"), + ) + op.alter_column( + "python_actions", + "created_at", + existing_type=sa.TIMESTAMP(timezone=True), + type_=postgresql.TIMESTAMP(), + existing_nullable=False, + existing_server_default=sa.text("CURRENT_TIMESTAMP"), + ) + op.alter_column( + "pip_lockfile_cache", + "expiration_time", + existing_type=sa.TIMESTAMP(timezone=True), + type_=postgresql.TIMESTAMP(), + existing_nullable=False, + ) + op.alter_column( + "pip_lockfile_cache", + "updated_at", + existing_type=sa.TIMESTAMP(timezone=True), + type_=postgresql.TIMESTAMP(), + existing_nullable=False, + existing_server_default=sa.text("CURRENT_TIMESTAMP"), + ) + op.alter_column( + "pip_lockfile_cache", + "created_at", + existing_type=sa.TIMESTAMP(timezone=True), + type_=postgresql.TIMESTAMP(), + existing_nullable=False, + existing_server_default=sa.text("CURRENT_TIMESTAMP"), + ) + op.alter_column( + "api_keys", + "updated_at", + existing_type=sa.TIMESTAMP(timezone=True), + type_=postgresql.TIMESTAMP(), + existing_nullable=False, + existing_server_default=sa.text("now()"), + ) + op.alter_column( + "api_keys", + "created_at", + existing_type=sa.TIMESTAMP(timezone=True), + type_=postgresql.TIMESTAMP(), + existing_nullable=False, + existing_server_default=sa.text("now()"), + ) + op.alter_column( + "User", + "email_verified", + existing_type=sa.TIMESTAMP(timezone=True), + type_=postgresql.TIMESTAMP(), + existing_nullable=True, + ) + op.alter_column( + "User", + "updated_at", + existing_type=sa.TIMESTAMP(timezone=True), + type_=postgresql.TIMESTAMP(), + existing_nullable=False, + existing_server_default=sa.text("now()"), + ) + op.alter_column( + "User", + "created_at", + existing_type=sa.TIMESTAMP(timezone=True), + type_=postgresql.TIMESTAMP(), + existing_nullable=False, + existing_server_default=sa.text("now()"), + ) + op.alter_column( + "Session", + "updated_at", + existing_type=sa.TIMESTAMP(timezone=True), + type_=postgresql.TIMESTAMP(), + existing_nullable=False, + existing_server_default=sa.text("now()"), + ) + op.alter_column( + "Session", + "created_at", + existing_type=sa.TIMESTAMP(timezone=True), + type_=postgresql.TIMESTAMP(), + existing_nullable=False, + existing_server_default=sa.text("now()"), + ) + op.alter_column( + "Account", + "updated_at", + existing_type=sa.TIMESTAMP(timezone=True), + type_=postgresql.TIMESTAMP(), + existing_nullable=False, + existing_server_default=sa.text("now()"), + ) + op.alter_column( + "Account", + "created_at", + existing_type=sa.TIMESTAMP(timezone=True), + type_=postgresql.TIMESTAMP(), + existing_nullable=False, + existing_server_default=sa.text("now()"), + ) + # ### end Alembic commands ### diff --git a/admyral/db/schemas/auth_schemas.py b/admyral/db/schemas/auth_schemas.py index 2145b0ea..2dbf3fbd 100644 --- a/admyral/db/schemas/auth_schemas.py +++ b/admyral/db/schemas/auth_schemas.py @@ -29,7 +29,9 @@ class UserSchema(BaseSchema, table=True): id: str = Field(sa_type=TEXT(), primary_key=True) name: str | None = Field(sa_type=TEXT(), nullable=True) email: str = Field(sa_type=TEXT()) - email_verified: datetime | None = Field(sa_type=TIMESTAMP(), nullable=True) + email_verified: datetime | None = Field( + sa_type=TIMESTAMP(timezone=True), nullable=True + ) image: str | None = Field(sa_type=TEXT(), nullable=True) # relationship children diff --git a/admyral/db/schemas/base_schemas.py b/admyral/db/schemas/base_schemas.py index c7824ba3..1040db27 100644 --- a/admyral/db/schemas/base_schemas.py +++ b/admyral/db/schemas/base_schemas.py @@ -9,10 +9,12 @@ class BaseSchema(SQLModel, table=False): created_at: datetime = Field( - sa_type=TIMESTAMP(), sa_column_kwargs=dict(server_default=func.now()) + sa_type=TIMESTAMP(timezone=True), + sa_column_kwargs=dict(server_default=func.now()), ) updated_at: datetime = Field( - sa_type=TIMESTAMP(), sa_column_kwargs=dict(server_default=func.now()) + sa_type=TIMESTAMP(timezone=True), + sa_column_kwargs=dict(server_default=func.now()), ) @abstractmethod diff --git a/admyral/db/schemas/pip_lockfile_cache_schemas.py b/admyral/db/schemas/pip_lockfile_cache_schemas.py index f975f603..c4314c45 100644 --- a/admyral/db/schemas/pip_lockfile_cache_schemas.py +++ b/admyral/db/schemas/pip_lockfile_cache_schemas.py @@ -18,7 +18,7 @@ class PipLockfileCacheSchema(BaseSchema, table=True): # other fields lockfile: str = Field(sa_type=TEXT()) - expiration_time: datetime = Field(sa_type=TIMESTAMP()) + expiration_time: datetime = Field(sa_type=TIMESTAMP(timezone=True)) def to_model(self) -> PipLockfile: return PipLockfile.model_validate( diff --git a/admyral/db/schemas/workflow_run_schemas.py b/admyral/db/schemas/workflow_run_schemas.py index 4571858b..b6772272 100644 --- a/admyral/db/schemas/workflow_run_schemas.py +++ b/admyral/db/schemas/workflow_run_schemas.py @@ -34,9 +34,13 @@ class WorkflowRunSchema(BaseSchema, table=True): # other fields source_name: str = Field(sa_type=TEXT()) - completed_at: datetime | None = Field(sa_type=TIMESTAMP(), nullable=True) - failed_at: datetime | None = Field(sa_type=TIMESTAMP(), nullable=True) - canceled_at: datetime | None = Field(sa_type=TIMESTAMP(), nullable=True) + completed_at: datetime | None = Field( + sa_type=TIMESTAMP(timezone=True), nullable=True + ) + failed_at: datetime | None = Field(sa_type=TIMESTAMP(timezone=True), nullable=True) + canceled_at: datetime | None = Field( + sa_type=TIMESTAMP(timezone=True), nullable=True + ) # relationship parents workflow: "WorkflowSchema" = Relationship(back_populates="workflow_runs")