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 31 commits into
base: main
Choose a base branch
from

Conversation

darkhaniop
Copy link

@darkhaniop darkhaniop commented Jun 19, 2025

Description

With this update, using A2AFastAPIApplication requires either adding the FastAPI package directly to the project or indicating the "fastapi" group when installing a2a-sdk, e.g., with uv "uv add a2a-sdk[fastapi]" or with pip "pip install a2a-sdk[fastapi]"

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.2.12

With this update, using A2AFastAPIApplication requires either adding
the FastAPI package directly to the project or indicating the "fastapi"
group when installing a2a-sdk, e.g., with uv "uv add a2a-sdk[fastapi]"
or with pip "pip install a2a-sdk[fastapi]"
@darkhaniop darkhaniop requested a review from a team as a code owner June 19, 2025 12:03
@darkhaniop darkhaniop requested a review from mvakoc June 19, 2025 12:03
When defining missing optional dependencies from fastapi, mypy issues
errors of the following form: 'Name "FastAPI" already defined
(possibly by an import) [no-redef]'. This commit fixes such mypy errors.
Updates the FastAPI optional dependency installation instructions in
README.md.
On app initialization with app = A2aFastAPIApplication(), check that the
FastAPI package is installed and raise ImportError with the custom
messsage if necessary.
@holtskinner
Copy link
Member

This won't solve the core issue on its own, because starlette is also a dependency and it depends on fastapi, so you'll need to remove that dependency as well.

@darkhaniop
Copy link
Author

Hi @holtskinner,

This won't solve the core issue on its own, because starlette is also a dependency and it depends on fastapi, so you'll need to remove that dependency as well.

It seems the dependencies are the other way around, so if we do it one framework at a time, I think refactoring FastAPI first makes sense.

Starlette dependency tree

A quick dependency check shows that starlette (also sse-starlette) depends only on the anyio` package:

dummy-project v0.1.0
└── starlette v0.47.0
    └── anyio v4.9.0
        ├── idna v3.10
        └── sniffio v1.3.1
for reference, see FastAPI dependency tree here

FastAPI dependency tree

On the other hand, if a project requires fastapi, the dependency tree would also include starlette:

another-dummy-project v0.1.0
└── fastapi v0.115.13
    ├── pydantic v2.11.7
    │   ├── annotated-types v0.7.0
    │   ├── pydantic-core v2.33.2
    │   │   └── typing-extensions v4.14.0
    │   ├── typing-extensions v4.14.0
    │   └── typing-inspection v0.4.1
    │       └── typing-extensions v4.14.0
    ├── starlette v0.46.2
    │   └── anyio v4.9.0
    │       ├── idna v3.10
    │       └── sniffio v1.3.1
    └── typing-extensions v4.14.0

SDK development vs downstream project dependencies

I kept FastAPI in dev dependencies so that when working on the SDK itself, i.e., running uv sync in the SDK dir, all frameworks (both Starlette and FastAPI) would be installed in the environment.

At the same time, with this refactoring non-FastAPI downstream projects (agents) that use a2a-sdk should become lighter without FastAPI and its dependencies. And those that use (a potentially different version of) FastAPI would less likely to encounter dependency conflicts.

Considerations for next steps (refactoring starlette)

In this PR, I propose refactoring only the FastAPI dependency, as it requires fewer changes. After that, we can look into making the Starlette package optional.

As I stated earlier (see my comment in #101), it would be nice to eventually make all frameworks (both FastAPI and Starlette) optional.

However, since all of the samples and documentation are written under the assumption that installing a2a-sdk adds Starlette, making it optional would introduce a bigger breaking change and require more involved refactoring and testing:

  • Docs and tutorials would also need to be updated (would require a follow-up PR in google-a2a/a2a).
  • Samples would have to be updated to directly require Starlette (would require follow-up PRs in google-a2a/a2a-samples).

Package respx was added by another PR, so we need to resync uv.lock.
@holtskinner holtskinner force-pushed the make-fastapi-package-optional branch from 3788450 to 1d06e63 Compare June 23, 2025 12:20
@holtskinner holtskinner requested a review from a team as a code owner June 24, 2025 09:47
@darkhaniop
Copy link
Author

It appears that the unit test task is failing because coverage has dropped below 90%. Running unit tests locally shows that all tests pass with a threshold of 89%.

Required test coverage of 89% reached. Total coverage: 89.47%
======================= 461 passed, 3 warnings in 2.75s =======================

Should we consider lowering option --cov-fail-under?

Copy link
Author

@darkhaniop darkhaniop left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Diff in 16059fb

--- a/src/a2a/server/apps/jsonrpc/fastapi_app.py
+++ b/src/a2a/server/apps/jsonrpc/fastapi_app.py
@@ -58,17 +58,6 @@ class A2AFastAPIApplication(JSONRPCApplication):
             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,

I think this removed code was a better way to check the installed FastAPI framework vs. how it was grouped with Starlette in a later update in this PR. Because, in the current version, we again lose the capability to use the a2a-sdk package with only Starlette (as both framework imports are within the same try-except block, which would fail if only one of the frameworks is present).

Also, maybe checking for the presence of a particular framework in the JSONRPCApplication constructor (which I believe is intended to be framework-agnostic) is not the best approach. For instance, a developer may want to bring another framework and pass it using the context_builder param.

Update: Looking more into this, because response generation methods defined in JSONRPCApplication are not abstract but return Starlette responses, it requires both starlette and sse-starlette (still it does not, and should not depend on FastAPI).


edit: clarity and add the commit ref with diff

edit2: add more details about JSONRPCApplication dependencies

Since all three apps A2AStarletteApplication, A2AFastAPIApplication, and
JSONRPCApplication are public APIs, we should assume that each can be
used direcly by downstream projects. Therefore, proper ImportError
messages should be printed when initializing each type of app with
missing optional dependencies.
@darkhaniop
Copy link
Author

Since all three apps A2AStarletteApplication, A2AFastAPIApplication, and JSONRPCApplication are a part of a2a-sdk public API, we should assume that any of them can be used directly by downstream projects. Therefore, proper ImportError messages should be printed when initializing each type of app with missing optional dependencies. So, I put raise ImportError() with corresponding messages in the three modules.

Also, as I explained above, it is better to decouple framework dependencies to allow the initialization of JSONRPCApplication with only the necessary framework installed (instead of requiring all supported frameworks to be present).

In the latest refactoring, instead of telling the developer that all frameworks should be added with a2a-sdk[http-server]

        if not _http_server_installed:
            raise ImportError(
                'The `a2a-sdk[http-server]` package is required to use the `JSONRPCApplication`.'
            )

I updated it to provide clearer details:

        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]`.'
            )

