-
Notifications
You must be signed in to change notification settings - Fork 9.8k
feat: Add timezone-aware datetime support for PostgreSQL #10535
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
Changes from 4 commits
106b83d
69521c5
d5c70cc
ebfd6e8
19e84c3
04b9922
af6fb14
49b933b
da97c8e
0f17b50
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,203 @@ | ||
| """Add timezone support for asyncpg compatibility | ||
|
|
||
| Revision ID: c8613607a100 | ||
| Revises: 182e5471b900 | ||
| Create Date: 2025-11-07 14:56:02.303392 | ||
|
|
||
| """ | ||
|
|
||
| from collections.abc import Sequence | ||
|
|
||
| import sqlalchemy as sa | ||
| from alembic import op | ||
|
|
||
| # revision identifiers, used by Alembic. | ||
| revision: str = "c8613607a100" # pragma: allowlist secret | ||
| down_revision: str | None = "182e5471b900" | ||
| branch_labels: str | Sequence[str] | None = None | ||
| depends_on: str | Sequence[str] | None = None | ||
|
|
||
|
|
||
| def upgrade() -> None: | ||
| conn = op.get_bind() | ||
|
|
||
| # Only apply changes if using PostgreSQL | ||
| # SQLite and other databases don't have timezone-aware datetime types | ||
| if conn.dialect.name == "postgresql": | ||
| # Alter datetime columns to use TIMESTAMP WITH TIME ZONE | ||
|
|
||
| # User table | ||
| op.alter_column( | ||
| "user", | ||
| "create_at", | ||
| type_=sa.DateTime(timezone=True), | ||
| existing_type=sa.DateTime(timezone=False), | ||
| existing_nullable=False, | ||
| ) | ||
| op.alter_column( | ||
| "user", | ||
| "updated_at", | ||
| type_=sa.DateTime(timezone=True), | ||
| existing_type=sa.DateTime(timezone=False), | ||
| existing_nullable=False, | ||
| ) | ||
| op.alter_column( | ||
| "user", | ||
| "last_login_at", | ||
| type_=sa.DateTime(timezone=True), | ||
| existing_type=sa.DateTime(timezone=False), | ||
| existing_nullable=True, | ||
| ) | ||
|
|
||
| # ApiKey table | ||
| op.alter_column( | ||
| "apikey", | ||
| "last_used_at", | ||
| type_=sa.DateTime(timezone=True), | ||
| existing_type=sa.DateTime(timezone=False), | ||
| existing_nullable=True, | ||
| ) | ||
|
|
||
| # Flow table | ||
| op.alter_column( | ||
| "flow", | ||
| "updated_at", | ||
| type_=sa.DateTime(timezone=True), | ||
| existing_type=sa.DateTime(timezone=False), | ||
| existing_nullable=True, | ||
| ) | ||
|
|
||
| # Message table | ||
| op.alter_column( | ||
| "message", | ||
| "timestamp", | ||
| type_=sa.DateTime(timezone=True), | ||
| existing_type=sa.DateTime(timezone=False), | ||
| existing_nullable=False, | ||
| ) | ||
|
|
||
| # File table | ||
| op.alter_column( | ||
| "file", | ||
| "created_at", | ||
| type_=sa.DateTime(timezone=True), | ||
| existing_type=sa.DateTime(timezone=False), | ||
| existing_nullable=False, | ||
| ) | ||
| op.alter_column( | ||
| "file", | ||
| "updated_at", | ||
| type_=sa.DateTime(timezone=True), | ||
| existing_type=sa.DateTime(timezone=False), | ||
| existing_nullable=False, | ||
| ) | ||
|
|
||
| # Transaction table | ||
| op.alter_column( | ||
| "transaction", | ||
| "timestamp", | ||
| type_=sa.DateTime(timezone=True), | ||
| existing_type=sa.DateTime(timezone=False), | ||
| existing_nullable=False, | ||
| ) | ||
|
|
||
| # VertexBuild table | ||
| op.alter_column( | ||
| "vertex_build", | ||
| "timestamp", | ||
| type_=sa.DateTime(timezone=True), | ||
| existing_type=sa.DateTime(timezone=False), | ||
| existing_nullable=False, | ||
| ) | ||
|
|
||
|
|
||
| def downgrade() -> None: | ||
| conn = op.get_bind() | ||
|
|
||
| # Only apply changes if using PostgreSQL | ||
| if conn.dialect.name == "postgresql": | ||
| # Revert datetime columns to TIMESTAMP WITHOUT TIME ZONE | ||
|
|
||
| # User table | ||
| op.alter_column( | ||
| "user", | ||
| "create_at", | ||
| type_=sa.DateTime(timezone=False), | ||
| existing_type=sa.DateTime(timezone=True), | ||
| existing_nullable=False, | ||
| ) | ||
| op.alter_column( | ||
| "user", | ||
| "updated_at", | ||
| type_=sa.DateTime(timezone=False), | ||
| existing_type=sa.DateTime(timezone=True), | ||
| existing_nullable=False, | ||
| ) | ||
| op.alter_column( | ||
| "user", | ||
| "last_login_at", | ||
| type_=sa.DateTime(timezone=False), | ||
| existing_type=sa.DateTime(timezone=True), | ||
| existing_nullable=True, | ||
| ) | ||
|
|
||
| # ApiKey table | ||
| op.alter_column( | ||
| "apikey", | ||
| "last_used_at", | ||
| type_=sa.DateTime(timezone=False), | ||
| existing_type=sa.DateTime(timezone=True), | ||
| existing_nullable=True, | ||
| ) | ||
|
|
||
| # Flow table | ||
| op.alter_column( | ||
| "flow", | ||
| "updated_at", | ||
| type_=sa.DateTime(timezone=False), | ||
| existing_type=sa.DateTime(timezone=True), | ||
| existing_nullable=True, | ||
| ) | ||
|
|
||
| # Message table | ||
| op.alter_column( | ||
| "message", | ||
| "timestamp", | ||
| type_=sa.DateTime(timezone=False), | ||
| existing_type=sa.DateTime(timezone=True), | ||
| existing_nullable=False, | ||
| ) | ||
|
|
||
| # File table | ||
| op.alter_column( | ||
| "file", | ||
| "created_at", | ||
| type_=sa.DateTime(timezone=False), | ||
| existing_type=sa.DateTime(timezone=True), | ||
| existing_nullable=False, | ||
| ) | ||
| op.alter_column( | ||
| "file", | ||
| "updated_at", | ||
| type_=sa.DateTime(timezone=False), | ||
| existing_type=sa.DateTime(timezone=True), | ||
| existing_nullable=False, | ||
| ) | ||
|
|
||
| # Transaction table | ||
| op.alter_column( | ||
| "transaction", | ||
| "timestamp", | ||
| type_=sa.DateTime(timezone=False), | ||
| existing_type=sa.DateTime(timezone=True), | ||
| existing_nullable=False, | ||
| ) | ||
|
|
||
| # VertexBuild table | ||
| op.alter_column( | ||
| "vertex_build", | ||
| "timestamp", | ||
| type_=sa.DateTime(timezone=False), | ||
| existing_type=sa.DateTime(timezone=True), | ||
| existing_nullable=False, | ||
| ) | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1 @@ | ||
| # This file makes the directory a Python package |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,18 @@ | ||
| """Conftest for PostgreSQL driver tests. | ||
|
|
||
| This conftest overrides the _start_app fixture from the parent integration/conftest.py | ||
| to prevent it from loading the client fixture, which would override the database URL. | ||
| """ | ||
|
|
||
| import pytest | ||
|
|
||
|
|
||
| @pytest.fixture(autouse=True) | ||
| def _start_app(): | ||
| """Override parent _start_app fixture to skip client loading. | ||
|
|
||
| PostgreSQL driver tests create their own database connections | ||
| and don't need the app client fixture. | ||
| """ | ||
| # Do nothing - this prevents the parent fixture from running | ||
| return | ||
|
Comment on lines
+1
to
+18
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Add The static analysis tool and pipeline both flag that this file is part of an implicit namespace package. To resolve this, create an To fix this, create an empty #!/bin/bash
touch src/backend/tests/integration/drivers/__init__.py🧰 Tools🪛 GitHub Actions: Ruff Style Check[error] 1-1: INP001: File is part of an implicit namespace package. Add an init.py. 🪛 GitHub Check: Ruff Style Check (3.13)[failure] 1-1: Ruff (INP001) 🤖 Prompt for AI Agents |
||
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.
@Cristhianzl let's add a paragraph explaining why we're doing this migration somewhere in this file. I think that'll be good practice for future changes, and easier than digging through commit history to find a PR description (especially if we do any refactoring).