Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for debug get object endpoint: #1507

Merged
merged 8 commits into from
Jan 10, 2025
Merged
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
1 change: 1 addition & 0 deletions .flake8
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ per-file-ignores =
weaviate/collections/classes/types.py:A005
weaviate/collections/collections/__init__.py:A005
weaviate/collections/__init__.py:A005
weaviate/debug/types.py:A005
weaviate/types.py:A005
weaviate/warnings.py:A005

Expand Down
38 changes: 38 additions & 0 deletions integration/test_client_debug.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
from integration.conftest import ClientFactory, CollectionFactory

from weaviate.classes.config import DataType, Property
from weaviate.classes.debug import DebugRESTObject


def test_get_object_single_node(
client_factory: ClientFactory, collection_factory: CollectionFactory
) -> None:
client = client_factory()
collection = collection_factory(properties=[Property(name="name", data_type=DataType.TEXT)])

uuid = collection.data.insert({"name": "John Doe"})

debug_obj = client.debug.get_object_over_rest(collection.name, uuid)
assert debug_obj is not None
assert isinstance(debug_obj, DebugRESTObject)
assert str(debug_obj.uuid) == str(uuid)

non_existant_uuid = "00000000-0000-0000-0000-000000000000"
debug_obj = client.debug.get_object_over_rest(collection.name, non_existant_uuid)
assert debug_obj is None


def test_get_object_multi_node(
client_factory: ClientFactory, collection_factory: CollectionFactory
) -> None:
client = client_factory(ports=(8087, 50058))
collection = collection_factory(
ports=(8087, 50058), properties=[Property(name="name", data_type=DataType.TEXT)]
)

uuid = collection.data.insert({"name": "John Doe"})

for node_name in ["node1", "node2", "node3"]:
debug_obj = client.debug.get_object_over_rest(collection.name, uuid, node_name=node_name)
assert debug_obj is not None
assert str(debug_obj.uuid) == str(uuid)
5 changes: 5 additions & 0 deletions weaviate/classes/debug.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
from weaviate.debug.types import DebugRESTObject

