Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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):
Expand All @@ -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}",
Expand All @@ -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)
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
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 _patch_fake_clients(monkeypatch):
monkeypatch.setattr(
qdrant_module,
"qdrant_client",
SimpleNamespace(
QdrantClient=_FakeQdrantClient,
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,
"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


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