Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
49 commits
Select commit Hold shift + click to select a range
95523a3
refactor!: Make the FastAPI dependency optional
darkhaniop Jun 19, 2025
3661aa8
Ignore name redef warnings on ImportError
darkhaniop Jun 19, 2025
a74f5e5
Update .github/actions/spelling/allow.txt
darkhaniop Jun 19, 2025
012ea77
Updates the installation instructions in README.md
darkhaniop Jun 19, 2025
d29c63a
Check FastAPI package on A2AFastAPIApplication()
darkhaniop Jun 20, 2025
800a29a
Update A2AFastAPIApplication.__init__ docstring
darkhaniop Jun 20, 2025
2af0be8
Merge branch 'main' into make-fastapi-package-optional
darkhaniop Jun 21, 2025
1d06e63
Regenerate uv.lock
darkhaniop Jun 21, 2025
abfa743
Merge branch 'main' into make-fastapi-package-optional
holtskinner Jun 23, 2025
fe26fa1
Refactor to make starlette optional
holtskinner Jun 23, 2025
17766ae
Fix mypy errors
holtskinner Jun 23, 2025
46e2a5c
Formatting
holtskinner Jun 23, 2025
61c5ca7
Remove uvlock
holtskinner Jun 24, 2025
1678fd1
Merge branch 'main' of https://github.com/google-a2a/a2a-python into …
holtskinner Jun 24, 2025
24c98e5
Recreate uv.lock
holtskinner Jun 24, 2025
16059fb
Remove extra check for FastAPI
holtskinner Jun 24, 2025
c2f3454
Spelling
holtskinner Jun 24, 2025
267bc4b
Merge branch 'main' into make-fastapi-package-optional
darkhaniop Jun 30, 2025
d1895af
Remove imports from starlette outside try-except
darkhaniop Jun 30, 2025
2d214f1
Split starlette and fastapi imports and error msg
darkhaniop Jul 1, 2025
acdd67b
Merge branch 'main' into make-fastapi-package-optional
darkhaniop Jul 1, 2025
c73492c
Merge branch 'main' into make-fastapi-package-optional
holtskinner Jul 1, 2025
7e159cd
Merge branch 'main' into make-fastapi-package-optional
darkhaniop Jul 2, 2025
975e865
Add tests for initiation of apps with missing deps
darkhaniop Jul 2, 2025
14d2db5
Merge branch 'main' into make-fastapi-package-optional
darkhaniop Jul 3, 2025
804a1f4
Merge branch 'main' into make-fastapi-package-optional
holtskinner Jul 7, 2025
3e1df0a
Formatting/tests
holtskinner Jul 7, 2025
180898b
Merge branch 'main' into make-fastapi-package-optional
holtskinner Jul 8, 2025
e8233d1
Merge branch 'main' into make-fastapi-package-optional
darkhaniop Jul 15, 2025
3f48966
Merge branch 'main' into make-fastapi-package-optional
holtskinner Jul 15, 2025
a65770f
Re-create uv lock
holtskinner Jul 15, 2025
aaacec1
Merge branch 'main' into make-fastapi-package-optional
holtskinner Jul 17, 2025
c5e4cbd
Merge branch 'main' into make-fastapi-package-optional
holtskinner Jul 21, 2025
ba5ec6e
regenerate uv lock
holtskinner Jul 21, 2025
0d3643c
Merge branch 'main' into make-fastapi-package-optional
holtskinner Jul 30, 2025
f93abd8
Regenerate uv lock
holtskinner Jul 30, 2025
35c3ea3
Merge branch 'main' of https://github.com/a2aproject/a2a-python into …
holtskinner Jul 31, 2025
bd41ca8
Re-add version for fastapi
holtskinner Jul 31, 2025
4fcf7c6
Formatting
holtskinner Jul 31, 2025
8f5e5a2
Merge branch 'main' into make-fastapi-package-optional
darkhaniop Aug 5, 2025
b5b3401
Merge branch 'main' into make-fastapi-package-optional
darkhaniop Aug 5, 2025
af76625
Update app constructor signatures
darkhaniop Aug 5, 2025
2f74bc8
Update mock agent card attributes in tests
darkhaniop Aug 5, 2025
6df65ac
Fix protobuf dependency
darkhaniop Aug 5, 2025
89605bc
Refactor optional deps imports in apps/rest/*
darkhaniop Aug 5, 2025
38739b7
Change coverage requirements to 88 percent
darkhaniop Aug 5, 2025
109488c
Refactor starlette imports in utility modules
darkhaniop Aug 6, 2025
fa45bc8
Fix docstring (addresses a spell-checker error)
darkhaniop Aug 6, 2025
9fa9e69
Merge branch 'main' into make-fastapi-package-optional
holtskinner Aug 7, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/unit-tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,6 @@ jobs:
- name: Install dependencies
run: uv sync --dev --extra sql --extra encryption --extra grpc --extra telemetry
- name: Run tests and check coverage
run: uv run pytest --cov=a2a --cov-report term --cov-fail-under=89
run: uv run pytest --cov=a2a --cov-report term --cov-fail-under=88
- name: Show coverage summary in log
run: uv run coverage report
12 changes: 12 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,12 @@ When you're working within a uv project or a virtual environment managed by uv,
uv add a2a-sdk
```

To include the optional HTTP server components (FastAPI, Starlette), install the `http-server` extra:

```bash
uv add a2a-sdk[http-server]
```

To install with gRPC support:

```bash
Expand Down Expand Up @@ -69,6 +75,12 @@ If you prefer to use pip, the standard Python package installer, you can install
pip install a2a-sdk
```

To include the optional HTTP server components (FastAPI, Starlette), install the `http-server` extra:

```bash
pip install a2a-sdk[http-server]
```

To install with gRPC support:

```bash
Expand Down
7 changes: 4 additions & 3 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,9 @@ authors = [{ name = "Google LLC", email = "googleapis-packages@google.com" }]
requires-python = ">=3.10"
keywords = ["A2A", "A2A SDK", "A2A Protocol", "Agent2Agent", "Agent 2 Agent"]
dependencies = [
"fastapi>=0.95.0",
"httpx>=0.28.1",
"httpx-sse>=0.4.0",
"pydantic>=2.11.3",
"sse-starlette",
"starlette",
"protobuf>=5.29.5",
"google-api-core>=1.26.0",
]
Expand All @@ -32,6 +29,7 @@ classifiers = [
]

[project.optional-dependencies]
http-server = ["fastapi>=0.115.2", "sse-starlette", "starlette"]
postgresql = ["sqlalchemy[asyncio,postgresql-asyncpg]>=2.0.0"]
mysql = ["sqlalchemy[asyncio,aiomysql]>=2.0.0"]
sqlite = ["sqlalchemy[asyncio,aiosqlite]>=2.0.0"]
Expand Down Expand Up @@ -89,6 +87,9 @@ dev = [
"types-protobuf",
"types-requests",
"pre-commit",
"fastapi>=0.115.2",
"sse-starlette",
"starlette",
"pyupgrade",
"autoflake",
"no_implicit_optional",
Expand Down
67 changes: 64 additions & 3 deletions src/a2a/server/apps/jsonrpc/fastapi_app.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,30 @@
import logging

from typing import Any
from collections.abc import Callable
from typing import TYPE_CHECKING, Any

from fastapi import FastAPI

if TYPE_CHECKING:
from fastapi import FastAPI

_package_fastapi_installed = True
else:
try:
from fastapi import FastAPI

_package_fastapi_installed = True
except ImportError:
FastAPI = Any

_package_fastapi_installed = False

from a2a.server.apps.jsonrpc.jsonrpc_app import (
CallContextBuilder,
JSONRPCApplication,
)
from a2a.types import A2ARequest
from a2a.server.context import ServerCallContext
from a2a.server.request_handlers.jsonrpc_handler import RequestHandler
from a2a.types import A2ARequest, AgentCard
from a2a.utils.constants import (
AGENT_CARD_WELL_KNOWN_PATH,
DEFAULT_RPC_URL,
Expand Down Expand Up @@ -49,6 +66,50 @@
(SSE).
"""

def __init__( # noqa: PLR0913
self,
agent_card: AgentCard,
http_handler: RequestHandler,
extended_agent_card: AgentCard | None = None,
context_builder: CallContextBuilder | None = None,
card_modifier: Callable[[AgentCard], AgentCard] | None = None,
extended_card_modifier: Callable[
[AgentCard, ServerCallContext], AgentCard
]
| None = None,
) -> None:
"""Initializes the A2AFastAPIApplication.

Args:
agent_card: The AgentCard describing the agent's capabilities.
http_handler: The handler instance responsible for processing A2A
requests via http.
extended_agent_card: An optional, distinct AgentCard to be served
at the authenticated extended card endpoint.
context_builder: The CallContextBuilder used to construct the
ServerCallContext passed to the http_handler. If None, no
ServerCallContext is passed.
card_modifier: An optional callback to dynamically modify the public
agent card before it is served.
extended_card_modifier: An optional callback to dynamically modify
the extended agent card before it is served. It receives the
call context.
"""

Check notice on line 97 in src/a2a/server/apps/jsonrpc/fastapi_app.py

View workflow job for this annotation

GitHub Actions / Lint Code Base

Copy/pasted code

see src/a2a/server/apps/jsonrpc/starlette_app.py (51-79)
if not _package_fastapi_installed:
raise ImportError(
'The `fastapi` package is required to use the `A2AFastAPIApplication`.'
' It can be added as a part of `a2a-sdk` optional dependencies,'
' `a2a-sdk[http-server]`.'
)
super().__init__(
agent_card=agent_card,
http_handler=http_handler,
extended_agent_card=extended_agent_card,
context_builder=context_builder,
card_modifier=card_modifier,
extended_card_modifier=extended_card_modifier,
)

def add_routes_to_app(
self,
app: FastAPI,
Expand Down
54 changes: 44 additions & 10 deletions src/a2a/server/apps/jsonrpc/jsonrpc_app.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,17 +5,9 @@

from abc import ABC, abstractmethod
from collections.abc import AsyncGenerator, Callable
from typing import Any
from typing import TYPE_CHECKING, Any

from fastapi import FastAPI
from pydantic import ValidationError
from sse_starlette.sse import EventSourceResponse
from starlette.applications import Starlette
from starlette.authentication import BaseUser
from starlette.exceptions import HTTPException
from starlette.requests import Request
from starlette.responses import JSONResponse, Response
from starlette.status import HTTP_413_REQUEST_ENTITY_TOO_LARGE

from a2a.auth.user import UnauthenticatedUser
from a2a.auth.user import User as A2AUser
Expand Down Expand Up @@ -61,6 +53,42 @@

logger = logging.getLogger(__name__)

if TYPE_CHECKING:
from fastapi import FastAPI
from sse_starlette.sse import EventSourceResponse
from starlette.applications import Starlette
from starlette.authentication import BaseUser
from starlette.exceptions import HTTPException
from starlette.requests import Request
from starlette.responses import JSONResponse, Response
from starlette.status import HTTP_413_REQUEST_ENTITY_TOO_LARGE

_package_starlette_installed = True
else:
FastAPI = Any
try:
from sse_starlette.sse import EventSourceResponse
from starlette.applications import Starlette
from starlette.authentication import BaseUser
from starlette.exceptions import HTTPException
from starlette.requests import Request
from starlette.responses import JSONResponse, Response
from starlette.status import HTTP_413_REQUEST_ENTITY_TOO_LARGE

_package_starlette_installed = True
except ImportError:
_package_starlette_installed = False
# Provide placeholder types for runtime type hinting when dependencies are not installed.
# These will not be used if the code path that needs them is guarded by _http_server_installed.
EventSourceResponse = Any
Starlette = Any
BaseUser = Any
HTTPException = Any
Request = Any
JSONResponse = Any
Response = Any
HTTP_413_REQUEST_ENTITY_TOO_LARGE = Any


class StarletteUserProxy(A2AUser):
"""Adapts the Starlette User class to the A2A user representation."""
Expand Down Expand Up @@ -123,35 +151,41 @@
(SSE).
"""

def __init__( # noqa: PLR0913
self,
agent_card: AgentCard,
http_handler: RequestHandler,
extended_agent_card: AgentCard | None = None,
context_builder: CallContextBuilder | None = None,
card_modifier: Callable[[AgentCard], AgentCard] | None = None,
extended_card_modifier: Callable[
[AgentCard, ServerCallContext], AgentCard
]
| None = None,
) -> None:
"""Initializes the A2AStarletteApplication.
"""Initializes the JSONRPCApplication.

Args:
agent_card: The AgentCard describing the agent's capabilities.
http_handler: The handler instance responsible for processing A2A
requests via http.
extended_agent_card: An optional, distinct AgentCard to be served
at the authenticated extended card endpoint.
context_builder: The CallContextBuilder used to construct the
ServerCallContext passed to the http_handler. If None, no
ServerCallContext is passed.
card_modifier: An optional callback to dynamically modify the public
agent card before it is served.
extended_card_modifier: An optional callback to dynamically modify
the extended agent card before it is served. It receives the
call context.
"""

Check notice on line 182 in src/a2a/server/apps/jsonrpc/jsonrpc_app.py

View workflow job for this annotation

GitHub Actions / Lint Code Base

Copy/pasted code

see src/a2a/server/apps/jsonrpc/starlette_app.py (51-79)
if not _package_starlette_installed:
raise ImportError(
'Packages `starlette` and `sse-starlette` are required to use the'
' `JSONRPCApplication`. They can be added as a part of `a2a-sdk`'
' optional dependencies, `a2a-sdk[http-server]`.'
)
self.agent_card = agent_card
self.extended_agent_card = extended_agent_card
self.card_modifier = card_modifier
Expand Down
71 changes: 68 additions & 3 deletions src/a2a/server/apps/jsonrpc/starlette_app.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,34 @@
import logging

from typing import Any
from collections.abc import Callable
from typing import TYPE_CHECKING, Any

from starlette.applications import Starlette
from starlette.routing import Route

if TYPE_CHECKING:
from starlette.applications import Starlette
from starlette.routing import Route

_package_starlette_installed = True

else:
try:
from starlette.applications import Starlette
from starlette.routing import Route

_package_starlette_installed = True
except ImportError:
Starlette = Any
Route = Any

_package_starlette_installed = False

from a2a.server.apps.jsonrpc.jsonrpc_app import (
CallContextBuilder,
JSONRPCApplication,
)
from a2a.server.context import ServerCallContext
from a2a.server.request_handlers.jsonrpc_handler import RequestHandler
from a2a.types import AgentCard
from a2a.utils.constants import (
AGENT_CARD_WELL_KNOWN_PATH,
DEFAULT_RPC_URL,
Expand All @@ -27,6 +48,50 @@
(SSE).
"""

def __init__( # noqa: PLR0913
self,
agent_card: AgentCard,
http_handler: RequestHandler,
extended_agent_card: AgentCard | None = None,
context_builder: CallContextBuilder | None = None,
card_modifier: Callable[[AgentCard], AgentCard] | None = None,
extended_card_modifier: Callable[
[AgentCard, ServerCallContext], AgentCard
]
| None = None,
) -> None:
"""Initializes the A2AStarletteApplication.

Args:
agent_card: The AgentCard describing the agent's capabilities.
http_handler: The handler instance responsible for processing A2A
requests via http.
extended_agent_card: An optional, distinct AgentCard to be served
at the authenticated extended card endpoint.
context_builder: The CallContextBuilder used to construct the
ServerCallContext passed to the http_handler. If None, no
ServerCallContext is passed.
card_modifier: An optional callback to dynamically modify the public
agent card before it is served.
extended_card_modifier: An optional callback to dynamically modify
the extended agent card before it is served. It receives the
call context.
"""

Check notice on line 79 in src/a2a/server/apps/jsonrpc/starlette_app.py

View workflow job for this annotation

GitHub Actions / Lint Code Base

Copy/pasted code

see src/a2a/server/apps/jsonrpc/fastapi_app.py (69-97)

Check notice on line 79 in src/a2a/server/apps/jsonrpc/starlette_app.py

View workflow job for this annotation

GitHub Actions / Lint Code Base

Copy/pasted code

see src/a2a/server/apps/jsonrpc/jsonrpc_app.py (154-182)
if not _package_starlette_installed:
raise ImportError(
'Packages `starlette` and `sse-starlette` are required to use the'
' `A2AStarletteApplication`. It can be added as a part of `a2a-sdk`'
' optional dependencies, `a2a-sdk[http-server]`.'
)
super().__init__(
agent_card=agent_card,
http_handler=http_handler,
extended_agent_card=extended_agent_card,
context_builder=context_builder,
card_modifier=card_modifier,
extended_card_modifier=extended_card_modifier,
)

def routes(
self,
agent_card_url: str = AGENT_CARD_WELL_KNOWN_PATH,
Expand Down
27 changes: 25 additions & 2 deletions src/a2a/server/apps/rest/fastapi_app.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,25 @@
import logging

from typing import Any
from typing import TYPE_CHECKING, Any


if TYPE_CHECKING:
from fastapi import APIRouter, FastAPI, Request, Response

_package_fastapi_installed = True
else:
try:
from fastapi import APIRouter, FastAPI, Request, Response

_package_fastapi_installed = True
except ImportError:
APIRouter = Any
FastAPI = Any
Request = Any
Response = Any

_package_fastapi_installed = False

from fastapi import APIRouter, FastAPI, Request, Response

from a2a.server.apps.jsonrpc.jsonrpc_app import CallContextBuilder
from a2a.server.apps.rest.rest_adapter import RESTAdapter
Expand Down Expand Up @@ -40,6 +57,12 @@ def __init__(
ServerCallContext passed to the http_handler. If None, no
ServerCallContext is passed.
"""
if not _package_fastapi_installed:
raise ImportError(
'The `fastapi` package is required to use the'
' `A2ARESTFastAPIApplication`. It can be added as a part of'
' `a2a-sdk` optional dependencies, `a2a-sdk[http-server]`.'
)
self._adapter = RESTAdapter(
agent_card=agent_card,
http_handler=http_handler,
Expand Down
Loading
Loading