From 55b6a4a72aed06702a17c4fb77e9a9b37c470783 Mon Sep 17 00:00:00 2001 From: Krishna Thota Date: Thu, 17 Jul 2025 13:23:13 -0700 Subject: [PATCH 1/3] feat: Set grpc dependencies as optional --- pyproject.toml | 8 ++---- src/a2a/server/request_handlers/__init__.py | 26 ++++++++++++++++++- .../server/request_handlers/grpc_handler.py | 12 +++++++-- src/a2a/utils/proto_utils.py | 8 +++--- uv.lock | 24 +++++++++-------- 5 files changed, 54 insertions(+), 24 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index a55f2074b..6d57a796a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -11,16 +11,11 @@ dependencies = [ "fastapi>=0.115.2", "httpx>=0.28.1", "httpx-sse>=0.4.0", - "google-api-core>=1.26.0", "opentelemetry-api>=1.33.0", "opentelemetry-sdk>=1.33.0", "pydantic>=2.11.3", "sse-starlette", - "starlette", - "grpcio>=1.60", - "grpcio-tools>=1.60", - "grpcio_reflection>=1.7.0", - "protobuf==5.29.5", + "starlette" ] classifiers = [ @@ -42,6 +37,7 @@ mysql = ["sqlalchemy[asyncio,aiomysql]>=2.0.0"] sqlite = ["sqlalchemy[asyncio,aiosqlite]>=2.0.0"] sql = ["sqlalchemy[asyncio,postgresql-asyncpg,aiomysql,aiosqlite]>=2.0.0"] encryption = ["cryptography>=43.0.0"] +grpc = ["grpcio>=1.60", "grpcio-tools>=1.60", "grpcio_reflection>=1.7.0", "protobuf==5.29.5", "google-api-core>=1.26.0"] [project.urls] homepage = "https://a2a-protocol.org/" diff --git a/src/a2a/server/request_handlers/__init__.py b/src/a2a/server/request_handlers/__init__.py index 8cf2fe8ce..0bd7f133d 100644 --- a/src/a2a/server/request_handlers/__init__.py +++ b/src/a2a/server/request_handlers/__init__.py @@ -1,9 +1,10 @@ """Request handler components for the A2A server.""" +import logging + from a2a.server.request_handlers.default_request_handler import ( DefaultRequestHandler, ) -from a2a.server.request_handlers.grpc_handler import GrpcHandler from a2a.server.request_handlers.jsonrpc_handler import JSONRPCHandler from a2a.server.request_handlers.request_handler import RequestHandler from a2a.server.request_handlers.response_helpers import ( @@ -12,6 +13,29 @@ ) +logger = logging.getLogger(__name__) + +try: + from a2a.server.request_handlers.grpc_handler import ( + GrpcHandler, # type: ignore + ) +except ImportError as e: + _original_error = e + logger.debug( + 'GrpcHandler not loaded. This is expected if database dependencies are not installed. Error: %s', + e, + ) + + class GrpcHandler: # type: ignore + """Placeholder for GrpcHandler when dependencies are not installed.""" + + def __init__(self, *args, **kwargs): + raise ImportError( + 'To use GrpcHandler, its dependencies must be installed. ' + 'You can install them with \'pip install "a2a-sdk[grpc]"\'' + ) from _original_error + + __all__ = [ 'DefaultRequestHandler', 'GrpcHandler', diff --git a/src/a2a/server/request_handlers/grpc_handler.py b/src/a2a/server/request_handlers/grpc_handler.py index d2323115c..987a0d6df 100644 --- a/src/a2a/server/request_handlers/grpc_handler.py +++ b/src/a2a/server/request_handlers/grpc_handler.py @@ -5,8 +5,16 @@ from abc import ABC, abstractmethod from collections.abc import AsyncIterable -import grpc -import grpc.aio + +try: + import grpc + import grpc.aio +except ImportError as e: + raise ImportError( + 'GrpcHandler requires grpcio and grpcio-tools to be installed. ' + 'Install with: ' + "'pip install a2a-sdk[grpc]'" + ) from e import a2a.grpc.a2a_pb2_grpc as a2a_grpc diff --git a/src/a2a/utils/proto_utils.py b/src/a2a/utils/proto_utils.py index 0871f05cb..423a83882 100644 --- a/src/a2a/utils/proto_utils.py +++ b/src/a2a/utils/proto_utils.py @@ -28,8 +28,8 @@ def message(cls, message: types.Message | None) -> a2a_pb2.Message | None: return a2a_pb2.Message( message_id=message.messageId, content=[ToProto.part(p) for p in message.parts], - context_id=message.contextId, - task_id=message.taskId, + context_id=message.contextId or '', + task_id=message.taskId or '', role=cls.role(message.role), metadata=ToProto.metadata(message.metadata), ) @@ -438,8 +438,8 @@ def message(cls, message: a2a_pb2.Message) -> types.Message: return types.Message( messageId=message.message_id, parts=[FromProto.part(p) for p in message.content], - contextId=message.context_id, - taskId=message.task_id, + contextId=message.context_id or None, + taskId=message.task_id or None, role=FromProto.role(message.role), metadata=FromProto.metadata(message.metadata), ) diff --git a/uv.lock b/uv.lock index 70a6421e3..7243f408d 100644 --- a/uv.lock +++ b/uv.lock @@ -11,15 +11,10 @@ name = "a2a-sdk" source = { editable = "." } dependencies = [ { name = "fastapi" }, - { name = "google-api-core" }, - { name = "grpcio" }, - { name = "grpcio-reflection" }, - { name = "grpcio-tools" }, { name = "httpx" }, { name = "httpx-sse" }, { name = "opentelemetry-api" }, { name = "opentelemetry-sdk" }, - { name = "protobuf" }, { name = "pydantic" }, { name = "sse-starlette" }, { name = "starlette" }, @@ -29,6 +24,13 @@ dependencies = [ encryption = [ { name = "cryptography" }, ] +grpc = [ + { name = "google-api-core" }, + { name = "grpcio" }, + { name = "grpcio-reflection" }, + { name = "grpcio-tools" }, + { name = "protobuf" }, +] mysql = [ { name = "sqlalchemy", extra = ["aiomysql", "asyncio"] }, ] @@ -62,15 +64,15 @@ dev = [ requires-dist = [ { name = "cryptography", marker = "extra == 'encryption'", specifier = ">=43.0.0" }, { name = "fastapi", specifier = ">=0.115.2" }, - { name = "google-api-core", specifier = ">=1.26.0" }, - { name = "grpcio", specifier = ">=1.60" }, - { name = "grpcio-reflection", specifier = ">=1.7.0" }, - { name = "grpcio-tools", specifier = ">=1.60" }, + { name = "google-api-core", marker = "extra == 'grpc'", specifier = ">=1.26.0" }, + { name = "grpcio", marker = "extra == 'grpc'", specifier = ">=1.60" }, + { name = "grpcio-reflection", marker = "extra == 'grpc'", specifier = ">=1.7.0" }, + { name = "grpcio-tools", marker = "extra == 'grpc'", specifier = ">=1.60" }, { name = "httpx", specifier = ">=0.28.1" }, { name = "httpx-sse", specifier = ">=0.4.0" }, { name = "opentelemetry-api", specifier = ">=1.33.0" }, { name = "opentelemetry-sdk", specifier = ">=1.33.0" }, - { name = "protobuf", specifier = "==5.29.5" }, + { name = "protobuf", marker = "extra == 'grpc'", specifier = "==5.29.5" }, { name = "pydantic", specifier = ">=2.11.3" }, { name = "sqlalchemy", extras = ["aiomysql", "aiosqlite", "asyncio", "postgresql-asyncpg"], marker = "extra == 'sql'", specifier = ">=2.0.0" }, { name = "sqlalchemy", extras = ["aiomysql", "asyncio"], marker = "extra == 'mysql'", specifier = ">=2.0.0" }, @@ -79,7 +81,7 @@ requires-dist = [ { name = "sse-starlette" }, { name = "starlette" }, ] -provides-extras = ["encryption", "mysql", "postgresql", "sql", "sqlite"] +provides-extras = ["encryption", "grpc", "mysql", "postgresql", "sql", "sqlite"] [package.metadata.requires-dev] dev = [ From 9bc3b363c799195aa68fa2ca6ddd2af872a6ebcd Mon Sep 17 00:00:00 2001 From: Krishna Thota Date: Thu, 17 Jul 2025 13:25:34 -0700 Subject: [PATCH 2/3] Enable grpc for unit tests --- .github/workflows/unit-tests.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/unit-tests.yml b/.github/workflows/unit-tests.yml index 88beeb1c6..33df56a62 100644 --- a/.github/workflows/unit-tests.yml +++ b/.github/workflows/unit-tests.yml @@ -55,7 +55,7 @@ jobs: run: | echo "$HOME/.cargo/bin" >> $GITHUB_PATH - name: Install dependencies - run: uv sync --dev --extra sql --extra encryption + run: uv sync --dev --extra sql --extra encryption --extra grpc - name: Run tests and check coverage run: uv run pytest --cov=a2a --cov-report=xml --cov-fail-under=89 - name: Show coverage summary in log From 1309d96620427860c2b5a272ec4a3b8285f766d3 Mon Sep 17 00:00:00 2001 From: kthota-g Date: Thu, 17 Jul 2025 13:31:08 -0700 Subject: [PATCH 3/3] Update src/a2a/server/request_handlers/__init__.py Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> --- src/a2a/server/request_handlers/__init__.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/a2a/server/request_handlers/__init__.py b/src/a2a/server/request_handlers/__init__.py index 0bd7f133d..6e5603de9 100644 --- a/src/a2a/server/request_handlers/__init__.py +++ b/src/a2a/server/request_handlers/__init__.py @@ -22,8 +22,7 @@ except ImportError as e: _original_error = e logger.debug( - 'GrpcHandler not loaded. This is expected if database dependencies are not installed. Error: %s', - e, + 'GrpcHandler not loaded. This is expected if gRPC dependencies are not installed. Error: %s', ) class GrpcHandler: # type: ignore