Skip to content

Commit e954551

Browse files
Use shared upload file context helper in session managers
Co-authored-by: Shri Sukhani <shrisukhani@users.noreply.github.com>
1 parent 86d7d69 commit e954551

6 files changed

Lines changed: 77 additions & 46 deletions

File tree

hyperbrowser/client/managers/async_manager/session.py

Lines changed: 2 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,13 @@
11
from os import PathLike
22
from typing import IO, List, Optional, Union, overload
33
import warnings
4-
from hyperbrowser.exceptions import HyperbrowserError
5-
from ...file_utils import open_binary_file
64
from ..serialization_utils import (
75
serialize_model_dump_or_default,
86
serialize_model_dump_to_dict,
97
serialize_optional_model_dump_to_dict,
108
)
119
from ..session_profile_update_utils import resolve_update_profile_params
12-
from ..session_upload_utils import normalize_upload_file_input
10+
from ..session_upload_utils import open_upload_files_from_input
1311
from ..session_utils import (
1412
parse_session_recordings_response_data,
1513
parse_session_response_model,
@@ -168,23 +166,7 @@ async def get_downloads_url(self, id: str) -> GetSessionDownloadsUrlResponse:
168166
async def upload_file(
169167
self, id: str, file_input: Union[str, PathLike[str], IO]
170168
) -> UploadFileResponse:
171-
file_path, file_obj = normalize_upload_file_input(file_input)
172-
if file_path is not None:
173-
with open_binary_file(
174-
file_path,
175-
open_error_message=f"Failed to open upload file at path: {file_path}",
176-
) as file_obj:
177-
files = {"file": file_obj}
178-
response = await self._client.transport.post(
179-
self._client._build_url(f"/session/{id}/uploads"),
180-
files=files,
181-
)
182-
else:
183-
if file_obj is None:
184-
raise HyperbrowserError(
185-
"file_input must be a file path or file-like object"
186-
)
187-
files = {"file": file_obj}
169+
with open_upload_files_from_input(file_input) as files:
188170
response = await self._client.transport.post(
189171
self._client._build_url(f"/session/{id}/uploads"),
190172
files=files,

hyperbrowser/client/managers/session_upload_utils.py

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,12 @@
11
import os
2+
from contextlib import contextmanager
23
from os import PathLike
3-
from typing import IO, Optional, Tuple, Union
4+
from typing import Dict, IO, Iterator, Optional, Tuple, Union
45

56
from hyperbrowser.exceptions import HyperbrowserError
67
from hyperbrowser.type_utils import is_plain_string, is_string_subclass_instance
78

8-
from ..file_utils import ensure_existing_file_path
9+
from ..file_utils import ensure_existing_file_path, open_binary_file
910

1011

1112
def normalize_upload_file_input(
@@ -56,3 +57,20 @@ def normalize_upload_file_input(
5657
raise HyperbrowserError("file_input file-like object must be open")
5758

5859
return None, file_input
60+
61+
62+
@contextmanager
63+
def open_upload_files_from_input(
64+
file_input: Union[str, PathLike[str], IO],
65+
) -> Iterator[Dict[str, IO]]:
66+
file_path, file_obj = normalize_upload_file_input(file_input)
67+
if file_path is not None:
68+
with open_binary_file(
69+
file_path,
70+
open_error_message=f"Failed to open upload file at path: {file_path}",
71+
) as opened_file:
72+
yield {"file": opened_file}
73+
return
74+
if file_obj is None:
75+
raise HyperbrowserError("file_input must be a file path or file-like object")
76+
yield {"file": file_obj}

hyperbrowser/client/managers/sync_manager/session.py

Lines changed: 2 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,13 @@
11
from os import PathLike
22
from typing import IO, List, Optional, Union, overload
33
import warnings
4-
from hyperbrowser.exceptions import HyperbrowserError
5-
from ...file_utils import open_binary_file
64
from ..serialization_utils import (
75
serialize_model_dump_or_default,
86
serialize_model_dump_to_dict,
97
serialize_optional_model_dump_to_dict,
108
)
119
from ..session_profile_update_utils import resolve_update_profile_params
12-
from ..session_upload_utils import normalize_upload_file_input
10+
from ..session_upload_utils import open_upload_files_from_input
1311
from ..session_utils import (
1412
parse_session_recordings_response_data,
1513
parse_session_response_model,
@@ -160,23 +158,7 @@ def get_downloads_url(self, id: str) -> GetSessionDownloadsUrlResponse:
160158
def upload_file(
161159
self, id: str, file_input: Union[str, PathLike[str], IO]
162160
) -> UploadFileResponse:
163-
file_path, file_obj = normalize_upload_file_input(file_input)
164-
if file_path is not None:
165-
with open_binary_file(
166-
file_path,
167-
open_error_message=f"Failed to open upload file at path: {file_path}",
168-
) as file_obj:
169-
files = {"file": file_obj}
170-
response = self._client.transport.post(
171-
self._client._build_url(f"/session/{id}/uploads"),
172-
files=files,
173-
)
174-
else:
175-
if file_obj is None:
176-
raise HyperbrowserError(
177-
"file_input must be a file path or file-like object"
178-
)
179-
files = {"file": file_obj}
161+
with open_upload_files_from_input(file_input) as files:
180162
response = self._client.transport.post(
181163
self._client._build_url(f"/session/{id}/uploads"),
182164
files=files,

tests/test_binary_file_open_helper_usage.py

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,16 +5,27 @@
55
pytestmark = pytest.mark.architecture
66

77

8-
MODULES = (
8+
EXTENSION_MODULES = (
99
"hyperbrowser/client/managers/sync_manager/extension.py",
1010
"hyperbrowser/client/managers/async_manager/extension.py",
11+
)
12+
13+
SESSION_MODULES = (
1114
"hyperbrowser/client/managers/sync_manager/session.py",
1215
"hyperbrowser/client/managers/async_manager/session.py",
1316
)
1417

1518

16-
def test_managers_use_shared_binary_file_open_helper():
17-
for module_path in MODULES:
19+
def test_extension_managers_use_shared_binary_file_open_helper():
20+
for module_path in EXTENSION_MODULES:
1821
module_text = Path(module_path).read_text(encoding="utf-8")
1922
assert "open_binary_file(" in module_text
2023
assert 'with open(file_path, "rb")' not in module_text
24+
25+
26+
def test_session_managers_use_upload_file_context_helper():
27+
for module_path in SESSION_MODULES:
28+
module_text = Path(module_path).read_text(encoding="utf-8")
29+
assert "open_upload_files_from_input(" in module_text
30+
assert "open_binary_file(" not in module_text
31+
assert 'with open(file_path, "rb")' not in module_text

tests/test_session_upload_helper_usage.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,8 @@
1414
def test_session_managers_use_shared_upload_input_normalizer():
1515
for module_path in SESSION_MANAGER_MODULES:
1616
module_text = Path(module_path).read_text(encoding="utf-8")
17-
assert "normalize_upload_file_input(" in module_text
17+
assert "open_upload_files_from_input(" in module_text
1818
assert "os.fspath(" not in module_text
1919
assert "ensure_existing_file_path(" not in module_text
2020
assert 'getattr(file_input, "read"' not in module_text
21+
assert "open_binary_file(" not in module_text

tests/test_session_upload_utils.py

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,9 @@
44

55
import pytest
66

7+
import hyperbrowser.client.managers.session_upload_utils as session_upload_utils
78
from hyperbrowser.client.managers.session_upload_utils import (
9+
open_upload_files_from_input,
810
normalize_upload_file_input,
911
)
1012
from hyperbrowser.exceptions import HyperbrowserError
@@ -134,3 +136,38 @@ def closed(self):
134136
normalize_upload_file_input(_BrokenFileLike()) # type: ignore[arg-type]
135137

136138
assert exc_info.value.original_error is None
139+
140+
141+
def test_open_upload_files_from_input_opens_and_closes_path_input(tmp_path: Path):
142+
file_path = tmp_path / "file.txt"
143+
file_path.write_text("content")
144+
145+
with open_upload_files_from_input(str(file_path)) as files:
146+
assert "file" in files
147+
assert files["file"].closed is False
148+
assert files["file"].read() == b"content"
149+
150+
assert files["file"].closed is True
151+
152+
153+
def test_open_upload_files_from_input_reuses_file_like_object():
154+
file_obj = io.BytesIO(b"content")
155+
156+
with open_upload_files_from_input(file_obj) as files:
157+
assert files == {"file": file_obj}
158+
159+
160+
def test_open_upload_files_from_input_rejects_missing_normalized_file_object(
161+
monkeypatch: pytest.MonkeyPatch,
162+
):
163+
monkeypatch.setattr(
164+
session_upload_utils,
165+
"normalize_upload_file_input",
166+
lambda file_input: (None, None),
167+
)
168+
169+
with pytest.raises(
170+
HyperbrowserError, match="file_input must be a file path or file-like object"
171+
):
172+
with open_upload_files_from_input(io.BytesIO(b"content")):
173+
pass

0 commit comments

Comments
 (0)