__all__ = [
"DebugRESTObject",
]
10 changes: 10 additions & 0 deletions weaviate/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
from .connect.base import (
ConnectionParams,
)
from .debug import _Debug, _DebugAsync
from .embedded import EmbeddedOptions
from .rbac import _RolesAsync, _Roles
from .types import NUMBER
Expand Down Expand Up @@ -87,7 +88,12 @@ def __init__(

Use it to retrieve collection objects using `client.collections.get("MyCollection")` or to create new collections using `client.collections.create("MyCollection", ...)`.
"""
self.debug = _Debug(self._connection)
"""This namespace contains functionality used to debug Weaviate clusters. As such, it is deemed experimental and is subject to change.

We can make no guarantees about the stability of this namespace nor the potential for future breaking changes. Use at your own risk."""
self.roles = _Roles(self._connection)
"""This namespace contains all functionality to manage Weaviate's RBAC functionality."""

def __enter__(self) -> "WeaviateClient":
self.connect() # pyright: ignore # gets patched by syncify.convert to be sync
Expand Down Expand Up @@ -146,6 +152,10 @@ def __init__(

Use it to retrieve collection objects using `client.collections.get("MyCollection")` or to create new collections using `await client.collections.create("MyCollection", ...)`.
"""
self.debug = _DebugAsync(self._connection)
"""This namespace contains functionality used to debug Weaviate clusters. As such, it is deemed experimental and is subject to change.

We can make no guarantees about the stability of this namespace nor the potential for future breaking changes. Use at your own risk."""
self.roles = _RolesAsync(self._connection)
"""This namespace contains all functionality to manage Weaviate's RBAC functionality."""

Expand Down
9 changes: 6 additions & 3 deletions weaviate/client.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ from weaviate.collections.collections.sync import _Collections
from .collections.batch.client import _BatchClientWrapper
from .collections.cluster import _Cluster, _ClusterAsync
from .connect import ConnectionV4
from .debug import _Debug, _DebugAsync
from .rbac import _Roles, _RolesAsync
from .types import NUMBER

Expand All @@ -25,9 +26,10 @@ from weaviate.client_base import _WeaviateClientInit

class WeaviateAsyncClient(_WeaviateClientInit):
_connection: ConnectionV4
collections: _CollectionsAsync
backup: _BackupAsync
collections: _CollectionsAsync
cluster: _ClusterAsync
debug: _DebugAsync
roles: _RolesAsync
async def close(self) -> None: ...
async def connect(self) -> None: ...
Expand All @@ -42,10 +44,11 @@ class WeaviateAsyncClient(_WeaviateClientInit):

class WeaviateClient(_WeaviateClientInit):
_connection: ConnectionV4
collections: _Collections
batch: _BatchClientWrapper
backup: _Backup
batch: _BatchClientWrapper
collections: _Collections
cluster: _Cluster
debug: _Debug
roles: _Roles
def close(self) -> None: ...
def connect(self) -> None: ...
Expand Down
7 changes: 7 additions & 0 deletions weaviate/debug/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
from .debug import _DebugAsync
from .sync import _Debug

__all__ = [
"_Debug",
"_DebugAsync",
]
51 changes: 51 additions & 0 deletions weaviate/debug/debug.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
from typing import Dict, Optional

from weaviate.classes.config import ConsistencyLevel
from weaviate.connect import ConnectionV4
from weaviate.connect.v4 import _ExpectedStatusCodes
from weaviate.debug.types import DebugRESTObject
from weaviate.types import UUID


class _DebugBase:
def __init__(
self,
connection: ConnectionV4,
) -> None:
self._connection = connection


class _DebugAsync(_DebugBase):
async def get_object_over_rest(
self,
collection: str,
uuid: UUID,
*,
consistency_level: Optional[ConsistencyLevel] = None,
node_name: Optional[str] = None,
tenant: Optional[str] = None,
) -> Optional[DebugRESTObject]:
"""Use the REST API endpoint /objects/{className}/{id} to retrieve an object directly from the database without search.

The key difference between `debug.get_object_over_rest` and `query.fetch_object_by_id` is the underlying protocol.
This method uses REST while that method uses gRPC.
"""
path = f"/objects/{collection}/{str(uuid)}"

params: Dict[str, str] = {}
if consistency_level is not None:
params["consistency"] = consistency_level.value
if node_name is not None:
params["node_name"] = node_name
if tenant is not None:
params["tenant"] = tenant

res = await self._connection.get(
path=path,
params=params,
error_msg="Object was not retrieved",
status_codes=_ExpectedStatusCodes(ok_in=[200, 404], error="get object"),
)
if res.status_code == 404:
return None
return DebugRESTObject(**res.json())
7 changes: 7 additions & 0 deletions weaviate/debug/sync.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
from weaviate import syncify
from weaviate.debug.debug import _DebugAsync


@syncify.convert
class _Debug(_DebugAsync):
pass
17 changes: 17 additions & 0 deletions weaviate/debug/sync.pyi
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
from typing import Optional

from weaviate.classes.config import ConsistencyLevel
from weaviate.debug.debug import _DebugBase
from weaviate.debug.types import DebugRESTObject
from weaviate.types import UUID

class _Debug(_DebugBase):
def get_object_over_rest(
self,
collection: str,
uuid: UUID,
*,
consistency_level: Optional[ConsistencyLevel] = None,
node_name: Optional[str] = None,
tenant: Optional[str] = None,
) -> Optional[DebugRESTObject]: ...
17 changes: 17 additions & 0 deletions weaviate/debug/types.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
from datetime import datetime
from typing import Any, Dict, Optional

from pydantic import BaseModel, Field

from weaviate.types import uuid_package


class DebugRESTObject(BaseModel):
collection: str = Field(..., alias="class")
creation_time: datetime = Field(..., alias="creationTimeUnix")
last_update_time: datetime = Field(..., alias="lastUpdateTimeUnix")
properties: Dict[str, Any] = Field(...)
tenant: Optional[str] = Field(None)
uuid: uuid_package.UUID = Field(..., alias="id")
vector: Optional[list[float]] = Field(None)
vectors: Optional[Dict[str, list[float]]] = Field(None)
Loading