Skip to content

Commit 9ad8b96

Browse files
authored
refactor(deps)!: Make opentelemetry an optional dependency (#369)
Reduce required dependencies and make new dependency group for telemetry. Release-As: 0.3.0
1 parent 270ea9b commit 9ad8b96

File tree

5 files changed

+959
-921
lines changed

5 files changed

+959
-921
lines changed

.github/workflows/unit-tests.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ jobs:
4545
with:
4646
python-version: ${{ matrix.python-version }}
4747
- name: Set up test environment variables
48-
run: |
48+
run: |
4949
echo "POSTGRES_TEST_DSN=postgresql+asyncpg://a2a:a2a_password@localhost:5432/a2a_test" >> $GITHUB_ENV
5050
echo "MYSQL_TEST_DSN=mysql+aiomysql://a2a:a2a_password@localhost:3306/a2a_test" >> $GITHUB_ENV
5151
@@ -55,7 +55,7 @@ jobs:
5555
run: |
5656
echo "$HOME/.cargo/bin" >> $GITHUB_PATH
5757
- name: Install dependencies
58-
run: uv sync --dev --extra sql --extra encryption --extra grpc
58+
run: uv sync --dev --extra sql --extra encryption --extra grpc --extra telemetry
5959
- name: Run tests and check coverage
6060
run: uv run pytest --cov=a2a --cov-report=xml --cov-fail-under=89
6161
- name: Show coverage summary in log

README.md

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,12 @@ To install with gRPC support:
3939
uv add "a2a-sdk[grpc]"
4040
```
4141

42+
To install with OpenTelemetry tracing support:
43+
44+
```bash
45+
uv add "a2a-sdk[telemetry]"
46+
```
47+
4248
To install with database support:
4349

4450
```bash
@@ -69,6 +75,12 @@ To install with gRPC support:
6975
pip install "a2a-sdk[grpc]"
7076
```
7177

78+
To install with OpenTelemetry tracing support:
79+
80+
```bash
81+
pip install "a2a-sdk[telemetry]"
82+
```
83+
7284
To install with database support:
7385

7486
```bash

pyproject.toml

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,6 @@ dependencies = [
1111
"fastapi>=0.115.2",
1212
"httpx>=0.28.1",
1313
"httpx-sse>=0.4.0",
14-
"opentelemetry-api>=1.33.0",
15-
"opentelemetry-sdk>=1.33.0",
1614
"pydantic>=2.11.3",
1715
"sse-starlette",
1816
"starlette"
@@ -38,6 +36,7 @@ sqlite = ["sqlalchemy[asyncio,aiosqlite]>=2.0.0"]
3836
sql = ["sqlalchemy[asyncio,postgresql-asyncpg,aiomysql,aiosqlite]>=2.0.0"]
3937
encryption = ["cryptography>=43.0.0"]
4038
grpc = ["grpcio>=1.60", "grpcio-tools>=1.60", "grpcio_reflection>=1.7.0", "protobuf==5.29.5", "google-api-core>=1.26.0"]
39+
telemetry = ["opentelemetry-api>=1.33.0", "opentelemetry-sdk>=1.33.0"]
4140

4241
[project.urls]
4342
homepage = "https://a2a-protocol.org/"

src/a2a/utils/telemetry.py

Lines changed: 41 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -59,26 +59,58 @@ def internal_method(self):
5959
import logging
6060

6161
from collections.abc import Callable
62-
from typing import Any, TypeAlias
62+
from typing import TYPE_CHECKING, Any
6363

64-
from opentelemetry import trace
65-
from opentelemetry.trace import SpanKind as _SpanKind
66-
from opentelemetry.trace import StatusCode
6764

65+
if TYPE_CHECKING:
66+
from opentelemetry.trace import SpanKind as SpanKindType
67+
else:
68+
SpanKindType = object
6869

69-
SpanKind: TypeAlias = _SpanKind
70+
logger = logging.getLogger(__name__)
71+
72+
try:
73+
from opentelemetry import trace
74+
from opentelemetry.trace import SpanKind as _SpanKind
75+
from opentelemetry.trace import StatusCode
76+
77+
except ImportError:
78+
logger.debug(
79+
'OpenTelemetry not found. Tracing will be disabled. '
80+
'Install with: \'pip install "a2a-sdk[telemetry]"\''
81+
)
82+
83+
class _NoOp:
84+
"""A no-op object that absorbs all tracing calls when OpenTelemetry is not installed."""
85+
86+
def __call__(self, *args: Any, **kwargs: Any) -> '_NoOp':
87+
return self
88+
89+
def __enter__(self) -> '_NoOp':
90+
return self
91+
92+
def __exit__(self, *args: object, **kwargs: Any) -> None:
93+
pass
94+
95+
def __getattr__(self, name: str) -> '_NoOp':
96+
return self
97+
98+
trace = _NoOp()
99+
_SpanKind = _NoOp()
100+
StatusCode = _NoOp()
101+
102+
SpanKind = _SpanKind
70103
__all__ = ['SpanKind']
104+
71105
INSTRUMENTING_MODULE_NAME = 'a2a-python-sdk'
72106
INSTRUMENTING_MODULE_VERSION = '1.0.0'
73107

74-
logger = logging.getLogger(__name__)
75-
76108

77109
def trace_function( # noqa: PLR0915
78110
func: Callable | None = None,
79111
*,
80112
span_name: str | None = None,
81-
kind: SpanKind = SpanKind.INTERNAL,
113+
kind: SpanKindType = SpanKind.INTERNAL,
82114
attributes: dict[str, Any] | None = None,
83115
attribute_extractor: Callable | None = None,
84116
) -> Callable:
@@ -225,7 +257,7 @@ def sync_wrapper(*args, **kwargs) -> Any:
225257
def trace_class(
226258
include_list: list[str] | None = None,
227259
exclude_list: list[str] | None = None,
228-
kind: SpanKind = SpanKind.INTERNAL,
260+
kind: SpanKindType = SpanKind.INTERNAL,
229261
) -> Callable:
230262
"""A class decorator to automatically trace specified methods of a class.
231263
@@ -280,22 +312,15 @@ def not_traced_method(self):
280312
exclude_list = exclude_list or []
281313

282314
def decorator(cls: Any) -> Any:
283-
all_methods = {}
284315
for name, method in inspect.getmembers(cls, inspect.isfunction):
285-
# Skip Dunders
286316
if name.startswith('__') and name.endswith('__'):
287317
continue
288-
289-
# Skip if include list is defined but the method not included.
290318
if include_list and name not in include_list:
291319
continue
292-
# Skip if include list is not defined but the method is in excludes.
293320
if not include_list and name in exclude_list:
294321
continue
295322

296-
all_methods[name] = method
297323
span_name = f'{cls.__module__}.{cls.__name__}.{name}'
298-
# Set the decorator on the method.
299324
setattr(
300325
cls,
301326
name,

0 commit comments

Comments
 (0)