From 0d560f9440a14841f0ade9c1254c0e00d31d8931 Mon Sep 17 00:00:00 2001 From: Phillip Verheyden Date: Fri, 17 Oct 2025 08:54:22 -0500 Subject: [PATCH 1/3] Replace asyncio.iscoroutinefunction with inspect.iscoroutinefunction Fixes #3879 --- CHANGELOG.md | 4 ++++ .../src/opentelemetry/instrumentation/aiokafka/__init__.py | 2 +- .../opentelemetry/instrumentation/aiopg/aiopg_integration.py | 4 ++-- .../src/opentelemetry/instrumentation/httpx/__init__.py | 2 +- .../tests/test_httpx_integration.py | 5 +++-- 5 files changed, 11 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 024990c91d..8692ebb707 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## Unreleased +### Fixed + +- Replace Python 3.14-deprecated `asyncio.iscoroutinefunction` with `inspect.iscoroutinefunction`. ([#3880](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/3880)) + ## Version 1.38.0/0.59b0 (2025-10-16) ### Fixed diff --git a/instrumentation/opentelemetry-instrumentation-aiokafka/src/opentelemetry/instrumentation/aiokafka/__init__.py b/instrumentation/opentelemetry-instrumentation-aiokafka/src/opentelemetry/instrumentation/aiokafka/__init__.py index 32837b23c5..d4662c9329 100644 --- a/instrumentation/opentelemetry-instrumentation-aiokafka/src/opentelemetry/instrumentation/aiokafka/__init__.py +++ b/instrumentation/opentelemetry-instrumentation-aiokafka/src/opentelemetry/instrumentation/aiokafka/__init__.py @@ -95,7 +95,7 @@ async def produce(): from __future__ import annotations -from asyncio import iscoroutinefunction +from inspect import iscoroutinefunction from typing import TYPE_CHECKING, Collection import aiokafka diff --git a/instrumentation/opentelemetry-instrumentation-aiopg/src/opentelemetry/instrumentation/aiopg/aiopg_integration.py b/instrumentation/opentelemetry-instrumentation-aiopg/src/opentelemetry/instrumentation/aiopg/aiopg_integration.py index 4e6257fbb1..948050818c 100644 --- a/instrumentation/opentelemetry-instrumentation-aiopg/src/opentelemetry/instrumentation/aiopg/aiopg_integration.py +++ b/instrumentation/opentelemetry-instrumentation-aiopg/src/opentelemetry/instrumentation/aiopg/aiopg_integration.py @@ -1,4 +1,4 @@ -import asyncio +import inspect import typing from collections.abc import Coroutine @@ -197,7 +197,7 @@ async def __aenter__(self): async def __aexit__(self, exc_type, exc, t_b): try: - if asyncio.iscoroutinefunction(self._obj.close): + if inspect.iscoroutinefunction(self._obj.close): await self._obj.close() else: self._obj.close() diff --git a/instrumentation/opentelemetry-instrumentation-httpx/src/opentelemetry/instrumentation/httpx/__init__.py b/instrumentation/opentelemetry-instrumentation-httpx/src/opentelemetry/instrumentation/httpx/__init__.py index c93f9b71c5..2a27c98f77 100644 --- a/instrumentation/opentelemetry-instrumentation-httpx/src/opentelemetry/instrumentation/httpx/__init__.py +++ b/instrumentation/opentelemetry-instrumentation-httpx/src/opentelemetry/instrumentation/httpx/__init__.py @@ -207,7 +207,7 @@ async def async_response_hook(span, request, response): import logging import typing -from asyncio import iscoroutinefunction +from inspect import iscoroutinefunction from functools import partial from timeit import default_timer from types import TracebackType diff --git a/instrumentation/opentelemetry-instrumentation-httpx/tests/test_httpx_integration.py b/instrumentation/opentelemetry-instrumentation-httpx/tests/test_httpx_integration.py index 2812aa7a2f..9f40f26dcf 100644 --- a/instrumentation/opentelemetry-instrumentation-httpx/tests/test_httpx_integration.py +++ b/instrumentation/opentelemetry-instrumentation-httpx/tests/test_httpx_integration.py @@ -16,6 +16,7 @@ import abc import asyncio +import inspect import typing from unittest import mock @@ -1046,7 +1047,7 @@ def test_custom_tracer_provider(self): def test_response_hook(self): response_hook_key = ( "async_response_hook" - if asyncio.iscoroutinefunction(self.response_hook) + if inspect.iscoroutinefunction(self.response_hook) else "response_hook" ) response_hook_kwargs = {response_hook_key: self.response_hook} @@ -1093,7 +1094,7 @@ def test_response_hook_sync_async_kwargs(self): def test_request_hook(self): request_hook_key = ( "async_request_hook" - if asyncio.iscoroutinefunction(self.request_hook) + if inspect.iscoroutinefunction(self.request_hook) else "request_hook" ) request_hook_kwargs = {request_hook_key: self.request_hook} From 488f793115192bce99e22c8386326a968457ffaa Mon Sep 17 00:00:00 2001 From: Phillip Verheyden Date: Tue, 21 Oct 2025 16:09:17 -0500 Subject: [PATCH 2/3] Fix import ordering --- .../src/opentelemetry/instrumentation/httpx/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/instrumentation/opentelemetry-instrumentation-httpx/src/opentelemetry/instrumentation/httpx/__init__.py b/instrumentation/opentelemetry-instrumentation-httpx/src/opentelemetry/instrumentation/httpx/__init__.py index 2a27c98f77..afc19164bd 100644 --- a/instrumentation/opentelemetry-instrumentation-httpx/src/opentelemetry/instrumentation/httpx/__init__.py +++ b/instrumentation/opentelemetry-instrumentation-httpx/src/opentelemetry/instrumentation/httpx/__init__.py @@ -207,8 +207,8 @@ async def async_response_hook(span, request, response): import logging import typing -from inspect import iscoroutinefunction from functools import partial +from inspect import iscoroutinefunction from timeit import default_timer from types import TracebackType From 74b85089bc99c8a7dca86fe8fef51d6437c5c72b Mon Sep 17 00:00:00 2001 From: Phillip Verheyden Date: Tue, 4 Nov 2025 21:14:24 -0600 Subject: [PATCH 3/3] aiokafka test workaround for CPython 3.9 bug --- .../tests/test_instrumentation.py | 35 ++++++++++++++----- 1 file changed, 27 insertions(+), 8 deletions(-) diff --git a/instrumentation/opentelemetry-instrumentation-aiokafka/tests/test_instrumentation.py b/instrumentation/opentelemetry-instrumentation-aiokafka/tests/test_instrumentation.py index c005920368..892689b853 100644 --- a/instrumentation/opentelemetry-instrumentation-aiokafka/tests/test_instrumentation.py +++ b/instrumentation/opentelemetry-instrumentation-aiokafka/tests/test_instrumentation.py @@ -261,11 +261,21 @@ async def async_consume_hook(span, *_) -> None: async def test_getone_consume_hook(self) -> None: async_consume_hook_mock = mock.AsyncMock() + def is_async_consume_hook_mock(obj: Any) -> bool: + return obj is async_consume_hook_mock + AIOKafkaInstrumentor().uninstrument() - AIOKafkaInstrumentor().instrument( - tracer_provider=self.tracer_provider, - async_consume_hook=async_consume_hook_mock, - ) + + # mock.patch is a hack for Python 3.9 see https://github.com/open-telemetry/opentelemetry-python-contrib/pull/3880 + with mock.patch( + "opentelemetry.instrumentation.aiokafka.iscoroutinefunction" + ) as iscoro: + iscoro.side_effect = is_async_consume_hook_mock + + AIOKafkaInstrumentor().instrument( + tracer_provider=self.tracer_provider, + async_consume_hook=async_consume_hook_mock, + ) consumer = await self.consumer_factory() self.addAsyncCleanup(consumer.stop) @@ -448,11 +458,20 @@ async def test_send_baggage(self) -> None: async def test_send_produce_hook(self) -> None: async_produce_hook_mock = mock.AsyncMock() + def is_async_produce_hook_mock(obj: Any) -> bool: + return obj is async_produce_hook_mock + AIOKafkaInstrumentor().uninstrument() - AIOKafkaInstrumentor().instrument( - tracer_provider=self.tracer_provider, - async_produce_hook=async_produce_hook_mock, - ) + # mock.patch is a hack for Python 3.9 see https://github.com/open-telemetry/opentelemetry-python-contrib/pull/3880 + with mock.patch( + "opentelemetry.instrumentation.aiokafka.iscoroutinefunction" + ) as iscoro: + iscoro.side_effect = is_async_produce_hook_mock + + AIOKafkaInstrumentor().instrument( + tracer_provider=self.tracer_provider, + async_produce_hook=async_produce_hook_mock, + ) producer = await self.producer_factory() self.addAsyncCleanup(producer.stop)