Skip to content

Commit bccb68b

Browse files
Centralize computer-action payload serialization helper
Co-authored-by: Shri Sukhani <shrisukhani@users.noreply.github.com>
1 parent 94b0e74 commit bccb68b

8 files changed

Lines changed: 115 additions & 22 deletions

CONTRIBUTING.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,7 @@ This runs lint, format checks, compile checks, tests, and package build.
8484
- `tests/test_browser_use_payload_helper_usage.py` (browser-use payload helper usage enforcement),
8585
- `tests/test_ci_workflow_quality_gates.py` (CI guard-stage + make-target enforcement),
8686
- `tests/test_computer_action_endpoint_helper_usage.py` (computer-action endpoint-normalization helper usage enforcement),
87+
- `tests/test_computer_action_payload_helper_usage.py` (computer-action payload helper usage enforcement),
8788
- `tests/test_contributing_architecture_guard_listing.py` (`CONTRIBUTING.md` architecture-guard inventory completeness enforcement),
8889
- `tests/test_core_type_helper_usage.py` (core transport/config/header/file/polling/session/error/parsing manager+tool module enforcement of shared plain-type helper usage),
8990
- `tests/test_default_serialization_helper_usage.py` (default optional-query serialization helper usage enforcement),

hyperbrowser/client/managers/async_manager/computer_action.py

