Skip to content

Commit fc46e63

Browse files
Centralize file-open error message formatting
Co-authored-by: Shri Sukhani <shrisukhani@users.noreply.github.com>
1 parent 03920b9 commit fc46e63

File tree

8 files changed

+82
-5
lines changed

8 files changed

+82
-5
lines changed

CONTRIBUTING.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -133,6 +133,7 @@ This runs lint, format checks, compile checks, tests, and package build.
133133
- `tests/test_extension_request_internal_reuse.py` (extension request-helper internal reuse of shared model request helpers),
134134
- `tests/test_extension_route_constants_usage.py` (extension manager route-constant usage enforcement),
135135
- `tests/test_extract_payload_helper_usage.py` (extract start-payload helper usage enforcement),
136+
- `tests/test_file_open_error_helper_usage.py` (shared file-open error-message helper usage enforcement),
136137
- `tests/test_file_path_display_helper_import_boundary.py` (shared file-path display helper import boundary enforcement),
137138
- `tests/test_file_path_display_helper_usage.py` (shared file-path display helper usage enforcement),
138139
- `tests/test_guardrail_ast_utils.py` (shared AST guard utility contract),

hyperbrowser/client/file_utils.py

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
from hyperbrowser.type_utils import is_plain_int, is_plain_string
88

99
_MAX_FILE_PATH_DISPLAY_LENGTH = 200
10+
_DEFAULT_OPEN_ERROR_MESSAGE_PREFIX = "Failed to open file at path"
1011

1112

