Skip to content

Commit a02a11c

Browse files
author
Nick Franck
committed
refactor: consolidate hostile-error attribute guards and quiet the precheck path
Share one guarded attribute reader between failure_category_of and status_code_of, apply status_code_of at the UnstructuredIngestError site it missed, compute the function signature once per request, and emit the missing-usage-parameter warning once at wrap time instead of on every request. Reuse existing test scaffolding instead of duplicating it.
1 parent c8ff7d1 commit a02a11c

2 files changed

Lines changed: 25 additions & 34 deletions

File tree

test/api/test_api.py

Lines changed: 4 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
import enum
21
from pathlib import Path
32
from typing import Any, Optional, Union
43

@@ -623,12 +622,12 @@ def test_precheck_success_has_no_failure_category():
623622

624623

625624
def test_precheck_ignores_non_string_failure_category():
626-
class _EnumCategoryFailure(Exception):
625+
class _NonStringCategoryFailure(Exception):
627626
status_code = 403
628-
failure_category = enum.Enum("Category", ["AUTH_PERMISSION_DENIED"]).AUTH_PERMISSION_DENIED
627+
failure_category = 403
629628

630629
def _enum_category_precheck() -> None:
631-
raise _EnumCategoryFailure("credential rejected")
630+
raise _NonStringCategoryFailure("credential rejected")
632631

633632
client = TestClient(
634633
wrap_in_fastapi(
@@ -645,14 +644,7 @@ def _enum_category_precheck() -> None:
645644

646645

647646
def test_invoke_reports_failure_category_from_raised_error():
648-
class _CategorizedError(Exception):
649-
status_code = 403
650-
failure_category = "AUTH_PERMISSION_DENIED"
651-
652-
def _raising_func() -> None:
653-
raise _CategorizedError("credential rejected")
654-
655-
client = TestClient(wrap_in_fastapi(func=_raising_func, plugin_id="mock_plugin"))
647+
client = TestClient(wrap_in_fastapi(func=_failing_precheck, plugin_id="mock_plugin"))
656648

657649
body = client.post("/invoke").json()
658650
assert body["status_code"] == 403

unstructured_platform_plugins/etl_uvicorn/api_generator.py

Lines changed: 21 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -62,31 +62,28 @@ def log_func_and_body(func: Callable, body: Optional[str] = None) -> None:
6262
logger.log(level=logger.level, msg=msg)
6363

6464

65-
def failure_category_of(error: BaseException) -> Optional[str]:
66-
"""Return the error's failure_category only when it is a plain string.
65+
def _error_attr(error: BaseException, name: str) -> Any:
66+
"""Read an attribute off a raised error, treating a raising property as absent.
6767
68-
Runs while an exception handler is building the sanitized response, so a
69-
non-string value — or an attribute access that itself raises — is treated
70-
as absent rather than allowed to replace that response with a raw 500.
68+
Runs while an exception handler is building the sanitized response; an
69+
attribute access that itself raises must not replace that response with a
70+
raw 500.
7171
"""
7272
try:
73-
category = getattr(error, "failure_category", None)
73+
return getattr(error, name, None)
7474
except Exception:
7575
return None
76+
77+
78+
def failure_category_of(error: BaseException) -> Optional[str]:
79+
"""Return the error's failure_category only when it is a plain string."""
80+
category = _error_attr(error, "failure_category")
7681
return category if isinstance(category, str) else None
7782

7883

7984
def status_code_of(error: BaseException) -> int:
80-
"""Return the error's status_code only when it is a usable integer.
81-
82-
Same contract as failure_category_of: runs inside exception handlers, so
83-
anything other than a plain int falls back to 500 instead of failing
84-
response-model validation.
85-
"""
86-
try:
87-
status_code = getattr(error, "status_code", None)
88-
except Exception:
89-
return status.HTTP_500_INTERNAL_SERVER_ERROR
85+
"""Return the error's status_code only when it is a usable integer, else 500."""
86+
status_code = _error_attr(error, "status_code")
9087
if isinstance(status_code, int) and not isinstance(status_code, bool):
9188
return status_code
9289
return status.HTTP_500_INTERNAL_SERVER_ERROR
@@ -168,6 +165,9 @@ def _wrap_in_fastapi(
168165

169166
logger.debug(f"set static id response to: {plugin_id}")
170167

168+
if "usage" not in inspect.signature(func).parameters:
169+
logger.warning("usage data not an expected parameter, omitting")
170+
171171
fastapi_app = FastAPI()
172172

173173
response_type = get_output_sig(func)
@@ -195,13 +195,12 @@ async def wrap_fn(func: Callable, kwargs: Optional[dict[str, Any]] = None) -> Re
195195
filedata_meta = FileDataMeta()
196196
message_channels = MessageChannels()
197197
request_dict = kwargs if kwargs else {}
198-
if "usage" in inspect.signature(func).parameters:
198+
params = inspect.signature(func).parameters
199+
if "usage" in params:
199200
request_dict["usage"] = usage
200-
else:
201-
logger.warning("usage data not an expected parameter, omitting")
202-
if "message_channels" in inspect.signature(func).parameters:
201+
if "message_channels" in params:
203202
request_dict["message_channels"] = message_channels
204-
if "filedata_meta" in inspect.signature(func).parameters:
203+
if "filedata_meta" in params:
205204
request_dict["filedata_meta"] = filedata_meta
206205
try:
207206
if inspect.isasyncgenfunction(func):
@@ -273,7 +272,7 @@ async def _stream_response():
273272
usage=usage,
274273
message_channels=message_channels,
275274
filedata_meta=filedata_meta_model.model_validate(filedata_meta.model_dump()),
276-
status_code=exc.status_code or status.HTTP_500_INTERNAL_SERVER_ERROR,
275+
status_code=status_code_of(exc),
277276
status_code_text=str(exc),
278277
failure_category=failure_category_of(exc),
279278
file_data=request_dict.get("file_data", None),

0 commit comments

Comments
 (0)