Lines changed: 2 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,9 @@
1-
from pydantic import BaseModel
21
from typing import Union, List, Optional
32
from hyperbrowser.exceptions import HyperbrowserError
43
from hyperbrowser.type_utils import is_plain_string, is_string_subclass_instance
54
from ..computer_action_utils import normalize_computer_action_endpoint
5+
from ..computer_action_payload_utils import build_computer_action_payload
66
from ..response_utils import parse_response_model
7-
from ..serialization_utils import serialize_model_dump_to_dict
87
from hyperbrowser.models import (
98
SessionDetail,
109
ComputerActionParams,
@@ -44,15 +43,7 @@ async def _execute_request(
4443
session
4544
)
4645

47-
if isinstance(params, BaseModel):
48-
payload = serialize_model_dump_to_dict(
49-
params,
50-
error_message="Failed to serialize computer action params",
51-
by_alias=True,
52-
exclude_none=True,
53-
)
54-
else:
55-
payload = params
46+
payload = build_computer_action_payload(params)
5647

5748
response = await self._client.transport.post(
5849
normalized_computer_action_endpoint,
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
from typing import Any
2+
3+
from pydantic import BaseModel
4+
5+
from .serialization_utils import serialize_model_dump_to_dict
6+
7+
8+
def build_computer_action_payload(params: Any) -> Any:
9+
if isinstance(params, BaseModel):
10+
return serialize_model_dump_to_dict(
11+
params,
12+
error_message="Failed to serialize computer action params",
13+
by_alias=True,
14+
exclude_none=True,
15+
)
16+
return params

hyperbrowser/client/managers/sync_manager/computer_action.py

Lines changed: 2 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,9 @@
1-
from pydantic import BaseModel
21
from typing import Union, List, Optional
32
from hyperbrowser.exceptions import HyperbrowserError
43
from hyperbrowser.type_utils import is_plain_string, is_string_subclass_instance
54
from ..computer_action_utils import normalize_computer_action_endpoint
5+
from ..computer_action_payload_utils import build_computer_action_payload
66
from ..response_utils import parse_response_model
7-
from ..serialization_utils import serialize_model_dump_to_dict
87
from hyperbrowser.models import (
98
SessionDetail,
109
ComputerActionParams,
@@ -44,15 +43,7 @@ def _execute_request(
4443
session
4544
)
4645

47-
if isinstance(params, BaseModel):
48-
payload = serialize_model_dump_to_dict(
49-
params,
50-
error_message="Failed to serialize computer action params",
51-
by_alias=True,
52-
exclude_none=True,
53-
)
54-
else:
55-
payload = params
46+
payload = build_computer_action_payload(params)
5647

5748
response = self._client.transport.post(
5849
normalized_computer_action_endpoint,

tests/test_architecture_marker_usage.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@
4343
"tests/test_example_sync_async_parity.py",
4444
"tests/test_example_run_instructions.py",
4545
"tests/test_computer_action_endpoint_helper_usage.py",
46+
"tests/test_computer_action_payload_helper_usage.py",
4647
"tests/test_schema_injection_helper_usage.py",
4748
"tests/test_session_profile_update_helper_usage.py",
4849
"tests/test_session_upload_helper_usage.py",
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
from pathlib import Path
2+
3+
import pytest
4+
5+
pytestmark = pytest.mark.architecture
6+
7+
8+
MODULES = (
9+
"hyperbrowser/client/managers/sync_manager/computer_action.py",
10+
"hyperbrowser/client/managers/async_manager/computer_action.py",
11+
)
12+
13+
14+
def test_computer_action_managers_use_shared_payload_helper():
15+
for module_path in MODULES:
16+
module_text = Path(module_path).read_text(encoding="utf-8")
17+
assert "build_computer_action_payload(" in module_text
18+
assert "serialize_model_dump_to_dict(" not in module_text
Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
from types import MappingProxyType
2+
3+
import pytest
4+
from pydantic import BaseModel
5+
from typing import Optional
6+
7+
from hyperbrowser.client.managers.computer_action_payload_utils import (
8+
build_computer_action_payload,
9+
)
10+
from hyperbrowser.exceptions import HyperbrowserError
11+
12+
13+
class _ActionParams(BaseModel):
14+
action_type: str
15+
return_screenshot: Optional[bool] = None
16+
17+
18+
def test_build_computer_action_payload_serializes_pydantic_models():
19+
payload = build_computer_action_payload(
20+
_ActionParams(action_type="screenshot", return_screenshot=True)
21+
)
22+
23+
assert payload == {"action_type": "screenshot", "return_screenshot": True}
24+
25+
26+
def test_build_computer_action_payload_passes_through_non_models():
27+
raw_payload = {"foo": "bar"}
28+
29+
assert build_computer_action_payload(raw_payload) is raw_payload
30+
31+
32+
def test_build_computer_action_payload_wraps_runtime_model_dump_errors():
33+
class _BrokenParams(BaseModel):
34+
def model_dump(self, *args, **kwargs): # type: ignore[override]
35+
_ = args
36+
_ = kwargs
37+
raise RuntimeError("broken model_dump")
38+
39+
with pytest.raises(
40+
HyperbrowserError, match="Failed to serialize computer action params"
41+
) as exc_info:
42+
build_computer_action_payload(_BrokenParams())
43+
44+
assert isinstance(exc_info.value.original_error, RuntimeError)
45+
46+
47+
def test_build_computer_action_payload_preserves_hyperbrowser_model_dump_errors():
48+
class _BrokenParams(BaseModel):
49+
def model_dump(self, *args, **kwargs): # type: ignore[override]
50+
_ = args
51+
_ = kwargs
52+
raise HyperbrowserError("custom model_dump failure")
53+
54+
with pytest.raises(
55+
HyperbrowserError, match="custom model_dump failure"
56+
) as exc_info:
57+
build_computer_action_payload(_BrokenParams())
58+
59+
assert exc_info.value.original_error is None
60+
61+
62+
def test_build_computer_action_payload_rejects_non_dict_model_dump_results():
63+
class _BrokenParams(BaseModel):
64+
def model_dump(self, *args, **kwargs): # type: ignore[override]
65+
_ = args
66+
_ = kwargs
67+
return MappingProxyType({"actionType": "screenshot"})
68+
69+
with pytest.raises(
70+
HyperbrowserError, match="Failed to serialize computer action params"
71+
) as exc_info:
72+
build_computer_action_payload(_BrokenParams())
73+
74+
assert exc_info.value.original_error is None

tests/test_core_type_helper_usage.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030
"hyperbrowser/client/managers/sync_manager/session.py",
3131
"hyperbrowser/client/managers/async_manager/session.py",
3232
"hyperbrowser/client/managers/computer_action_utils.py",
33+
"hyperbrowser/client/managers/computer_action_payload_utils.py",
3334
"hyperbrowser/client/managers/extension_create_utils.py",
3435
"hyperbrowser/client/managers/extract_payload_utils.py",
3536
"hyperbrowser/client/managers/job_pagination_utils.py",

0 commit comments

Comments
 (0)