1213
def format_file_path_for_error(
@@ -43,6 +44,23 @@ def format_file_path_for_error(
4344
return f"{sanitized_path[:truncated_length]}..."
4445

4546

47+
def build_open_file_error_message(file_path: object, *, prefix: str) -> str:
48+
normalized_prefix = prefix
49+
if not is_plain_string(normalized_prefix):
50+
normalized_prefix = _DEFAULT_OPEN_ERROR_MESSAGE_PREFIX
51+
else:
52+
try:
53+
stripped_prefix = normalized_prefix.strip()
54+
except Exception:
55+
stripped_prefix = _DEFAULT_OPEN_ERROR_MESSAGE_PREFIX
56+
if not is_plain_string(stripped_prefix) or not stripped_prefix:
57+
normalized_prefix = _DEFAULT_OPEN_ERROR_MESSAGE_PREFIX
58+
else:
59+
normalized_prefix = stripped_prefix
60+
file_path_display = format_file_path_for_error(file_path)
61+
return f"{normalized_prefix}: {file_path_display}"
62+
63+
4664
def _normalize_file_path_text(file_path: Union[str, PathLike[str]]) -> str:
4765
try:
4866
normalized_path = os.fspath(file_path)

hyperbrowser/client/managers/async_manager/extension.py

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
from typing import List
22

3-
from ...file_utils import open_binary_file
3+
from ...file_utils import build_open_file_error_message, open_binary_file
44
from ..extension_create_utils import normalize_extension_create_input
55
from ..extension_operation_metadata import EXTENSION_OPERATION_METADATA
66
from ..extension_request_utils import (
@@ -27,7 +27,10 @@ async def create(self, params: CreateExtensionParams) -> ExtensionResponse:
2727

2828
with open_binary_file(
2929
file_path,
30-
open_error_message=f"Failed to open extension file at path: {file_path}",
30+
open_error_message=build_open_file_error_message(
31+
file_path,
32+
prefix="Failed to open extension file at path",
33+
),
3134
) as extension_file:
3235
return await create_extension_resource_async(
3336
client=self._client,

hyperbrowser/client/managers/session_upload_utils.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
from hyperbrowser.type_utils import is_plain_string, is_string_subclass_instance
88

99
from ..file_utils import (
10+
build_open_file_error_message,
1011
ensure_existing_file_path,
1112
format_file_path_for_error,
1213
open_binary_file,
@@ -72,7 +73,10 @@ def open_upload_files_from_input(
7273
if file_path is not None:
7374
with open_binary_file(
7475
file_path,
75-
open_error_message=f"Failed to open upload file at path: {file_path}",
76+
open_error_message=build_open_file_error_message(
77+
file_path,
78+
prefix="Failed to open upload file at path",
79+
),
7680
) as opened_file:
7781
yield {"file": opened_file}
7882
return

hyperbrowser/client/managers/sync_manager/extension.py

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
from typing import List
22

3-
from ...file_utils import open_binary_file
3+
from ...file_utils import build_open_file_error_message, open_binary_file
44
from ..extension_create_utils import normalize_extension_create_input
55
from ..extension_operation_metadata import EXTENSION_OPERATION_METADATA
66
from ..extension_request_utils import (
@@ -27,7 +27,10 @@ def create(self, params: CreateExtensionParams) -> ExtensionResponse:
2727

2828
with open_binary_file(
2929
file_path,
30-
open_error_message=f"Failed to open extension file at path: {file_path}",
30+
open_error_message=build_open_file_error_message(
31+
file_path,
32+
prefix="Failed to open extension file at path",
33+
),
3134
) as extension_file:
3235
return create_extension_resource(
3336
client=self._client,

tests/test_architecture_marker_usage.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@
5050
"tests/test_model_request_wrapper_internal_reuse.py",
5151
"tests/test_tool_mapping_reader_usage.py",
5252
"tests/test_display_helper_usage.py",
53+
"tests/test_file_open_error_helper_usage.py",
5354
"tests/test_file_path_display_helper_import_boundary.py",
5455
"tests/test_file_path_display_helper_usage.py",
5556
"tests/test_binary_file_open_helper_usage.py",
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
from pathlib import Path
2+
3+
import pytest
4+
5+
pytestmark = pytest.mark.architecture
6+
7+
8+
OPEN_ERROR_HELPER_MODULES = (
9+
"hyperbrowser/client/managers/session_upload_utils.py",
10+
"hyperbrowser/client/managers/sync_manager/extension.py",
11+
"hyperbrowser/client/managers/async_manager/extension.py",
12+
)
13+
14+
15+
def test_file_open_error_messages_use_shared_helper():
16+
for module_path in OPEN_ERROR_HELPER_MODULES:
17+
module_text = Path(module_path).read_text(encoding="utf-8")
18+
assert "build_open_file_error_message(" in module_text
19+
assert "open_error_message=f\"" not in module_text

tests/test_file_utils.py

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

55
import hyperbrowser.client.file_utils as file_utils
66
from hyperbrowser.client.file_utils import (
7+
build_open_file_error_message,
78
ensure_existing_file_path,
89
format_file_path_for_error,
910
open_binary_file,
@@ -321,6 +322,33 @@ def __fspath__(self) -> str:
321322
assert format_file_path_for_error(_PathLike()) == "/tmp/path-value"
322323

323324

325+
def test_build_open_file_error_message_uses_prefix_and_sanitized_path():
326+
message = build_open_file_error_message(
327+
"bad\tpath.txt",
328+
prefix="Failed to open upload file at path",
329+
)
330+
331+
assert message == "Failed to open upload file at path: bad?path.txt"
332+
333+
334+
def test_build_open_file_error_message_uses_default_prefix_for_non_string():
335+
message = build_open_file_error_message(
336+
"/tmp/path.txt",
337+
prefix=123, # type: ignore[arg-type]
338+
)
339+
340+
assert message == "Failed to open file at path: /tmp/path.txt"
341+
342+
343+
def test_build_open_file_error_message_uses_default_prefix_for_blank_string():
344+
message = build_open_file_error_message(
345+
"/tmp/path.txt",
346+
prefix=" ",
347+
)
348+
349+
assert message == "Failed to open file at path: /tmp/path.txt"
350+
351+
324352
def test_ensure_existing_file_path_wraps_invalid_path_os_errors(monkeypatch):
325353
def raising_exists(path: str) -> bool:
326354
raise OSError("invalid path")

0 commit comments

Comments
 (0)