Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion src/google/adk/sessions/database_session_service.py
Original file line number Diff line number Diff line change
Expand Up @@ -461,7 +461,8 @@ async def create_session(
now = datetime.fromtimestamp(platform_time.get_time(), tz=timezone.utc)
is_sqlite = self.db_engine.dialect.name == _SQLITE_DIALECT
is_postgresql = self.db_engine.dialect.name == _POSTGRESQL_DIALECT
if is_sqlite or is_postgresql:
is_mysql = self.db_engine.dialect.name == _MYSQL_DIALECT
if is_sqlite or is_postgresql or is_mysql:
now = now.replace(tzinfo=None)

storage_session = schema.StorageSession(
Expand Down
34 changes: 9 additions & 25 deletions tests/unittests/sessions/test_session_service.py
Original file line number Diff line number Diff line change
Expand Up @@ -88,47 +88,31 @@ def fake_create_async_engine(_db_url: str, **kwargs):
assert captured_kwargs.get('pool_pre_ping') is True


@pytest.mark.parametrize('dialect_name', ['sqlite', 'postgresql'])
@pytest.mark.parametrize('dialect_name', ['sqlite', 'postgresql', 'mysql'])
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.
for SQLite, PostgreSQL, and MySQL.

PostgreSQL's default TIMESTAMP type is WITHOUT TIME ZONE, which cannot
accept timezone-aware datetime objects when using asyncpg. SQLite also
requires naive datetimes.
These databases store DATETIME/TIMESTAMP WITHOUT TIME ZONE, so keeping
tzinfo on the Python datetime causes a mismatch between the marker
produced by create_session (with +00:00) and the marker read back by
get_session (without +00:00), triggering a false stale-writer error
on the first append_event after create_session.
"""
# Simulate the logic in create_session
is_sqlite = dialect_name == 'sqlite'
is_postgres = dialect_name == 'postgresql'
is_mysql = dialect_name == 'mysql'

now = datetime.now(timezone.utc)
assert now.tzinfo is not None # Starts with timezone

if is_sqlite or is_postgres:
if is_sqlite or is_postgres or is_mysql:
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 = {}

Expand Down