Checks

I tested this version with a sample agent and verified that it prints the correct message if optional dependencies are missing.

Also, I checked if a project adds only a2a-sdk (without the [http-server] group), starlette, and sse-starlette, excluding fastapi (requirements that correspond to all or most Python sample agents in a2a-samples), it can initialize the app without issues.


P.S. The initial version of this PR proposed to introduce framework-specific dependency groups a2a-sdk[fastapi], etc. But in the later update, it was changed to a2a-sdk[http-server], which combines both Starlette and FastAPI. Therefore now, I think, it makes sense to give a clearer ImportError message.

Copy link
Member

@holtskinner holtskinner left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the updates, can you add new tests for the updated sections?

The added tests check that ImportError with relevant messages are raised
when creating instances of A2AStarletteApplication,
A2AFastAPIApplication, and JSONRPCApplication with missing optional
dependencies.
darkhaniop added a commit to darkhaniop/a2a-samples that referenced this pull request Jul 2, 2025
In future versions of a2a-sdk, HTTP server packages may become optional.
See a2aproject/a2a-python/pull/217 for details. This PR ensures that
agents in `samples/python/agents/` continue to work with the future
versions on a2a-sdk.

Refs a2aproject#190
@darkhaniop
Copy link
Author

Thanks for the updates, can you add new tests for the updated sections?

I added some tests. BTW, I did not want to make them "too hacky" to force certain imports to truly fail, so I tested "import failures" by flipping _package_fastapi_installed and _package_starlette_installed during the tests.

@holtskinner holtskinner changed the title refactor!: Make the FastAPI dependency optional refactor!: Make the FastAPI and Starlette dependencies optional Jul 7, 2025
@darkhaniop darkhaniop force-pushed the make-fastapi-package-optional branch from fc1ac9a to e8233d1 Compare July 15, 2025 12:08
@darkhaniop
Copy link
Author

darkhaniop commented Jul 15, 2025

The last force push of this branch from fc1ac9a to e8233d1 above is to avoid re-adding a notebook file tests/eval/eval_sample_a2a_expense_reimburse_agent_server.ipynb to the git tree.

According to this explanation, it was removed from git history to keep the SDK tree lighter.

Hi, I removed this commit from the repo and moved the notebook to a2a-samples since notebooks in commit history can significantly increase the size of cloned repos.

@holtskinner
Copy link
Member

The last force push of this branch from fc1ac9a to e8233d1 above is to avoid re-adding a notebook file tests/eval/eval_sample_a2a_expense_reimburse_agent_server.ipynb to the git tree.

According to this explanation, it was removed from git history to keep the SDK tree lighter.

Hi, I removed this commit from the repo and moved the notebook to a2a-samples since notebooks in commit history can significantly increase the size of cloned repos.

Thanks for this.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

6 participants