diff --git a/src/google/adk/a2a/converters/event_converter.py b/src/google/adk/a2a/converters/event_converter.py index 59bbefa1f0..9cec8db7ca 100644 --- a/src/google/adk/a2a/converters/event_converter.py +++ b/src/google/adk/a2a/converters/event_converter.py @@ -22,7 +22,9 @@ from typing import Dict from typing import List from typing import Optional -import uuid + +from google.adk.platform import time as platform_time +from google.adk.platform import uuid as platform_uuid from a2a.server.events import Event as A2AEvent from a2a.types import DataPart @@ -254,7 +256,7 @@ def convert_a2a_task_to_event( invocation_id=( invocation_context.invocation_id if invocation_context - else str(uuid.uuid4()) + else platform_uuid.new_uuid() ), author=author or "a2a agent", branch=invocation_context.branch if invocation_context else None, @@ -299,7 +301,7 @@ def convert_a2a_message_to_event( invocation_id=( invocation_context.invocation_id if invocation_context - else str(uuid.uuid4()) + else platform_uuid.new_uuid() ), author=author or "a2a agent", branch=invocation_context.branch if invocation_context else None, @@ -349,7 +351,7 @@ def convert_a2a_message_to_event( invocation_id=( invocation_context.invocation_id if invocation_context - else str(uuid.uuid4()) + else platform_uuid.new_uuid() ), author=author or "a2a agent", branch=invocation_context.branch if invocation_context else None, @@ -408,7 +410,7 @@ def convert_event_to_a2a_message( if output_parts: return Message( - message_id=str(uuid.uuid4()), role=role, parts=output_parts + message_id=platform_uuid.new_uuid(), role=role, parts=output_parts ) except Exception as e: @@ -449,7 +451,7 @@ def _create_error_status_event( status=TaskStatus( state=TaskState.failed, message=Message( - message_id=str(uuid.uuid4()), + message_id=platform_uuid.new_uuid(), role=Role.agent, parts=[TextPart(text=error_message)], metadata={ @@ -458,7 +460,7 @@ def _create_error_status_event( if event.error_code else {}, ), - timestamp=datetime.now(timezone.utc).isoformat(), + timestamp=datetime.fromtimestamp(platform_time.get_time(), tz=timezone.utc).isoformat(), ), final=False, ) @@ -486,7 +488,7 @@ def _create_status_update_event( status = TaskStatus( state=TaskState.working, message=message, - timestamp=datetime.now(timezone.utc).isoformat(), + timestamp=datetime.fromtimestamp(platform_time.get_time(), tz=timezone.utc).isoformat(), ) if any( diff --git a/src/google/adk/a2a/executor/a2a_agent_executor.py b/src/google/adk/a2a/executor/a2a_agent_executor.py index 956b1233a0..c25d58b698 100644 --- a/src/google/adk/a2a/executor/a2a_agent_executor.py +++ b/src/google/adk/a2a/executor/a2a_agent_executor.py @@ -21,7 +21,9 @@ from typing import Awaitable from typing import Callable from typing import Optional -import uuid + +from google.adk.platform import time as platform_time +from google.adk.platform import uuid as platform_uuid from a2a.server.agent_execution import AgentExecutor from a2a.server.agent_execution.context import RequestContext @@ -154,7 +156,7 @@ async def execute( status=TaskStatus( state=TaskState.submitted, message=context.message, - timestamp=datetime.now(timezone.utc).isoformat(), + timestamp=datetime.fromtimestamp(platform_time.get_time(), tz=timezone.utc).isoformat(), ), context_id=context.context_id, final=False, @@ -173,9 +175,9 @@ async def execute( task_id=context.task_id, status=TaskStatus( state=TaskState.failed, - timestamp=datetime.now(timezone.utc).isoformat(), + timestamp=datetime.fromtimestamp(platform_time.get_time(), tz=timezone.utc).isoformat(), message=Message( - message_id=str(uuid.uuid4()), + message_id=platform_uuid.new_uuid(), role=Role.agent, parts=[TextPart(text=str(e))], ), @@ -226,7 +228,7 @@ async def _handle_request( task_id=context.task_id, status=TaskStatus( state=TaskState.working, - timestamp=datetime.now(timezone.utc).isoformat(), + timestamp=datetime.fromtimestamp(platform_time.get_time(), tz=timezone.utc).isoformat(), ), context_id=context.context_id, final=False, @@ -274,7 +276,7 @@ async def _handle_request( last_chunk=True, context_id=context.context_id, artifact=Artifact( - artifact_id=str(uuid.uuid4()), + artifact_id=platform_uuid.new_uuid(), parts=task_result_aggregator.task_status_message.parts, ), ) @@ -284,7 +286,7 @@ async def _handle_request( task_id=context.task_id, status=TaskStatus( state=TaskState.completed, - timestamp=datetime.now(timezone.utc).isoformat(), + timestamp=datetime.fromtimestamp(platform_time.get_time(), tz=timezone.utc).isoformat(), ), context_id=context.context_id, final=True, @@ -294,7 +296,7 @@ async def _handle_request( task_id=context.task_id, status=TaskStatus( state=task_result_aggregator.task_state, - timestamp=datetime.now(timezone.utc).isoformat(), + timestamp=datetime.fromtimestamp(platform_time.get_time(), tz=timezone.utc).isoformat(), message=task_result_aggregator.task_status_message, ), context_id=context.context_id, diff --git a/src/google/adk/agents/invocation_context.py b/src/google/adk/agents/invocation_context.py index 35b8dc9769..6b4a9a2b68 100644 --- a/src/google/adk/agents/invocation_context.py +++ b/src/google/adk/agents/invocation_context.py @@ -16,7 +16,8 @@ from typing import Any from typing import Optional -import uuid + +from google.adk.platform import uuid as platform_uuid from google.genai import types from pydantic import BaseModel @@ -413,4 +414,4 @@ def _find_matching_function_call( def new_invocation_context_id() -> str: - return "e-" + str(uuid.uuid4()) + return "e-" + platform_uuid.new_uuid() diff --git a/src/google/adk/agents/remote_a2a_agent.py b/src/google/adk/agents/remote_a2a_agent.py index 5ffd123f42..34f6e03252 100644 --- a/src/google/adk/agents/remote_a2a_agent.py +++ b/src/google/adk/agents/remote_a2a_agent.py @@ -24,7 +24,8 @@ from typing import Optional from typing import Union from urllib.parse import urlparse -import uuid + +from google.adk.platform import uuid as platform_uuid from a2a.client import Client as A2AClient from a2a.client import ClientEvent as A2AClientEvent @@ -557,7 +558,7 @@ async def _run_async_impl( return a2a_request = A2AMessage( - message_id=str(uuid.uuid4()), + message_id=platform_uuid.new_uuid(), parts=message_parts, role="user", context_id=context_id, diff --git a/src/google/adk/artifacts/base_artifact_service.py b/src/google/adk/artifacts/base_artifact_service.py index 1a265f8ad9..875f5b9d66 100644 --- a/src/google/adk/artifacts/base_artifact_service.py +++ b/src/google/adk/artifacts/base_artifact_service.py @@ -15,10 +15,11 @@ from abc import ABC from abc import abstractmethod -from datetime import datetime from typing import Any from typing import Optional +from google.adk.platform import time as platform_time + from google.genai import types from pydantic import alias_generators from pydantic import BaseModel @@ -47,7 +48,7 @@ class ArtifactVersion(BaseModel): description="Optional user-supplied metadata stored with the artifact.", ) create_time: float = Field( - default_factory=lambda: datetime.now().timestamp(), + default_factory=lambda: platform_time.get_time(), description=( "Unix timestamp (seconds) when the version record was created." ), diff --git a/src/google/adk/events/event.py b/src/google/adk/events/event.py index 2c6a6cd66c..a512323fe4 100644 --- a/src/google/adk/events/event.py +++ b/src/google/adk/events/event.py @@ -14,10 +14,10 @@ from __future__ import annotations -from datetime import datetime from typing import Optional -import uuid +from google.adk.platform import time as platform_time +from google.adk.platform import uuid as platform_uuid from google.genai import types from pydantic import alias_generators from pydantic import ConfigDict @@ -70,7 +70,7 @@ class Event(LlmResponse): # Do not assign the ID. It will be assigned by the session. id: str = '' """The unique identifier of the event.""" - timestamp: float = Field(default_factory=lambda: datetime.now().timestamp()) + timestamp: float = Field(default_factory=lambda: platform_time.get_time()) """The timestamp of the event.""" def model_post_init(self, __context): @@ -125,4 +125,4 @@ def has_trailing_code_execution_result( @staticmethod def new_id(): - return str(uuid.uuid4()) + return platform_uuid.new_uuid() diff --git a/src/google/adk/flows/llm_flows/_code_execution.py b/src/google/adk/flows/llm_flows/_code_execution.py index 8c3edfc95b..fc5f755d16 100644 --- a/src/google/adk/flows/llm_flows/_code_execution.py +++ b/src/google/adk/flows/llm_flows/_code_execution.py @@ -20,6 +20,8 @@ import copy import dataclasses import datetime + +from google.adk.platform import time as platform_time import logging import os import re @@ -288,7 +290,7 @@ async def _run_post_processor( if part.inline_data.display_name: file_name = part.inline_data.display_name else: - now = datetime.datetime.now().astimezone() + now = datetime.datetime.fromtimestamp(platform_time.get_time()).astimezone() timestamp = now.strftime('%Y%m%d_%H%M%S') file_extension = part.inline_data.mime_type.split('/')[-1] file_name = f'{timestamp}.{file_extension}' diff --git a/src/google/adk/flows/llm_flows/audio_cache_manager.py b/src/google/adk/flows/llm_flows/audio_cache_manager.py index e7e276089f..3817146321 100644 --- a/src/google/adk/flows/llm_flows/audio_cache_manager.py +++ b/src/google/adk/flows/llm_flows/audio_cache_manager.py @@ -15,7 +15,8 @@ from __future__ import annotations import logging -import time + +from google.adk.platform import time as platform_time from typing import TYPE_CHECKING from google.genai import types @@ -70,7 +71,7 @@ def cache_audio( raise ValueError("cache_type must be either 'input' or 'output'") audio_entry = RealtimeCacheEntry( - role=role, data=audio_blob, timestamp=time.time() + role=role, data=audio_blob, timestamp=platform_time.get_time() ) cache.append(audio_entry) diff --git a/src/google/adk/flows/llm_flows/base_llm_flow.py b/src/google/adk/flows/llm_flows/base_llm_flow.py index 5368ca93cc..af839cd9f8 100644 --- a/src/google/adk/flows/llm_flows/base_llm_flow.py +++ b/src/google/adk/flows/llm_flows/base_llm_flow.py @@ -16,7 +16,8 @@ from abc import ABC import asyncio -import datetime + +from google.adk.platform import time as platform_time import inspect import logging from typing import AsyncGenerator @@ -845,7 +846,7 @@ async def _run_one_step_async( async for event in agen: # Update the mutable event id to avoid conflict model_response_event.id = Event.new_id() - model_response_event.timestamp = datetime.datetime.now().timestamp() + model_response_event.timestamp = platform_time.get_time() yield event async def _preprocess_async( diff --git a/src/google/adk/flows/llm_flows/functions.py b/src/google/adk/flows/llm_flows/functions.py index 66274d3dd1..612138347c 100644 --- a/src/google/adk/flows/llm_flows/functions.py +++ b/src/google/adk/flows/llm_flows/functions.py @@ -31,8 +31,8 @@ from typing import Dict from typing import Optional from typing import TYPE_CHECKING -import uuid +from google.adk.platform import uuid as platform_uuid from google.adk.tools.computer_use.computer_use_tool import ComputerUseTool from google.genai import types @@ -178,7 +178,7 @@ def run_async_tool_in_new_loop(): def generate_client_function_call_id() -> str: - return f'{AF_FUNCTION_CALL_ID_PREFIX}{uuid.uuid4()}' + return f'{AF_FUNCTION_CALL_ID_PREFIX}{platform_uuid.new_uuid()}' def populate_client_function_call_id(model_response_event: Event) -> None: diff --git a/src/google/adk/flows/llm_flows/transcription_manager.py b/src/google/adk/flows/llm_flows/transcription_manager.py index 30ad48edb6..6fe467239e 100644 --- a/src/google/adk/flows/llm_flows/transcription_manager.py +++ b/src/google/adk/flows/llm_flows/transcription_manager.py @@ -15,7 +15,8 @@ from __future__ import annotations import logging -import time + +from google.adk.platform import time as platform_time from typing import TYPE_CHECKING from google.genai import types @@ -89,7 +90,7 @@ async def _create_and_save_transcription_event( author=author, input_transcription=transcription if is_input else None, output_transcription=transcription if not is_input else None, - timestamp=time.time(), + timestamp=platform_time.get_time(), ) # Save transcription event to session diff --git a/src/google/adk/platform/time.py b/src/google/adk/platform/time.py new file mode 100644 index 0000000000..cfba7d4ab5 --- /dev/null +++ b/src/google/adk/platform/time.py @@ -0,0 +1,44 @@ +# Copyright 2026 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""Platform module for abstracting system time generation.""" + +from contextvars import ContextVar +import time +from typing import Callable + +_default_time_provider: Callable[[], float] = time.time +_time_provider_context_var: ContextVar[Callable[[], float]] = ( + ContextVar("time_provider", default=_default_time_provider) +) + + +def set_time_provider(provider: Callable[[], float]) -> None: + """Sets the provider for the current time. + + Args: + provider: A callable that returns the current time in seconds since the + epoch. + """ + _time_provider_context_var.set(provider) + + +def reset_time_provider() -> None: + """Resets the time provider to its default implementation.""" + _time_provider_context_var.set(_default_time_provider) + + +def get_time() -> float: + """Returns the current time in seconds since the epoch.""" + return _time_provider_context_var.get()() diff --git a/src/google/adk/platform/uuid.py b/src/google/adk/platform/uuid.py new file mode 100644 index 0000000000..a2df9ad990 --- /dev/null +++ b/src/google/adk/platform/uuid.py @@ -0,0 +1,43 @@ +# Copyright 2026 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""Platform module for abstracting unique ID generation.""" + +from contextvars import ContextVar +import uuid +from typing import Callable + +_default_id_provider: Callable[[], str] = lambda: str(uuid.uuid4()) +_id_provider_context_var: ContextVar[Callable[[], str]] = ( + ContextVar("id_provider", default=_default_id_provider) +) + + +def set_id_provider(provider: Callable[[], str]) -> None: + """Sets the provider for generating unique IDs. + + Args: + provider: A callable that returns a unique ID string. + """ + _id_provider_context_var.set(provider) + + +def reset_id_provider() -> None: + """Resets the ID provider to its default implementation.""" + _id_provider_context_var.set(_default_id_provider) + + +def new_uuid() -> str: + """Returns a new unique ID.""" + return _id_provider_context_var.get()() diff --git a/src/google/adk/sessions/database_session_service.py b/src/google/adk/sessions/database_session_service.py index 24f525bae0..2ad5d44094 100644 --- a/src/google/adk/sessions/database_session_service.py +++ b/src/google/adk/sessions/database_session_service.py @@ -18,6 +18,8 @@ import copy from datetime import datetime from datetime import timezone + +from google.adk.platform import time as platform_time import logging from typing import Any from typing import AsyncIterator @@ -362,7 +364,7 @@ async def create_session( storage_user_state.state = storage_user_state.state | user_state_delta # Store the session - now = datetime.now(timezone.utc) + 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: diff --git a/src/google/adk/sessions/in_memory_session_service.py b/src/google/adk/sessions/in_memory_session_service.py index d782072d6a..720b02b18b 100644 --- a/src/google/adk/sessions/in_memory_session_service.py +++ b/src/google/adk/sessions/in_memory_session_service.py @@ -15,13 +15,13 @@ import copy import logging -import time from typing import Any from typing import Optional -import uuid from typing_extensions import override +from google.adk.platform import time as platform_time +from google.adk.platform import uuid as platform_uuid from . import _session_util from ..errors.already_exists_error import AlreadyExistsError from ..events.event import Event @@ -108,14 +108,14 @@ def _create_session_impl( session_id = ( session_id.strip() if session_id and session_id.strip() - else str(uuid.uuid4()) + else platform_uuid.new_uuid() ) session = Session( app_name=app_name, user_id=user_id, id=session_id, state=session_state or {}, - last_update_time=time.time(), + last_update_time=platform_time.get_time(), ) if app_name not in self.sessions: diff --git a/src/google/adk/sessions/schemas/v0.py b/src/google/adk/sessions/schemas/v0.py index a5f00c1b5e..97ba09cad1 100644 --- a/src/google/adk/sessions/schemas/v0.py +++ b/src/google/adk/sessions/schemas/v0.py @@ -32,7 +32,8 @@ import pickle from typing import Any from typing import Optional -import uuid + +from google.adk.platform import uuid as platform_uuid from google.genai import types from sqlalchemy import Boolean @@ -109,7 +110,7 @@ class StorageSession(Base): id: Mapped[str] = mapped_column( String(DEFAULT_MAX_KEY_LENGTH), primary_key=True, - default=lambda: str(uuid.uuid4()), + default=platform_uuid.new_uuid, ) state: Mapped[MutableDict[str, Any]] = mapped_column( diff --git a/src/google/adk/sessions/schemas/v1.py b/src/google/adk/sessions/schemas/v1.py index ae3b2cb6ad..f7668ba35c 100644 --- a/src/google/adk/sessions/schemas/v1.py +++ b/src/google/adk/sessions/schemas/v1.py @@ -26,7 +26,8 @@ from datetime import datetime from datetime import timezone from typing import Any -import uuid + +from google.adk.platform import uuid as platform_uuid from sqlalchemy import ForeignKeyConstraint from sqlalchemy import func @@ -81,7 +82,7 @@ class StorageSession(Base): id: Mapped[str] = mapped_column( String(DEFAULT_MAX_KEY_LENGTH), primary_key=True, - default=lambda: str(uuid.uuid4()), + default=platform_uuid.new_uuid, ) state: Mapped[MutableDict[str, Any]] = mapped_column( diff --git a/src/google/adk/sessions/sqlite_session_service.py b/src/google/adk/sessions/sqlite_session_service.py index d23c8278cf..a3b4f5ee2b 100644 --- a/src/google/adk/sessions/sqlite_session_service.py +++ b/src/google/adk/sessions/sqlite_session_service.py @@ -19,16 +19,17 @@ import logging import os import sqlite3 -import time from typing import Any from typing import Optional from urllib.parse import unquote from urllib.parse import urlparse -import uuid import aiosqlite from typing_extensions import override +from google.adk.platform import time as platform_time +from google.adk.platform import uuid as platform_uuid + from . import _session_util from ..errors.already_exists_error import AlreadyExistsError from ..events.event import Event @@ -165,8 +166,8 @@ async def create_session( if session_id: session_id = session_id.strip() if not session_id: - session_id = str(uuid.uuid4()) - now = time.time() + session_id = platform_uuid.new_uuid() + now = platform_time.get_time() async with self._get_db_connection() as db: # Check if session_id already exists diff --git a/tests/unittests/a2a/converters/test_event_converter.py b/tests/unittests/a2a/converters/test_event_converter.py index 47f0bbf435..61f8c3aca6 100644 --- a/tests/unittests/a2a/converters/test_event_converter.py +++ b/tests/unittests/a2a/converters/test_event_converter.py @@ -462,7 +462,7 @@ def test_create_status_update_event_with_auth_required_state(self): with patch( "google.adk.a2a.converters.event_converter.datetime" ) as mock_datetime: - mock_datetime.now.return_value.isoformat.return_value = ( + mock_datetime.fromtimestamp.return_value.isoformat.return_value = ( "2023-01-01T00:00:00" ) @@ -526,7 +526,7 @@ def test_create_status_update_event_with_input_required_state(self): with patch( "google.adk.a2a.converters.event_converter.datetime" ) as mock_datetime: - mock_datetime.now.return_value.isoformat.return_value = ( + mock_datetime.fromtimestamp.return_value.isoformat.return_value = ( "2023-01-01T00:00:00" ) @@ -742,7 +742,7 @@ def test_convert_a2a_task_to_event_no_message(self): assert result.branch == "test-branch" assert result.invocation_id == "test-invocation-id" - @patch("google.adk.a2a.converters.event_converter.uuid.uuid4") + @patch("google.adk.a2a.converters.event_converter.platform_uuid.new_uuid") def test_convert_a2a_task_to_event_default_author(self, mock_uuid): """Test converting A2A task with default author and no invocation context.""" from google.adk.a2a.converters.event_converter import convert_a2a_task_to_event @@ -974,7 +974,7 @@ def test_convert_a2a_message_to_event_missing_tool_id(self): # Parts will be empty since conversion returned None assert len(result.content.parts) == 0 - @patch("google.adk.a2a.converters.event_converter.uuid.uuid4") + @patch("google.adk.a2a.converters.event_converter.platform_uuid.new_uuid") def test_convert_a2a_message_to_event_default_author(self, mock_uuid): """Test conversion with default author and no invocation context.""" from google.adk.a2a.converters.event_converter import convert_a2a_message_to_event diff --git a/tests/unittests/artifacts/test_artifact_service.py b/tests/unittests/artifacts/test_artifact_service.py index ec74f8abe3..f9790e9257 100644 --- a/tests/unittests/artifacts/test_artifact_service.py +++ b/tests/unittests/artifacts/test_artifact_service.py @@ -418,9 +418,9 @@ async def test_list_artifact_versions_and_get_artifact_version( ] with patch( - "google.adk.artifacts.base_artifact_service.datetime" - ) as mock_datetime: - mock_datetime.now.return_value = FIXED_DATETIME + "google.adk.artifacts.base_artifact_service.platform_time" + ) as mock_platform_time: + mock_platform_time.get_time.return_value = FIXED_DATETIME.timestamp() for i in range(4): custom_metadata = {"key": "value" + str(i)} @@ -505,9 +505,9 @@ async def test_list_artifact_versions_with_user_prefix( ] with patch( - "google.adk.artifacts.base_artifact_service.datetime" - ) as mock_datetime: - mock_datetime.now.return_value = FIXED_DATETIME + "google.adk.artifacts.base_artifact_service.platform_time" + ) as mock_platform_time: + mock_platform_time.get_time.return_value = FIXED_DATETIME.timestamp() for i in range(4): custom_metadata = {"key": "value" + str(i)} diff --git a/tests/unittests/flows/llm_flows/test_code_execution.py b/tests/unittests/flows/llm_flows/test_code_execution.py index de13a69679..f26c27af5f 100644 --- a/tests/unittests/flows/llm_flows/test_code_execution.py +++ b/tests/unittests/flows/llm_flows/test_code_execution.py @@ -36,7 +36,7 @@ async def test_builtin_code_executor_image_artifact_creation(mock_datetime): """Test BuiltInCodeExecutor creates artifacts for images in response.""" mock_now = datetime.datetime(2025, 1, 1, 12, 0, 0) - mock_datetime.datetime.now.return_value.astimezone.return_value = mock_now + mock_datetime.datetime.fromtimestamp.return_value.astimezone.return_value = mock_now code_executor = BuiltInCodeExecutor() agent = Agent(name='test_agent', code_executor=code_executor) invocation_context = await testing_utils.create_invocation_context( diff --git a/tests/unittests/platform/__init__.py b/tests/unittests/platform/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/unittests/platform/test_time.py b/tests/unittests/platform/test_time.py new file mode 100644 index 0000000000..badff75f2a --- /dev/null +++ b/tests/unittests/platform/test_time.py @@ -0,0 +1,40 @@ +# Copyright 2026 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""Unit tests for the platform time module.""" + +import time +import unittest + +from google.adk.platform import time as platform_time + + +class TestTime(unittest.TestCase): + + def tearDown(self): + # Reset provider to default after each test + platform_time.reset_time_provider() + + def test_default_time_provider(self): + # Verify it returns a float that is close to now + now = time.time() + rt_time = platform_time.get_time() + self.assertIsInstance(rt_time, float) + self.assertAlmostEqual(rt_time, now, delta=1.0) + + def test_custom_time_provider(self): + # Test override + mock_time = 123456789.0 + platform_time.set_time_provider(lambda: mock_time) + self.assertEqual(platform_time.get_time(), mock_time) diff --git a/tests/unittests/platform/test_uuid.py b/tests/unittests/platform/test_uuid.py new file mode 100644 index 0000000000..afc8e378a7 --- /dev/null +++ b/tests/unittests/platform/test_uuid.py @@ -0,0 +1,40 @@ +# Copyright 2026 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""Unit tests for the platform uuid module.""" + +import uuid +import unittest + +from google.adk.platform import uuid as platform_uuid + + +class TestUUID(unittest.TestCase): + + def tearDown(self): + # Reset provider to default after each test + platform_uuid.reset_id_provider() + + def test_default_id_provider(self): + # Verify it returns a string uuid + uid = platform_uuid.new_uuid() + self.assertIsInstance(uid, str) + # Should be parseable as uuid + uuid.UUID(uid) + + def test_custom_id_provider(self): + # Test override + mock_id = "test-id-123" + platform_uuid.set_id_provider(lambda: mock_id) + self.assertEqual(platform_uuid.new_uuid(), mock_id) diff --git a/tests/unittests/sessions/test_session_service.py b/tests/unittests/sessions/test_session_service.py index 25530bed89..e3399116c5 100644 --- a/tests/unittests/sessions/test_session_service.py +++ b/tests/unittests/sessions/test_session_service.py @@ -544,6 +544,7 @@ async def test_append_event_complete(session_service): ), citation_metadata=types.CitationMetadata(), custom_metadata={'custom_key': 'custom_value'}, + timestamp=1700000000.123, input_transcription=types.Transcription( text='input transcription', finished=True,