From 8ad8c28e42873d7916c68199edb64d95e388e607 Mon Sep 17 00:00:00 2001 From: 1cbyc Date: Sat, 4 Jul 2026 15:48:29 +0000 Subject: [PATCH 1/2] fix(qdrant): skip missing client methods --- .../instrumentation/qdrant/__init__.py | 10 +++- .../tests/test_missing_methods.py | 53 +++++++++++++++++++ 2 files changed, 61 insertions(+), 2 deletions(-) create mode 100644 packages/opentelemetry-instrumentation-qdrant/tests/test_missing_methods.py diff --git a/packages/opentelemetry-instrumentation-qdrant/opentelemetry/instrumentation/qdrant/__init__.py b/packages/opentelemetry-instrumentation-qdrant/opentelemetry/instrumentation/qdrant/__init__.py index b4e146f31e..7d46a3220d 100644 --- a/packages/opentelemetry-instrumentation-qdrant/opentelemetry/instrumentation/qdrant/__init__.py +++ b/packages/opentelemetry-instrumentation-qdrant/opentelemetry/instrumentation/qdrant/__init__.py @@ -2,6 +2,7 @@ import json import logging +from inspect import getattr_static from pathlib import Path from opentelemetry.instrumentation.qdrant.config import Config from opentelemetry.instrumentation.qdrant.wrapper import _wrap @@ -33,6 +34,11 @@ WRAPPED_METHODS = QDRANT_CLIENT_METHODS + ASYNC_QDRANT_CLIENT_METHODS MODULE = "qdrant_client" +_MISSING = object() + + +def _has_static_attribute(obj, name): + return getattr_static(obj, name, _MISSING) is not _MISSING class QdrantInstrumentor(BaseInstrumentor): @@ -52,7 +58,7 @@ def _instrument(self, **kwargs): wrap_object = wrapped_method.get("object") wrap_method = wrapped_method.get("method") obj = getattr(qdrant_client, wrap_object, None) - if obj and hasattr(obj, wrap_method): + if obj and _has_static_attribute(obj, wrap_method): wrap_function_wrapper( MODULE, f"{wrap_object}.{wrap_method}", @@ -64,5 +70,5 @@ def _uninstrument(self, **kwargs): wrap_object = wrapped_method.get("object") wrap_method = wrapped_method.get("method") obj = getattr(qdrant_client, wrap_object, None) - if obj and hasattr(obj, wrap_method): + if obj and _has_static_attribute(obj, wrap_method): unwrap(f"{MODULE}.{wrap_object}", wrap_method) diff --git a/packages/opentelemetry-instrumentation-qdrant/tests/test_missing_methods.py b/packages/opentelemetry-instrumentation-qdrant/tests/test_missing_methods.py new file mode 100644 index 0000000000..796b074e0c --- /dev/null +++ b/packages/opentelemetry-instrumentation-qdrant/tests/test_missing_methods.py @@ -0,0 +1,53 @@ +from types import SimpleNamespace + +from opentelemetry.instrumentation import qdrant as qdrant_module + + +class _DynamicMethodMeta(type): + def __getattr__(cls, name): + if name == "upload_records": + return lambda *args, **kwargs: None + raise AttributeError(name) + + +class _FakeQdrantClient(metaclass=_DynamicMethodMeta): + def upload_points(self): + return None + + def upload_collection(self): + return None + + +class _FakeAsyncQdrantClient(_FakeQdrantClient): + pass + + +def test_static_attribute_check_ignores_dynamic_class_lookup(): + assert hasattr(_FakeQdrantClient, "upload_records") + assert not qdrant_module._has_static_attribute(_FakeQdrantClient, "upload_records") + + +def test_instrumentor_skips_methods_missing_from_client_class(monkeypatch): + wrapped_paths = [] + + monkeypatch.setattr( + qdrant_module, + "qdrant_client", + SimpleNamespace( + QdrantClient=_FakeQdrantClient, + AsyncQdrantClient=_FakeAsyncQdrantClient, + ), + ) + monkeypatch.setattr(qdrant_module, "get_tracer", lambda *args, **kwargs: object()) + monkeypatch.setattr( + qdrant_module, + "wrap_function_wrapper", + lambda module, path, wrapper: wrapped_paths.append(path), + ) + + qdrant_module.QdrantInstrumentor()._instrument() + + assert "QdrantClient.upload_points" in wrapped_paths + assert "AsyncQdrantClient.upload_points" in wrapped_paths + assert "QdrantClient.upload_records" not in wrapped_paths + assert "AsyncQdrantClient.upload_records" not in wrapped_paths From 823044e84a612834aede8ee50b17e1d67aa9cf9c Mon Sep 17 00:00:00 2001 From: 1cbyc Date: Tue, 7 Jul 2026 09:04:16 +0000 Subject: [PATCH 2/2] test(qdrant): cover static uninstrument checks --- .../tests/test_missing_methods.py | 28 +++++++++++++++++-- 1 file changed, 25 insertions(+), 3 deletions(-) diff --git a/packages/opentelemetry-instrumentation-qdrant/tests/test_missing_methods.py b/packages/opentelemetry-instrumentation-qdrant/tests/test_missing_methods.py index 796b074e0c..1ba0171765 100644 --- a/packages/opentelemetry-instrumentation-qdrant/tests/test_missing_methods.py +++ b/packages/opentelemetry-instrumentation-qdrant/tests/test_missing_methods.py @@ -27,9 +27,7 @@ def test_static_attribute_check_ignores_dynamic_class_lookup(): assert not qdrant_module._has_static_attribute(_FakeQdrantClient, "upload_records") -def test_instrumentor_skips_methods_missing_from_client_class(monkeypatch): - wrapped_paths = [] - +def _patch_fake_clients(monkeypatch): monkeypatch.setattr( qdrant_module, "qdrant_client", @@ -38,6 +36,12 @@ def test_instrumentor_skips_methods_missing_from_client_class(monkeypatch): AsyncQdrantClient=_FakeAsyncQdrantClient, ), ) + + +def test_instrumentor_skips_methods_missing_from_client_class(monkeypatch): + wrapped_paths = [] + + _patch_fake_clients(monkeypatch) monkeypatch.setattr(qdrant_module, "get_tracer", lambda *args, **kwargs: object()) monkeypatch.setattr( qdrant_module, @@ -51,3 +55,21 @@ def test_instrumentor_skips_methods_missing_from_client_class(monkeypatch): assert "AsyncQdrantClient.upload_points" in wrapped_paths assert "QdrantClient.upload_records" not in wrapped_paths assert "AsyncQdrantClient.upload_records" not in wrapped_paths + + +def test_uninstrumentor_skips_methods_missing_from_client_class(monkeypatch): + unwrapped_methods = [] + + _patch_fake_clients(monkeypatch) + monkeypatch.setattr( + qdrant_module, + "unwrap", + lambda module_path, method: unwrapped_methods.append((module_path, method)), + ) + + qdrant_module.QdrantInstrumentor()._uninstrument() + + assert ("qdrant_client.QdrantClient", "upload_points") in unwrapped_methods + assert ("qdrant_client.AsyncQdrantClient", "upload_points") in unwrapped_methods + assert ("qdrant_client.QdrantClient", "upload_records") not in unwrapped_methods + assert ("qdrant_client.AsyncQdrantClient", "upload_records") not in unwrapped_methods