Skip to content

Commit eb24654

Browse files
refactor!: Make the FastAPI and Starlette dependencies optional (#217)
# Description This update makes Starlette and FastAPI libraries as optional dependencies. Users can run `uv add a2a-sdk[http-server]` to install these dependencies. ## Additional Checks - Verified that the "helloworld" agent sample works with this change. - Verified that a reasonable error message is printed when trying to use `A2AFastAPIApplication`. Release-As: 0.3.0 --------- Co-authored-by: Holt Skinner <13262395+holtskinner@users.noreply.github.com> Co-authored-by: Holt Skinner <holtskinner@google.com>
1 parent fb7ee6f commit eb24654

File tree

15 files changed

+942
-328
lines changed

15 files changed

+942
-328
lines changed

.github/workflows/unit-tests.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,6 @@ jobs:
5757
- name: Install dependencies
5858
run: uv sync --dev --extra sql --extra encryption --extra grpc --extra telemetry
5959
- name: Run tests and check coverage
60-
run: uv run pytest --cov=a2a --cov-report term --cov-fail-under=89
60+
run: uv run pytest --cov=a2a --cov-report term --cov-fail-under=88
6161
- name: Show coverage summary in log
6262
run: uv run coverage report

README.md

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,12 @@ When you're working within a uv project or a virtual environment managed by uv,
3333
uv add a2a-sdk
3434
```
3535

36+
To include the optional HTTP server components (FastAPI, Starlette), install the `http-server` extra:
37+
38+
```bash
39+
uv add a2a-sdk[http-server]
40+
```
41+
3642
To install with gRPC support:
3743

3844
```bash
@@ -69,6 +75,12 @@ If you prefer to use pip, the standard Python package installer, you can install
6975
pip install a2a-sdk
7076
```
7177

78+
To include the optional HTTP server components (FastAPI, Starlette), install the `http-server` extra:
79+
80+
```bash
81+
pip install a2a-sdk[http-server]
82+
```
83+
7284
To install with gRPC support:
7385

7486
```bash

pyproject.toml

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,12 +8,9 @@ authors = [{ name = "Google LLC", email = "googleapis-packages@google.com" }]
88
requires-python = ">=3.10"
99
keywords = ["A2A", "A2A SDK", "A2A Protocol", "Agent2Agent", "Agent 2 Agent"]
1010
dependencies = [
11-
"fastapi>=0.95.0",
1211
"httpx>=0.28.1",
1312
"httpx-sse>=0.4.0",
1413
"pydantic>=2.11.3",
15-
"sse-starlette",
16-
"starlette",
1714
"protobuf>=5.29.5",
1815
"google-api-core>=1.26.0",
1916
]
@@ -32,6 +29,7 @@ classifiers = [
3229
]
3330

3431
[project.optional-dependencies]
32+
http-server = ["fastapi>=0.115.2", "sse-starlette", "starlette"]
3533
postgresql = ["sqlalchemy[asyncio,postgresql-asyncpg]>=2.0.0"]
3634
mysql = ["sqlalchemy[asyncio,aiomysql]>=2.0.0"]
3735
sqlite = ["sqlalchemy[asyncio,aiosqlite]>=2.0.0"]
@@ -89,6 +87,9 @@ dev = [
8987
"types-protobuf",
9088
"types-requests",
9189
"pre-commit",
90+
"fastapi>=0.115.2",
91+
"sse-starlette",
92+
"starlette",
9293
"pyupgrade",
9394
"autoflake",
9495
"no_implicit_optional",

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

Lines changed: 64 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,30 @@
11
import logging
22

3-
from typing import Any
3+
from collections.abc import Callable
4+
from typing import TYPE_CHECKING, Any
45

5-
from fastapi import FastAPI
6+
7+
if TYPE_CHECKING:
8+
from fastapi import FastAPI
9+
10+
_package_fastapi_installed = True
11+
else:
12+
try:
13+
from fastapi import FastAPI
14+
15+
_package_fastapi_installed = True
16+
except ImportError:
17+
FastAPI = Any
18+
19+
_package_fastapi_installed = False
620

721
from a2a.server.apps.jsonrpc.jsonrpc_app import (
22+
CallContextBuilder,
823
JSONRPCApplication,
924
)
10-
from a2a.types import A2ARequest
25+
from a2a.server.context import ServerCallContext
26+
from a2a.server.request_handlers.jsonrpc_handler import RequestHandler
27+
from a2a.types import A2ARequest, AgentCard
1128
from a2a.utils.constants import (
1229
AGENT_CARD_WELL_KNOWN_PATH,
1330
DEFAULT_RPC_URL,
@@ -49,6 +66,50 @@ class A2AFastAPIApplication(JSONRPCApplication):
4966
(SSE).
5067
"""
5168

69+
def __init__( # noqa: PLR0913
70+
self,
71+
agent_card: AgentCard,
72+
http_handler: RequestHandler,
73+
extended_agent_card: AgentCard | None = None,
74+
context_builder: CallContextBuilder | None = None,
75+
card_modifier: Callable[[AgentCard], AgentCard] | None = None,
76+
extended_card_modifier: Callable[
77+
[AgentCard, ServerCallContext], AgentCard
78+
]
79+
| None = None,
80+
) -> None:
81+
"""Initializes the A2AFastAPIApplication.
82+
83+
Args:
84+
agent_card: The AgentCard describing the agent's capabilities.
85+
http_handler: The handler instance responsible for processing A2A
86+
requests via http.
87+
extended_agent_card: An optional, distinct AgentCard to be served
88+
at the authenticated extended card endpoint.
89+
context_builder: The CallContextBuilder used to construct the
90+
ServerCallContext passed to the http_handler. If None, no
91+
ServerCallContext is passed.
92+
card_modifier: An optional callback to dynamically modify the public
93+
agent card before it is served.
94+
extended_card_modifier: An optional callback to dynamically modify
95+
the extended agent card before it is served. It receives the
96+
call context.
97+
"""
98+
if not _package_fastapi_installed:
99+
raise ImportError(
100+
'The `fastapi` package is required to use the `A2AFastAPIApplication`.'
101+
' It can be added as a part of `a2a-sdk` optional dependencies,'
102+
' `a2a-sdk[http-server]`.'
103+
)
104+
super().__init__(
105+
agent_card=agent_card,
106+
http_handler=http_handler,
107+
extended_agent_card=extended_agent_card,
108+
context_builder=context_builder,
109+
card_modifier=card_modifier,
110+
extended_card_modifier=extended_card_modifier,
111+
)
112+
52113
def add_routes_to_app(
53114
self,
54115
app: FastAPI,

src/a2a/server/apps/jsonrpc/jsonrpc_app.py

Lines changed: 44 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -5,17 +5,9 @@
55

66
from abc import ABC, abstractmethod
77
from collections.abc import AsyncGenerator, Callable
8-
from typing import Any
8+
from typing import TYPE_CHECKING, Any
99

10-
from fastapi import FastAPI
1110
from pydantic import ValidationError
12-
from sse_starlette.sse import EventSourceResponse
13-
from starlette.applications import Starlette
14-
from starlette.authentication import BaseUser
15-
from starlette.exceptions import HTTPException
16-
from starlette.requests import Request
17-
from starlette.responses import JSONResponse, Response
18-
from starlette.status import HTTP_413_REQUEST_ENTITY_TOO_LARGE
1911

2012
from a2a.auth.user import UnauthenticatedUser
2113
from a2a.auth.user import User as A2AUser
@@ -61,6 +53,42 @@
6153

6254
logger = logging.getLogger(__name__)
6355

56+
if TYPE_CHECKING:
57+
from fastapi import FastAPI
58+
from sse_starlette.sse import EventSourceResponse
59+
from starlette.applications import Starlette
60+
from starlette.authentication import BaseUser
61+
from starlette.exceptions import HTTPException
62+
from starlette.requests import Request
63+
from starlette.responses import JSONResponse, Response
64+
from starlette.status import HTTP_413_REQUEST_ENTITY_TOO_LARGE
65+
66+
_package_starlette_installed = True
67+
else:
68+
FastAPI = Any
69+
try:
70+
from sse_starlette.sse import EventSourceResponse
71+
from starlette.applications import Starlette
72+
from starlette.authentication import BaseUser
73+
from starlette.exceptions import HTTPException
74+
from starlette.requests import Request
75+
from starlette.responses import JSONResponse, Response
76+
from starlette.status import HTTP_413_REQUEST_ENTITY_TOO_LARGE
77+
78+
_package_starlette_installed = True
79+
except ImportError:
80+
_package_starlette_installed = False
81+
# Provide placeholder types for runtime type hinting when dependencies are not installed.
82+
# These will not be used if the code path that needs them is guarded by _http_server_installed.
83+
EventSourceResponse = Any
84+
Starlette = Any
85+
BaseUser = Any
86+
HTTPException = Any
87+
Request = Any
88+
JSONResponse = Any
89+
Response = Any
90+
HTTP_413_REQUEST_ENTITY_TOO_LARGE = Any
91+
6492

6593
class StarletteUserProxy(A2AUser):
6694
"""Adapts the Starlette User class to the A2A user representation."""
@@ -135,7 +163,7 @@ def __init__( # noqa: PLR0913
135163
]
136164
| None = None,
137165
) -> None:
138-
"""Initializes the A2AStarletteApplication.
166+
"""Initializes the JSONRPCApplication.
139167
140168
Args:
141169
agent_card: The AgentCard describing the agent's capabilities.
@@ -152,6 +180,12 @@ def __init__( # noqa: PLR0913
152180
the extended agent card before it is served. It receives the
153181
call context.
154182
"""
183+
if not _package_starlette_installed:
184+
raise ImportError(
185+
'Packages `starlette` and `sse-starlette` are required to use the'
186+
' `JSONRPCApplication`. They can be added as a part of `a2a-sdk`'
187+
' optional dependencies, `a2a-sdk[http-server]`.'
188+
)
155189
self.agent_card = agent_card
156190
self.extended_agent_card = extended_agent_card
157191
self.card_modifier = card_modifier

src/a2a/server/apps/jsonrpc/starlette_app.py

Lines changed: 68 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,34 @@
11
import logging
22

3-
from typing import Any
3+
from collections.abc import Callable
4+
from typing import TYPE_CHECKING, Any
45

5-
from starlette.applications import Starlette
6-
from starlette.routing import Route
6+
7+
if TYPE_CHECKING:
8+
from starlette.applications import Starlette
9+
from starlette.routing import Route
10+
11+
_package_starlette_installed = True
12+
13+
else:
14+
try:
15+
from starlette.applications import Starlette
16+
from starlette.routing import Route
17+
18+
_package_starlette_installed = True
19+
except ImportError:
20+
Starlette = Any
21+
Route = Any
22+
23+
_package_starlette_installed = False
724

825
from a2a.server.apps.jsonrpc.jsonrpc_app import (
26+
CallContextBuilder,
927
JSONRPCApplication,
1028
)
29+
from a2a.server.context import ServerCallContext
30+
from a2a.server.request_handlers.jsonrpc_handler import RequestHandler
31+
from a2a.types import AgentCard
1132
from a2a.utils.constants import (
1233
AGENT_CARD_WELL_KNOWN_PATH,
1334
DEFAULT_RPC_URL,
@@ -27,6 +48,50 @@ class A2AStarletteApplication(JSONRPCApplication):
2748
(SSE).
2849
"""
2950

51+
def __init__( # noqa: PLR0913
52+
self,
53+
agent_card: AgentCard,
54+
http_handler: RequestHandler,
55+
extended_agent_card: AgentCard | None = None,
56+
context_builder: CallContextBuilder | None = None,
57+
card_modifier: Callable[[AgentCard], AgentCard] | None = None,
58+
extended_card_modifier: Callable[
59+
[AgentCard, ServerCallContext], AgentCard
60+
]
61+
| None = None,
62+
) -> None:
63+
"""Initializes the A2AStarletteApplication.
64+
65+
Args:
66+
agent_card: The AgentCard describing the agent's capabilities.
67+
http_handler: The handler instance responsible for processing A2A
68+
requests via http.
69+
extended_agent_card: An optional, distinct AgentCard to be served
70+
at the authenticated extended card endpoint.
71+
context_builder: The CallContextBuilder used to construct the
72+
ServerCallContext passed to the http_handler. If None, no
73+
ServerCallContext is passed.
74+
card_modifier: An optional callback to dynamically modify the public
75+
agent card before it is served.
76+
extended_card_modifier: An optional callback to dynamically modify
77+
the extended agent card before it is served. It receives the
78+
call context.
79+
"""
80+
if not _package_starlette_installed:
81+
raise ImportError(
82+
'Packages `starlette` and `sse-starlette` are required to use the'
83+
' `A2AStarletteApplication`. It can be added as a part of `a2a-sdk`'
84+
' optional dependencies, `a2a-sdk[http-server]`.'
85+
)
86+
super().__init__(
87+
agent_card=agent_card,
88+
http_handler=http_handler,
89+
extended_agent_card=extended_agent_card,
90+
context_builder=context_builder,
91+
card_modifier=card_modifier,
92+
extended_card_modifier=extended_card_modifier,
93+
)
94+
3095
def routes(
3196
self,
3297
agent_card_url: str = AGENT_CARD_WELL_KNOWN_PATH,

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,

0 commit comments

Comments
 (0)