From 62930b2985172554e17c2d323e8cc680fa52108b Mon Sep 17 00:00:00 2001 From: Andrew Hoblitzell Date: Sat, 16 Aug 2025 09:44:38 -0400 Subject: [PATCH 1/8] refactor: optimize logging performance and modernize string formatting MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Replace f-strings with % formatting in logging calls for lazy evaluation - Convert .format() to f-strings in server/models.py __repr__ methods - Add test_venv/ to .gitignore - Use logger.exception() for better error debugging with full tracebacks - Improves performance by avoiding string formatting when log levels are disabled 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude --- .gitignore | 1 + src/a2a/server/events/event_consumer.py | 4 +-- src/a2a/server/events/event_queue.py | 6 ++-- src/a2a/server/models.py | 36 +++++++-------------- src/a2a/server/tasks/database_task_store.py | 10 +++--- src/a2a/utils/error_handlers.py | 4 +-- src/a2a/utils/helpers.py | 6 ++-- src/a2a/utils/telemetry.py | 14 +++++--- tests/utils/test_telemetry.py | 5 ++- 9 files changed, 41 insertions(+), 45 deletions(-) diff --git a/.gitignore b/.gitignore index 6252577e7..bd3c1ca83 100644 --- a/.gitignore +++ b/.gitignore @@ -6,6 +6,7 @@ __pycache__ .pytest_cache .ruff_cache .venv +test_venv/ coverage.xml .nox spec.json \ No newline at end of file diff --git a/src/a2a/server/events/event_consumer.py b/src/a2a/server/events/event_consumer.py index 23ab9487e..db811f3a8 100644 --- a/src/a2a/server/events/event_consumer.py +++ b/src/a2a/server/events/event_consumer.py @@ -62,7 +62,7 @@ async def consume_one(self) -> Event: InternalError(message='Agent did not return any response') ) from e - logger.debug(f'Dequeued event of type: {type(event)} in consume_one.') + logger.debug('Dequeued event of type: %s in consume_one.', type(event)) self.queue.task_done() @@ -95,7 +95,7 @@ async def consume_all(self) -> AsyncGenerator[Event]: self.queue.dequeue_event(), timeout=self._timeout ) logger.debug( - f'Dequeued event of type: {type(event)} in consume_all.' + 'Dequeued event of type: %s in consume_all.', type(event) ) self.queue.task_done() logger.debug( diff --git a/src/a2a/server/events/event_queue.py b/src/a2a/server/events/event_queue.py index 6bf9650c7..5ac22a07f 100644 --- a/src/a2a/server/events/event_queue.py +++ b/src/a2a/server/events/event_queue.py @@ -54,7 +54,7 @@ async def enqueue_event(self, event: Event) -> None: logger.warning('Queue is closed. Event will not be enqueued.') return - logger.debug(f'Enqueuing event of type: {type(event)}') + logger.debug('Enqueuing event of type: %s', type(event)) # Make sure to use put instead of put_nowait to avoid blocking the event loop. await self.queue.put(event) @@ -98,13 +98,13 @@ async def dequeue_event(self, no_wait: bool = False) -> Event: logger.debug('Attempting to dequeue event (no_wait=True).') event = self.queue.get_nowait() logger.debug( - f'Dequeued event (no_wait=True) of type: {type(event)}' + 'Dequeued event (no_wait=True) of type: %s', type(event) ) return event logger.debug('Attempting to dequeue event (waiting).') event = await self.queue.get() - logger.debug(f'Dequeued event (waited) of type: {type(event)}') + logger.debug('Dequeued event (waited) of type: %s', type(event)) return event def task_done(self) -> None: diff --git a/src/a2a/server/models.py b/src/a2a/server/models.py index a094005d4..c677fa8c0 100644 --- a/src/a2a/server/models.py +++ b/src/a2a/server/models.py @@ -147,14 +147,9 @@ def task_metadata(cls) -> Mapped[dict[str, Any] | None]: @override def __repr__(self) -> str: """Return a string representation of the task.""" - repr_template = ( - '<{CLS}(id="{ID}", context_id="{CTX_ID}", status="{STATUS}")>' - ) - return repr_template.format( - CLS=self.__class__.__name__, - ID=self.id, - CTX_ID=self.context_id, - STATUS=self.status, + return ( + f'<{self.__class__.__name__}(id="{self.id}", ' + f'context_id="{self.context_id}", status="{self.status}")>' ) @@ -188,12 +183,9 @@ class TaskModel(TaskMixin, base): # type: ignore @override def __repr__(self) -> str: """Return a string representation of the task.""" - repr_template = '' - return repr_template.format( - TABLE=table_name, - ID=self.id, - CTX_ID=self.context_id, - STATUS=self.status, + return ( + f'' ) # Set a dynamic name for better debugging @@ -221,11 +213,9 @@ class PushNotificationConfigMixin: @override def __repr__(self) -> str: """Return a string representation of the push notification config.""" - repr_template = '<{CLS}(task_id="{TID}", config_id="{CID}")>' - return repr_template.format( - CLS=self.__class__.__name__, - TID=self.task_id, - CID=self.config_id, + return ( + f'<{self.__class__.__name__}(task_id="{self.task_id}", ' + f'config_id="{self.config_id}")>' ) @@ -241,11 +231,9 @@ class PushNotificationConfigModel(PushNotificationConfigMixin, base): # type: i @override def __repr__(self) -> str: """Return a string representation of the push notification config.""" - repr_template = '' - return repr_template.format( - TABLE=table_name, - TID=self.task_id, - CID=self.config_id, + return ( + f'' ) PushNotificationConfigModel.__name__ = ( diff --git a/src/a2a/server/tasks/database_task_store.py b/src/a2a/server/tasks/database_task_store.py index cff94c1e7..1fd479b3a 100644 --- a/src/a2a/server/tasks/database_task_store.py +++ b/src/a2a/server/tasks/database_task_store.py @@ -124,7 +124,7 @@ async def save(self, task: Task) -> None: db_task = self._to_orm(task) async with self.async_session_maker.begin() as session: await session.merge(db_task) - logger.debug(f'Task {task.id} saved/updated successfully.') + logger.debug('Task %s saved/updated successfully.', task.id) async def get(self, task_id: str) -> Task | None: """Retrieves a task from the database by ID.""" @@ -135,10 +135,10 @@ async def get(self, task_id: str) -> Task | None: task_model = result.scalar_one_or_none() if task_model: task = self._from_orm(task_model) - logger.debug(f'Task {task_id} retrieved successfully.') + logger.debug('Task %s retrieved successfully.', task_id) return task - logger.debug(f'Task {task_id} not found in store.') + logger.debug('Task %s not found in store.', task_id) return None async def delete(self, task_id: str) -> None: @@ -151,8 +151,8 @@ async def delete(self, task_id: str) -> None: # Commit is automatic when using session.begin() if result.rowcount > 0: - logger.info(f'Task {task_id} deleted successfully.') + logger.info('Task %s deleted successfully.', task_id) else: logger.warning( - f'Attempted to delete nonexistent task with id: {task_id}' + 'Attempted to delete nonexistent task with id: %s', task_id ) diff --git a/src/a2a/utils/error_handlers.py b/src/a2a/utils/error_handlers.py index a9629ce44..f76857c42 100644 --- a/src/a2a/utils/error_handlers.py +++ b/src/a2a/utils/error_handlers.py @@ -80,8 +80,8 @@ async def wrapper(*args: Any, **kwargs: Any) -> Response: return JSONResponse( content={'message': error.message}, status_code=http_code ) - except Exception as e: # noqa: BLE001 - logger.log(logging.ERROR, f'Unknown error occurred {e}') + except Exception: + logger.exception('Unknown error occurred') return JSONResponse( content={'message': 'unknown exception'}, status_code=500 ) diff --git a/src/a2a/utils/helpers.py b/src/a2a/utils/helpers.py index 0760690b8..aa6214486 100644 --- a/src/a2a/utils/helpers.py +++ b/src/a2a/utils/helpers.py @@ -142,7 +142,7 @@ def decorator(function: Callable) -> Callable: async def async_wrapper(self: Any, *args, **kwargs) -> Any: if not expression(self): final_message = error_message or str(expression) - logger.error(f'Unsupported Operation: {final_message}') + logger.error('Unsupported Operation: %s', final_message) raise ServerError( UnsupportedOperationError(message=final_message) ) @@ -154,7 +154,7 @@ async def async_wrapper(self: Any, *args, **kwargs) -> Any: def sync_wrapper(self: Any, *args, **kwargs) -> Any: if not expression(self): final_message = error_message or str(expression) - logger.error(f'Unsupported Operation: {final_message}') + logger.error('Unsupported Operation: %s', final_message) raise ServerError( UnsupportedOperationError(message=final_message) ) @@ -186,7 +186,7 @@ def decorator(function): async def wrapper(self, *args, **kwargs): if not expression(self): final_message = error_message or str(expression) - logger.error(f'Unsupported Operation: {final_message}') + logger.error('Unsupported Operation: %s', final_message) raise ServerError( UnsupportedOperationError(message=final_message) ) diff --git a/src/a2a/utils/telemetry.py b/src/a2a/utils/telemetry.py index 298b86e42..c73d2ac92 100644 --- a/src/a2a/utils/telemetry.py +++ b/src/a2a/utils/telemetry.py @@ -171,7 +171,9 @@ def trace_function( # noqa: PLR0915 is_async_func = inspect.iscoroutinefunction(func) logger.debug( - f'Start tracing for {actual_span_name}, is_async_func {is_async_func}' + 'Start tracing for %s, is_async_func %s', + actual_span_name, + is_async_func, ) @functools.wraps(func) @@ -196,7 +198,7 @@ async def async_wrapper(*args, **kwargs) -> Any: # asyncio.CancelledError extends from BaseException except asyncio.CancelledError as ce: exception = None - logger.debug(f'CancelledError in span {actual_span_name}') + logger.debug('CancelledError in span %s', actual_span_name) span.record_exception(ce) raise except Exception as e: @@ -212,7 +214,8 @@ async def async_wrapper(*args, **kwargs) -> Any: ) except Exception: logger.exception( - f'attribute_extractor error in span {actual_span_name}' + 'attribute_extractor error in span %s', + actual_span_name, ) return result @@ -246,7 +249,8 @@ def sync_wrapper(*args, **kwargs) -> Any: ) except Exception: logger.exception( - f'attribute_extractor error in span {actual_span_name}' + 'attribute_extractor error in span %s', + actual_span_name, ) return result @@ -307,7 +311,7 @@ def not_traced_method(self): pass ``` """ - logger.debug(f'Trace all class {include_list}, {exclude_list}') + logger.debug('Trace all class %s, %s', include_list, exclude_list) exclude_list = exclude_list or [] def decorator(cls: Any) -> Any: diff --git a/tests/utils/test_telemetry.py b/tests/utils/test_telemetry.py index 62955ccbd..5109379b4 100644 --- a/tests/utils/test_telemetry.py +++ b/tests/utils/test_telemetry.py @@ -78,7 +78,10 @@ def foo() -> int: return 1 foo() - logger.exception.assert_any_call(mock.ANY) + logger.exception.assert_any_call( + 'attribute_extractor error in span %s', + 'test_telemetry.foo', + ) @pytest.mark.asyncio From ecd9b3c21ec7822f6616e8e797600263c07d280e Mon Sep 17 00:00:00 2001 From: Holt Skinner Date: Wed, 20 Aug 2025 15:13:25 +0100 Subject: [PATCH 2/8] Remove ruff exception for G004 --- .gitignore | 2 +- .ruff.toml | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/.gitignore b/.gitignore index bd3c1ca83..91cbb9938 100644 --- a/.gitignore +++ b/.gitignore @@ -9,4 +9,4 @@ __pycache__ test_venv/ coverage.xml .nox -spec.json \ No newline at end of file +spec.json diff --git a/.ruff.toml b/.ruff.toml index 44e034549..42a2340a2 100644 --- a/.ruff.toml +++ b/.ruff.toml @@ -30,7 +30,6 @@ ignore = [ "ANN003", "ANN401", "TRY003", - "G004", "TRY201", "FIX002", ] From c7b39d50a6845fae4c794301ee4100243e05f785 Mon Sep 17 00:00:00 2001 From: Andrew Hoblitzell Date: Thu, 21 Aug 2025 06:45:56 +0530 Subject: [PATCH 3/8] formatting and comments --- src/a2a/client/auth/interceptor.py | 11 ++++++++--- src/a2a/server/apps/jsonrpc/jsonrpc_app.py | 18 ++++++++++++------ .../default_request_handler.py | 4 +++- .../tasks/base_push_notification_sender.py | 8 +++++--- .../database_push_notification_config_store.py | 15 +++++++++++---- src/a2a/server/tasks/database_task_store.py | 3 ++- src/a2a/utils/error_handlers.py | 14 ++++++++------ src/a2a/utils/helpers.py | 14 ++++++++++---- 8 files changed, 59 insertions(+), 28 deletions(-) diff --git a/src/a2a/client/auth/interceptor.py b/src/a2a/client/auth/interceptor.py index 605594841..65c971921 100644 --- a/src/a2a/client/auth/interceptor.py +++ b/src/a2a/client/auth/interceptor.py @@ -62,7 +62,9 @@ async def intercept( ): headers['Authorization'] = f'Bearer {credential}' logger.debug( - f"Added Bearer token for scheme '{scheme_name}' (type: {scheme_def.type})." + "Added Bearer token for scheme '%s' (type: %s).", + scheme_name, + scheme_def.type, ) http_kwargs['headers'] = headers return request_payload, http_kwargs @@ -74,7 +76,9 @@ async def intercept( ): headers['Authorization'] = f'Bearer {credential}' logger.debug( - f"Added Bearer token for scheme '{scheme_name}' (type: {scheme_def.type})." + "Added Bearer token for scheme '%s' (type: %s).", + scheme_name, + scheme_def.type, ) http_kwargs['headers'] = headers return request_payload, http_kwargs @@ -83,7 +87,8 @@ async def intercept( case APIKeySecurityScheme(in_=In.header): headers[scheme_def.name] = credential logger.debug( - f"Added API Key Header for scheme '{scheme_name}'." + "Added API Key Header for scheme '%s'.", + scheme_name, ) http_kwargs['headers'] = headers return request_payload, http_kwargs diff --git a/src/a2a/server/apps/jsonrpc/jsonrpc_app.py b/src/a2a/server/apps/jsonrpc/jsonrpc_app.py index 46e79a2cc..d3e422fbe 100644 --- a/src/a2a/server/apps/jsonrpc/jsonrpc_app.py +++ b/src/a2a/server/apps/jsonrpc/jsonrpc_app.py @@ -233,9 +233,13 @@ def _generate_error_response( ) logger.log( log_level, - f'Request Error (ID: {request_id}): ' - f"Code={error_resp.error.code}, Message='{error_resp.error.message}'" - f'{", Data=" + str(error_resp.error.data) if error_resp.error.data else ""}', + "Request Error (ID: %s): Code=%s, Message='%s'%s", + request_id, + error_resp.error.code, + error_resp.error.message, + ', Data=' + str(error_resp.error.data) + if error_resp.error.data + else '', ) return JSONResponse( error_resp.model_dump(mode='json', exclude_none=True), @@ -422,7 +426,7 @@ async def _process_non_streaming_request( ) case _: logger.error( - f'Unhandled validated request type: {type(request_obj)}' + 'Unhandled validated request type: %s', type(request_obj) ) error = UnsupportedOperationError( message=f'Request type {type(request_obj).__name__} is unknown.' @@ -497,8 +501,10 @@ async def _handle_get_agent_card(self, request: Request) -> JSONResponse: """ if request.url.path == PREV_AGENT_CARD_WELL_KNOWN_PATH: logger.warning( - f"Deprecated agent card endpoint '{PREV_AGENT_CARD_WELL_KNOWN_PATH}' accessed. " - f"Please use '{AGENT_CARD_WELL_KNOWN_PATH}' instead. This endpoint will be removed in a future version." + "Deprecated agent card endpoint '%s' accessed. " + "Please use '%s' instead. This endpoint will be removed in a future version.", + PREV_AGENT_CARD_WELL_KNOWN_PATH, + AGENT_CARD_WELL_KNOWN_PATH, ) card_to_serve = self.agent_card diff --git a/src/a2a/server/request_handlers/default_request_handler.py b/src/a2a/server/request_handlers/default_request_handler.py index 25fe25258..054f78f23 100644 --- a/src/a2a/server/request_handlers/default_request_handler.py +++ b/src/a2a/server/request_handlers/default_request_handler.py @@ -244,7 +244,9 @@ def _validate_task_id_match(self, task_id: str, event_task_id: str) -> None: """Validates that agent-generated task ID matches the expected task ID.""" if task_id != event_task_id: logger.error( - f'Agent generated task_id={event_task_id} does not match the RequestContext task_id={task_id}.' + 'Agent generated task_id=%s does not match the RequestContext task_id=%s.', + event_task_id, + task_id, ) raise ServerError( InternalError(message='Task ID mismatch in agent response') diff --git a/src/a2a/server/tasks/base_push_notification_sender.py b/src/a2a/server/tasks/base_push_notification_sender.py index 22139ee76..087d2973d 100644 --- a/src/a2a/server/tasks/base_push_notification_sender.py +++ b/src/a2a/server/tasks/base_push_notification_sender.py @@ -44,7 +44,7 @@ async def send_notification(self, task: Task) -> None: if not all(results): logger.warning( - f'Some push notifications failed to send for task_id={task.id}' + 'Some push notifications failed to send for task_id=%s', task.id ) async def _dispatch_notification( @@ -62,11 +62,13 @@ async def _dispatch_notification( ) response.raise_for_status() logger.info( - f'Push-notification sent for task_id={task.id} to URL: {url}' + 'Push-notification sent for task_id=%s to URL: %s', task.id, url ) except Exception: logger.exception( - f'Error sending push-notification for task_id={task.id} to URL: {url}.' + 'Error sending push-notification for task_id=%s to URL: %s.', + task.id, + url, ) return False return True diff --git a/src/a2a/server/tasks/database_push_notification_config_store.py b/src/a2a/server/tasks/database_push_notification_config_store.py index f4a05e6ee..e125f22a1 100644 --- a/src/a2a/server/tasks/database_push_notification_config_store.py +++ b/src/a2a/server/tasks/database_push_notification_config_store.py @@ -78,7 +78,8 @@ def __init__( The key must be a URL-safe base64-encoded 32-byte key. """ logger.debug( - f'Initializing DatabasePushNotificationConfigStore with existing engine, table: {table_name}' + 'Initializing DatabasePushNotificationConfigStore with existing engine, table: %s', + table_name, ) self.engine = engine self.async_session_maker = async_sessionmaker( @@ -235,7 +236,9 @@ async def set_info( async with self.async_session_maker.begin() as session: await session.merge(db_config) logger.debug( - f'Push notification config for task {task_id} with config id {config_to_save.id} saved/updated.' + 'Push notification config for task %s with config id %s saved/updated.', + task_id, + config_to_save.id, ) async def get_info(self, task_id: str) -> list[PushNotificationConfig]: @@ -280,9 +283,13 @@ async def delete_info( if result.rowcount > 0: logger.info( - f'Deleted {result.rowcount} push notification config(s) for task {task_id}.' + 'Deleted %s push notification config(s) for task %s.', + result.rowcount, + task_id, ) else: logger.warning( - f'Attempted to delete push notification config for task {task_id} with config_id: {config_id} that does not exist.' + 'Attempted to delete push notification config for task %s with config_id: %s that does not exist.', + task_id, + config_id, ) diff --git a/src/a2a/server/tasks/database_task_store.py b/src/a2a/server/tasks/database_task_store.py index 1fd479b3a..b46d71939 100644 --- a/src/a2a/server/tasks/database_task_store.py +++ b/src/a2a/server/tasks/database_task_store.py @@ -53,7 +53,8 @@ def __init__( table_name: Name of the database table. Defaults to 'tasks'. """ logger.debug( - f'Initializing DatabaseTaskStore with existing engine, table: {table_name}' + 'Initializing DatabaseTaskStore with existing engine, table: %s', + table_name, ) self.engine = engine self.async_session_maker = async_sessionmaker( diff --git a/src/a2a/utils/error_handlers.py b/src/a2a/utils/error_handlers.py index f76857c42..d13c5e506 100644 --- a/src/a2a/utils/error_handlers.py +++ b/src/a2a/utils/error_handlers.py @@ -73,9 +73,10 @@ async def wrapper(*args: Any, **kwargs: Any) -> Response: ) logger.log( log_level, - 'Request error: ' - f"Code={error.code}, Message='{error.message}'" - f'{", Data=" + str(error.data) if error.data else ""}', + "Request error: Code=%s, Message='%s'%s", + error.code, + error.message, + ', Data=' + str(error.data) if error.data else '', ) return JSONResponse( content={'message': error.message}, status_code=http_code @@ -110,9 +111,10 @@ async def wrapper(*args: Any, **kwargs: Any) -> Any: ) logger.log( log_level, - 'Request error: ' - f"Code={error.code}, Message='{error.message}'" - f'{", Data=" + str(error.data) if error.data else ""}', + "Request error: Code=%s, Message='%s'%s", + error.code, + error.message, + ', Data=' + str(error.data) if error.data else '', ) # Since the stream has started, we can't return a JSONResponse. # Instead, we runt the error handling logic (provides logging) diff --git a/src/a2a/utils/helpers.py b/src/a2a/utils/helpers.py index aa6214486..af0959a2c 100644 --- a/src/a2a/utils/helpers.py +++ b/src/a2a/utils/helpers.py @@ -81,26 +81,32 @@ def append_artifact_to_task(task: Task, event: TaskArtifactUpdateEvent) -> None: if existing_artifact_list_index is not None: # Replace the existing artifact entirely with the new data logger.debug( - f'Replacing artifact at id {artifact_id} for task {task.id}' + 'Replacing artifact at id %s for task %s', artifact_id, task.id ) task.artifacts[existing_artifact_list_index] = new_artifact_data else: # Append the new artifact since no artifact with this index exists yet logger.debug( - f'Adding new artifact with id {artifact_id} for task {task.id}' + 'Adding new artifact with id %s for task %s', + artifact_id, + task.id, ) task.artifacts.append(new_artifact_data) elif existing_artifact: # Append new parts to the existing artifact's part list logger.debug( - f'Appending parts to artifact id {artifact_id} for task {task.id}' + 'Appending parts to artifact id %s for task %s', + artifact_id, + task.id, ) existing_artifact.parts.extend(new_artifact_data.parts) else: # We received a chunk to append, but we don't have an existing artifact. # we will ignore this chunk logger.warning( - f'Received append=True for nonexistent artifact index {artifact_id} in task {task.id}. Ignoring chunk.' + 'Received append=True for nonexistent artifact index %s in task %s. Ignoring chunk.', + artifact_id, + task.id, ) From 34f110e717874e7e2cd037c6a82c6a2e24f5e51d Mon Sep 17 00:00:00 2001 From: Andrew Hoblitzell Date: Thu, 21 Aug 2025 08:55:26 +0530 Subject: [PATCH 4/8] lint/formatting --- src/a2a/server/events/event_queue.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/a2a/server/events/event_queue.py b/src/a2a/server/events/event_queue.py index af53e1f16..814bc879a 100644 --- a/src/a2a/server/events/event_queue.py +++ b/src/a2a/server/events/event_queue.py @@ -193,7 +193,9 @@ async def clear_events(self, clear_child_queues: bool = True) -> None: while True: event = self.queue.get_nowait() logger.debug( - f'Discarding unprocessed event of type: {type(event)}, content: {event}' + 'Discarding unprocessed event of type: %s, content: %s', + type(event), + event, ) self.queue.task_done() cleared_count += 1 @@ -211,7 +213,8 @@ async def clear_events(self, clear_child_queues: bool = True) -> None: if cleared_count > 0: logger.debug( - f'Cleared {cleared_count} unprocessed events from EventQueue.' + 'Cleared %d unprocessed events from EventQueue.', + cleared_count, ) # Clear all child queues (lock released before awaiting child tasks) From a1089db6c102e1ead406140baf8924352ddcbdee Mon Sep 17 00:00:00 2001 From: Andrew Hoblitzell Date: Thu, 21 Aug 2025 09:30:22 +0530 Subject: [PATCH 5/8] few more f-strings --- src/a2a/client/card_resolver.py | 2 +- src/a2a/client/transports/rest.py | 8 ++++---- src/a2a/server/apps/rest/fastapi_app.py | 4 ++-- src/a2a/utils/telemetry.py | 6 ++++-- tests/client/test_auth_middleware.py | 4 ++-- tests/client/test_jsonrpc_client.py | 6 +++--- 6 files changed, 16 insertions(+), 14 deletions(-) diff --git a/src/a2a/client/card_resolver.py b/src/a2a/client/card_resolver.py index 9df551525..7932e427e 100644 --- a/src/a2a/client/card_resolver.py +++ b/src/a2a/client/card_resolver.py @@ -71,7 +71,7 @@ async def get_agent_card( else: path_segment = relative_card_path.lstrip('/') - target_url = f'{self.base_url}/{path_segment}' + target_url = '{}/{}'.format(self.base_url, path_segment) try: response = await self.httpx_client.get( diff --git a/src/a2a/client/transports/rest.py b/src/a2a/client/transports/rest.py index 3a72a5b14..51a2dab12 100644 --- a/src/a2a/client/transports/rest.py +++ b/src/a2a/client/transports/rest.py @@ -135,7 +135,7 @@ async def send_message_streaming( async with aconnect_sse( self.httpx_client, 'POST', - f'{self.url}/v1/message:stream', + '{}/v1/message:stream'.format(self.url), json=payload, **modified_kwargs, ) as event_source: @@ -178,7 +178,7 @@ async def _send_post_request( return await self._send_request( self.httpx_client.build_request( 'POST', - f'{self.url}{target}', + '{}{}'.format(self.url, target), json=rpc_request_payload, **(http_kwargs or {}), ) @@ -193,7 +193,7 @@ async def _send_get_request( return await self._send_request( self.httpx_client.build_request( 'GET', - f'{self.url}{target}', + '{}{}'.format(self.url, target), params=query_params, **(http_kwargs or {}), ) @@ -308,7 +308,7 @@ async def resubscribe( async with aconnect_sse( self.httpx_client, 'GET', - f'{self.url}/v1/tasks/{request.id}:subscribe', + '{}/v1/tasks/{}:subscribe'.format(self.url, request.id), **http_kwargs, ) as event_source: try: diff --git a/src/a2a/server/apps/rest/fastapi_app.py b/src/a2a/server/apps/rest/fastapi_app.py index 3ae5ad6fe..9b4050f3c 100644 --- a/src/a2a/server/apps/rest/fastapi_app.py +++ b/src/a2a/server/apps/rest/fastapi_app.py @@ -108,10 +108,10 @@ def build( router = APIRouter() for route, callback in self._adapter.routes().items(): router.add_api_route( - f'{rpc_url}{route[0]}', callback, methods=[route[1]] + '{}{}'.format(rpc_url, route[0]), callback, methods=[route[1]] ) - @router.get(f'{rpc_url}{agent_card_url}') + @router.get('{}{}'.format(rpc_url, agent_card_url)) async def get_agent_card(request: Request) -> Response: card = await self._adapter.handle_get_agent_card(request) return JSONResponse(card) diff --git a/src/a2a/utils/telemetry.py b/src/a2a/utils/telemetry.py index c73d2ac92..a39443a22 100644 --- a/src/a2a/utils/telemetry.py +++ b/src/a2a/utils/telemetry.py @@ -166,7 +166,9 @@ def trace_function( # noqa: PLR0915 attribute_extractor=attribute_extractor, ) - actual_span_name = span_name or f'{func.__module__}.{func.__name__}' + actual_span_name = span_name or '{}.{}'.format( + func.__module__, func.__name__ + ) is_async_func = inspect.iscoroutinefunction(func) @@ -323,7 +325,7 @@ def decorator(cls: Any) -> Any: if not include_list and name in exclude_list: continue - span_name = f'{cls.__module__}.{cls.__name__}.{name}' + span_name = '{}.{}.{}'.format(cls.__module__, cls.__name__, name) setattr( cls, name, diff --git a/tests/client/test_auth_middleware.py b/tests/client/test_auth_middleware.py index 4f53ca3f2..553410a09 100644 --- a/tests/client/test_auth_middleware.py +++ b/tests/client/test_auth_middleware.py @@ -295,8 +295,8 @@ async def test_auth_interceptor_variants(test_case, store): auth_interceptor = AuthInterceptor(credential_service=store) agent_card = AgentCard( url=test_case.url, - name=f'{test_case.scheme_name}bot', - description=f'A bot that uses {test_case.scheme_name}', + name='{}bot'.format(test_case.scheme_name), + description='A bot that uses {}'.format(test_case.scheme_name), version='1.0', default_input_modes=[], default_output_modes=[], diff --git a/tests/client/test_jsonrpc_client.py b/tests/client/test_jsonrpc_client.py index 58feec25d..56b7eda9b 100644 --- a/tests/client/test_jsonrpc_client.py +++ b/tests/client/test_jsonrpc_client.py @@ -116,7 +116,7 @@ async def async_iterable_from_list( class TestA2ACardResolver: BASE_URL = 'http://example.com' AGENT_CARD_PATH = AGENT_CARD_WELL_KNOWN_PATH - FULL_AGENT_CARD_URL = f'{BASE_URL}{AGENT_CARD_PATH}' + FULL_AGENT_CARD_URL = '{}{}'.format(BASE_URL, AGENT_CARD_PATH) EXTENDED_AGENT_CARD_PATH = '/agent/authenticatedExtendedCard' @pytest.mark.asyncio @@ -200,8 +200,8 @@ async def test_get_agent_card_success_with_specified_path_for_extended_card( http_kwargs=auth_kwargs, ) - expected_extended_url = ( - f'{self.BASE_URL}/{self.EXTENDED_AGENT_CARD_PATH.lstrip("/")}' + expected_extended_url = '{}/{}'.format( + self.BASE_URL, self.EXTENDED_AGENT_CARD_PATH.lstrip('/') ) mock_httpx_client.get.assert_called_once_with( expected_extended_url, **auth_kwargs From 6282902a4502d7dbb8935050a7ec34122fa21f13 Mon Sep 17 00:00:00 2001 From: Andrew Hoblitzell Date: Thu, 21 Aug 2025 09:57:23 +0530 Subject: [PATCH 6/8] linter --- src/a2a/client/card_resolver.py | 2 +- src/a2a/client/transports/rest.py | 8 ++++---- src/a2a/server/apps/rest/fastapi_app.py | 4 ++-- src/a2a/utils/telemetry.py | 6 ++---- 4 files changed, 9 insertions(+), 11 deletions(-) diff --git a/src/a2a/client/card_resolver.py b/src/a2a/client/card_resolver.py index 7932e427e..9df551525 100644 --- a/src/a2a/client/card_resolver.py +++ b/src/a2a/client/card_resolver.py @@ -71,7 +71,7 @@ async def get_agent_card( else: path_segment = relative_card_path.lstrip('/') - target_url = '{}/{}'.format(self.base_url, path_segment) + target_url = f'{self.base_url}/{path_segment}' try: response = await self.httpx_client.get( diff --git a/src/a2a/client/transports/rest.py b/src/a2a/client/transports/rest.py index 51a2dab12..3a72a5b14 100644 --- a/src/a2a/client/transports/rest.py +++ b/src/a2a/client/transports/rest.py @@ -135,7 +135,7 @@ async def send_message_streaming( async with aconnect_sse( self.httpx_client, 'POST', - '{}/v1/message:stream'.format(self.url), + f'{self.url}/v1/message:stream', json=payload, **modified_kwargs, ) as event_source: @@ -178,7 +178,7 @@ async def _send_post_request( return await self._send_request( self.httpx_client.build_request( 'POST', - '{}{}'.format(self.url, target), + f'{self.url}{target}', json=rpc_request_payload, **(http_kwargs or {}), ) @@ -193,7 +193,7 @@ async def _send_get_request( return await self._send_request( self.httpx_client.build_request( 'GET', - '{}{}'.format(self.url, target), + f'{self.url}{target}', params=query_params, **(http_kwargs or {}), ) @@ -308,7 +308,7 @@ async def resubscribe( async with aconnect_sse( self.httpx_client, 'GET', - '{}/v1/tasks/{}:subscribe'.format(self.url, request.id), + f'{self.url}/v1/tasks/{request.id}:subscribe', **http_kwargs, ) as event_source: try: diff --git a/src/a2a/server/apps/rest/fastapi_app.py b/src/a2a/server/apps/rest/fastapi_app.py index 9b4050f3c..3ae5ad6fe 100644 --- a/src/a2a/server/apps/rest/fastapi_app.py +++ b/src/a2a/server/apps/rest/fastapi_app.py @@ -108,10 +108,10 @@ def build( router = APIRouter() for route, callback in self._adapter.routes().items(): router.add_api_route( - '{}{}'.format(rpc_url, route[0]), callback, methods=[route[1]] + f'{rpc_url}{route[0]}', callback, methods=[route[1]] ) - @router.get('{}{}'.format(rpc_url, agent_card_url)) + @router.get(f'{rpc_url}{agent_card_url}') async def get_agent_card(request: Request) -> Response: card = await self._adapter.handle_get_agent_card(request) return JSONResponse(card) diff --git a/src/a2a/utils/telemetry.py b/src/a2a/utils/telemetry.py index a39443a22..c73d2ac92 100644 --- a/src/a2a/utils/telemetry.py +++ b/src/a2a/utils/telemetry.py @@ -166,9 +166,7 @@ def trace_function( # noqa: PLR0915 attribute_extractor=attribute_extractor, ) - actual_span_name = span_name or '{}.{}'.format( - func.__module__, func.__name__ - ) + actual_span_name = span_name or f'{func.__module__}.{func.__name__}' is_async_func = inspect.iscoroutinefunction(func) @@ -325,7 +323,7 @@ def decorator(cls: Any) -> Any: if not include_list and name in exclude_list: continue - span_name = '{}.{}.{}'.format(cls.__module__, cls.__name__, name) + span_name = f'{cls.__module__}.{cls.__name__}.{name}' setattr( cls, name, From f8a893b9bacbb1f64e372497c5e9e5cb3d7a2e7f Mon Sep 17 00:00:00 2001 From: Holt Skinner Date: Thu, 21 Aug 2025 16:59:52 +0100 Subject: [PATCH 7/8] Formatting --- tests/client/test_auth_middleware.py | 4 ++-- tests/client/test_jsonrpc_client.py | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/client/test_auth_middleware.py b/tests/client/test_auth_middleware.py index 553410a09..4f53ca3f2 100644 --- a/tests/client/test_auth_middleware.py +++ b/tests/client/test_auth_middleware.py @@ -295,8 +295,8 @@ async def test_auth_interceptor_variants(test_case, store): auth_interceptor = AuthInterceptor(credential_service=store) agent_card = AgentCard( url=test_case.url, - name='{}bot'.format(test_case.scheme_name), - description='A bot that uses {}'.format(test_case.scheme_name), + name=f'{test_case.scheme_name}bot', + description=f'A bot that uses {test_case.scheme_name}', version='1.0', default_input_modes=[], default_output_modes=[], diff --git a/tests/client/test_jsonrpc_client.py b/tests/client/test_jsonrpc_client.py index 56b7eda9b..e70936393 100644 --- a/tests/client/test_jsonrpc_client.py +++ b/tests/client/test_jsonrpc_client.py @@ -116,7 +116,7 @@ async def async_iterable_from_list( class TestA2ACardResolver: BASE_URL = 'http://example.com' AGENT_CARD_PATH = AGENT_CARD_WELL_KNOWN_PATH - FULL_AGENT_CARD_URL = '{}{}'.format(BASE_URL, AGENT_CARD_PATH) + FULL_AGENT_CARD_URL = f'{BASE_URL}{AGENT_CARD_PATH}' EXTENDED_AGENT_CARD_PATH = '/agent/authenticatedExtendedCard' @pytest.mark.asyncio From eedb71835637ad882ec22af5cfaeb07d16f309a3 Mon Sep 17 00:00:00 2001 From: Holt Skinner Date: Thu, 21 Aug 2025 17:13:03 +0100 Subject: [PATCH 8/8] undoing a fstring --- tests/client/test_jsonrpc_client.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/client/test_jsonrpc_client.py b/tests/client/test_jsonrpc_client.py index e70936393..58feec25d 100644 --- a/tests/client/test_jsonrpc_client.py +++ b/tests/client/test_jsonrpc_client.py @@ -200,8 +200,8 @@ async def test_get_agent_card_success_with_specified_path_for_extended_card( http_kwargs=auth_kwargs, ) - expected_extended_url = '{}/{}'.format( - self.BASE_URL, self.EXTENDED_AGENT_CARD_PATH.lstrip('/') + expected_extended_url = ( + f'{self.BASE_URL}/{self.EXTENDED_AGENT_CARD_PATH.lstrip("/")}' ) mock_httpx_client.get.assert_called_once_with( expected_extended_url, **auth_kwargs