diff --git a/CHANGELOG.md b/CHANGELOG.md index f44c4e22d2..2a32b439fc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -20,6 +20,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - `opentelemetry-instrumentation-pika` Use `ObjectProxy` instead of `BaseObjectProxy` for `ReadyMessagesDequeProxy` to restore iterability with wrapt 2.x ([#4461](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/4461)) +- `opentelemetry-instrumentation-dbapi` Use `ObjectProxy` instead of `BaseObjectProxy` for `TracedCursorProxy` to restore iterability with wrapt 2.x + ([#4427](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/4427)) ### Breaking changes diff --git a/docs-requirements.txt b/docs-requirements.txt index 8f743798bc..9d0be53704 100644 --- a/docs-requirements.txt +++ b/docs-requirements.txt @@ -49,6 +49,7 @@ sqlalchemy>=1.0 starlette~=0.50 tornado>=5.1.1 tortoise-orm>=0.17.0 +wrapt~=2.1 # required by opamp uuid_utils diff --git a/docs/nitpick-exceptions.ini b/docs/nitpick-exceptions.ini index 73febacaad..128e406a70 100644 --- a/docs/nitpick-exceptions.ini +++ b/docs/nitpick-exceptions.ini @@ -44,6 +44,7 @@ py-class= psycopg.Connection psycopg.AsyncConnection ObjectProxy + wrapt.proxies.ObjectProxy fastapi.applications.FastAPI starlette.applications.Starlette _contextvars.Token diff --git a/instrumentation/opentelemetry-instrumentation-dbapi/src/opentelemetry/instrumentation/dbapi/__init__.py b/instrumentation/opentelemetry-instrumentation-dbapi/src/opentelemetry/instrumentation/dbapi/__init__.py index 9eb386b0ea..8be92c13fe 100644 --- a/instrumentation/opentelemetry-instrumentation-dbapi/src/opentelemetry/instrumentation/dbapi/__init__.py +++ b/instrumentation/opentelemetry-instrumentation-dbapi/src/opentelemetry/instrumentation/dbapi/__init__.py @@ -177,8 +177,12 @@ try: # wrapt 2.0.0+ - from wrapt import BaseObjectProxy # pylint: disable=no-name-in-module + from wrapt import ( # pylint: disable=no-name-in-module + BaseObjectProxy, + ObjectProxy, + ) except ImportError: + from wrapt import ObjectProxy from wrapt import ObjectProxy as BaseObjectProxy from opentelemetry import trace as trace_api @@ -805,14 +809,14 @@ async def traced_execution_async( # pylint: disable=abstract-method,no-member -class TracedCursorProxy(BaseObjectProxy, Generic[CursorT]): +class TracedCursorProxy(ObjectProxy, Generic[CursorT]): # pylint: disable=unused-argument def __init__( self, cursor: CursorT, db_api_integration: DatabaseApiIntegration, ): - BaseObjectProxy.__init__(self, cursor) + ObjectProxy.__init__(self, cursor) self._self_cursor_tracer = CursorTracer[CursorT](db_api_integration) def execute(self, *args: Any, **kwargs: Any): diff --git a/instrumentation/opentelemetry-instrumentation-dbapi/tests/test_dbapi_integration.py b/instrumentation/opentelemetry-instrumentation-dbapi/tests/test_dbapi_integration.py index 2b6ed0e478..27b43f05ad 100644 --- a/instrumentation/opentelemetry-instrumentation-dbapi/tests/test_dbapi_integration.py +++ b/instrumentation/opentelemetry-instrumentation-dbapi/tests/test_dbapi_integration.py @@ -306,6 +306,20 @@ def test_executemany(self): span = spans_list[0] self.assertEqual(span.attributes[DB_STATEMENT], "Test query") + # pylint: disable=no-self-use + def test_executemany_iterable_cursor(self): + db_integration = dbapi.DatabaseApiIntegration( + "instrumenting_module_test_name", "testcomponent" + ) + mock_connection = db_integration.wrapped_connection( + mock_connect, {}, {} + ) + cursor = mock_connection.cursor() + cursor.executemany("Test query") + + for _row in cursor: + pass + def test_executemany_comment(self): connect_module = mock.MagicMock() connect_module.__name__ = "test" @@ -1296,6 +1310,7 @@ def __init__(self) -> None: self._cnx._cmysql.get_client_info = mock.MagicMock( return_value="1.2.3" ) + self._items = [] # pylint: disable=unused-argument, no-self-use def execute(self, query, params=None, throw_exception=False): @@ -1303,6 +1318,9 @@ def execute(self, query, params=None, throw_exception=False): # pylint: disable=broad-exception-raised raise Exception("Test Exception") + def __iter__(self): + yield from self._items + # pylint: disable=unused-argument, no-self-use def executemany(self, query, params=None, throw_exception=False): if throw_exception: diff --git a/tests/opentelemetry-docker-tests/tests/mysql/test_mysql_functional.py b/tests/opentelemetry-docker-tests/tests/mysql/test_mysql_functional.py index 30bcc6a298..f3da251d41 100644 --- a/tests/opentelemetry-docker-tests/tests/mysql/test_mysql_functional.py +++ b/tests/opentelemetry-docker-tests/tests/mysql/test_mysql_functional.py @@ -111,6 +111,16 @@ def test_executemany(self): self._cursor.executemany(stmt, data) self.validate_spans("INSERT") + def test_executemany_with_cursor_iteration(self): + """Should create a child span for executemany while iterating over the cursor""" + stmt = "SELECT * FROM test" + with self._tracer.start_as_current_span("rootSpan"): + with self._connection.cursor() as cursor: + cursor.execute(stmt) + for _row in cursor: + pass + self.validate_spans("SELECT") + def test_callproc(self): """Should create a child span for callproc""" with ( diff --git a/tests/opentelemetry-docker-tests/tests/pymysql/test_pymysql_functional.py b/tests/opentelemetry-docker-tests/tests/pymysql/test_pymysql_functional.py index e33b6c0f88..8ff457f195 100644 --- a/tests/opentelemetry-docker-tests/tests/pymysql/test_pymysql_functional.py +++ b/tests/opentelemetry-docker-tests/tests/pymysql/test_pymysql_functional.py @@ -102,6 +102,16 @@ def test_executemany(self): self._cursor.executemany(stmt, data) self.validate_spans("INSERT") + def test_executemany_with_cursor_iteration(self): + """Should create a child span for executemany while iterating over the cursor""" + stmt = "SELECT * FROM test" + with self._tracer.start_as_current_span("rootSpan"): + with self._connection.cursor() as cursor: + cursor.execute(stmt) + for _row in cursor: + pass + self.validate_spans("SELECT") + def test_callproc(self): """Should create a child span for callproc""" with (