Skip to content

Commit 52273e6

Browse files
authored
fix: handle objects in a unified way (#109)
1 parent 4651845 commit 52273e6

5 files changed

Lines changed: 31 additions & 43 deletions

File tree

src/deepset_mcp/tools/tokonomics/decorators.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -207,7 +207,7 @@ def resolve_reference(ref_str: str) -> Any:
207207

208208
obj = object_store.get(ref.obj_id)
209209
if obj is None:
210-
raise ValueError(f"Object {ref.obj_id} not found or expired")
210+
raise ValueError(f"Object @{ref.obj_id} not found or expired")
211211

212212
if ref.path:
213213
try:

src/deepset_mcp/tools/tokonomics/explorer.py

Lines changed: 16 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
from rich.console import Console
1515
from rich.pretty import Pretty
1616

17-
from .object_store import ObjectStore
17+
from .object_store import ObjectRef, ObjectStore
1818

1919

2020
class RichExplorer:
@@ -58,21 +58,10 @@ def explore(self, obj_id: str, path: str = "") -> str:
5858
"""Return a string preview of the requested object.
5959
6060
:param obj_id: Identifier obtained from the store.
61-
:param path: Navigation path using ``.`` or ``[...]`` notation.
61+
:param path: Navigation path using ``.`` or ``[...]`` notation (e.g. ``@obj_001.path.to.attribute``).
6262
:return: String representation of the object.
6363
"""
64-
obj = self.store.get(obj_id)
65-
if obj is None:
66-
return f"Object {obj_id} not found or expired"
67-
68-
# Navigate to path if provided
69-
if path:
70-
try:
71-
self._validate_path(path)
72-
obj = glom(obj, self._parse_path(path))
73-
except GlomError as exc:
74-
return f"Navigation error at {path}: {exc}"
75-
# Let ValueError from _validate_path bubble up
64+
obj = self._get_object_at_path(obj_id, path)
7665

7766
# Generate header and body
7867
header = self._make_header(obj_id, path, obj)
@@ -90,8 +79,6 @@ def search(self, obj_id: str, pattern: str, path: str = "", case_sensitive: bool
9079
:return: Search results as formatted string.
9180
"""
9281
obj = self._get_object_at_path(obj_id, path)
93-
if isinstance(obj, str) and obj.startswith("Object") and "not found" in obj:
94-
return obj
9582

9683
# Generate header
9784
header = self._make_header(obj_id, path, obj)
@@ -138,15 +125,13 @@ def search(self, obj_id: str, pattern: str, path: str = "", case_sensitive: bool
138125
def slice(self, obj_id: str, start: int = 0, end: int | None = None, path: str = "") -> str:
139126
"""Extract a slice from a string or list object.
140127
141-
:param obj_id: Identifier obtained from the store.
128+
:param obj_id: Identifier of the object.
142129
:param start: Start index for slicing.
143130
:param end: End index for slicing (None for end of sequence).
144131
:param path: Navigation path to object to slice (optional).
145132
:return: String representation of the slice.
146133
"""
147134
obj = self._get_object_at_path(obj_id, path)
148-
if isinstance(obj, str) and obj.startswith("Object") and "not found" in obj:
149-
return obj
150135

151136
# Generate header
152137
header = self._make_header(obj_id, path, obj)
@@ -193,17 +178,23 @@ def _get_object_at_path(self, obj_id: str, path: str) -> Any:
193178
:param path: Navigation path (optional).
194179
:return: Object at path or error string.
195180
"""
196-
obj = self.store.get(obj_id)
181+
ref = ObjectRef.parse(obj_id)
182+
# We accept @obj_001 as well as obj_001
183+
if ref is None:
184+
resolved_obj_id = obj_id
185+
else:
186+
resolved_obj_id = ref.obj_id
187+
188+
obj = self.store.get(resolved_obj_id)
197189
if obj is None:
198-
return f"Object {obj_id} not found or expired"
190+
raise ValueError(f"Object {obj_id} not found or expired.")
199191

200192
if path:
193+
self._validate_path(path)
201194
try:
202-
self._validate_path(path)
203195
obj = glom(obj, self._parse_path(path))
204-
except GlomError as exc:
205-
return f"Navigation error at {path}: {exc}"
206-
# Let ValueError from _validate_path bubble up
196+
except GlomError as e:
197+
raise ValueError(f"Object '{obj_id}' does not have a value at path '{path}'.") from e
207198

208199
return obj
209200

test/unit/tools/tokonomics/test_decorators.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -305,7 +305,7 @@ def test_referenceable_nonexistent_object(self, store: ObjectStore, explorer: Ri
305305
def test_func(data: dict) -> str:
306306
return "success"
307307

308-
with pytest.raises(ValueError, match="Object obj_999 not found or expired"):
308+
with pytest.raises(ValueError, match="Object @obj_999 not found or expired"):
309309
test_func("@obj_999")
310310

311311
def test_referenceable_invalid_path(self, store: ObjectStore, explorer: RichExplorer) -> None:

test/unit/tools/tokonomics/test_explorer.py

Lines changed: 8 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -50,9 +50,8 @@ def test_init_custom_params(self, store: ObjectStore) -> None:
5050

5151
def test_explore_nonexistent_object(self, explorer: RichExplorer) -> None:
5252
"""Test exploring a non-existent object."""
53-
result = explorer.explore("obj_999")
54-
55-
assert "Object obj_999 not found or expired" in result
53+
with pytest.raises(ValueError, match="Object obj_999 not found or expired"):
54+
explorer.explore("obj_999")
5655

5756
def test_explore_simple_object(self, store: ObjectStore, explorer: RichExplorer) -> None:
5857
"""Test exploring a simple object."""
@@ -83,9 +82,8 @@ def test_explore_invalid_path(self, store: ObjectStore, explorer: RichExplorer)
8382
test_data = {"key": "value"}
8483
obj_id = store.put(test_data)
8584

86-
result = explorer.explore(obj_id, "nonexistent.path")
87-
88-
assert "Navigation error" in result
85+
with pytest.raises(ValueError, match="does not have a value at path"):
86+
explorer.explore(obj_id, "nonexistent.path")
8987

9088
def test_explore_disallowed_attribute(self, store: ObjectStore, explorer: RichExplorer) -> None:
9189
"""Test exploring with disallowed attribute name."""
@@ -377,19 +375,16 @@ def test_get_object_at_path_success(self, store: ObjectStore, explorer: RichExpl
377375

378376
def test_get_object_at_path_nonexistent(self, explorer: RichExplorer) -> None:
379377
"""Test object retrieval for non-existent object."""
380-
result = explorer._get_object_at_path("obj_999", "")
381-
382-
assert "Object obj_999 not found or expired" in result
378+
with pytest.raises(ValueError, match="Object obj_999 not found or expired"):
379+
explorer._get_object_at_path("obj_999", "")
383380

384381
def test_get_object_at_path_invalid_path(self, store: ObjectStore, explorer: RichExplorer) -> None:
385382
"""Test object retrieval with invalid path."""
386383
test_data = {"key": "value"}
387384
obj_id = store.put(test_data)
388385

389-
result = explorer._get_object_at_path(obj_id, "nonexistent")
390-
391-
assert isinstance(result, str)
392-
assert "Navigation error" in result
386+
with pytest.raises(ValueError, match="does not have a value at path"):
387+
explorer._get_object_at_path(obj_id, "nonexistent")
393388

394389
def test_search_context_length(self, store: ObjectStore) -> None:
395390
"""Test search context length configuration."""

test/unit/tools/tokonomics/test_integration.py

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -129,7 +129,7 @@ def process_data(data: dict) -> str:
129129
return f"Processed {len(data)} items"
130130

131131
# Test non-existent object reference
132-
with pytest.raises(ValueError, match="Object obj_999 not found or expired"):
132+
with pytest.raises(ValueError, match="Object @obj_999 not found or expired"):
133133
process_data("@obj_999")
134134

135135
# Test invalid reference format
@@ -232,12 +232,14 @@ def test_ttl_expiration_integration(self, explorer: RichExplorer) -> None:
232232
"""Test TTL expiration in integrated workflow."""
233233
# Create store with short TTL
234234
short_ttl_store = ObjectStore(ttl=0.1) # 100ms TTL
235+
# Create explorer that uses the same store
236+
short_ttl_explorer = RichExplorer(short_ttl_store)
235237

236-
@explorable(object_store=short_ttl_store, explorer=explorer)
238+
@explorable(object_store=short_ttl_store, explorer=short_ttl_explorer)
237239
def create_data() -> dict:
238240
return {"data": "will_expire"}
239241

240-
@referenceable(object_store=short_ttl_store, explorer=explorer)
242+
@referenceable(object_store=short_ttl_store, explorer=short_ttl_explorer)
241243
def use_data(data: dict) -> str:
242244
return data["data"]
243245

0 commit comments

Comments
 (0)