From a94c91cbc096b59cb0e21b3baad5f57fd8c82370 Mon Sep 17 00:00:00 2001 From: Lucas Soares Date: Mon, 20 Jul 2026 10:15:41 +0200 Subject: [PATCH 1/9] App-level instrumentation --- pyproject.toml | 12 +- .../core/telemetry/auto_instrument.py | 8 + .../telemetry/instrumentation/__init__.py | 31 +++ .../telemetry/instrumentation/_registry.py | 16 ++ .../core/telemetry/instrumentation/aiohttp.py | 22 ++ .../core/telemetry/instrumentation/base.py | 48 ++++ .../core/telemetry/instrumentation/fastapi.py | 22 ++ .../core/telemetry/instrumentation/grpc.py | 31 +++ .../core/telemetry/instrumentation/httpx.py | 22 ++ .../telemetry/instrumentation/requests.py | 22 ++ .../telemetry/instrumentation/starlette.py | 22 ++ .../telemetry/instrumentation/__init__.py | 0 .../instrumentation/test_instrumentation.py | 221 ++++++++++++++++++ 13 files changed, 476 insertions(+), 1 deletion(-) create mode 100644 src/sap_cloud_sdk/core/telemetry/instrumentation/__init__.py create mode 100644 src/sap_cloud_sdk/core/telemetry/instrumentation/_registry.py create mode 100644 src/sap_cloud_sdk/core/telemetry/instrumentation/aiohttp.py create mode 100644 src/sap_cloud_sdk/core/telemetry/instrumentation/base.py create mode 100644 src/sap_cloud_sdk/core/telemetry/instrumentation/fastapi.py create mode 100644 src/sap_cloud_sdk/core/telemetry/instrumentation/grpc.py create mode 100644 src/sap_cloud_sdk/core/telemetry/instrumentation/httpx.py create mode 100644 src/sap_cloud_sdk/core/telemetry/instrumentation/requests.py create mode 100644 src/sap_cloud_sdk/core/telemetry/instrumentation/starlette.py create mode 100644 tests/core/unit/telemetry/instrumentation/__init__.py create mode 100644 tests/core/unit/telemetry/instrumentation/test_instrumentation.py diff --git a/pyproject.toml b/pyproject.toml index 94264213..dc2c3cb5 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -26,12 +26,17 @@ dependencies = [ "grpcio>=1.60.0", "opentelemetry-api>=1.42.1", "opentelemetry-sdk>=1.42.1", + "opentelemetry-instrumentation-httpx>=0.43b0", + "opentelemetry-instrumentation-requests>=0.43b0", + "opentelemetry-instrumentation-grpc>=0.43b0", "mcp>=1.1.0", ] [project.optional-dependencies] extensibility = ["a2a-sdk>=0.2.0"] -starlette = ["starlette>=0.40.0"] +starlette = ["starlette>=0.40.0", "opentelemetry-instrumentation-starlette>=0.43b0"] +fastapi = ["fastapi>=0.100.0", "opentelemetry-instrumentation-fastapi>=0.43b0"] +aiohttp = ["aiohttp>=3.9.0", "opentelemetry-instrumentation-aiohttp-client>=0.43b0"] langchain = ["langchain-core>=1.2.7"] langgraph = ["langgraph>=1.0.0"] @@ -55,6 +60,11 @@ dev = [ "starlette>=0.40.0", "anyio>=3.6.2", "httpx>=0.27.0", + "fastapi>=0.100.0", + "aiohttp>=3.9.0", + "opentelemetry-instrumentation-starlette>=0.43b0", + "opentelemetry-instrumentation-fastapi>=0.43b0", + "opentelemetry-instrumentation-aiohttp-client>=0.43b0", "a2a-sdk>=0.2.0", "langchain-core>=1.2.7", "langgraph>=0.2.0", diff --git a/src/sap_cloud_sdk/core/telemetry/auto_instrument.py b/src/sap_cloud_sdk/core/telemetry/auto_instrument.py index fdd3c34f..1901eab4 100644 --- a/src/sap_cloud_sdk/core/telemetry/auto_instrument.py +++ b/src/sap_cloud_sdk/core/telemetry/auto_instrument.py @@ -36,6 +36,7 @@ from sap_cloud_sdk.core.telemetry.span_processors.propagated_attributes_processor import ( PropagatedAttributesSpanProcessor, ) +from sap_cloud_sdk.core.telemetry.instrumentation import get_registry logger = logging.getLogger(__name__) @@ -90,6 +91,8 @@ def auto_instrument( if middlewares: _register_middleware_processors(middlewares) + _instrument_libraries() + logger.info("Cloud auto instrumentation initialized successfully") @@ -159,6 +162,11 @@ def _register_middleware_processors(middlewares: list[TelemetryMiddleware]) -> N ) +def _instrument_libraries() -> None: + for instrumentor in get_registry(): + instrumentor.instrument() + + def _merge_resource_attrs_into_active_provider_if_wrapper_installed( sap_attrs: dict, ) -> None: diff --git a/src/sap_cloud_sdk/core/telemetry/instrumentation/__init__.py b/src/sap_cloud_sdk/core/telemetry/instrumentation/__init__.py new file mode 100644 index 00000000..2df9cf4b --- /dev/null +++ b/src/sap_cloud_sdk/core/telemetry/instrumentation/__init__.py @@ -0,0 +1,31 @@ +from sap_cloud_sdk.core.telemetry.instrumentation.base import LibraryInstrumentor +from sap_cloud_sdk.core.telemetry.instrumentation._registry import register, get_registry + +# Import concrete instrumentors to trigger their register() calls. +from sap_cloud_sdk.core.telemetry.instrumentation import ( # noqa: F401 + httpx, + requests, + grpc, +) + +# Optional — guarded so missing extras don't break the import. +try: + from sap_cloud_sdk.core.telemetry.instrumentation import starlette # noqa: F401 +except ImportError: + pass + +try: + from sap_cloud_sdk.core.telemetry.instrumentation import fastapi # noqa: F401 +except ImportError: + pass + +try: + from sap_cloud_sdk.core.telemetry.instrumentation import aiohttp # noqa: F401 +except ImportError: + pass + +__all__ = [ + "LibraryInstrumentor", + "register", + "get_registry", +] diff --git a/src/sap_cloud_sdk/core/telemetry/instrumentation/_registry.py b/src/sap_cloud_sdk/core/telemetry/instrumentation/_registry.py new file mode 100644 index 00000000..d0aa9155 --- /dev/null +++ b/src/sap_cloud_sdk/core/telemetry/instrumentation/_registry.py @@ -0,0 +1,16 @@ +from sap_cloud_sdk.core.telemetry.instrumentation.base import LibraryInstrumentor + +_registry: list[LibraryInstrumentor] = [] + + +def register(instrumentor: LibraryInstrumentor) -> None: + """Add an instrumentor to the registry. + + Call this at module level in each concrete instrumentor file, or from + third-party code that wants to plug in additional library coverage. + """ + _registry.append(instrumentor) + + +def get_registry() -> list[LibraryInstrumentor]: + return list(_registry) diff --git a/src/sap_cloud_sdk/core/telemetry/instrumentation/aiohttp.py b/src/sap_cloud_sdk/core/telemetry/instrumentation/aiohttp.py new file mode 100644 index 00000000..cf4fe019 --- /dev/null +++ b/src/sap_cloud_sdk/core/telemetry/instrumentation/aiohttp.py @@ -0,0 +1,22 @@ +from opentelemetry.instrumentation.aiohttp_client import AioHttpClientInstrumentor + +from sap_cloud_sdk.core.telemetry.instrumentation.base import LibraryInstrumentor +from sap_cloud_sdk.core.telemetry.instrumentation._registry import register + +_instrumentor = AioHttpClientInstrumentor() + + +class AiohttpInstrumentor(LibraryInstrumentor): + library_name = "aiohttp" + + def is_instrumented(self) -> bool: + return _instrumentor.is_instrumented_by_opentelemetry + + def _instrument(self) -> None: + _instrumentor.instrument() + + def _uninstrument(self) -> None: + _instrumentor.uninstrument() + + +register(AiohttpInstrumentor()) diff --git a/src/sap_cloud_sdk/core/telemetry/instrumentation/base.py b/src/sap_cloud_sdk/core/telemetry/instrumentation/base.py new file mode 100644 index 00000000..237c30c8 --- /dev/null +++ b/src/sap_cloud_sdk/core/telemetry/instrumentation/base.py @@ -0,0 +1,48 @@ +import importlib.util +import logging +from abc import ABC, abstractmethod + +logger = logging.getLogger(__name__) + + +class LibraryInstrumentor(ABC): + """Base class for optional library instrumentors. + + Subclasses wrap a single opentelemetry-instrumentation-* package. + The target library (e.g. httpx) is checked at runtime via find_spec so + that missing optional dependencies are silently skipped rather than + raising ImportError. + """ + + #: Import name of the library being instrumented (e.g. "httpx"). + library_name: str + + def instrument(self) -> None: + if not self._is_library_installed(): + logger.debug( + "%s not installed, skipping instrumentation", self.library_name + ) + return + if self.is_instrumented(): + logger.debug("%s already instrumented", self.library_name) + return + self._instrument() + logger.debug("Instrumented %s", self.library_name) + + def uninstrument(self) -> None: + if not self.is_instrumented(): + return + self._uninstrument() + logger.debug("Uninstrumented %s", self.library_name) + + @abstractmethod + def is_instrumented(self) -> bool: ... + + @abstractmethod + def _instrument(self) -> None: ... + + @abstractmethod + def _uninstrument(self) -> None: ... + + def _is_library_installed(self) -> bool: + return importlib.util.find_spec(self.library_name) is not None diff --git a/src/sap_cloud_sdk/core/telemetry/instrumentation/fastapi.py b/src/sap_cloud_sdk/core/telemetry/instrumentation/fastapi.py new file mode 100644 index 00000000..35e9aa81 --- /dev/null +++ b/src/sap_cloud_sdk/core/telemetry/instrumentation/fastapi.py @@ -0,0 +1,22 @@ +from opentelemetry.instrumentation.fastapi import FastAPIInstrumentor + +from sap_cloud_sdk.core.telemetry.instrumentation.base import LibraryInstrumentor +from sap_cloud_sdk.core.telemetry.instrumentation._registry import register + +_instrumentor = FastAPIInstrumentor() + + +class FastAPIInstrumentorWrapper(LibraryInstrumentor): + library_name = "fastapi" + + def is_instrumented(self) -> bool: + return _instrumentor.is_instrumented_by_opentelemetry + + def _instrument(self) -> None: + _instrumentor.instrument() + + def _uninstrument(self) -> None: + _instrumentor.uninstrument() + + +register(FastAPIInstrumentorWrapper()) diff --git a/src/sap_cloud_sdk/core/telemetry/instrumentation/grpc.py b/src/sap_cloud_sdk/core/telemetry/instrumentation/grpc.py new file mode 100644 index 00000000..f7428d77 --- /dev/null +++ b/src/sap_cloud_sdk/core/telemetry/instrumentation/grpc.py @@ -0,0 +1,31 @@ +from opentelemetry.instrumentation.grpc import ( + GrpcInstrumentorClient, + GrpcInstrumentorServer, +) + +from sap_cloud_sdk.core.telemetry.instrumentation.base import LibraryInstrumentor +from sap_cloud_sdk.core.telemetry.instrumentation._registry import register + +_client_instrumentor = GrpcInstrumentorClient() +_server_instrumentor = GrpcInstrumentorServer() + + +class GrpcInstrumentorWrapper(LibraryInstrumentor): + library_name = "grpc" + + def is_instrumented(self) -> bool: + return ( + _client_instrumentor.is_instrumented_by_opentelemetry + or _server_instrumentor.is_instrumented_by_opentelemetry + ) + + def _instrument(self) -> None: + _client_instrumentor.instrument() + _server_instrumentor.instrument() + + def _uninstrument(self) -> None: + _client_instrumentor.uninstrument() + _server_instrumentor.uninstrument() + + +register(GrpcInstrumentorWrapper()) diff --git a/src/sap_cloud_sdk/core/telemetry/instrumentation/httpx.py b/src/sap_cloud_sdk/core/telemetry/instrumentation/httpx.py new file mode 100644 index 00000000..b32cdf11 --- /dev/null +++ b/src/sap_cloud_sdk/core/telemetry/instrumentation/httpx.py @@ -0,0 +1,22 @@ +from opentelemetry.instrumentation.httpx import HTTPXClientInstrumentor + +from sap_cloud_sdk.core.telemetry.instrumentation.base import LibraryInstrumentor +from sap_cloud_sdk.core.telemetry.instrumentation._registry import register + +_instrumentor = HTTPXClientInstrumentor() + + +class HttpxInstrumentor(LibraryInstrumentor): + library_name = "httpx" + + def is_instrumented(self) -> bool: + return _instrumentor.is_instrumented_by_opentelemetry + + def _instrument(self) -> None: + _instrumentor.instrument() + + def _uninstrument(self) -> None: + _instrumentor.uninstrument() + + +register(HttpxInstrumentor()) diff --git a/src/sap_cloud_sdk/core/telemetry/instrumentation/requests.py b/src/sap_cloud_sdk/core/telemetry/instrumentation/requests.py new file mode 100644 index 00000000..d60ad2ea --- /dev/null +++ b/src/sap_cloud_sdk/core/telemetry/instrumentation/requests.py @@ -0,0 +1,22 @@ +from opentelemetry.instrumentation.requests import RequestsInstrumentor + +from sap_cloud_sdk.core.telemetry.instrumentation.base import LibraryInstrumentor +from sap_cloud_sdk.core.telemetry.instrumentation._registry import register + +_instrumentor = RequestsInstrumentor() + + +class RequestsInstrumentorWrapper(LibraryInstrumentor): + library_name = "requests" + + def is_instrumented(self) -> bool: + return _instrumentor.is_instrumented_by_opentelemetry + + def _instrument(self) -> None: + _instrumentor.instrument() + + def _uninstrument(self) -> None: + _instrumentor.uninstrument() + + +register(RequestsInstrumentorWrapper()) diff --git a/src/sap_cloud_sdk/core/telemetry/instrumentation/starlette.py b/src/sap_cloud_sdk/core/telemetry/instrumentation/starlette.py new file mode 100644 index 00000000..363f0ec3 --- /dev/null +++ b/src/sap_cloud_sdk/core/telemetry/instrumentation/starlette.py @@ -0,0 +1,22 @@ +from opentelemetry.instrumentation.starlette import StarletteInstrumentor + +from sap_cloud_sdk.core.telemetry.instrumentation.base import LibraryInstrumentor +from sap_cloud_sdk.core.telemetry.instrumentation._registry import register + +_instrumentor = StarletteInstrumentor() + + +class StarletteInstrumentorWrapper(LibraryInstrumentor): + library_name = "starlette" + + def is_instrumented(self) -> bool: + return _instrumentor.is_instrumented_by_opentelemetry + + def _instrument(self) -> None: + _instrumentor.instrument() + + def _uninstrument(self) -> None: + _instrumentor.uninstrument() + + +register(StarletteInstrumentorWrapper()) diff --git a/tests/core/unit/telemetry/instrumentation/__init__.py b/tests/core/unit/telemetry/instrumentation/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/tests/core/unit/telemetry/instrumentation/test_instrumentation.py b/tests/core/unit/telemetry/instrumentation/test_instrumentation.py new file mode 100644 index 00000000..ca3cd56c --- /dev/null +++ b/tests/core/unit/telemetry/instrumentation/test_instrumentation.py @@ -0,0 +1,221 @@ +import importlib +from unittest.mock import MagicMock, patch + +import pytest + +from sap_cloud_sdk.core.telemetry.instrumentation.base import LibraryInstrumentor +from sap_cloud_sdk.core.telemetry.instrumentation._registry import ( + _registry, + get_registry, + register, +) + + +# --------------------------------------------------------------------------- +# Helpers +# --------------------------------------------------------------------------- + +class _ConcreteInstrumentor(LibraryInstrumentor): + library_name = "sys" # always installed + + def __init__(self): + self._instrumented = False + + def is_instrumented(self) -> bool: + return self._instrumented + + def _instrument(self) -> None: + self._instrumented = True + + def _uninstrument(self) -> None: + self._instrumented = False + + +class _MissingLibraryInstrumentor(LibraryInstrumentor): + library_name = "_nonexistent_library_xyz" + + def __init__(self): + self._instrumented = False + + def is_instrumented(self) -> bool: + return self._instrumented + + def _instrument(self) -> None: + self._instrumented = True + + def _uninstrument(self) -> None: + self._instrumented = False + + +# --------------------------------------------------------------------------- +# Base class +# --------------------------------------------------------------------------- + +class TestLibraryInstrumentor: + def test_instrument_calls_inner_when_library_present(self): + inst = _ConcreteInstrumentor() + inst.instrument() + assert inst.is_instrumented() + + def test_instrument_is_idempotent(self): + inst = _ConcreteInstrumentor() + inst.instrument() + inst._instrument = MagicMock() + inst.instrument() + inst._instrument.assert_not_called() + + def test_instrument_skips_when_library_missing(self): + inst = _MissingLibraryInstrumentor() + inst.instrument() + assert not inst.is_instrumented() + + def test_uninstrument_calls_inner_when_instrumented(self): + inst = _ConcreteInstrumentor() + inst.instrument() + inst.uninstrument() + assert not inst.is_instrumented() + + def test_uninstrument_is_noop_when_not_instrumented(self): + inst = _ConcreteInstrumentor() + inst._uninstrument = MagicMock() + inst.uninstrument() + inst._uninstrument.assert_not_called() + + def test_is_library_installed_true_for_stdlib(self): + inst = _ConcreteInstrumentor() + assert inst._is_library_installed() + + def test_is_library_installed_false_for_missing(self): + inst = _MissingLibraryInstrumentor() + assert not inst._is_library_installed() + + +# --------------------------------------------------------------------------- +# Registry +# --------------------------------------------------------------------------- + +class TestRegistry: + def test_register_appends_to_registry(self): + before = len(_registry) + inst = _ConcreteInstrumentor() + register(inst) + assert len(_registry) == before + 1 + _registry.remove(inst) + + def test_get_registry_returns_copy(self): + snapshot = get_registry() + snapshot.clear() + assert len(get_registry()) > 0 # original unchanged + + def test_builtin_instrumentors_are_registered(self): + names = {type(i).__name__ for i in get_registry()} + assert "HttpxInstrumentor" in names + assert "RequestsInstrumentorWrapper" in names + assert "GrpcInstrumentorWrapper" in names + + +# --------------------------------------------------------------------------- +# Concrete instrumentors — instrument / uninstrument / is_instrumented +# --------------------------------------------------------------------------- + +def _make_otel_instrumentor_mock(is_instrumented: bool = False) -> MagicMock: + m = MagicMock() + m.is_instrumented_by_opentelemetry = is_instrumented + return m + + +class TestHttpxInstrumentor: + def test_instrument_delegates_to_otel(self): + from sap_cloud_sdk.core.telemetry.instrumentation import httpx as httpx_mod + mock = _make_otel_instrumentor_mock() + with patch.object(httpx_mod, "_instrumentor", mock): + httpx_mod.HttpxInstrumentor().instrument() + mock.instrument.assert_called_once() + + def test_is_instrumented_reflects_otel_state(self): + from sap_cloud_sdk.core.telemetry.instrumentation import httpx as httpx_mod + mock = _make_otel_instrumentor_mock(is_instrumented=True) + with patch.object(httpx_mod, "_instrumentor", mock): + assert httpx_mod.HttpxInstrumentor().is_instrumented() + + def test_uninstrument_delegates_to_otel(self): + from sap_cloud_sdk.core.telemetry.instrumentation import httpx as httpx_mod + mock = _make_otel_instrumentor_mock(is_instrumented=True) + with patch.object(httpx_mod, "_instrumentor", mock): + httpx_mod.HttpxInstrumentor().uninstrument() + mock.uninstrument.assert_called_once() + + +class TestRequestsInstrumentor: + def test_instrument_delegates_to_otel(self): + from sap_cloud_sdk.core.telemetry.instrumentation import requests as req_mod + mock = _make_otel_instrumentor_mock() + with patch.object(req_mod, "_instrumentor", mock): + req_mod.RequestsInstrumentorWrapper().instrument() + mock.instrument.assert_called_once() + + def test_uninstrument_delegates_to_otel(self): + from sap_cloud_sdk.core.telemetry.instrumentation import requests as req_mod + mock = _make_otel_instrumentor_mock(is_instrumented=True) + with patch.object(req_mod, "_instrumentor", mock): + req_mod.RequestsInstrumentorWrapper().uninstrument() + mock.uninstrument.assert_called_once() + + +class TestGrpcInstrumentor: + def test_instrument_delegates_to_otel(self): + from sap_cloud_sdk.core.telemetry.instrumentation import grpc as grpc_mod + client_mock = _make_otel_instrumentor_mock() + server_mock = _make_otel_instrumentor_mock() + with ( + patch.object(grpc_mod, "_client_instrumentor", client_mock), + patch.object(grpc_mod, "_server_instrumentor", server_mock), + ): + grpc_mod.GrpcInstrumentorWrapper().instrument() + client_mock.instrument.assert_called_once() + server_mock.instrument.assert_called_once() + + +class TestStarletteInstrumentor: + def test_instrument_delegates_to_otel(self): + pytest.importorskip("starlette") + from sap_cloud_sdk.core.telemetry.instrumentation import starlette as starlette_mod + mock = _make_otel_instrumentor_mock() + with patch.object(starlette_mod, "_instrumentor", mock): + starlette_mod.StarletteInstrumentorWrapper().instrument() + mock.instrument.assert_called_once() + + +class TestFastAPIInstrumentor: + def test_instrument_delegates_to_otel(self): + pytest.importorskip("fastapi") + from sap_cloud_sdk.core.telemetry.instrumentation import fastapi as fastapi_mod + mock = _make_otel_instrumentor_mock() + with patch.object(fastapi_mod, "_instrumentor", mock): + fastapi_mod.FastAPIInstrumentorWrapper().instrument() + mock.instrument.assert_called_once() + + +class TestAiohttpInstrumentor: + def test_instrument_delegates_to_otel(self): + pytest.importorskip("aiohttp") + from sap_cloud_sdk.core.telemetry.instrumentation import aiohttp as aiohttp_mod + mock = _make_otel_instrumentor_mock() + with patch.object(aiohttp_mod, "_instrumentor", mock): + aiohttp_mod.AiohttpInstrumentor().instrument() + mock.instrument.assert_called_once() + + +# --------------------------------------------------------------------------- +# auto_instrument integration +# --------------------------------------------------------------------------- + +class TestAutoInstrumentCallsRegistry: + def test_instrument_libraries_calls_all_registered(self): + from sap_cloud_sdk.core.telemetry.auto_instrument import _instrument_libraries + from sap_cloud_sdk.core.telemetry.instrumentation import _registry as registry_mod + + mock_inst = MagicMock(spec=LibraryInstrumentor) + with patch.object(registry_mod, "_registry", [mock_inst]): + _instrument_libraries() + mock_inst.instrument.assert_called_once() From 4667bc55648aee310134f33310d11a43a12a073b Mon Sep 17 00:00:00 2001 From: Lucas Soares Date: Mon, 20 Jul 2026 10:20:26 +0200 Subject: [PATCH 2/9] File structure --- pyproject.toml | 18 +++++++++--------- .../core/telemetry/instrumentation/__init__.py | 8 ++++---- .../instrumentation/instrumentors/__init__.py | 0 .../{ => instrumentors}/aiohttp.py | 0 .../{ => instrumentors}/fastapi.py | 0 .../{ => instrumentors}/grpc.py | 0 .../{ => instrumentors}/httpx.py | 0 .../{ => instrumentors}/requests.py | 0 .../{ => instrumentors}/starlette.py | 0 9 files changed, 13 insertions(+), 13 deletions(-) create mode 100644 src/sap_cloud_sdk/core/telemetry/instrumentation/instrumentors/__init__.py rename src/sap_cloud_sdk/core/telemetry/instrumentation/{ => instrumentors}/aiohttp.py (100%) rename src/sap_cloud_sdk/core/telemetry/instrumentation/{ => instrumentors}/fastapi.py (100%) rename src/sap_cloud_sdk/core/telemetry/instrumentation/{ => instrumentors}/grpc.py (100%) rename src/sap_cloud_sdk/core/telemetry/instrumentation/{ => instrumentors}/httpx.py (100%) rename src/sap_cloud_sdk/core/telemetry/instrumentation/{ => instrumentors}/requests.py (100%) rename src/sap_cloud_sdk/core/telemetry/instrumentation/{ => instrumentors}/starlette.py (100%) diff --git a/pyproject.toml b/pyproject.toml index dc2c3cb5..98804dd8 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -26,17 +26,17 @@ dependencies = [ "grpcio>=1.60.0", "opentelemetry-api>=1.42.1", "opentelemetry-sdk>=1.42.1", - "opentelemetry-instrumentation-httpx>=0.43b0", - "opentelemetry-instrumentation-requests>=0.43b0", - "opentelemetry-instrumentation-grpc>=0.43b0", + "opentelemetry-instrumentation-httpx~=0.65b0", + "opentelemetry-instrumentation-requests~=0.65b0", + "opentelemetry-instrumentation-grpc~=0.65b0", "mcp>=1.1.0", ] [project.optional-dependencies] extensibility = ["a2a-sdk>=0.2.0"] -starlette = ["starlette>=0.40.0", "opentelemetry-instrumentation-starlette>=0.43b0"] -fastapi = ["fastapi>=0.100.0", "opentelemetry-instrumentation-fastapi>=0.43b0"] -aiohttp = ["aiohttp>=3.9.0", "opentelemetry-instrumentation-aiohttp-client>=0.43b0"] +starlette = ["starlette>=0.40.0", "opentelemetry-instrumentation-starlette~=0.65b0"] +fastapi = ["fastapi>=0.100.0", "opentelemetry-instrumentation-fastapi~=0.65b0"] +aiohttp = ["aiohttp>=3.9.0", "opentelemetry-instrumentation-aiohttp-client~=0.65b0"] langchain = ["langchain-core>=1.2.7"] langgraph = ["langgraph>=1.0.0"] @@ -62,9 +62,9 @@ dev = [ "httpx>=0.27.0", "fastapi>=0.100.0", "aiohttp>=3.9.0", - "opentelemetry-instrumentation-starlette>=0.43b0", - "opentelemetry-instrumentation-fastapi>=0.43b0", - "opentelemetry-instrumentation-aiohttp-client>=0.43b0", + "opentelemetry-instrumentation-starlette~=0.65b0", + "opentelemetry-instrumentation-fastapi~=0.65b0", + "opentelemetry-instrumentation-aiohttp-client~=0.65b0", "a2a-sdk>=0.2.0", "langchain-core>=1.2.7", "langgraph>=0.2.0", diff --git a/src/sap_cloud_sdk/core/telemetry/instrumentation/__init__.py b/src/sap_cloud_sdk/core/telemetry/instrumentation/__init__.py index 2df9cf4b..e98f06e7 100644 --- a/src/sap_cloud_sdk/core/telemetry/instrumentation/__init__.py +++ b/src/sap_cloud_sdk/core/telemetry/instrumentation/__init__.py @@ -2,7 +2,7 @@ from sap_cloud_sdk.core.telemetry.instrumentation._registry import register, get_registry # Import concrete instrumentors to trigger their register() calls. -from sap_cloud_sdk.core.telemetry.instrumentation import ( # noqa: F401 +from sap_cloud_sdk.core.telemetry.instrumentation.instrumentors import ( # noqa: F401 httpx, requests, grpc, @@ -10,17 +10,17 @@ # Optional — guarded so missing extras don't break the import. try: - from sap_cloud_sdk.core.telemetry.instrumentation import starlette # noqa: F401 + from sap_cloud_sdk.core.telemetry.instrumentation.instrumentors import starlette # noqa: F401 except ImportError: pass try: - from sap_cloud_sdk.core.telemetry.instrumentation import fastapi # noqa: F401 + from sap_cloud_sdk.core.telemetry.instrumentation.instrumentors import fastapi # noqa: F401 except ImportError: pass try: - from sap_cloud_sdk.core.telemetry.instrumentation import aiohttp # noqa: F401 + from sap_cloud_sdk.core.telemetry.instrumentation.instrumentors import aiohttp # noqa: F401 except ImportError: pass diff --git a/src/sap_cloud_sdk/core/telemetry/instrumentation/instrumentors/__init__.py b/src/sap_cloud_sdk/core/telemetry/instrumentation/instrumentors/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/src/sap_cloud_sdk/core/telemetry/instrumentation/aiohttp.py b/src/sap_cloud_sdk/core/telemetry/instrumentation/instrumentors/aiohttp.py similarity index 100% rename from src/sap_cloud_sdk/core/telemetry/instrumentation/aiohttp.py rename to src/sap_cloud_sdk/core/telemetry/instrumentation/instrumentors/aiohttp.py diff --git a/src/sap_cloud_sdk/core/telemetry/instrumentation/fastapi.py b/src/sap_cloud_sdk/core/telemetry/instrumentation/instrumentors/fastapi.py similarity index 100% rename from src/sap_cloud_sdk/core/telemetry/instrumentation/fastapi.py rename to src/sap_cloud_sdk/core/telemetry/instrumentation/instrumentors/fastapi.py diff --git a/src/sap_cloud_sdk/core/telemetry/instrumentation/grpc.py b/src/sap_cloud_sdk/core/telemetry/instrumentation/instrumentors/grpc.py similarity index 100% rename from src/sap_cloud_sdk/core/telemetry/instrumentation/grpc.py rename to src/sap_cloud_sdk/core/telemetry/instrumentation/instrumentors/grpc.py diff --git a/src/sap_cloud_sdk/core/telemetry/instrumentation/httpx.py b/src/sap_cloud_sdk/core/telemetry/instrumentation/instrumentors/httpx.py similarity index 100% rename from src/sap_cloud_sdk/core/telemetry/instrumentation/httpx.py rename to src/sap_cloud_sdk/core/telemetry/instrumentation/instrumentors/httpx.py diff --git a/src/sap_cloud_sdk/core/telemetry/instrumentation/requests.py b/src/sap_cloud_sdk/core/telemetry/instrumentation/instrumentors/requests.py similarity index 100% rename from src/sap_cloud_sdk/core/telemetry/instrumentation/requests.py rename to src/sap_cloud_sdk/core/telemetry/instrumentation/instrumentors/requests.py diff --git a/src/sap_cloud_sdk/core/telemetry/instrumentation/starlette.py b/src/sap_cloud_sdk/core/telemetry/instrumentation/instrumentors/starlette.py similarity index 100% rename from src/sap_cloud_sdk/core/telemetry/instrumentation/starlette.py rename to src/sap_cloud_sdk/core/telemetry/instrumentation/instrumentors/starlette.py From c0306cd7e34a0a18397d266337e6953876d8d5f2 Mon Sep 17 00:00:00 2001 From: Lucas Soares Date: Mon, 20 Jul 2026 10:57:08 +0200 Subject: [PATCH 3/9] redis and sqlalchemy --- pyproject.toml | 25 +++++++----- .../telemetry/instrumentation/__init__.py | 10 +++++ .../instrumentation/instrumentors/redis.py | 22 +++++++++++ .../instrumentors/sqlalchemy.py | 22 +++++++++++ .../instrumentation/test_instrumentation.py | 38 ++++++++++++++----- 5 files changed, 98 insertions(+), 19 deletions(-) create mode 100644 src/sap_cloud_sdk/core/telemetry/instrumentation/instrumentors/redis.py create mode 100644 src/sap_cloud_sdk/core/telemetry/instrumentation/instrumentors/sqlalchemy.py diff --git a/pyproject.toml b/pyproject.toml index 98804dd8..24ae269a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -26,18 +26,24 @@ dependencies = [ "grpcio>=1.60.0", "opentelemetry-api>=1.42.1", "opentelemetry-sdk>=1.42.1", - "opentelemetry-instrumentation-httpx~=0.65b0", - "opentelemetry-instrumentation-requests~=0.65b0", - "opentelemetry-instrumentation-grpc~=0.65b0", + "opentelemetry-instrumentation-httpx>=0.65b0", + "opentelemetry-instrumentation-requests>=0.65b0", + "opentelemetry-instrumentation-grpc>=0.65b0", + "opentelemetry-instrumentation-starlette>=0.65b0", + "opentelemetry-instrumentation-fastapi>=0.65b0", + "opentelemetry-instrumentation-aiohttp-client>=0.65b0", + "opentelemetry-instrumentation-sqlalchemy>=0.65b0", + "opentelemetry-instrumentation-redis>=0.65b0", "mcp>=1.1.0", ] [project.optional-dependencies] extensibility = ["a2a-sdk>=0.2.0"] -starlette = ["starlette>=0.40.0", "opentelemetry-instrumentation-starlette~=0.65b0"] -fastapi = ["fastapi>=0.100.0", "opentelemetry-instrumentation-fastapi~=0.65b0"] -aiohttp = ["aiohttp>=3.9.0", "opentelemetry-instrumentation-aiohttp-client~=0.65b0"] -langchain = ["langchain-core>=1.2.7"] +starlette = ["starlette>=0.40.0"] +fastapi = ["fastapi>=0.100.0"] +aiohttp = ["aiohttp>=3.9.0"] +sqlalchemy = ["sqlalchemy>=2.0.0"] +redis = ["redis>=5.0.0"] langgraph = ["langgraph>=1.0.0"] [build-system] @@ -62,9 +68,8 @@ dev = [ "httpx>=0.27.0", "fastapi>=0.100.0", "aiohttp>=3.9.0", - "opentelemetry-instrumentation-starlette~=0.65b0", - "opentelemetry-instrumentation-fastapi~=0.65b0", - "opentelemetry-instrumentation-aiohttp-client~=0.65b0", + "sqlalchemy>=2.0.0", + "redis>=5.0.0", "a2a-sdk>=0.2.0", "langchain-core>=1.2.7", "langgraph>=0.2.0", diff --git a/src/sap_cloud_sdk/core/telemetry/instrumentation/__init__.py b/src/sap_cloud_sdk/core/telemetry/instrumentation/__init__.py index e98f06e7..e73726d9 100644 --- a/src/sap_cloud_sdk/core/telemetry/instrumentation/__init__.py +++ b/src/sap_cloud_sdk/core/telemetry/instrumentation/__init__.py @@ -24,6 +24,16 @@ except ImportError: pass +try: + from sap_cloud_sdk.core.telemetry.instrumentation.instrumentors import sqlalchemy # noqa: F401 +except ImportError: + pass + +try: + from sap_cloud_sdk.core.telemetry.instrumentation.instrumentors import redis # noqa: F401 +except ImportError: + pass + __all__ = [ "LibraryInstrumentor", "register", diff --git a/src/sap_cloud_sdk/core/telemetry/instrumentation/instrumentors/redis.py b/src/sap_cloud_sdk/core/telemetry/instrumentation/instrumentors/redis.py new file mode 100644 index 00000000..6440063c --- /dev/null +++ b/src/sap_cloud_sdk/core/telemetry/instrumentation/instrumentors/redis.py @@ -0,0 +1,22 @@ +from opentelemetry.instrumentation.redis import RedisInstrumentor + +from sap_cloud_sdk.core.telemetry.instrumentation.base import LibraryInstrumentor +from sap_cloud_sdk.core.telemetry.instrumentation._registry import register + +_instrumentor = RedisInstrumentor() + + +class RedisInstrumentorWrapper(LibraryInstrumentor): + library_name = "redis" + + def is_instrumented(self) -> bool: + return _instrumentor.is_instrumented_by_opentelemetry + + def _instrument(self) -> None: + _instrumentor.instrument() + + def _uninstrument(self) -> None: + _instrumentor.uninstrument() + + +register(RedisInstrumentorWrapper()) diff --git a/src/sap_cloud_sdk/core/telemetry/instrumentation/instrumentors/sqlalchemy.py b/src/sap_cloud_sdk/core/telemetry/instrumentation/instrumentors/sqlalchemy.py new file mode 100644 index 00000000..1f865198 --- /dev/null +++ b/src/sap_cloud_sdk/core/telemetry/instrumentation/instrumentors/sqlalchemy.py @@ -0,0 +1,22 @@ +from opentelemetry.instrumentation.sqlalchemy import SQLAlchemyInstrumentor + +from sap_cloud_sdk.core.telemetry.instrumentation.base import LibraryInstrumentor +from sap_cloud_sdk.core.telemetry.instrumentation._registry import register + +_instrumentor = SQLAlchemyInstrumentor() + + +class SQLAlchemyInstrumentorWrapper(LibraryInstrumentor): + library_name = "sqlalchemy" + + def is_instrumented(self) -> bool: + return _instrumentor.is_instrumented_by_opentelemetry + + def _instrument(self) -> None: + _instrumentor.instrument() + + def _uninstrument(self) -> None: + _instrumentor.uninstrument() + + +register(SQLAlchemyInstrumentorWrapper()) diff --git a/tests/core/unit/telemetry/instrumentation/test_instrumentation.py b/tests/core/unit/telemetry/instrumentation/test_instrumentation.py index ca3cd56c..5a73c95d 100644 --- a/tests/core/unit/telemetry/instrumentation/test_instrumentation.py +++ b/tests/core/unit/telemetry/instrumentation/test_instrumentation.py @@ -126,20 +126,20 @@ def _make_otel_instrumentor_mock(is_instrumented: bool = False) -> MagicMock: class TestHttpxInstrumentor: def test_instrument_delegates_to_otel(self): - from sap_cloud_sdk.core.telemetry.instrumentation import httpx as httpx_mod + from sap_cloud_sdk.core.telemetry.instrumentation.instrumentors import httpx as httpx_mod mock = _make_otel_instrumentor_mock() with patch.object(httpx_mod, "_instrumentor", mock): httpx_mod.HttpxInstrumentor().instrument() mock.instrument.assert_called_once() def test_is_instrumented_reflects_otel_state(self): - from sap_cloud_sdk.core.telemetry.instrumentation import httpx as httpx_mod + from sap_cloud_sdk.core.telemetry.instrumentation.instrumentors import httpx as httpx_mod mock = _make_otel_instrumentor_mock(is_instrumented=True) with patch.object(httpx_mod, "_instrumentor", mock): assert httpx_mod.HttpxInstrumentor().is_instrumented() def test_uninstrument_delegates_to_otel(self): - from sap_cloud_sdk.core.telemetry.instrumentation import httpx as httpx_mod + from sap_cloud_sdk.core.telemetry.instrumentation.instrumentors import httpx as httpx_mod mock = _make_otel_instrumentor_mock(is_instrumented=True) with patch.object(httpx_mod, "_instrumentor", mock): httpx_mod.HttpxInstrumentor().uninstrument() @@ -148,14 +148,14 @@ def test_uninstrument_delegates_to_otel(self): class TestRequestsInstrumentor: def test_instrument_delegates_to_otel(self): - from sap_cloud_sdk.core.telemetry.instrumentation import requests as req_mod + from sap_cloud_sdk.core.telemetry.instrumentation.instrumentors import requests as req_mod mock = _make_otel_instrumentor_mock() with patch.object(req_mod, "_instrumentor", mock): req_mod.RequestsInstrumentorWrapper().instrument() mock.instrument.assert_called_once() def test_uninstrument_delegates_to_otel(self): - from sap_cloud_sdk.core.telemetry.instrumentation import requests as req_mod + from sap_cloud_sdk.core.telemetry.instrumentation.instrumentors import requests as req_mod mock = _make_otel_instrumentor_mock(is_instrumented=True) with patch.object(req_mod, "_instrumentor", mock): req_mod.RequestsInstrumentorWrapper().uninstrument() @@ -164,7 +164,7 @@ def test_uninstrument_delegates_to_otel(self): class TestGrpcInstrumentor: def test_instrument_delegates_to_otel(self): - from sap_cloud_sdk.core.telemetry.instrumentation import grpc as grpc_mod + from sap_cloud_sdk.core.telemetry.instrumentation.instrumentors import grpc as grpc_mod client_mock = _make_otel_instrumentor_mock() server_mock = _make_otel_instrumentor_mock() with ( @@ -179,7 +179,7 @@ def test_instrument_delegates_to_otel(self): class TestStarletteInstrumentor: def test_instrument_delegates_to_otel(self): pytest.importorskip("starlette") - from sap_cloud_sdk.core.telemetry.instrumentation import starlette as starlette_mod + from sap_cloud_sdk.core.telemetry.instrumentation.instrumentors import starlette as starlette_mod mock = _make_otel_instrumentor_mock() with patch.object(starlette_mod, "_instrumentor", mock): starlette_mod.StarletteInstrumentorWrapper().instrument() @@ -189,7 +189,7 @@ def test_instrument_delegates_to_otel(self): class TestFastAPIInstrumentor: def test_instrument_delegates_to_otel(self): pytest.importorskip("fastapi") - from sap_cloud_sdk.core.telemetry.instrumentation import fastapi as fastapi_mod + from sap_cloud_sdk.core.telemetry.instrumentation.instrumentors import fastapi as fastapi_mod mock = _make_otel_instrumentor_mock() with patch.object(fastapi_mod, "_instrumentor", mock): fastapi_mod.FastAPIInstrumentorWrapper().instrument() @@ -199,13 +199,33 @@ def test_instrument_delegates_to_otel(self): class TestAiohttpInstrumentor: def test_instrument_delegates_to_otel(self): pytest.importorskip("aiohttp") - from sap_cloud_sdk.core.telemetry.instrumentation import aiohttp as aiohttp_mod + from sap_cloud_sdk.core.telemetry.instrumentation.instrumentors import aiohttp as aiohttp_mod mock = _make_otel_instrumentor_mock() with patch.object(aiohttp_mod, "_instrumentor", mock): aiohttp_mod.AiohttpInstrumentor().instrument() mock.instrument.assert_called_once() +class TestSQLAlchemyInstrumentor: + def test_instrument_delegates_to_otel(self): + pytest.importorskip("sqlalchemy") + from sap_cloud_sdk.core.telemetry.instrumentation.instrumentors import sqlalchemy as sqlalchemy_mod + mock = _make_otel_instrumentor_mock() + with patch.object(sqlalchemy_mod, "_instrumentor", mock): + sqlalchemy_mod.SQLAlchemyInstrumentorWrapper().instrument() + mock.instrument.assert_called_once() + + +class TestRedisInstrumentor: + def test_instrument_delegates_to_otel(self): + pytest.importorskip("redis") + from sap_cloud_sdk.core.telemetry.instrumentation.instrumentors import redis as redis_mod + mock = _make_otel_instrumentor_mock() + with patch.object(redis_mod, "_instrumentor", mock): + redis_mod.RedisInstrumentorWrapper().instrument() + mock.instrument.assert_called_once() + + # --------------------------------------------------------------------------- # auto_instrument integration # --------------------------------------------------------------------------- From 8ec08a6d0ab71976e59cc1acbbff95dacadd1ab7 Mon Sep 17 00:00:00 2001 From: Lucas Soares Date: Mon, 20 Jul 2026 11:06:54 +0200 Subject: [PATCH 4/9] django and flask --- pyproject.toml | 6 +++++ .../telemetry/instrumentation/__init__.py | 10 +++++++++ .../instrumentation/instrumentors/django.py | 22 +++++++++++++++++++ .../instrumentation/instrumentors/flask.py | 22 +++++++++++++++++++ .../instrumentation/test_instrumentation.py | 20 +++++++++++++++++ 5 files changed, 80 insertions(+) create mode 100644 src/sap_cloud_sdk/core/telemetry/instrumentation/instrumentors/django.py create mode 100644 src/sap_cloud_sdk/core/telemetry/instrumentation/instrumentors/flask.py diff --git a/pyproject.toml b/pyproject.toml index 24ae269a..fa961ab1 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -34,6 +34,8 @@ dependencies = [ "opentelemetry-instrumentation-aiohttp-client>=0.65b0", "opentelemetry-instrumentation-sqlalchemy>=0.65b0", "opentelemetry-instrumentation-redis>=0.65b0", + "opentelemetry-instrumentation-django>=0.65b0", + "opentelemetry-instrumentation-flask>=0.65b0", "mcp>=1.1.0", ] @@ -44,6 +46,8 @@ fastapi = ["fastapi>=0.100.0"] aiohttp = ["aiohttp>=3.9.0"] sqlalchemy = ["sqlalchemy>=2.0.0"] redis = ["redis>=5.0.0"] +django = ["django>=4.0"] +flask = ["flask>=3.0"] langgraph = ["langgraph>=1.0.0"] [build-system] @@ -70,6 +74,8 @@ dev = [ "aiohttp>=3.9.0", "sqlalchemy>=2.0.0", "redis>=5.0.0", + "django>=4.0", + "flask>=3.0", "a2a-sdk>=0.2.0", "langchain-core>=1.2.7", "langgraph>=0.2.0", diff --git a/src/sap_cloud_sdk/core/telemetry/instrumentation/__init__.py b/src/sap_cloud_sdk/core/telemetry/instrumentation/__init__.py index e73726d9..7529a118 100644 --- a/src/sap_cloud_sdk/core/telemetry/instrumentation/__init__.py +++ b/src/sap_cloud_sdk/core/telemetry/instrumentation/__init__.py @@ -34,6 +34,16 @@ except ImportError: pass +try: + from sap_cloud_sdk.core.telemetry.instrumentation.instrumentors import django # noqa: F401 +except ImportError: + pass + +try: + from sap_cloud_sdk.core.telemetry.instrumentation.instrumentors import flask # noqa: F401 +except ImportError: + pass + __all__ = [ "LibraryInstrumentor", "register", diff --git a/src/sap_cloud_sdk/core/telemetry/instrumentation/instrumentors/django.py b/src/sap_cloud_sdk/core/telemetry/instrumentation/instrumentors/django.py new file mode 100644 index 00000000..8cf4fc83 --- /dev/null +++ b/src/sap_cloud_sdk/core/telemetry/instrumentation/instrumentors/django.py @@ -0,0 +1,22 @@ +from opentelemetry.instrumentation.django import DjangoInstrumentor + +from sap_cloud_sdk.core.telemetry.instrumentation.base import LibraryInstrumentor +from sap_cloud_sdk.core.telemetry.instrumentation._registry import register + +_instrumentor = DjangoInstrumentor() + + +class DjangoInstrumentorWrapper(LibraryInstrumentor): + library_name = "django" + + def is_instrumented(self) -> bool: + return _instrumentor.is_instrumented_by_opentelemetry + + def _instrument(self) -> None: + _instrumentor.instrument() + + def _uninstrument(self) -> None: + _instrumentor.uninstrument() + + +register(DjangoInstrumentorWrapper()) diff --git a/src/sap_cloud_sdk/core/telemetry/instrumentation/instrumentors/flask.py b/src/sap_cloud_sdk/core/telemetry/instrumentation/instrumentors/flask.py new file mode 100644 index 00000000..3574e154 --- /dev/null +++ b/src/sap_cloud_sdk/core/telemetry/instrumentation/instrumentors/flask.py @@ -0,0 +1,22 @@ +from opentelemetry.instrumentation.flask import FlaskInstrumentor + +from sap_cloud_sdk.core.telemetry.instrumentation.base import LibraryInstrumentor +from sap_cloud_sdk.core.telemetry.instrumentation._registry import register + +_instrumentor = FlaskInstrumentor() + + +class FlaskInstrumentorWrapper(LibraryInstrumentor): + library_name = "flask" + + def is_instrumented(self) -> bool: + return _instrumentor.is_instrumented_by_opentelemetry + + def _instrument(self) -> None: + _instrumentor.instrument() + + def _uninstrument(self) -> None: + _instrumentor.uninstrument() + + +register(FlaskInstrumentorWrapper()) diff --git a/tests/core/unit/telemetry/instrumentation/test_instrumentation.py b/tests/core/unit/telemetry/instrumentation/test_instrumentation.py index 5a73c95d..8e2b187f 100644 --- a/tests/core/unit/telemetry/instrumentation/test_instrumentation.py +++ b/tests/core/unit/telemetry/instrumentation/test_instrumentation.py @@ -226,6 +226,26 @@ def test_instrument_delegates_to_otel(self): mock.instrument.assert_called_once() +class TestDjangoInstrumentor: + def test_instrument_delegates_to_otel(self): + pytest.importorskip("django") + from sap_cloud_sdk.core.telemetry.instrumentation.instrumentors import django as django_mod + mock = _make_otel_instrumentor_mock() + with patch.object(django_mod, "_instrumentor", mock): + django_mod.DjangoInstrumentorWrapper().instrument() + mock.instrument.assert_called_once() + + +class TestFlaskInstrumentor: + def test_instrument_delegates_to_otel(self): + pytest.importorskip("flask") + from sap_cloud_sdk.core.telemetry.instrumentation.instrumentors import flask as flask_mod + mock = _make_otel_instrumentor_mock() + with patch.object(flask_mod, "_instrumentor", mock): + flask_mod.FlaskInstrumentorWrapper().instrument() + mock.instrument.assert_called_once() + + # --------------------------------------------------------------------------- # auto_instrument integration # --------------------------------------------------------------------------- From 10dd2cdf8dce9ae7c98a014d314d6eeb11cd6845 Mon Sep 17 00:00:00 2001 From: Lucas Soares Date: Mon, 20 Jul 2026 11:08:10 +0200 Subject: [PATCH 5/9] version bump --- pyproject.toml | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index fa961ab1..6ce54502 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -26,16 +26,16 @@ dependencies = [ "grpcio>=1.60.0", "opentelemetry-api>=1.42.1", "opentelemetry-sdk>=1.42.1", - "opentelemetry-instrumentation-httpx>=0.65b0", - "opentelemetry-instrumentation-requests>=0.65b0", - "opentelemetry-instrumentation-grpc>=0.65b0", - "opentelemetry-instrumentation-starlette>=0.65b0", - "opentelemetry-instrumentation-fastapi>=0.65b0", - "opentelemetry-instrumentation-aiohttp-client>=0.65b0", - "opentelemetry-instrumentation-sqlalchemy>=0.65b0", - "opentelemetry-instrumentation-redis>=0.65b0", - "opentelemetry-instrumentation-django>=0.65b0", - "opentelemetry-instrumentation-flask>=0.65b0", + "opentelemetry-instrumentation-httpx~=0.63b1", + "opentelemetry-instrumentation-requests~=0.63b1", + "opentelemetry-instrumentation-grpc~=0.63b1", + "opentelemetry-instrumentation-starlette~=0.63b1", + "opentelemetry-instrumentation-fastapi~=0.63b1", + "opentelemetry-instrumentation-aiohttp-client~=0.63b1", + "opentelemetry-instrumentation-sqlalchemy~=0.63b1", + "opentelemetry-instrumentation-redis~=0.63b1", + "opentelemetry-instrumentation-django~=0.63b1", + "opentelemetry-instrumentation-flask~=0.63b1", "mcp>=1.1.0", ] From 65e8d1422c9b1b504a6da381881f4b34828f432f Mon Sep 17 00:00:00 2001 From: Lucas Soares Date: Mon, 20 Jul 2026 11:25:01 +0200 Subject: [PATCH 6/9] user guide --- .../core/telemetry/user-guide.md | 25 +++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/src/sap_cloud_sdk/core/telemetry/user-guide.md b/src/sap_cloud_sdk/core/telemetry/user-guide.md index 019bc1ac..c76c6c8a 100644 --- a/src/sap_cloud_sdk/core/telemetry/user-guide.md +++ b/src/sap_cloud_sdk/core/telemetry/user-guide.md @@ -52,6 +52,31 @@ def handle_request(request): --- +## Library instrumentation + +`auto_instrument()` automatically instruments any supported library that is already installed in the service — no extra configuration needed. If a library is not installed, it is silently skipped. + +**Supported libraries:** + +| Library | What is traced | +|--------------|------------------------------------------| +| `httpx` | Outbound HTTP requests (sync and async) | +| `requests` | Outbound HTTP requests | +| `grpcio` | gRPC client and server calls | +| `starlette` | Inbound HTTP requests | +| `fastapi` | Inbound HTTP requests with route details | +| `aiohttp` | Outbound async HTTP requests | +| `django` | Inbound HTTP requests | +| `flask` | Inbound HTTP requests | +| `sqlalchemy` | Database queries | +| `redis` | Redis commands | + +Instrumentation activates based on what is installed in the service, not on what extras were used to install the SDK. If your service has `django` in its own requirements, the SDK will instrument it automatically. + +The SDK ships `opentelemetry-instrumentation-*` packages for all of the above as hard dependencies. The target frameworks themselves are optional — install them via your service's own requirements or via the SDK's convenience extras (e.g. `sap-cloud-sdk[django]`). + +--- + ## Span functions For operations following [OpenTelemetry GenAI conventions](https://opentelemetry.io/docs/specs/semconv/gen-ai/gen-ai-agent-spans/): From fb1c06271e2eb436353b85081b215764cec7e334 Mon Sep 17 00:00:00 2001 From: Lucas Soares Date: Mon, 20 Jul 2026 16:33:44 +0200 Subject: [PATCH 7/9] Logging instrumentation --- pyproject.toml | 1 + .../telemetry/instrumentation/__init__.py | 1 + .../instrumentation/instrumentors/logging.py | 22 +++++++++++++++++++ .../core/telemetry/user-guide.md | 1 + .../instrumentation/test_instrumentation.py | 10 +++++++++ 5 files changed, 35 insertions(+) create mode 100644 src/sap_cloud_sdk/core/telemetry/instrumentation/instrumentors/logging.py diff --git a/pyproject.toml b/pyproject.toml index 6ce54502..1847da26 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -29,6 +29,7 @@ dependencies = [ "opentelemetry-instrumentation-httpx~=0.63b1", "opentelemetry-instrumentation-requests~=0.63b1", "opentelemetry-instrumentation-grpc~=0.63b1", + "opentelemetry-instrumentation-logging~=0.63b1", "opentelemetry-instrumentation-starlette~=0.63b1", "opentelemetry-instrumentation-fastapi~=0.63b1", "opentelemetry-instrumentation-aiohttp-client~=0.63b1", diff --git a/src/sap_cloud_sdk/core/telemetry/instrumentation/__init__.py b/src/sap_cloud_sdk/core/telemetry/instrumentation/__init__.py index 7529a118..ddcb2628 100644 --- a/src/sap_cloud_sdk/core/telemetry/instrumentation/__init__.py +++ b/src/sap_cloud_sdk/core/telemetry/instrumentation/__init__.py @@ -6,6 +6,7 @@ httpx, requests, grpc, + logging, ) # Optional — guarded so missing extras don't break the import. diff --git a/src/sap_cloud_sdk/core/telemetry/instrumentation/instrumentors/logging.py b/src/sap_cloud_sdk/core/telemetry/instrumentation/instrumentors/logging.py new file mode 100644 index 00000000..a61a67b4 --- /dev/null +++ b/src/sap_cloud_sdk/core/telemetry/instrumentation/instrumentors/logging.py @@ -0,0 +1,22 @@ +from opentelemetry.instrumentation.logging import LoggingInstrumentor + +from sap_cloud_sdk.core.telemetry.instrumentation.base import LibraryInstrumentor +from sap_cloud_sdk.core.telemetry.instrumentation._registry import register + +_instrumentor = LoggingInstrumentor() + + +class LoggingInstrumentorWrapper(LibraryInstrumentor): + library_name = "logging" + + def is_instrumented(self) -> bool: + return _instrumentor.is_instrumented_by_opentelemetry + + def _instrument(self) -> None: + _instrumentor.instrument(set_logging_format=True) + + def _uninstrument(self) -> None: + _instrumentor.uninstrument() + + +register(LoggingInstrumentorWrapper()) diff --git a/src/sap_cloud_sdk/core/telemetry/user-guide.md b/src/sap_cloud_sdk/core/telemetry/user-guide.md index c76c6c8a..a6e597a1 100644 --- a/src/sap_cloud_sdk/core/telemetry/user-guide.md +++ b/src/sap_cloud_sdk/core/telemetry/user-guide.md @@ -70,6 +70,7 @@ def handle_request(request): | `flask` | Inbound HTTP requests | | `sqlalchemy` | Database queries | | `redis` | Redis commands | +| `logging` | Injects `trace_id` and `span_id` into every log record for log-trace correlation | Instrumentation activates based on what is installed in the service, not on what extras were used to install the SDK. If your service has `django` in its own requirements, the SDK will instrument it automatically. diff --git a/tests/core/unit/telemetry/instrumentation/test_instrumentation.py b/tests/core/unit/telemetry/instrumentation/test_instrumentation.py index 8e2b187f..ef489e26 100644 --- a/tests/core/unit/telemetry/instrumentation/test_instrumentation.py +++ b/tests/core/unit/telemetry/instrumentation/test_instrumentation.py @@ -112,6 +112,7 @@ def test_builtin_instrumentors_are_registered(self): assert "HttpxInstrumentor" in names assert "RequestsInstrumentorWrapper" in names assert "GrpcInstrumentorWrapper" in names + assert "LoggingInstrumentorWrapper" in names # --------------------------------------------------------------------------- @@ -162,6 +163,15 @@ def test_uninstrument_delegates_to_otel(self): mock.uninstrument.assert_called_once() +class TestLoggingInstrumentor: + def test_instrument_delegates_to_otel(self): + from sap_cloud_sdk.core.telemetry.instrumentation.instrumentors import logging as logging_mod + mock = _make_otel_instrumentor_mock() + with patch.object(logging_mod, "_instrumentor", mock): + logging_mod.LoggingInstrumentorWrapper().instrument() + mock.instrument.assert_called_once_with(set_logging_format=True) + + class TestGrpcInstrumentor: def test_instrument_delegates_to_otel(self): from sap_cloud_sdk.core.telemetry.instrumentation.instrumentors import grpc as grpc_mod From 42f2393772c9f861dbc56dec87437c6748c9f15c Mon Sep 17 00:00:00 2001 From: Lucas Soares Date: Tue, 21 Jul 2026 09:26:48 +0200 Subject: [PATCH 8/9] Version bump --- pyproject.toml | 2 +- uv.lock | 348 ++++++++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 344 insertions(+), 6 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 1847da26..734168e7 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "sap-cloud-sdk" -version = "0.37.0" +version = "0.38.0" description = "SAP Cloud SDK for Python" readme = "README.md" license = "Apache-2.0" diff --git a/uv.lock b/uv.lock index c4d7eca3..9275783b 100644 --- a/uv.lock +++ b/uv.lock @@ -257,6 +257,24 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/42/b9/f8d6fa329ab25128b7e98fd83a3cb34d9db5b059a9847eddb840a0af45dd/argon2_cffi_bindings-25.1.0-cp39-abi3-win_arm64.whl", hash = "sha256:b0fdbcf513833809c882823f98dc2f931cf659d9a1429616ac3adebb49f5db94", size = 27149, upload-time = "2025-07-30T10:01:59.329Z" }, ] +[[package]] +name = "asgiref" +version = "3.12.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/e6/26/3b59f2bdae5f640389becb1f673cded775287f5fc4f816309d9ca9a3f93d/asgiref-3.12.1.tar.gz", hash = "sha256:59dcb51c272ad209d59bed5708a64a333083e86017d7fcdd67498eeab7784340", size = 42378, upload-time = "2026-07-14T09:56:18.087Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c0/1b/54f4ad77cd8a584fa70746c47df988e002cf1ee1eba43364d46f87803647/asgiref-3.12.1-py3-none-any.whl", hash = "sha256:fe386d1c2bff7259ea95929266d12a8cf9a8b5a1c2598402967d8792e7a7c094", size = 25478, upload-time = "2026-07-14T09:56:16.926Z" }, +] + +[[package]] +name = "async-timeout" +version = "5.0.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/a5/ae/136395dfbfe00dfc94da3f3e136d0b13f394cba8f4841120e34226265780/async_timeout-5.0.1.tar.gz", hash = "sha256:d9321a7a3d5a6a5e187e824d2fa0793ce379a202935782d555d6e9d2735677d3", size = 9274, upload-time = "2024-11-06T16:41:39.6Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/fe/ba/e2081de779ca30d473f21f5b30e0e737c438205440784c7dfc81efc2b029/async_timeout-5.0.1-py3-none-any.whl", hash = "sha256:39e3809566ff85354557ec2398b55e096c8364bacac9405a7a1fa429e77fe76c", size = 6233, upload-time = "2024-11-06T16:41:37.9Z" }, +] + [[package]] name = "attrs" version = "25.4.0" @@ -266,6 +284,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/3a/2a/7cc015f5b9f5db42b7d48157e23356022889fc354a2813c15934b7cb5c0e/attrs-25.4.0-py3-none-any.whl", hash = "sha256:adcf7e2a1fb3b36ac48d97835bb6d8ade15b8dcce26aba8bf1d14847b57a3373", size = 67615, upload-time = "2025-10-06T13:54:43.17Z" }, ] +[[package]] +name = "blinker" +version = "1.9.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/21/28/9b3f50ce0e048515135495f198351908d99540d69bfdc8c1d15b73dc55ce/blinker-1.9.0.tar.gz", hash = "sha256:b4ce2265a7abece45e7cc896e98dbebe6cead56bcf805a3d23136d145f5445bf", size = 22460, upload-time = "2024-11-08T17:25:47.436Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/10/cb/f2ad4230dc2eb1a74edf38f1a38b9b52277f75bef262d8908e60d957e13c/blinker-1.9.0-py3-none-any.whl", hash = "sha256:ba0efaa9080b619ff2f3459d1d500c57bddea4a6b424b60a91141db6fd2f08bc", size = 8458, upload-time = "2024-11-08T17:25:46.184Z" }, +] + [[package]] name = "cel-python" version = "0.5.0" @@ -639,6 +666,59 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/12/b3/231ffd4ab1fc9d679809f356cebee130ac7daa00d6d6f3206dd4fd137e9e/distro-1.9.0-py3-none-any.whl", hash = "sha256:7bffd925d65168f85027d8da9af6bddab658135b840670a223589bc0c8ef02b2", size = 20277, upload-time = "2023-12-24T09:54:30.421Z" }, ] +[[package]] +name = "django" +version = "5.2.16" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version < '3.12'", +] +dependencies = [ + { name = "asgiref", marker = "python_full_version < '3.12'" }, + { name = "sqlparse", marker = "python_full_version < '3.12'" }, + { name = "tzdata", marker = "python_full_version < '3.12' and sys_platform == 'win32'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/a9/26/889449d521ae508b26de715954faecd8bcf3f740affb81b2d146a83b42a5/django-5.2.16.tar.gz", hash = "sha256:59ea02020c3136fce14bef0bbece21a10a4febef5eed1c51c22ae468efa22200", size = 10890894, upload-time = "2026-07-07T13:52:17.005Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/4e/13/1e5e3e4c15dcecb04281b3cb2a46a4670e1cef131068e202f6040df19224/django-5.2.16-py3-none-any.whl", hash = "sha256:04f354bf9d807a86ad1a8392fe3808d362358a8eafc322848e0e43e59b24371d", size = 8311943, upload-time = "2026-07-07T13:52:11.223Z" }, +] + +[[package]] +name = "django" +version = "6.0.7" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.15'", + "python_full_version == '3.14.*'", + "python_full_version == '3.13.*'", + "python_full_version == '3.12.*'", +] +dependencies = [ + { name = "asgiref", marker = "python_full_version >= '3.12'" }, + { name = "sqlparse", marker = "python_full_version >= '3.12'" }, + { name = "tzdata", marker = "python_full_version >= '3.12' and sys_platform == 'win32'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/89/55/664f24ff81c9ea19cb7dfc851afeae1f3c2390c7aee01d4ded68b5c1580d/django-6.0.7.tar.gz", hash = "sha256:2998503fc083124fb58037084bfa00de323c7c743f05f1b4284e77bff0ab8890", size = 10921299, upload-time = "2026-07-07T13:51:26.485Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ba/ec/1ce5334b6a2c52ce619c23a0be8d366a57a0e080ebb2d88266e5c849157c/django-6.0.7-py3-none-any.whl", hash = "sha256:a037427c2288443a8c02a1b02295a31c239663aa682bc50b1976afb7cf6a769e", size = 8373344, upload-time = "2026-07-07T13:51:20.007Z" }, +] + +[[package]] +name = "fastapi" +version = "0.139.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "annotated-doc" }, + { name = "pydantic" }, + { name = "starlette" }, + { name = "typing-extensions" }, + { name = "typing-inspection" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/cd/95/d3f0ae10836324a2eab98a52b61210ac609f08200bf4bb0dc8132d32f78a/fastapi-0.139.2.tar.gz", hash = "sha256:333145a6891e9b5b3cfceb69baf817e8240cde4d4588ae5a10bf56ffacb6255e", size = 423428, upload-time = "2026-07-16T15:06:17.912Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/5f/c7/cb03251d9dfb177246a9809a76f189d21df32dbd4a845951881d11323b7f/fastapi-0.139.2-py3-none-any.whl", hash = "sha256:b9ad015a835173d59865e2f5d8296fbc2b317bf56a2ba1a5bfbdd03de2fd4b1c", size = 130234, upload-time = "2026-07-16T15:06:19.557Z" }, +] + [[package]] name = "fastuuid" version = "0.14.0" @@ -700,6 +780,23 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/4c/a0/614c5fe402fd88951df45f4dda2fa3b4e17a99ecd92340771929169b3b95/filelock-3.29.1-py3-none-any.whl", hash = "sha256:85199dfd706869641b72b2e8955d5416a4b2b7dc4b0e8e6d97b4cc1299a6983b", size = 40750, upload-time = "2026-06-03T15:19:02.959Z" }, ] +[[package]] +name = "flask" +version = "3.1.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "blinker" }, + { name = "click" }, + { name = "itsdangerous" }, + { name = "jinja2" }, + { name = "markupsafe" }, + { name = "werkzeug" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/26/00/35d85dcce6c57fdc871f3867d465d780f302a175ea360f62533f12b27e2b/flask-3.1.3.tar.gz", hash = "sha256:0ef0e52b8a9cd932855379197dd8f94047b359ca0a78695144304cb45f87c9eb", size = 759004, upload-time = "2026-02-19T05:00:57.678Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7f/9c/34f6962f9b9e9c71f6e5ed806e0d0ff03c9d1b0b2340088a0cf4bce09b18/flask-3.1.3-py3-none-any.whl", hash = "sha256:f4bcbefc124291925f1a26446da31a5178f9483862233b23c0c96a20701f670c", size = 103424, upload-time = "2026-02-19T05:00:56.027Z" }, +] + [[package]] name = "frozenlist" version = "1.8.0" @@ -1182,6 +1279,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/2c/e1/e6716421ea10d38022b952c159d5161ca1193197fb744506875fbb87ea7b/iniconfig-2.1.0-py3-none-any.whl", hash = "sha256:9deba5723312380e77435581c6bf4935c94cbfab9b1ed33ef8d238ea168eb760", size = 6050, upload-time = "2025-03-19T20:10:01.071Z" }, ] +[[package]] +name = "itsdangerous" +version = "2.2.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/9c/cb/8ac0172223afbccb63986cc25049b154ecfb5e85932587206f42317be31d/itsdangerous-2.2.0.tar.gz", hash = "sha256:e0050c0b7da1eea53ffaf149c0cfbb5c6e2e2b69c4bef22c81fa6eb73e5f6173", size = 54410, upload-time = "2024-04-16T21:28:15.614Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/04/96/92447566d16df59b2a776c0fb82dbc4d9e07cd95062562af01e408583fc4/itsdangerous-2.2.0-py3-none-any.whl", hash = "sha256:c6242fc49e35958c8b15141343aa660db5fc54d4f13a1db01a3f5891b98700ef", size = 16234, upload-time = "2024-04-16T21:28:14.499Z" }, +] + [[package]] name = "jinja2" version = "3.1.6" @@ -2042,6 +2148,22 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/02/04/21a5c8e0da4f00c89954402fb70c6520804c0163db6800ed28ae890ccc49/opentelemetry_instrumentation_agno-0.60.0-py3-none-any.whl", hash = "sha256:191529e70cdde4d7cfb405e17b75bc93b7883b197aabaa007c820a82617dbb71", size = 8902, upload-time = "2026-04-19T12:41:50.501Z" }, ] +[[package]] +name = "opentelemetry-instrumentation-aiohttp-client" +version = "0.63b1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "opentelemetry-api" }, + { name = "opentelemetry-instrumentation" }, + { name = "opentelemetry-semantic-conventions" }, + { name = "opentelemetry-util-http" }, + { name = "wrapt" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/47/6f/e7105760ec528b465238a06a05f8e6c358063e00ad53fed76fd625c6230c/opentelemetry_instrumentation_aiohttp_client-0.63b1.tar.gz", hash = "sha256:ec97399c02a7e278359efffdf16e93d59a7103b16f66790cda9b9496b171b136", size = 19041, upload-time = "2026-05-21T16:36:15.62Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/41/f8/f18666128e4b602601316ee73f35986c0a42ce44a615fd6b0f566c15e282/opentelemetry_instrumentation_aiohttp_client-0.63b1-py3-none-any.whl", hash = "sha256:5259c2c5103a5919941e0c45f2c95b055a50eb2ab39dc252f4b1e41ce6d984bb", size = 13675, upload-time = "2026-05-21T16:34:59.263Z" }, +] + [[package]] name = "opentelemetry-instrumentation-alephalpha" version = "0.60.0" @@ -2072,6 +2194,22 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/f3/af/0fcc8cf246888404ea31da14f3115d418d7f52b140361bfa2da983b96e96/opentelemetry_instrumentation_anthropic-0.60.0-py3-none-any.whl", hash = "sha256:04a366e235c32b185b676b3b5ce2a995d4d508e473c6b6881aeb24369290bb80", size = 19264, upload-time = "2026-04-19T12:41:52.997Z" }, ] +[[package]] +name = "opentelemetry-instrumentation-asgi" +version = "0.63b1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "asgiref" }, + { name = "opentelemetry-api" }, + { name = "opentelemetry-instrumentation" }, + { name = "opentelemetry-semantic-conventions" }, + { name = "opentelemetry-util-http" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/a0/b5/7ea3a9fd1b80e89786c14250bfaecf32a753c3fd08232690f4da8dc16e29/opentelemetry_instrumentation_asgi-0.63b1.tar.gz", hash = "sha256:267b422416d768f3c7f4054883b41d9c3a7c943d86d20032b738c99a3dbb5862", size = 26151, upload-time = "2026-05-21T16:36:18.368Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/57/7e/83986f27b421de04fab1e1a84e892621dac42e6432a9c66779505f4d1381/opentelemetry_instrumentation_asgi-0.63b1-py3-none-any.whl", hash = "sha256:1a22453dfa965f14799b10a674b8acbcb897a8a75c79136060af54214cc7886e", size = 15906, upload-time = "2026-05-21T16:35:04.162Z" }, +] + [[package]] name = "opentelemetry-instrumentation-bedrock" version = "0.60.0" @@ -2132,6 +2270,55 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/80/e6/5cd72dd49236c9a4adf5ade77c712926d368b57cf760d2b9cd86594e96c0/opentelemetry_instrumentation_crewai-0.60.0-py3-none-any.whl", hash = "sha256:2d9ed4a093793bf68619b4df3a39c25b16ae01ca199fb4a3d65fc7912de11138", size = 8691, upload-time = "2026-04-19T12:41:58.104Z" }, ] +[[package]] +name = "opentelemetry-instrumentation-django" +version = "0.63b1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "opentelemetry-api" }, + { name = "opentelemetry-instrumentation" }, + { name = "opentelemetry-instrumentation-wsgi" }, + { name = "opentelemetry-semantic-conventions" }, + { name = "opentelemetry-util-http" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/f7/37/90fd5f0dc4a2042dfdb609a9dbe42fde26b3690f9c33ece529b024251015/opentelemetry_instrumentation_django-0.63b1.tar.gz", hash = "sha256:f2071d2f92e4779c5a14dd452b0dfe426343599e6efa9d888304fb639a9f3101", size = 25565, upload-time = "2026-05-21T16:36:27.727Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c8/14/3efdfb34393f6780ab3dc916dba9221e7be5f07eaa768c4575e14cea98fa/opentelemetry_instrumentation_django-0.63b1-py3-none-any.whl", hash = "sha256:909ea4afbb18f16eb811d7ac330ed0f6fda7765191d49bccb8cca38c4fbc005f", size = 19125, upload-time = "2026-05-21T16:35:25.29Z" }, +] + +[[package]] +name = "opentelemetry-instrumentation-fastapi" +version = "0.63b1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "opentelemetry-api" }, + { name = "opentelemetry-instrumentation" }, + { name = "opentelemetry-instrumentation-asgi" }, + { name = "opentelemetry-semantic-conventions" }, + { name = "opentelemetry-util-http" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/32/d6/0c128fac2e34b7d526a8d3c6edc45b875a97f8a987861b00511151b6337d/opentelemetry_instrumentation_fastapi-0.63b1.tar.gz", hash = "sha256:cc42dff56c96d0a2921510c4abab2a4c2e27fe64b26dc1254727fb550df100ba", size = 25387, upload-time = "2026-05-21T16:36:32.071Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b1/3d/2eae63f13f36d7a8ab5bf03d06ecaf169c2069b524547f24947be6d92094/opentelemetry_instrumentation_fastapi-0.63b1-py3-none-any.whl", hash = "sha256:52ee2cde9a2ac094bdd45d79f85860e03a972928a2553006071fe61d94cf7281", size = 12795, upload-time = "2026-05-21T16:35:28.68Z" }, +] + +[[package]] +name = "opentelemetry-instrumentation-flask" +version = "0.63b1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "opentelemetry-api" }, + { name = "opentelemetry-instrumentation" }, + { name = "opentelemetry-instrumentation-wsgi" }, + { name = "opentelemetry-semantic-conventions" }, + { name = "opentelemetry-util-http" }, + { name = "packaging" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/fd/8d/78ad1354ec4df8ec2817ff86fac16cf1c9f80cce9e17797e0f515bee1943/opentelemetry_instrumentation_flask-0.63b1.tar.gz", hash = "sha256:60dd3c54107fad5b2615f811a45a8600259a78e4105034f87741b092c2011f89", size = 23873, upload-time = "2026-05-21T16:36:32.857Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/57/c1/84f17990829744c4d53d504f4d0623a4ad0cd9c11ca1052ff77d80d547c9/opentelemetry_instrumentation_flask-0.63b1-py3-none-any.whl", hash = "sha256:482f073083845f9f45200f2384de0018afab90fbe666c114ac71b7a3dc033264", size = 15071, upload-time = "2026-05-21T16:35:29.696Z" }, +] + [[package]] name = "opentelemetry-instrumentation-google-generativeai" version = "0.60.0" @@ -2162,6 +2349,21 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/22/52/eb1e8e61435d3d9a8a8a10d59024d2668cfb5869d57e7e5fc154476fd920/opentelemetry_instrumentation_groq-0.60.0-py3-none-any.whl", hash = "sha256:e6058679a212e0948769f35ae4288450b0d96afa7e6de9f3a29268b233094859", size = 12442, upload-time = "2026-04-19T12:42:00.493Z" }, ] +[[package]] +name = "opentelemetry-instrumentation-grpc" +version = "0.63b1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "opentelemetry-api" }, + { name = "opentelemetry-instrumentation" }, + { name = "opentelemetry-semantic-conventions" }, + { name = "wrapt" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/db/84/7c297486a6e9b99db041e5bd47f054aab29422be69659272908e292ed57a/opentelemetry_instrumentation_grpc-0.63b1.tar.gz", hash = "sha256:48d93e06872208a15ed720555ded9f366039924f41f3e16ea0da6ec937701e84", size = 31746, upload-time = "2026-05-21T16:36:33.561Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/fd/2a/f4ad01d283550affaf3fa434671e3f6625095a4de90b9156bebaa4426aba/opentelemetry_instrumentation_grpc-0.63b1-py3-none-any.whl", hash = "sha256:40d30a2c5e97f90a28a8c4e688e368b4dcacc7654d358489cea92744311ff8d9", size = 24244, upload-time = "2026-05-21T16:35:31.098Z" }, +] + [[package]] name = "opentelemetry-instrumentation-haystack" version = "0.60.0" @@ -2177,6 +2379,22 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/0f/60/9031936740f3bce5dddde49674f49ce67a36d7560d06e067b2e977910858/opentelemetry_instrumentation_haystack-0.60.0-py3-none-any.whl", hash = "sha256:ddbb5cf0e9467f493d4b0eead7cc756a6861eafc26ee9627ba56a6d5a872ff5e", size = 7506, upload-time = "2026-04-19T12:42:01.8Z" }, ] +[[package]] +name = "opentelemetry-instrumentation-httpx" +version = "0.63b1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "opentelemetry-api" }, + { name = "opentelemetry-instrumentation" }, + { name = "opentelemetry-semantic-conventions" }, + { name = "opentelemetry-util-http" }, + { name = "wrapt" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/02/27/c2b4335bca030e893acbe5ff2b4f434868773bf94508be7e6bf5af981b24/opentelemetry_instrumentation_httpx-0.63b1.tar.gz", hash = "sha256:f41ec82f25c3abcdada621052db3e5fd648e3b43d55eec4b9c0c5d3ecb7b4ff4", size = 23557, upload-time = "2026-05-21T16:36:34.583Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ba/b8/f536780996195c3b9f2354998554671e05a7a262df8c043f63fe9e5a6f0b/opentelemetry_instrumentation_httpx-0.63b1-py3-none-any.whl", hash = "sha256:14df6e99d81be9a8cd238f6639b6fa52404c4d3ce219058fcb5dc8c0f2211f86", size = 16336, upload-time = "2026-05-21T16:35:32.221Z" }, +] + [[package]] name = "opentelemetry-instrumentation-lancedb" version = "0.60.0" @@ -2448,6 +2666,22 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/3c/bc/c0984c4c51da64cc2c37ce031b4fb7fab61d223f2188a6bc6b5f18035ae3/opentelemetry_instrumentation_sqlalchemy-0.63b1-py3-none-any.whl", hash = "sha256:d417414f6517963e9c1ee91ec971b94938b46904499114d035a43937bd62b6a1", size = 14410, upload-time = "2026-05-21T16:35:53.342Z" }, ] +[[package]] +name = "opentelemetry-instrumentation-starlette" +version = "0.63b1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "opentelemetry-api" }, + { name = "opentelemetry-instrumentation" }, + { name = "opentelemetry-instrumentation-asgi" }, + { name = "opentelemetry-semantic-conventions" }, + { name = "opentelemetry-util-http" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/1e/22/3b1ba84543f7311b4a26b8735bbfa64e54eb2a0560976e703bc22cd7f3cd/opentelemetry_instrumentation_starlette-0.63b1.tar.gz", hash = "sha256:e6cc1798169362d2ef000905477d145b75798d45c955b181f07622a10d3d6c37", size = 14384, upload-time = "2026-05-21T16:36:48.539Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a3/b1/cbd8f3185186054b25a9f8ab2ee7ed70b950de15269c888dac4e3698de46/opentelemetry_instrumentation_starlette-0.63b1-py3-none-any.whl", hash = "sha256:824dbcc9039b89cee24df641d02aad6132cb09c9c67c2fd6ef55955ae90252c5", size = 11104, upload-time = "2026-05-21T16:35:55.906Z" }, +] + [[package]] name = "opentelemetry-instrumentation-threading" version = "0.63b1" @@ -2583,6 +2817,21 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/5e/a3/f2c38b8cd448ee536870bc7771d863ec57232446c850124adcd08bc8e3c0/opentelemetry_instrumentation_writer-0.52.3-py3-none-any.whl", hash = "sha256:c5155413d6f4e6ae7b842d6b1b2df21a244969c669e3ffb78a7ef7c57afb1da5", size = 11516, upload-time = "2026-02-10T14:54:43.197Z" }, ] +[[package]] +name = "opentelemetry-instrumentation-wsgi" +version = "0.63b1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "opentelemetry-api" }, + { name = "opentelemetry-instrumentation" }, + { name = "opentelemetry-semantic-conventions" }, + { name = "opentelemetry-util-http" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/0b/55/832f287fb153adc25c05bc2594d00ac4d1dbeca8b19388b2666d5154c912/opentelemetry_instrumentation_wsgi-0.63b1.tar.gz", hash = "sha256:03d61c4678ce82402e7f37b6a3dbd84cb97b85b3cb416a78c2e74c7c6d9451fa", size = 19667, upload-time = "2026-05-21T16:36:53.873Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/14/b6/0854591a78960f376c7f943a6927c2863e1f8b0c93003aafe03aa49c089b/opentelemetry_instrumentation_wsgi-0.63b1-py3-none-any.whl", hash = "sha256:86779715262227d3436bdfb16aabd1c524b0f236725a69e8754ceda76c6d79dc", size = 13786, upload-time = "2026-05-21T16:36:05.221Z" }, +] + [[package]] name = "opentelemetry-proto" version = "1.42.1" @@ -3379,6 +3628,18 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/f1/12/de94a39c2ef588c7e6455cfbe7343d3b2dc9d6b6b2f40c4c6565744c873d/pyyaml-6.0.3-cp314-cp314t-win_arm64.whl", hash = "sha256:ebc55a14a21cb14062aa4162f906cd962b28e2e9ea38f9b4391244cd8de4ae0b", size = 149341, upload-time = "2025-09-25T21:32:56.828Z" }, ] +[[package]] +name = "redis" +version = "8.0.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "async-timeout", marker = "python_full_version < '3.11.3'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/cc/c3/928b290c2c0ca99ab96eea5b4ff8f30be8112b075301a7d3ba214a3c8c12/redis-8.0.1.tar.gz", hash = "sha256:afc5a7a2f5a084f5b1880dec548dd45be17db7e43c82a30d84f952aefb05cfb0", size = 5114170, upload-time = "2026-06-23T14:52:37.728Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/fd/0a/c2345ebf1ebe70840ce3f6c6ee612f8fa749cfbd1b03069c53bf0c62aaad/redis-8.0.1-py3-none-any.whl", hash = "sha256:47daa35a058c23468d6437f17a8c76882cb316b838ef763036af99b96cedd743", size = 502406, upload-time = "2026-06-23T14:52:36.137Z" }, +] + [[package]] name = "referencing" version = "0.37.0" @@ -3685,7 +3946,7 @@ wheels = [ [[package]] name = "sap-cloud-sdk" -version = "0.36.0" +version = "0.38.0" source = { editable = "." } dependencies = [ { name = "grpcio" }, @@ -3696,7 +3957,18 @@ dependencies = [ { name = "opentelemetry-api" }, { name = "opentelemetry-exporter-otlp-proto-grpc" }, { name = "opentelemetry-exporter-otlp-proto-http" }, + { name = "opentelemetry-instrumentation-aiohttp-client" }, + { name = "opentelemetry-instrumentation-django" }, + { name = "opentelemetry-instrumentation-fastapi" }, + { name = "opentelemetry-instrumentation-flask" }, + { name = "opentelemetry-instrumentation-grpc" }, + { name = "opentelemetry-instrumentation-httpx" }, { name = "opentelemetry-instrumentation-langchain" }, + { name = "opentelemetry-instrumentation-logging" }, + { name = "opentelemetry-instrumentation-redis" }, + { name = "opentelemetry-instrumentation-requests" }, + { name = "opentelemetry-instrumentation-sqlalchemy" }, + { name = "opentelemetry-instrumentation-starlette" }, { name = "opentelemetry-sdk" }, { name = "protobuf" }, { name = "protovalidate" }, @@ -3709,15 +3981,31 @@ dependencies = [ ] [package.optional-dependencies] +aiohttp = [ + { name = "aiohttp" }, +] +django = [ + { name = "django", version = "5.2.16", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.12'" }, + { name = "django", version = "6.0.7", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.12'" }, +] extensibility = [ { name = "a2a-sdk" }, ] -langchain = [ - { name = "langchain-core" }, +fastapi = [ + { name = "fastapi" }, +] +flask = [ + { name = "flask" }, ] langgraph = [ { name = "langgraph" }, ] +redis = [ + { name = "redis" }, +] +sqlalchemy = [ + { name = "sqlalchemy" }, +] starlette = [ { name = "starlette" }, ] @@ -3725,8 +4013,13 @@ starlette = [ [package.dev-dependencies] dev = [ { name = "a2a-sdk" }, + { name = "aiohttp" }, { name = "anyio" }, { name = "cryptography" }, + { name = "django", version = "5.2.16", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.12'" }, + { name = "django", version = "6.0.7", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.12'" }, + { name = "fastapi" }, + { name = "flask" }, { name = "httpx" }, { name = "langchain-community" }, { name = "langchain-core" }, @@ -3738,7 +4031,9 @@ dev = [ { name = "pytest-bdd" }, { name = "pytest-cov" }, { name = "python-dotenv" }, + { name = "redis" }, { name = "ruff" }, + { name = "sqlalchemy" }, { name = "starlette" }, { name = "ty" }, ] @@ -3746,35 +4041,55 @@ dev = [ [package.metadata] requires-dist = [ { name = "a2a-sdk", marker = "extra == 'extensibility'", specifier = ">=0.2.0" }, + { name = "aiohttp", marker = "extra == 'aiohttp'", specifier = ">=3.9.0" }, + { name = "django", marker = "extra == 'django'", specifier = ">=4.0" }, + { name = "fastapi", marker = "extra == 'fastapi'", specifier = ">=0.100.0" }, + { name = "flask", marker = "extra == 'flask'", specifier = ">=3.0" }, { name = "grpcio", specifier = ">=1.60.0" }, { name = "hatchling", specifier = "~=1.27.0" }, { name = "httpx", specifier = ">=0.27.0" }, - { name = "langchain-core", marker = "extra == 'langchain'", specifier = ">=1.2.7" }, { name = "langgraph", marker = "extra == 'langgraph'", specifier = ">=1.0.0" }, { name = "mcp", specifier = ">=1.1.0" }, { name = "minio", specifier = "~=7.2.16" }, { name = "opentelemetry-api", specifier = ">=1.42.1" }, { name = "opentelemetry-exporter-otlp-proto-grpc", specifier = "~=1.42.1" }, { name = "opentelemetry-exporter-otlp-proto-http", specifier = "~=1.42.1" }, + { name = "opentelemetry-instrumentation-aiohttp-client", specifier = "~=0.63b1" }, + { name = "opentelemetry-instrumentation-django", specifier = "~=0.63b1" }, + { name = "opentelemetry-instrumentation-fastapi", specifier = "~=0.63b1" }, + { name = "opentelemetry-instrumentation-flask", specifier = "~=0.63b1" }, + { name = "opentelemetry-instrumentation-grpc", specifier = "~=0.63b1" }, + { name = "opentelemetry-instrumentation-httpx", specifier = "~=0.63b1" }, { name = "opentelemetry-instrumentation-langchain", specifier = ">=0.61.0" }, + { name = "opentelemetry-instrumentation-logging", specifier = "~=0.63b1" }, + { name = "opentelemetry-instrumentation-redis", specifier = "~=0.63b1" }, + { name = "opentelemetry-instrumentation-requests", specifier = "~=0.63b1" }, + { name = "opentelemetry-instrumentation-sqlalchemy", specifier = "~=0.63b1" }, + { name = "opentelemetry-instrumentation-starlette", specifier = "~=0.63b1" }, { name = "opentelemetry-sdk", specifier = ">=1.42.1" }, { name = "protobuf", specifier = ">=4.25.0" }, { name = "protovalidate", specifier = ">=0.13.0" }, { name = "pydantic", specifier = "~=2.12.3" }, { name = "pyjwt", specifier = ">=2.13.0" }, + { name = "redis", marker = "extra == 'redis'", specifier = ">=5.0.0" }, { name = "requests", specifier = ">=2.33.0" }, { name = "requests-oauthlib", specifier = "~=2.0.0" }, { name = "setuptools", specifier = "~=80.9.0" }, + { name = "sqlalchemy", marker = "extra == 'sqlalchemy'", specifier = ">=2.0.0" }, { name = "starlette", marker = "extra == 'starlette'", specifier = ">=0.40.0" }, { name = "traceloop-sdk", specifier = "~=0.61.0" }, ] -provides-extras = ["extensibility", "starlette", "langchain", "langgraph"] +provides-extras = ["extensibility", "starlette", "fastapi", "aiohttp", "sqlalchemy", "redis", "django", "flask", "langgraph"] [package.metadata.requires-dev] dev = [ { name = "a2a-sdk", specifier = ">=0.2.0" }, + { name = "aiohttp", specifier = ">=3.9.0" }, { name = "anyio", specifier = ">=3.6.2" }, { name = "cryptography", specifier = ">=46.0.3" }, + { name = "django", specifier = ">=4.0" }, + { name = "fastapi", specifier = ">=0.100.0" }, + { name = "flask", specifier = ">=3.0" }, { name = "httpx", specifier = ">=0.27.0" }, { name = "langchain-community", specifier = ">=0.3.0" }, { name = "langchain-core", specifier = ">=1.2.7" }, @@ -3786,7 +4101,9 @@ dev = [ { name = "pytest-bdd", specifier = ">=7.2.0" }, { name = "pytest-cov", specifier = ">=7.0.0" }, { name = "python-dotenv", specifier = ">=1.0.0" }, + { name = "redis", specifier = ">=5.0.0" }, { name = "ruff", specifier = ">=0.8.0" }, + { name = "sqlalchemy", specifier = ">=2.0.0" }, { name = "starlette", specifier = ">=0.40.0" }, { name = "ty", specifier = ">=0.0.21" }, ] @@ -3875,6 +4192,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/d0/10/f7220e9b784d295d241c86ed99aeb537f92afcd469a64861f2717e9bb077/sqlalchemy-2.0.50-py3-none-any.whl", hash = "sha256:92064363517a3ff8212b5a93b8c62876579d8dfd1ca5b561335f30152d884fa9", size = 1943861, upload-time = "2026-05-24T19:59:01.119Z" }, ] +[[package]] +name = "sqlparse" +version = "0.5.5" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/90/76/437d71068094df0726366574cf3432a4ed754217b436eb7429415cf2d480/sqlparse-0.5.5.tar.gz", hash = "sha256:e20d4a9b0b8585fdf63b10d30066c7c94c5d7a7ec47c889a2d83a3caa93ff28e", size = 120815, upload-time = "2025-12-19T07:17:45.073Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/49/4b/359f28a903c13438ef59ebeee215fb25da53066db67b305c125f1c6d2a25/sqlparse-0.5.5-py3-none-any.whl", hash = "sha256:12a08b3bf3eec877c519589833aed092e2444e68240a3577e8e26148acc7b1ba", size = 46138, upload-time = "2025-12-19T07:17:46.573Z" }, +] + [[package]] name = "sse-starlette" version = "3.4.2" @@ -4374,6 +4700,18 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/6f/28/258ebab549c2bf3e64d2b0217b973467394a9cea8c42f70418ca2c5d0d2e/websockets-16.0-py3-none-any.whl", hash = "sha256:1637db62fad1dc833276dded54215f2c7fa46912301a24bd94d45d46a011ceec", size = 171598, upload-time = "2026-01-10T09:23:45.395Z" }, ] +[[package]] +name = "werkzeug" +version = "3.1.8" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "markupsafe" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/dd/b2/381be8cfdee792dd117872481b6e378f85c957dd7c5bca38897b08f765fd/werkzeug-3.1.8.tar.gz", hash = "sha256:9bad61a4268dac112f1c5cd4630a56ede601b6ed420300677a869083d70a4c44", size = 875852, upload-time = "2026-04-02T18:49:14.268Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/93/8c/2e650f2afeb7ee576912636c23ddb621c91ac6a98e66dc8d29c3c69446e1/werkzeug-3.1.8-py3-none-any.whl", hash = "sha256:63a77fb8892bf28ebc3178683445222aa500e48ebad5ec77b0ad80f8726b1f50", size = 226459, upload-time = "2026-04-02T18:49:12.72Z" }, +] + [[package]] name = "wrapt" version = "1.17.3" From b5e6ce3376072e99b2ec6b94ebca83e23c2e2214 Mon Sep 17 00:00:00 2001 From: Lucas Soares Date: Tue, 21 Jul 2026 10:26:08 +0200 Subject: [PATCH 9/9] Linting --- src/sap_cloud_sdk/core/telemetry/instrumentation/__init__.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/sap_cloud_sdk/core/telemetry/instrumentation/__init__.py b/src/sap_cloud_sdk/core/telemetry/instrumentation/__init__.py index ddcb2628..f92b88a6 100644 --- a/src/sap_cloud_sdk/core/telemetry/instrumentation/__init__.py +++ b/src/sap_cloud_sdk/core/telemetry/instrumentation/__init__.py @@ -1,5 +1,8 @@ from sap_cloud_sdk.core.telemetry.instrumentation.base import LibraryInstrumentor -from sap_cloud_sdk.core.telemetry.instrumentation._registry import register, get_registry +from sap_cloud_sdk.core.telemetry.instrumentation._registry import ( + register, + get_registry, +) # Import concrete instrumentors to trigger their register() calls. from sap_cloud_sdk.core.telemetry.instrumentation.instrumentors import ( # noqa: F401