Skip to content

refactor!: Make the FastAPI and Starlette dependencies optional #217

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 34 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 15 commits
Commits
Show all changes
34 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
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
1 change: 1 addition & 0 deletions .github/actions/spelling/allow.txt
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ opensource
protoc
pyi
pyversions
redef
respx
resub
socio
Expand Down
16 changes: 13 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
[![License](https://img.shields.io/badge/License-Apache_2.0-blue.svg)](LICENSE)
[![PyPI version](https://img.shields.io/pypi/v/a2a-sdk)](https://pypi.org/project/a2a-sdk/)
![PyPI - Python Version](https://img.shields.io/pypi/pyversions/a2a-sdk)
[![PyPI - Downloads](https://img.shields.io/pypi/dw/a2a-sdk)](https://pypistats.org/packages/a2a-sdk)

Check failure on line 6 in README.md

View workflow job for this annotation

GitHub Actions / Check Spelling

`pypistats` is not a recognized word. (unrecognized-spelling)

<!-- markdownlint-disable no-inline-html -->

Expand All @@ -27,20 +27,30 @@

### Using `uv`

When you're working within a uv project or a virtual environment managed by uv, the preferred way to add packages is using uv add.

```bash
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]
```

### Using `pip`

If you prefer to use pip, the standard Python package installer, you can install `a2a-sdk` as follows
You can install `a2a-sdk` using pip, the standard Python package installer.

```bash
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]
```

## Examples

### [Helloworld Example](https://github.com/a2aproject/a2a-samples/tree/main/samples/python/agents/helloworld)
Expand Down
9 changes: 6 additions & 3 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -8,15 +8,12 @@ authors = [{ name = "Google LLC", email = "googleapis-packages@google.com" }]
requires-python = ">=3.10"
keywords = ["A2A", "A2A SDK", "A2A Protocol", "Agent2Agent"]
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",
Expand All @@ -36,6 +33,9 @@ classifiers = [
"License :: OSI Approved :: Apache Software License",
]

[project.optional-dependencies]
http-server = ["fastapi>=0.115.2", "sse-starlette", "starlette"]

[project.urls]
homepage = "https://a2aproject.github.io/A2A/"
repository = "https://github.com/a2aproject/a2a-python"
Expand Down Expand Up @@ -85,6 +85,9 @@ dev = [
"types-protobuf",
"types-requests",
"pre-commit",
"fastapi>=0.115.2",
"sse-starlette",
"starlette",
]

[[tool.uv.index]]
Expand Down
26 changes: 23 additions & 3 deletions src/a2a/server/apps/jsonrpc/fastapi_app.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,17 @@
import logging

from typing import Any
from typing import TYPE_CHECKING, Any

from fastapi import FastAPI, Request, Response

if TYPE_CHECKING:
from fastapi import FastAPI, Request, Response
else:
try:
from fastapi import FastAPI, Request, Response
except ImportError:
FastAPI = Any
Request = Any
Response = Any

from a2a.server.apps.jsonrpc.jsonrpc_app import (
CallContextBuilder,
Expand Down Expand Up @@ -30,7 +39,7 @@ def __init__(
extended_agent_card: AgentCard | None = None,
context_builder: CallContextBuilder | None = None,
):
"""Initializes the A2AStarletteApplication.
"""Initializes the A2AFastAPIApplication.

Args:
agent_card: The AgentCard describing the agent's capabilities.
Expand All @@ -49,6 +58,17 @@ def __init__(
context_builder=context_builder,
)

self._check_fastapi_dependency()

def _check_fastapi_dependency(self) -> None:
"""Checks if the FastAPI package is installed.

If instead of the actual FastAPI class, a dummy implementation from
./fastapi_import_helpers.py is imported, initializing FastAPI() would
raise ImportError.
"""
_app = FastAPI()

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

from abc import ABC, abstractmethod
from collections.abc import AsyncGenerator
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
Expand Down Expand Up @@ -45,6 +44,37 @@

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.requests import Request
from starlette.responses import JSONResponse, Response

_http_server_installed = True
else:
try:
from fastapi import FastAPI
from sse_starlette.sse import EventSourceResponse
from starlette.applications import Starlette
from starlette.authentication import BaseUser
from starlette.requests import Request
from starlette.responses import JSONResponse, Response

_http_server_installed = True
except ImportError:
_http_server_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.
FastAPI = Any
EventSourceResponse = Any
Starlette = Any
BaseUser = Any
Request = Any
JSONResponse = Any
Response = Any


class StarletteUserProxy(A2AUser):
"""Adapts the Starlette User class to the A2A user representation."""
Expand Down Expand Up @@ -108,7 +138,7 @@ def __init__(
extended_agent_card: AgentCard | None = None,
context_builder: CallContextBuilder | None = None,
):
"""Initializes the A2AStarletteApplication.
"""Initializes the JSONRPCApplication.

Args:
agent_card: The AgentCard describing the agent's capabilities.
Expand All @@ -120,6 +150,10 @@ def __init__(
ServerCallContext passed to the http_handler. If None, no
ServerCallContext is passed.
"""
if not _http_server_installed:
raise ImportError(
'The `a2a-sdk[http-server]` package is required to use the `JSONRPCApplication`.'
)
self.agent_card = agent_card
self.extended_agent_card = extended_agent_card
self.handler = JSONRPCHandler(
Expand Down
17 changes: 13 additions & 4 deletions src/a2a/server/apps/jsonrpc/starlette_app.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,18 @@
import logging

from typing import Any

from starlette.applications import Starlette
from starlette.routing import Route
from typing import TYPE_CHECKING, Any


if TYPE_CHECKING:
from starlette.applications import Starlette
from starlette.routing import Route
else:
try:
from starlette.applications import Starlette
from starlette.routing import Route
except ImportError:
Starlette = Any
Route = Any

from a2a.server.apps.jsonrpc.jsonrpc_app import (
CallContextBuilder,
Expand Down
49 changes: 30 additions & 19 deletions uv.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading