Skip to content

Commit 89605bc

Browse files
committed
Refactor optional deps imports in apps/rest/*
This ensures the importing of the upper level packages a2a.server.apps succeed when optional http-server dependencies are not installed.
1 parent 6df65ac commit 89605bc

File tree

3 files changed

+138
-3
lines changed

3 files changed

+138
-3
lines changed

src/a2a/server/apps/rest/fastapi_app.py

Lines changed: 25 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,25 @@
11
import logging
22

3-
from typing import Any
3+
from typing import TYPE_CHECKING, Any
4+
5+
6+
if TYPE_CHECKING:
7+
from fastapi import APIRouter, FastAPI, Request, Response
8+
9+
_package_fastapi_installed = True
10+
else:
11+
try:
12+
from fastapi import APIRouter, FastAPI, Request, Response
13+
14+
_package_fastapi_installed = True
15+
except ImportError:
16+
APIRouter = Any
17+
FastAPI = Any
18+
Request = Any
19+
Response = Any
20+
21+
_package_fastapi_installed = False
422

5-
from fastapi import APIRouter, FastAPI, Request, Response
623

724
from a2a.server.apps.jsonrpc.jsonrpc_app import CallContextBuilder
825
from a2a.server.apps.rest.rest_adapter import RESTAdapter
@@ -40,6 +57,12 @@ def __init__(
4057
ServerCallContext passed to the http_handler. If None, no
4158
ServerCallContext is passed.
4259
"""
60+
if not _package_fastapi_installed:
61+
raise ImportError(
62+
'The `fastapi` package is required to use the'
63+
' `A2ARESTFastAPIApplication`. It can be added as a part of'
64+
' `a2a-sdk` optional dependencies, `a2a-sdk[http-server]`.'
65+
)
4366
self._adapter = RESTAdapter(
4467
agent_card=agent_card,
4568
http_handler=http_handler,

src/a2a/server/apps/rest/rest_adapter.py

Lines changed: 30 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,35 @@
22
import logging
33

44
from collections.abc import AsyncIterable, AsyncIterator, Awaitable, Callable
5-
from typing import Any
5+
from typing import TYPE_CHECKING, Any
66

77
from sse_starlette.sse import EventSourceResponse
88
from starlette.requests import Request
99
from starlette.responses import JSONResponse, Response
1010

11+
12+
if TYPE_CHECKING:
13+
from sse_starlette.sse import EventSourceResponse
14+
from starlette.requests import Request
15+
from starlette.responses import JSONResponse, Response
16+
17+
_package_starlette_installed = True
18+
19+
else:
20+
try:
21+
from sse_starlette.sse import EventSourceResponse
22+
from starlette.requests import Request
23+
from starlette.responses import JSONResponse, Response
24+
25+
_package_starlette_installed = True
26+
except ImportError:
27+
EventSourceResponse = Any
28+
Request = Any
29+
JSONResponse = Any
30+
Response = Any
31+
32+
_package_starlette_installed = False
33+
1134
from a2a.server.apps.jsonrpc import (
1235
CallContextBuilder,
1336
DefaultCallContextBuilder,
@@ -49,6 +72,12 @@ def __init__(
4972
ServerCallContext passed to the http_handler. If None, no
5073
ServerCallContext is passed.
5174
"""
75+
if not _package_starlette_installed:
76+
raise ImportError(
77+
'Packages `starlette` and `sse-starlette` are required to use'
78+
' the `RESTAdapter`. They can be added as a part of `a2a-sdk`'
79+
' optional dependencies, `a2a-sdk[http-server]`.'
80+
)
5281
self.agent_card = agent_card
5382
self.handler = RESTHandler(
5483
agent_card=agent_card, request_handler=http_handler

tests/server/apps/rest/test_rest_fastapi_app.py

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
import logging
22

3+
from typing import Any
4+
35
from unittest.mock import MagicMock
46

57
import pytest
@@ -9,6 +11,9 @@
911
from httpx import ASGITransport, AsyncClient
1012

1113
from a2a.grpc import a2a_pb2
14+
from a2a.server.apps.rest import fastapi_app
15+
from a2a.server.apps.rest import rest_adapter
16+
from a2a.server.apps.rest.rest_adapter import RESTAdapter
1217
from a2a.server.apps.rest.fastapi_app import A2ARESTFastAPIApplication
1318
from a2a.server.request_handlers.request_handler import RequestHandler
1419
from a2a.types import (
@@ -57,6 +62,84 @@ async def client(app: FastAPI) -> AsyncClient:
5762
)
5863

5964

65+
@pytest.fixture
66+
def mark_pkg_starlette_not_installed():
67+
pkg_starlette_installed_flag = rest_adapter._package_starlette_installed
68+
rest_adapter._package_starlette_installed = False
69+
yield
70+
rest_adapter._package_starlette_installed = pkg_starlette_installed_flag
71+
72+
73+
@pytest.fixture
74+
def mark_pkg_fastapi_not_installed():
75+
pkg_fastapi_installed_flag = fastapi_app._package_fastapi_installed
76+
fastapi_app._package_fastapi_installed = False
77+
yield
78+
fastapi_app._package_fastapi_installed = pkg_fastapi_installed_flag
79+
80+
81+
@pytest.mark.anyio
82+
async def test_create_rest_adapter_with_present_deps_succeeds(
83+
agent_card: AgentCard, request_handler: RequestHandler
84+
):
85+
try:
86+
_app = RESTAdapter(agent_card, request_handler)
87+
except ImportError:
88+
pytest.fail(
89+
'With packages starlette and see-starlette present, creating an'
90+
' RESTAdapter instance should not raise ImportError'
91+
)
92+
93+
94+
@pytest.mark.anyio
95+
async def test_create_rest_adapter_with_missing_deps_raises_importerror(
96+
agent_card: AgentCard,
97+
request_handler: RequestHandler,
98+
mark_pkg_starlette_not_installed: Any,
99+
):
100+
with pytest.raises(
101+
ImportError,
102+
match=(
103+
'Packages `starlette` and `sse-starlette` are required to use'
104+
' the `RESTAdapter`.'
105+
),
106+
):
107+
_app = RESTAdapter(agent_card, request_handler)
108+
109+
110+
@pytest.mark.anyio
111+
async def test_create_a2a_rest_fastapi_app_with_present_deps_succeeds(
112+
agent_card: AgentCard, request_handler: RequestHandler
113+
):
114+
try:
115+
_app = A2ARESTFastAPIApplication(agent_card, request_handler).build(
116+
agent_card_url='/well-known/agent.json', rpc_url=''
117+
)
118+
except ImportError:
119+
pytest.fail(
120+
'With the fastapi package present, creating a'
121+
' A2ARESTFastAPIApplication instance should not raise ImportError'
122+
)
123+
124+
125+
@pytest.mark.anyio
126+
async def test_create_a2a_rest_fastapi_app_with_missing_deps_raises_importerror(
127+
agent_card: AgentCard,
128+
request_handler: RequestHandler,
129+
mark_pkg_fastapi_not_installed: Any,
130+
):
131+
with pytest.raises(
132+
ImportError,
133+
match=(
134+
'The `fastapi` package is required to use the'
135+
' `A2ARESTFastAPIApplication`'
136+
),
137+
):
138+
_app = A2ARESTFastAPIApplication(agent_card, request_handler).build(
139+
agent_card_url='/well-known/agent.json', rpc_url=''
140+
)
141+
142+
60143
@pytest.mark.anyio
61144
async def test_send_message_success_message(
62145
client: AsyncClient, request_handler: MagicMock

0 commit comments

Comments
 (0)