You've already forked adk-python
mirror of
https://github.com/encounter/adk-python.git
synced 2026-03-30 10:57:20 -07:00
fix: Strip timezone for PostgreSQL timestamps in DatabaseSessionService
Merge https://github.com/google/adk-python/pull/4365
## Summary
- Fixes `DataError` when using PostgreSQL with `asyncpg` for session storage
- PostgreSQL's default `TIMESTAMP` type is `WITHOUT TIME ZONE`, which cannot accept timezone-aware datetime objects
- The existing code handled this for SQLite but not PostgreSQL - this fix applies the same timezone stripping
## Error
When creating a session with PostgreSQL + asyncpg, the following error occurs:
```
sqlalchemy.dialects.postgresql.asyncpg.Error: <class 'asyncpg.exceptions.DataError'>:
invalid input for query argument $5: datetime.datetime(2026, 2, 3, 21, 32, 50, 353909,
tzinfo=datetime.timezone.utc) (can't subtract offset-naive and offset-aware datetimes)
```
During the INSERT:
```sql
INSERT INTO sessions (app_name, user_id, id, state, create_time, update_time)
VALUES ($1, $2, $3, $4, $5, $6)
```
Where `$5` and `$6` are timezone-aware datetimes being inserted into `TIMESTAMP WITHOUT TIME ZONE` columns.
## Root Cause
Commit 1063fa53 changed from database-generated timestamps (`func.now()`) to explicit Python datetimes (`datetime.now(timezone.utc)`). The SQLite case was handled by stripping the timezone, but PostgreSQL was overlooked.
## Test plan
- [x] Verified fix resolves the error when creating sessions with PostgreSQL + asyncpg
- [ ] Existing unit tests pass
Fixes regression from #1733
COPYBARA_INTEGRATE_REVIEW=https://github.com/google/adk-python/pull/4365 from filipecaixeta:fix-postgresql-timestamp-timezone 9d788ba99e7167a53962d93e59a80f78af091ca9
PiperOrigin-RevId: 867800330
This commit is contained in:
committed by
Copybara-Service
parent
8d0279251c
commit
19b607684f
@@ -374,7 +374,8 @@ class DatabaseSessionService(BaseSessionService):
|
||||
# Store the session
|
||||
now = datetime.now(timezone.utc)
|
||||
is_sqlite = self.db_engine.dialect.name == _SQLITE_DIALECT
|
||||
if is_sqlite:
|
||||
is_postgresql = self.db_engine.dialect.name == _POSTGRESQL_DIALECT
|
||||
if is_sqlite or is_postgresql:
|
||||
now = now.replace(tzinfo=None)
|
||||
|
||||
storage_session = schema.StorageSession(
|
||||
|
||||
@@ -87,6 +87,47 @@ def test_database_session_service_enables_pool_pre_ping_by_default():
|
||||
assert captured_kwargs.get('pool_pre_ping') is True
|
||||
|
||||
|
||||
@pytest.mark.parametrize('dialect_name', ['sqlite', 'postgresql'])
|
||||
def test_database_session_service_strips_timezone_for_dialect(dialect_name):
|
||||
"""Verifies that timezone-aware datetimes are converted to naive datetimes
|
||||
for SQLite and PostgreSQL to avoid 'can't subtract offset-naive and
|
||||
offset-aware datetimes' errors.
|
||||
|
||||
PostgreSQL's default TIMESTAMP type is WITHOUT TIME ZONE, which cannot
|
||||
accept timezone-aware datetime objects when using asyncpg. SQLite also
|
||||
requires naive datetimes.
|
||||
"""
|
||||
# Simulate the logic in create_session
|
||||
is_sqlite = dialect_name == 'sqlite'
|
||||
is_postgres = dialect_name == 'postgresql'
|
||||
|
||||
now = datetime.now(timezone.utc)
|
||||
assert now.tzinfo is not None # Starts with timezone
|
||||
|
||||
if is_sqlite or is_postgres:
|
||||
now = now.replace(tzinfo=None)
|
||||
|
||||
# Both SQLite and PostgreSQL should have timezone stripped
|
||||
assert now.tzinfo is None
|
||||
|
||||
|
||||
def test_database_session_service_preserves_timezone_for_other_dialects():
|
||||
"""Verifies that timezone info is preserved for dialects that support it."""
|
||||
# For dialects like MySQL with explicit timezone support, we don't strip
|
||||
dialect_name = 'mysql'
|
||||
is_sqlite = dialect_name == 'sqlite'
|
||||
is_postgres = dialect_name == 'postgresql'
|
||||
|
||||
now = datetime.now(timezone.utc)
|
||||
assert now.tzinfo is not None
|
||||
|
||||
if is_sqlite or is_postgres:
|
||||
now = now.replace(tzinfo=None)
|
||||
|
||||
# MySQL should preserve timezone (if the column type supports it)
|
||||
assert now.tzinfo is not None
|
||||
|
||||
|
||||
def test_database_session_service_respects_pool_pre_ping_override():
|
||||
captured_kwargs = {}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user