Skip to content

Commit 7dce68f

Browse files
Reuse mapping key-copy helper in tools
Co-authored-by: Shri Sukhani <shrisukhani@users.noreply.github.com>
1 parent f7cfed4 commit 7dce68f

File tree

3 files changed

+96
-27
lines changed

3 files changed

+96
-27
lines changed

hyperbrowser/mapping_utils.py

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,3 +46,30 @@ def read_string_key_mapping(
4646
original_error=exc,
4747
) from exc
4848
return normalized_mapping
49+
50+
51+
def copy_mapping_values_by_string_keys(
52+
mapping_value: MappingABC[object, Any],
53+
keys: list[str],
54+
*,
55+
read_value_error_builder: Callable[[str], str],
56+
key_display: Callable[[str], str],
57+
) -> Dict[str, object]:
58+
normalized_mapping: Dict[str, object] = {}
59+
for key in keys:
60+
try:
61+
normalized_mapping[key] = mapping_value[key]
62+
except HyperbrowserError:
63+
raise
64+
except Exception as exc:
65+
try:
66+
key_text = key_display(key)
67+
if type(key_text) is not str:
68+
raise TypeError("mapping key display must be a string")
69+
except Exception:
70+
key_text = "<unreadable key>"
71+
raise HyperbrowserError(
72+
read_value_error_builder(key_text),
73+
original_error=exc,
74+
) from exc
75+
return normalized_mapping

hyperbrowser/tools/__init__.py

Lines changed: 8 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
11
import inspect
22
import json
33
from collections.abc import Mapping as MappingABC
4-
from typing import Any, Callable, Dict, Mapping
4+
from typing import Any, Dict, Mapping
55

66
from hyperbrowser.display_utils import normalize_display_text
77
from hyperbrowser.exceptions import HyperbrowserError
8+
from hyperbrowser.mapping_utils import copy_mapping_values_by_string_keys
89
from hyperbrowser.models.agents.browser_use import StartBrowserUseTaskParams
910
from hyperbrowser.models.crawl import StartCrawlJobParams
1011
from hyperbrowser.models.extract import StartExtractJobParams
@@ -62,27 +63,6 @@ def _format_tool_param_key_for_error(key: str) -> str:
6263
return normalized_key
6364

6465

65-
def _copy_mapping_values_by_keys(
66-
source_mapping: MappingABC[object, Any],
67-
keys: list[str],
68-
*,
69-
read_error_message_builder: Callable[[str], str],
70-
) -> Dict[str, Any]:
71-
normalized_values: Dict[str, Any] = {}
72-
for key in keys:
73-
try:
74-
normalized_values[key] = source_mapping[key]
75-
except HyperbrowserError:
76-
raise
77-
except Exception as exc:
78-
key_display = _format_tool_param_key_for_error(key)
79-
raise HyperbrowserError(
80-
read_error_message_builder(key_display),
81-
original_error=exc,
82-
) from exc
83-
return normalized_values
84-
85-
8666
def _normalize_extract_schema_mapping(
8767
schema_value: MappingABC[object, Any],
8868
) -> Dict[str, Any]:
@@ -100,12 +80,13 @@ def _normalize_extract_schema_mapping(
10080
if type(key) is not str:
10181
raise HyperbrowserError("Extract tool `schema` object keys must be strings")
10282
normalized_schema_keys.append(key)
103-
return _copy_mapping_values_by_keys(
83+
return copy_mapping_values_by_string_keys(
10484
schema_value,
10585
normalized_schema_keys,
106-
read_error_message_builder=lambda key_display: (
86+
read_value_error_builder=lambda key_display: (
10787
f"Failed to read extract tool `schema` value for key '{key_display}'"
10888
),
89+
key_display=_format_tool_param_key_for_error,
10990
)
11091

11192

@@ -197,12 +178,13 @@ def _to_param_dict(params: Mapping[str, Any]) -> Dict[str, Any]:
197178
continue
198179
raise HyperbrowserError("tool params keys must be strings")
199180
normalized_param_keys = [key for key in param_keys if type(key) is str]
200-
return _copy_mapping_values_by_keys(
181+
return copy_mapping_values_by_string_keys(
201182
params,
202183
normalized_param_keys,
203-
read_error_message_builder=lambda key_display: (
184+
read_value_error_builder=lambda key_display: (
204185
f"Failed to read tool param '{key_display}'"
205186
),
187+
key_display=_format_tool_param_key_for_error,
206188
)
207189

208190

tests/test_mapping_utils.py

Lines changed: 61 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,10 @@
33
import pytest
44

55
from hyperbrowser.exceptions import HyperbrowserError
6-
from hyperbrowser.mapping_utils import read_string_key_mapping
6+
from hyperbrowser.mapping_utils import (
7+
copy_mapping_values_by_string_keys,
8+
read_string_key_mapping,
9+
)
710

811

912
class _BrokenKeysMapping(Mapping[object, object]):
@@ -111,3 +114,60 @@ def test_read_string_key_mapping_falls_back_for_unreadable_key_display():
111114
),
112115
key_display=lambda key: key.encode("utf-8"),
113116
)
117+
118+
119+
def test_copy_mapping_values_by_string_keys_returns_selected_values():
120+
copied_values = copy_mapping_values_by_string_keys(
121+
{"field": "value", "other": "ignored"},
122+
["field"],
123+
read_value_error_builder=lambda key_display: (
124+
f"failed value for '{key_display}'"
125+
),
126+
key_display=lambda key: key,
127+
)
128+
129+
assert copied_values == {"field": "value"}
130+
131+
132+
def test_copy_mapping_values_by_string_keys_wraps_value_read_failures():
133+
with pytest.raises(
134+
HyperbrowserError, match="failed value for 'field'"
135+
) as exc_info:
136+
copy_mapping_values_by_string_keys(
137+
_BrokenValueMapping(),
138+
["field"],
139+
read_value_error_builder=lambda key_display: (
140+
f"failed value for '{key_display}'"
141+
),
142+
key_display=lambda key: key,
143+
)
144+
145+
assert isinstance(exc_info.value.original_error, RuntimeError)
146+
147+
148+
def test_copy_mapping_values_by_string_keys_preserves_hyperbrowser_failures():
149+
with pytest.raises(HyperbrowserError, match="custom value read failure") as exc_info:
150+
copy_mapping_values_by_string_keys(
151+
_HyperbrowserValueFailureMapping(),
152+
["field"],
153+
read_value_error_builder=lambda key_display: (
154+
f"failed value for '{key_display}'"
155+
),
156+
key_display=lambda key: key,
157+
)
158+
159+
assert exc_info.value.original_error is None
160+
161+
162+
def test_copy_mapping_values_by_string_keys_falls_back_for_unreadable_key_display():
163+
with pytest.raises(
164+
HyperbrowserError, match="failed value for '<unreadable key>'"
165+
):
166+
copy_mapping_values_by_string_keys(
167+
_BrokenValueMapping(),
168+
["field"],
169+
read_value_error_builder=lambda key_display: (
170+
f"failed value for '{key_display}'"
171+
),
172+
key_display=lambda key: key.encode("utf-8"),
173+
)

0 commit comments

Comments
 (0)