diff --git a/contributing/samples/gepa/experiment.py b/contributing/samples/gepa/experiment.py index f3751206a8..2710c3894c 100644 --- a/contributing/samples/gepa/experiment.py +++ b/contributing/samples/gepa/experiment.py @@ -43,7 +43,6 @@ from tau_bench.types import EnvRunResult from tau_bench.types import RunConfig import tau_bench_agent as tau_bench_agent_lib - import utils diff --git a/contributing/samples/gepa/run_experiment.py b/contributing/samples/gepa/run_experiment.py index d857da9635..e31db15788 100644 --- a/contributing/samples/gepa/run_experiment.py +++ b/contributing/samples/gepa/run_experiment.py @@ -25,7 +25,6 @@ from absl import flags import experiment from google.genai import types - import utils _OUTPUT_DIR = flags.DEFINE_string( diff --git a/src/google/adk/a2a/converters/event_converter.py b/src/google/adk/a2a/converters/event_converter.py index a2a0ee75d3..e6a890941f 100644 --- a/src/google/adk/a2a/converters/event_converter.py +++ b/src/google/adk/a2a/converters/event_converter.py @@ -22,7 +22,6 @@ from typing import Dict from typing import List from typing import Optional -import uuid from a2a.server.events import Event as A2AEvent from a2a.types import DataPart @@ -34,6 +33,8 @@ from a2a.types import TaskStatus from a2a.types import TaskStatusUpdateEvent from a2a.types import TextPart +from google.adk.platform import time as platform_time +from google.adk.platform import uuid as platform_uuid from google.genai import types as genai_types from ...agents.invocation_context import InvocationContext @@ -254,7 +255,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 +300,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 +350,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, @@ -406,7 +407,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: @@ -447,7 +448,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={ @@ -456,7 +457,9 @@ 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, ) @@ -484,7 +487,9 @@ 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 da28955a71..f2f9852ab6 100644 --- a/src/google/adk/a2a/executor/a2a_agent_executor.py +++ b/src/google/adk/a2a/executor/a2a_agent_executor.py @@ -21,7 +21,6 @@ from typing import Awaitable from typing import Callable from typing import Optional -import uuid from a2a.server.agent_execution import AgentExecutor from a2a.server.agent_execution.context import RequestContext @@ -34,6 +33,8 @@ from a2a.types import TaskStatus from a2a.types import TaskStatusUpdateEvent from a2a.types import TextPart +from google.adk.platform import time as platform_time +from google.adk.platform import uuid as platform_uuid from google.adk.runners import Runner from typing_extensions import override @@ -147,7 +148,9 @@ 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, @@ -166,9 +169,11 @@ 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))], ), @@ -219,7 +224,9 @@ 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, @@ -267,7 +274,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, ), ) @@ -277,7 +284,9 @@ 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, @@ -287,7 +296,9 @@ 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..6e116d2beb 100644 --- a/src/google/adk/agents/invocation_context.py +++ b/src/google/adk/agents/invocation_context.py @@ -15,9 +15,10 @@ from __future__ import annotations from typing import Any +from typing import cast from typing import Optional -import uuid +from google.adk.platform import uuid as platform_uuid from google.genai import types from pydantic import BaseModel from pydantic import ConfigDict @@ -413,4 +414,4 @@ def _find_matching_function_call( def new_invocation_context_id() -> str: - return "e-" + str(uuid.uuid4()) + return "e-" + str(cast(str, 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 9b3a7b2244..2e3d2495cb 100644 --- a/src/google/adk/agents/remote_a2a_agent.py +++ b/src/google/adk/agents/remote_a2a_agent.py @@ -24,7 +24,6 @@ from typing import Optional from typing import Union from urllib.parse import urlparse -import uuid from a2a.client import Client as A2AClient from a2a.client import ClientEvent as A2AClientEvent @@ -43,6 +42,7 @@ from a2a.types import TaskState from a2a.types import TaskStatusUpdateEvent as A2ATaskStatusUpdateEvent from a2a.types import TransportProtocol as A2ATransport +from google.adk.platform import uuid as platform_uuid from google.genai import types as genai_types import httpx from pydantic import BaseModel @@ -629,7 +629,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 23f5e44f5b..0ccb8d6c77 100644 --- a/src/google/adk/artifacts/base_artifact_service.py +++ b/src/google/adk/artifacts/base_artifact_service.py @@ -21,6 +21,7 @@ from typing import Optional from typing import Union +from google.adk.platform import time as platform_time from google.genai import types from pydantic import alias_generators from pydantic import BaseModel @@ -51,7 +52,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..19ec4cc219 100644 --- a/src/google/adk/flows/llm_flows/_code_execution.py +++ b/src/google/adk/flows/llm_flows/_code_execution.py @@ -27,6 +27,7 @@ from typing import Optional from typing import TYPE_CHECKING +from google.adk.platform import time as platform_time from google.genai import types from typing_extensions import override @@ -288,7 +289,9 @@ 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..4556a72cee 100644 --- a/src/google/adk/flows/llm_flows/audio_cache_manager.py +++ b/src/google/adk/flows/llm_flows/audio_cache_manager.py @@ -15,9 +15,9 @@ from __future__ import annotations import logging -import time from typing import TYPE_CHECKING +from google.adk.platform import time as platform_time from google.genai import types from ...agents.invocation_context import RealtimeCacheEntry @@ -70,7 +70,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..5203ea4fe8 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,6 @@ from abc import ABC import asyncio -import datetime import inspect import logging from typing import AsyncGenerator @@ -24,6 +23,7 @@ from typing import Optional from typing import TYPE_CHECKING +from google.adk.platform import time as platform_time from google.genai import types from websockets.exceptions import ConnectionClosed from websockets.exceptions import ConnectionClosedOK @@ -845,7 +845,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..f0ef0e6a47 100644 --- a/src/google/adk/flows/llm_flows/transcription_manager.py +++ b/src/google/adk/flows/llm_flows/transcription_manager.py @@ -15,9 +15,9 @@ from __future__ import annotations import logging -import time from typing import TYPE_CHECKING +from google.adk.platform import time as platform_time from google.genai import types from ...events.event import Event @@ -89,7 +89,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..04ca151706 --- /dev/null +++ b/src/google/adk/platform/time.py @@ -0,0 +1,46 @@ +# 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 __future__ import annotations + +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..ccf3952846 --- /dev/null +++ b/src/google/adk/platform/uuid.py @@ -0,0 +1,45 @@ +# 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 __future__ import annotations + +from contextvars import ContextVar +from typing import Callable +import uuid + +_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 321a5cc61d..eed1d9eae6 100644 --- a/src/google/adk/sessions/database_session_service.py +++ b/src/google/adk/sessions/database_session_service.py @@ -25,6 +25,7 @@ from typing import TypeAlias from typing import TypeVar +from google.adk.platform import time as platform_time from sqlalchemy import delete from sqlalchemy import event from sqlalchemy import select @@ -362,7 +363,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..e0f9b49ff3 100644 --- a/src/google/adk/sessions/in_memory_session_service.py +++ b/src/google/adk/sessions/in_memory_session_service.py @@ -15,11 +15,11 @@ import copy import logging -import time from typing import Any 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 typing_extensions import override from . import _session_util @@ -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..a95d0b9de5 100644 --- a/src/google/adk/sessions/schemas/v0.py +++ b/src/google/adk/sessions/schemas/v0.py @@ -32,8 +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 from sqlalchemy import ForeignKeyConstraint @@ -109,7 +109,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..7ee8c811fd 100644 --- a/src/google/adk/sessions/schemas/v1.py +++ b/src/google/adk/sessions/schemas/v1.py @@ -26,8 +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 from sqlalchemy import inspect @@ -81,7 +81,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 600f89c4b9..3ad84e9d1a 100644 --- a/src/google/adk/sessions/sqlite_session_service.py +++ b/src/google/adk/sessions/sqlite_session_service.py @@ -19,14 +19,14 @@ 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 google.adk.platform import time as platform_time +from google.adk.platform import uuid as platform_uuid from typing_extensions import override from . import _session_util @@ -165,8 +165,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 f3e7380b6c..25294d4909 100644 --- a/tests/unittests/artifacts/test_artifact_service.py +++ b/tests/unittests/artifacts/test_artifact_service.py @@ -419,9 +419,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)} @@ -506,9 +506,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..69f2d7832d 100644 --- a/tests/unittests/flows/llm_flows/test_code_execution.py +++ b/tests/unittests/flows/llm_flows/test_code_execution.py @@ -36,7 +36,9 @@ 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..4706c38aed --- /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..f20b350f23 --- /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 unittest +import uuid + +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 5c5aa83e7d..9686596b93 100644 --- a/tests/unittests/sessions/test_session_service.py +++ b/tests/unittests/sessions/test_session_service.py @@ -569,6 +569,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,