Skip to content

Commit 8972630

Browse files
Wrap broken file-like state access in upload validation
Co-authored-by: Shri Sukhani <shrisukhani@users.noreply.github.com>
1 parent 86da89e commit 8972630

File tree

3 files changed

+81
-22
lines changed

3 files changed

+81
-22
lines changed

hyperbrowser/client/managers/async_manager/session.py

Lines changed: 26 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -133,18 +133,33 @@ async def upload_file(
133133
f"Failed to open upload file at path: {file_path}",
134134
original_error=exc,
135135
) from exc
136-
elif callable(getattr(file_input, "read", None)):
137-
if getattr(file_input, "closed", False):
138-
raise HyperbrowserError("file_input file-like object must be open")
139-
files = {"file": file_input}
140-
response = await self._client.transport.post(
141-
self._client._build_url(f"/session/{id}/uploads"),
142-
files=files,
143-
)
144136
else:
145-
raise HyperbrowserError(
146-
"file_input must be a file path or file-like object"
147-
)
137+
try:
138+
read_method = getattr(file_input, "read", None)
139+
except Exception as exc:
140+
raise HyperbrowserError(
141+
"file_input file-like object state is invalid",
142+
original_error=exc,
143+
) from exc
144+
if callable(read_method):
145+
try:
146+
is_closed = bool(getattr(file_input, "closed", False))
147+
except Exception as exc:
148+
raise HyperbrowserError(
149+
"file_input file-like object state is invalid",
150+
original_error=exc,
151+
) from exc
152+
if is_closed:
153+
raise HyperbrowserError("file_input file-like object must be open")
154+
files = {"file": file_input}
155+
response = await self._client.transport.post(
156+
self._client._build_url(f"/session/{id}/uploads"),
157+
files=files,
158+
)
159+
else:
160+
raise HyperbrowserError(
161+
"file_input must be a file path or file-like object"
162+
)
148163

149164
return UploadFileResponse(**response.data)
150165

hyperbrowser/client/managers/sync_manager/session.py

Lines changed: 26 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -125,18 +125,33 @@ def upload_file(
125125
f"Failed to open upload file at path: {file_path}",
126126
original_error=exc,
127127
) from exc
128-
elif callable(getattr(file_input, "read", None)):
129-
if getattr(file_input, "closed", False):
130-
raise HyperbrowserError("file_input file-like object must be open")
131-
files = {"file": file_input}
132-
response = self._client.transport.post(
133-
self._client._build_url(f"/session/{id}/uploads"),
134-
files=files,
135-
)
136128
else:
137-
raise HyperbrowserError(
138-
"file_input must be a file path or file-like object"
139-
)
129+
try:
130+
read_method = getattr(file_input, "read", None)
131+
except Exception as exc:
132+
raise HyperbrowserError(
133+
"file_input file-like object state is invalid",
134+
original_error=exc,
135+
) from exc
136+
if callable(read_method):
137+
try:
138+
is_closed = bool(getattr(file_input, "closed", False))
139+
except Exception as exc:
140+
raise HyperbrowserError(
141+
"file_input file-like object state is invalid",
142+
original_error=exc,
143+
) from exc
144+
if is_closed:
145+
raise HyperbrowserError("file_input file-like object must be open")
146+
files = {"file": file_input}
147+
response = self._client.transport.post(
148+
self._client._build_url(f"/session/{id}/uploads"),
149+
files=files,
150+
)
151+
else:
152+
raise HyperbrowserError(
153+
"file_input must be a file path or file-like object"
154+
)
140155

141156
return UploadFileResponse(**response.data)
142157

tests/test_session_upload_file.py

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -157,6 +157,18 @@ def test_sync_session_upload_file_rejects_closed_file_like_object():
157157
manager.upload_file("session_123", closed_file_obj)
158158

159159

160+
def test_sync_session_upload_file_wraps_invalid_file_like_state_errors():
161+
manager = SyncSessionManager(_FakeClient(_SyncTransport()))
162+
163+
class _BrokenFileLike:
164+
@property
165+
def read(self):
166+
raise RuntimeError("broken read")
167+
168+
with pytest.raises(HyperbrowserError, match="file-like object state is invalid"):
169+
manager.upload_file("session_123", _BrokenFileLike())
170+
171+
160172
def test_async_session_upload_file_rejects_non_callable_read_attribute():
161173
manager = AsyncSessionManager(_FakeClient(_AsyncTransport()))
162174
fake_file = type("FakeFile", (), {"read": "not-callable"})()
@@ -180,6 +192,23 @@ async def run():
180192
asyncio.run(run())
181193

182194

195+
def test_async_session_upload_file_wraps_invalid_file_like_state_errors():
196+
manager = AsyncSessionManager(_FakeClient(_AsyncTransport()))
197+
198+
class _BrokenFileLike:
199+
@property
200+
def read(self):
201+
raise RuntimeError("broken read")
202+
203+
async def run():
204+
with pytest.raises(
205+
HyperbrowserError, match="file-like object state is invalid"
206+
):
207+
await manager.upload_file("session_123", _BrokenFileLike())
208+
209+
asyncio.run(run())
210+
211+
183212
def test_sync_session_upload_file_raises_hyperbrowser_error_for_missing_path(tmp_path):
184213
manager = SyncSessionManager(_FakeClient(_SyncTransport()))
185214
missing_path = tmp_path / "missing-file.txt"

0 commit comments

Comments
 (0)