Skip to content

Commit 6b9bb10

Browse files
Wrap declared-attribute inspection failures in tools
Co-authored-by: Shri Sukhani <shrisukhani@users.noreply.github.com>
1 parent 24b8f24 commit 6b9bb10

File tree

2 files changed

+89
-6
lines changed

2 files changed

+89
-6
lines changed

hyperbrowser/tools/__init__.py

Lines changed: 26 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -38,14 +38,18 @@
3838
)
3939

4040

41-
def _has_declared_attribute(value: Any, attribute_name: str) -> bool:
41+
def _has_declared_attribute(
42+
value: Any, attribute_name: str, *, error_message: str
43+
) -> bool:
4244
try:
4345
inspect.getattr_static(value, attribute_name)
4446
return True
4547
except AttributeError:
4648
return False
47-
except Exception:
48-
return False
49+
except HyperbrowserError:
50+
raise
51+
except Exception as exc:
52+
raise HyperbrowserError(error_message, original_error=exc) from exc
4953

5054

5155
def _format_tool_param_key_for_error(key: str) -> str:
@@ -224,7 +228,11 @@ def _read_tool_response_data(response: Any, *, tool_name: str) -> Any:
224228
try:
225229
return response.data
226230
except AttributeError as exc:
227-
if _has_declared_attribute(response, "data"):
231+
if _has_declared_attribute(
232+
response,
233+
"data",
234+
error_message=f"Failed to inspect {tool_name} response data field",
235+
):
228236
raise HyperbrowserError(
229237
f"Failed to read {tool_name} response data",
230238
original_error=exc,
@@ -277,7 +285,13 @@ def _read_optional_tool_response_field(
277285
try:
278286
field_value = getattr(response_data, field_name)
279287
except AttributeError as exc:
280-
if _has_declared_attribute(response_data, field_name):
288+
if _has_declared_attribute(
289+
response_data,
290+
field_name,
291+
error_message=(
292+
f"Failed to inspect {tool_name} response field '{field_name}'"
293+
),
294+
):
281295
raise HyperbrowserError(
282296
f"Failed to read {tool_name} response field '{field_name}'",
283297
original_error=exc,
@@ -330,7 +344,13 @@ def _read_crawl_page_field(page: Any, *, field_name: str, page_index: int) -> An
330344
try:
331345
return getattr(page, field_name)
332346
except AttributeError as exc:
333-
if _has_declared_attribute(page, field_name):
347+
if _has_declared_attribute(
348+
page,
349+
field_name,
350+
error_message=(
351+
f"Failed to inspect crawl tool page field '{field_name}' at index {page_index}"
352+
),
353+
):
334354
raise HyperbrowserError(
335355
f"Failed to read crawl tool page field '{field_name}' at index {page_index}",
336356
original_error=exc,

tests/test_tools_response_handling.py

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55

66
import pytest
77

8+
import hyperbrowser.tools as tools_module
89
from hyperbrowser.exceptions import HyperbrowserError
910
from hyperbrowser.tools import (
1011
BrowserUseTool,
@@ -143,6 +144,26 @@ def data(self):
143144
assert exc_info.value.original_error is not None
144145

145146

147+
def test_scrape_tool_wraps_declared_data_inspection_failures(
148+
monkeypatch: pytest.MonkeyPatch,
149+
):
150+
class _MissingDataResponse:
151+
pass
152+
153+
def _raise_runtime_error(_obj: object, _attr: str):
154+
raise RuntimeError("attribute inspection exploded")
155+
156+
monkeypatch.setattr(tools_module.inspect, "getattr_static", _raise_runtime_error)
157+
client = _SyncScrapeClient(_MissingDataResponse()) # type: ignore[arg-type]
158+
159+
with pytest.raises(
160+
HyperbrowserError, match="Failed to inspect scrape tool response data field"
161+
) as exc_info:
162+
WebsiteScrapeTool.runnable(client, {"url": "https://example.com"})
163+
164+
assert exc_info.value.original_error is not None
165+
166+
146167
def test_scrape_tool_supports_mapping_response_objects():
147168
client = _SyncScrapeClient({"data": {"markdown": "from response mapping"}}) # type: ignore[arg-type]
148169

@@ -332,6 +353,27 @@ def markdown(self):
332353
assert exc_info.value.original_error is not None
333354

334355

356+
def test_scrape_tool_wraps_declared_markdown_inspection_failures(
357+
monkeypatch: pytest.MonkeyPatch,
358+
):
359+
class _MissingMarkdownData:
360+
pass
361+
362+
def _raise_runtime_error(_obj: object, _attr: str):
363+
raise RuntimeError("attribute inspection exploded")
364+
365+
monkeypatch.setattr(tools_module.inspect, "getattr_static", _raise_runtime_error)
366+
client = _SyncScrapeClient(_Response(data=_MissingMarkdownData()))
367+
368+
with pytest.raises(
369+
HyperbrowserError,
370+
match="Failed to inspect scrape tool response field 'markdown'",
371+
) as exc_info:
372+
WebsiteScrapeTool.runnable(client, {"url": "https://example.com"})
373+
374+
assert exc_info.value.original_error is not None
375+
376+
335377
def test_scrape_tool_decodes_utf8_bytes_markdown_field():
336378
client = _SyncScrapeClient(_Response(data=SimpleNamespace(markdown=b"hello")))
337379

@@ -552,6 +594,27 @@ def markdown(self):
552594
assert exc_info.value.original_error is not None
553595

554596

597+
def test_crawl_tool_wraps_declared_page_field_inspection_failures(
598+
monkeypatch: pytest.MonkeyPatch,
599+
):
600+
class _MissingMarkdownPage:
601+
pass
602+
603+
def _raise_runtime_error(_obj: object, _attr: str):
604+
raise RuntimeError("attribute inspection exploded")
605+
606+
monkeypatch.setattr(tools_module.inspect, "getattr_static", _raise_runtime_error)
607+
client = _SyncCrawlClient(_Response(data=[_MissingMarkdownPage()]))
608+
609+
with pytest.raises(
610+
HyperbrowserError,
611+
match="Failed to inspect crawl tool page field 'markdown' at index 0",
612+
) as exc_info:
613+
WebsiteCrawlTool.runnable(client, {"url": "https://example.com"})
614+
615+
assert exc_info.value.original_error is not None
616+
617+
555618
def test_crawl_tool_rejects_non_object_page_items():
556619
client = _SyncCrawlClient(_Response(data=[123]))
557620

0 commit comments

Comments
 (0)