fix(api): chain original exception in 13 except blocks per PEP 3134 (#40738)

Co-authored-by: Harsh Kashyap <Harsh23Kashyap@users.noreply.github.com>
This commit is contained in:
Harsh Kashyap 2026-08-13 17:50:05 +00:00 committed by GitHub
parent c9c7fb9962
commit 8238684334
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
12 changed files with 54 additions and 30 deletions

View File

@ -96,8 +96,8 @@ class PartnerTenants(Resource):
try:
click_id = req_data.click_id
decoded_partner_key = base64.b64decode(partner_key).decode("utf-8")
except Exception:
raise BadRequest("Invalid partner_key")
except Exception as e:
raise BadRequest("Invalid partner_key") from e
if not click_id or not decoded_partner_key or not current_user.id:
raise BadRequest("Invalid partner information")

View File

@ -302,8 +302,8 @@ class SwitchWorkspaceApi(Resource):
# Check whether the tenant_id belongs to the current account.
try:
TenantService.switch_tenant(current_user, args.tenant_id, session=session)
except Exception:
raise AccountNotLinkTenantError("Account not link tenant")
except Exception as e:
raise AccountNotLinkTenantError("Account not link tenant") from e
new_tenant = TenantService.get_tenant_by_id(args.tenant_id, session=session)
if new_tenant is None:

View File

@ -67,8 +67,8 @@ class ToolFileApi(Resource):
except NotFound:
raise
except Exception:
raise UnsupportedFileTypeError()
except Exception as e:
raise UnsupportedFileTypeError() from e
mime_type = tool_file.mime_type
filename = tool_file.filename

View File

@ -84,8 +84,8 @@ def get_user(tenant_id: str, user_id: str | None) -> EndUser:
session.flush()
session.refresh(user_model)
except Exception:
raise ValueError("user not found")
except Exception as e:
raise ValueError("user not found") from e
return user_model
@ -131,13 +131,13 @@ def plugin_data[**P, R](
def decorated_view(*args: P.args, **kwargs: P.kwargs) -> R:
try:
data = request.get_json()
except Exception:
raise ValueError("invalid json")
except Exception as e:
raise ValueError("invalid json") from e
try:
payload = payload_type.model_validate(data)
except Exception as e:
raise ValueError(f"invalid payload: {str(e)}")
raise ValueError(f"invalid payload: {str(e)}") from e
kwargs["payload"] = payload
return view_func(*args, **kwargs)

View File

@ -270,8 +270,8 @@ class PluginAppBackwardsInvocation(BaseBackwardsInvocation):
app = session.scalar(select(App).where(App.id == app_id, App.tenant_id == tenant_id).limit(1))
if app:
session.expunge(app)
except Exception:
raise ValueError("app not found")
except Exception as e:
raise ValueError("app not found") from e
if not app:
raise ValueError("app not found")

View File

@ -241,8 +241,10 @@ def cast_parameter_value(typ: StrEnum, value: Any, /):
return str(value)
except ValueError:
raise
except Exception:
raise ValueError(f"The tool parameter value {repr(value)} is not in correct type of {as_normal_type(typ)}.")
except Exception as e:
raise ValueError(
f"The tool parameter value {repr(value)} is not in correct type of {as_normal_type(typ)}."
) from e
def init_frontend_parameter(rule: PluginParameter, type: StrEnum, value: Any):

View File

@ -276,19 +276,19 @@ class BasePluginClient:
json_response = transformer(json_response)
# https://stackoverflow.com/questions/59634937/variable-foo-class-is-not-valid-as-type-but-why
rep = PluginDaemonBasicResponse[type_].model_validate(json_response) # type: ignore
except Exception:
except Exception as e:
msg = (
f"Failed to parse response from plugin daemon to PluginDaemonBasicResponse [{str(type_.__name__)}],"
f" url: {path}"
)
logger.exception(msg)
raise ValueError(msg)
raise ValueError(msg) from e
if rep.code != 0:
try:
error = PluginDaemonError.model_validate(json.loads(rep.message))
except Exception:
raise ValueError(f"{rep.message}, code: {rep.code}")
except Exception as e:
raise ValueError(f"{rep.message}, code: {rep.code}") from e
self._handle_plugin_daemon_error(error.error_type, error.message)
if rep.data is None:

View File

@ -19,5 +19,5 @@ class StructuredChatOutputParser:
return ReactAction(response["action"], response.get("action_input", {}), text)
else:
return ReactFinish({"output": text}, text)
except Exception:
raise ValueError(f"Could not parse LLM output: {text}")
except Exception as e:
raise ValueError(f"Could not parse LLM output: {text}") from e

View File

@ -238,9 +238,9 @@ class ApiToolManageService:
# try to parse schema, avoid SSRF attack
ApiToolManageService.parser_api_schema(schema)
except Exception:
except Exception as e:
logger.exception("parse api schema error")
raise ValueError("invalid schema, please check the url you provided")
raise ValueError("invalid schema, please check the url you provided") from e
return {"schema": schema}
@ -453,8 +453,8 @@ class ApiToolManageService:
try:
tool_bundles, _ = ApiBasedToolSchemaParser.auto_parse_to_tool_bundle(schema)
except Exception:
raise ValueError("invalid schema")
except Exception as e:
raise ValueError("invalid schema") from e
# get tool bundle
tool_bundle = next(filter(lambda tb: tb.operation_id == tool_name, tool_bundles), None)

View File

@ -219,8 +219,11 @@ class TestGetUser:
event.listen(sqlite_plugin_engine, "before_cursor_execute", _raise_database_error)
try:
with app.app_context(), pytest.raises(ValueError, match="user not found"):
with app.app_context(), pytest.raises(ValueError, match="user not found") as exc_info:
get_user("tenant123", "user123")
# PEP 3134: the re-raised ValueError must chain the underlying database error.
assert isinstance(exc_info.value.__cause__, RuntimeError)
assert str(exc_info.value.__cause__) == "Database error"
finally:
event.remove(sqlite_plugin_engine, "before_cursor_execute", _raise_database_error)
@ -351,8 +354,11 @@ class TestPluginData:
# Act & Assert - Malformed JSON triggers ValueError
with app.test_request_context(data="not valid json", content_type="application/json"):
with pytest.raises(ValueError):
with pytest.raises(ValueError) as exc_info:
protected_view()
# PEP 3134: the re-raised ValueError must chain the underlying JSON parse failure.
assert exc_info.value.__cause__ is not None
assert isinstance(exc_info.value.__cause__, Exception)
def test_should_raise_error_on_invalid_payload(self, app: Flask):
"""Test that ValueError is raised when payload validation fails"""
@ -369,8 +375,11 @@ class TestPluginData:
# Act & Assert
with app.test_request_context(json={"data": "test"}):
with pytest.raises(ValueError, match="invalid payload"):
with pytest.raises(ValueError, match="invalid payload") as exc_info:
protected_view()
# PEP 3134: the re-raised ValueError must chain the original Exception from model_validate.
assert exc_info.value.__cause__ is not None
assert str(exc_info.value.__cause__) == "Validation failed"
def test_should_work_as_parameterized_decorator(self, app: Flask):
"""Test that decorator works when used with parentheses"""

View File

@ -244,9 +244,15 @@ class TestPluginParameterEntities:
with pytest.raises(
ValueError,
match=r"The tool parameter value <.*_BadString object at .* is not in correct type of string\.",
):
) as exc_info:
cast_parameter_value(PluginParameterType.STRING, _BadString())
# PEP 3134: the re-raised ValueError must chain the underlying RuntimeError
# so the original traceback is preserved for debugging.
assert exc_info.value.__cause__ is not None
assert isinstance(exc_info.value.__cause__, RuntimeError)
assert str(exc_info.value.__cause__) == "boom"
def test_init_frontend_parameter(self):
rule = PluginParameter(
name="choice",

View File

@ -1,3 +1,5 @@
import json
import pytest
from core.rag.retrieval.output_parser.react_output import ReactAction, ReactFinish
@ -65,5 +67,10 @@ class TestStructuredChatOutputParser:
parser = StructuredChatOutputParser()
text = 'Action:\n```json\n{"action":"search","action_input": }\n```'
with pytest.raises(ValueError, match="Could not parse LLM output"):
with pytest.raises(ValueError, match="Could not parse LLM output") as exc_info:
parser.parse(text)
# PEP 3134: the re-raised ValueError must chain the underlying parse failure
# (json.JSONDecodeError on the malformed JSON) so the original traceback is preserved.
assert exc_info.value.__cause__ is not None
assert isinstance(exc_info.value.__cause__, json.JSONDecodeError)