Skip to content

Commit d2072ff

Browse files
committed
fix: no module named 'grpc'
1 parent 24db37e commit d2072ff

4 files changed

Lines changed: 162 additions & 22 deletions

File tree

Lines changed: 17 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
---
2-
name: Minimal Install Smoke Test
2+
name: Install Smoke Test
33
on:
44
push:
55
branches: [main, 1.0-dev]
@@ -30,13 +30,18 @@ permissions:
3030
contents: read
3131

3232
jobs:
33-
minimal-install:
34-
name: Verify base-only install
33+
install-smoke:
34+
name: Verify ${{ matrix.profile.name }} install
3535
runs-on: ubuntu-latest
3636
if: github.repository == 'a2aproject/a2a-python'
3737
strategy:
3838
matrix:
3939
python-version: ['3.10', '3.11', '3.12', '3.13', '3.14']
40+
profile:
41+
- name: base
42+
extras: ''
43+
- name: http-server
44+
extras: '[http-server]'
4045
steps:
4146
- name: Checkout code
4247
uses: actions/checkout@v6
@@ -49,15 +54,17 @@ jobs:
4954
- name: Build package
5055
run: uv build --wheel
5156

52-
- name: Install with base dependencies only
57+
- name: Install with ${{ matrix.profile.name }} dependencies only
5358
run: |
54-
uv venv .venv-minimal
55-
# Install only the built wheel -- no extras, no dev deps.
56-
# This simulates what an end-user gets with `pip install a2a-sdk`.
57-
VIRTUAL_ENV=.venv-minimal uv pip install dist/*.whl
59+
uv venv .venv-smoke
60+
# Install only the built wheel + the profile's extras -- no
61+
# dev deps. This simulates what an end-user gets with
62+
# `pip install a2a-sdk${{ matrix.profile.extras }}`.
63+
WHEEL=$(ls dist/*.whl)
64+
VIRTUAL_ENV=.venv-smoke uv pip install "${WHEEL}${{ matrix.profile.extras }}"
5865
5966
- name: List installed packages
60-
run: VIRTUAL_ENV=.venv-minimal uv pip list
67+
run: VIRTUAL_ENV=.venv-smoke uv pip list
6168

6269
- name: Run import smoke test
63-
run: .venv-minimal/bin/python scripts/test_minimal_install.py
70+
run: .venv-smoke/bin/python scripts/test_install_smoke.py ${{ matrix.profile.name }}
Lines changed: 45 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,24 @@
11
#!/usr/bin/env python3
2-
"""Smoke test for minimal (base-only) installation of a2a-sdk.
2+
"""Smoke test for installations of a2a-sdk with various extras.
33
4-
This script verifies that all core public API modules can be imported
5-
when only the base dependencies are installed (no optional extras).
4+
This script verifies that the public API modules associated with a
5+
given installation profile can be imported without pulling in modules
6+
that belong to other (uninstalled) optional extras.
67
78
It is designed to run WITHOUT pytest or any dev dependencies -- just
8-
a clean venv with `pip install a2a-sdk`.
9+
a clean venv with `pip install a2a-sdk[<profile>]`.
910
1011
Usage:
11-
python scripts/test_minimal_install.py
12+
python scripts/test_install_smoke.py [profile]
13+
14+
profile defaults to "base" and selects which set of modules to
15+
smoke-test. Available profiles:
16+
base -- `pip install a2a-sdk`
17+
http-server -- `pip install a2a-sdk[http-server]`
1218
1319
Exit codes:
14-
0 - All core imports succeeded
15-
1 - One or more core imports failed
20+
0 - All imports for the profile succeeded
21+
1 - One or more imports failed
1622
"""
1723

1824
from __future__ import annotations
@@ -58,19 +64,48 @@
5864
'a2a.helpers.proto_helpers',
5965
]
6066

67+
# Modules that MUST be importable with only the base + `http-server`
68+
# extras installed (no `grpc`, `sql`, `signing`, `telemetry`, etc.).
69+
#
70+
# A user building a Starlette/FastAPI A2A server with
71+
# `pip install a2a-sdk[http-server]` should be able to import these
72+
# without the gRPC stack being present on the system.
73+
HTTP_SERVER_MODULES = [
74+
'a2a.server.routes',
75+
'a2a.server.routes.agent_card_routes',
76+
'a2a.server.routes.common',
77+
'a2a.server.routes.jsonrpc_dispatcher',
78+
'a2a.server.routes.jsonrpc_routes',
79+
'a2a.server.routes.rest_dispatcher',
80+
'a2a.server.routes.rest_routes',
81+
]
82+
83+
84+
PROFILES: dict[str, list[str]] = {
85+
'base': CORE_MODULES,
86+
'http-server': CORE_MODULES + HTTP_SERVER_MODULES,
87+
}
88+
6189

6290
def main() -> int:
91+
profile = sys.argv[1] if len(sys.argv) > 1 else 'base'
92+
if profile not in PROFILES:
93+
print(f'Unknown profile {profile!r}. Available: {sorted(PROFILES)}')
94+
return 1
95+
96+
modules = PROFILES[profile]
6397
failures: list[str] = []
6498
successes: list[str] = []
6599

66-
for module_name in CORE_MODULES:
100+
for module_name in modules:
67101
try:
68102
importlib.import_module(module_name)
69103
successes.append(module_name)
70104
except Exception as e: # noqa: BLE001, PERF203
71105
failures.append(f'{module_name}: {e}')
72106

73-
print(f'Tested {len(CORE_MODULES)} core modules')
107+
print(f'Profile: {profile}')
108+
print(f'Tested {len(modules)} modules')
74109
print(f' Passed: {len(successes)}')
75110
print(f' Failed: {len(failures)}')
76111

@@ -80,7 +115,7 @@ def main() -> int:
80115
print(f' - {failure}')
81116
return 1
82117

83-
print('\nAll core modules imported successfully.')
118+
print('\nAll modules imported successfully.')
84119
return 0
85120

86121

scripts/test_install_smoke.sh

Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
#!/bin/bash
2+
# Local equivalent of .github/workflows/install-smoke.yml.
3+
#
4+
# For each install profile, builds the wheel and installs it into a
5+
# clean venv (no dev deps), then runs the import smoke test for that
6+
# profile. By default runs every known profile; pass a profile name
7+
# to run just one.
8+
#
9+
# Available profiles (must match those in scripts/test_install_smoke.py):
10+
# base -- `pip install a2a-sdk`
11+
# http-server -- `pip install a2a-sdk[http-server]`
12+
#
13+
# Usage:
14+
# scripts/test_install_smoke.sh [profile] [python-version]
15+
#
16+
# Examples:
17+
# scripts/test_install_smoke.sh # all profiles, default python
18+
# scripts/test_install_smoke.sh '' 3.13 # all profiles on python 3.13
19+
# scripts/test_install_smoke.sh http-server # http-server only
20+
# scripts/test_install_smoke.sh http-server 3.13 # http-server on python 3.13
21+
set -e
22+
set -o pipefail
23+
24+
REPO_ROOT="$(cd "$(dirname "$0")/.." && pwd)"
25+
cd "$REPO_ROOT"
26+
27+
ALL_PROFILES=(base http-server)
28+
29+
PROFILE_ARG="${1:-}"
30+
PYTHON_VERSION="${2:-}"
31+
32+
if [ -z "$PROFILE_ARG" ]; then
33+
PROFILES=("${ALL_PROFILES[@]}")
34+
else
35+
PROFILES=("$PROFILE_ARG")
36+
fi
37+
38+
extras_for_profile() {
39+
case "$1" in
40+
base) echo "" ;;
41+
http-server) echo "[http-server]" ;;
42+
*)
43+
echo "Unknown profile '$1'. Available: ${ALL_PROFILES[*]}" >&2
44+
return 1
45+
;;
46+
esac
47+
}
48+
49+
# Validate profiles up-front so we fail fast.
50+
for profile in "${PROFILES[@]}"; do
51+
extras_for_profile "$profile" >/dev/null
52+
done
53+
54+
echo "--- Building wheel ---"
55+
rm -rf dist
56+
uv build --wheel
57+
WHEEL=$(ls dist/*.whl)
58+
59+
FAILED_PROFILES=()
60+
61+
for profile in "${PROFILES[@]}"; do
62+
extras=$(extras_for_profile "$profile")
63+
venv_dir=".venv-smoke-${profile}"
64+
65+
echo
66+
echo "=================================================================="
67+
echo " Profile: $profile (extras='$extras')"
68+
echo "=================================================================="
69+
70+
echo "--- Creating clean venv at $venv_dir ---"
71+
rm -rf "$venv_dir"
72+
if [ -n "$PYTHON_VERSION" ]; then
73+
uv venv "$venv_dir" --python "$PYTHON_VERSION"
74+
else
75+
uv venv "$venv_dir"
76+
fi
77+
78+
echo "--- Installing built wheel with '$profile' dependencies only ---"
79+
VIRTUAL_ENV="$venv_dir" uv pip install "${WHEEL}${extras}"
80+
81+
echo "--- Installed packages ---"
82+
VIRTUAL_ENV="$venv_dir" uv pip list
83+
84+
echo "--- Running import smoke test ---"
85+
if ! "$venv_dir/bin/python" scripts/test_install_smoke.py "$profile"; then
86+
FAILED_PROFILES+=("$profile")
87+
fi
88+
done
89+
90+
echo
91+
echo "=================================================================="
92+
if [ ${#FAILED_PROFILES[@]} -eq 0 ]; then
93+
echo " All profiles passed: ${PROFILES[*]}"
94+
exit 0
95+
fi
96+
97+
echo " Failed profiles: ${FAILED_PROFILES[*]}" >&2
98+
exit 1

src/a2a/compat/v0_3/context_builders.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,14 +7,14 @@
77

88
from typing import TYPE_CHECKING, Any
99

10-
import grpc
11-
1210
from a2a.compat.v0_3.extension_headers import LEGACY_HTTP_EXTENSION_HEADER
1311
from a2a.extensions.common import get_requested_extensions
1412
from a2a.server.context import ServerCallContext
1513

1614

1715
if TYPE_CHECKING:
16+
import grpc
17+
1818
from starlette.requests import Request
1919

2020
from a2a.server.request_handlers.grpc_handler import (

0 commit comments

Comments
 (0)