From c479bd12523e35d5bfa7b3455da71c117021cf81 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Sun, 13 Sep 2026 00:06:16 +0200 Subject: [PATCH] Fix failed batch operations printing result and passing silently - '_validate_operations_result' printed the whole result to stdout. It is logged as warning instead. - When the batch failed but no single operation was reported as failed, the method returned without raising, so the failure was ignored. - Missing 'operations' key or an unknown operation id raised KeyError or StopIteration instead of FailedOperations. Co-Authored-By: Claude Opus 5 --- ayon_api/server_api.py | 20 ++++++++++++++------ tests/test_operations_result.py | 26 ++++++++++++++++++++++++++ 2 files changed, 40 insertions(+), 6 deletions(-) create mode 100644 tests/test_operations_result.py diff --git a/ayon_api/server_api.py b/ayon_api/server_api.py index e42add68c..11b325ce2 100644 --- a/ayon_api/server_api.py +++ b/ayon_api/server_api.py @@ -2678,24 +2678,32 @@ def _validate_operations_result( if result.get("success"): return None - print(result) - for op_result in result["operations"]: + self.log.warning( + "Operations failed. Server response:\n%s", + json.dumps(result, indent=4, default=str), + ) + for op_result in result.get("operations") or []: if op_result["success"]: continue operation_id = op_result["id"] operation = next( - op - for op in operations_body - if op["id"] == operation_id + (op for op in operations_body if op["id"] == operation_id), + op_result, ) detail = op_result["detail"] raise FailedOperations( f"Operation \"{operation_id}\" failed with data:" - f"\n{json.dumps(operation, indent=4)}" + f"\n{json.dumps(operation, indent=4, default=str)}" f"\nDetail: {detail}." ) + # Server did not report which operation failed + raise FailedOperations( + "Operations failed. Server response:" + f"\n{json.dumps(result, indent=4, default=str)}" + ) + def _prepare_fields( self, entity_type: str, diff --git a/tests/test_operations_result.py b/tests/test_operations_result.py new file mode 100644 index 000000000..c5bde3233 --- /dev/null +++ b/tests/test_operations_result.py @@ -0,0 +1,26 @@ +"""Validation of operations result. Does not require running AYON server.""" +import pytest + +from ayon_api.exceptions import FailedOperations +from ayon_api.server_api import ServerAPI + + +@pytest.fixture +def con(): + return ServerAPI("http://localhost:0", create_session=False) + + +def test_failed_result_without_failed_operation_raises(con, capsys): + result = {"success": False, "operations": [{"id": "a", "success": True}]} + with pytest.raises(FailedOperations, match="Server response"): + con._validate_operations_result(result, [{"id": "a"}]) + assert capsys.readouterr().out == "" + + +def test_failed_operation_raises_with_detail(con): + result = { + "success": False, + "operations": [{"id": "a", "success": False, "detail": "Boom"}], + } + with pytest.raises(FailedOperations, match="Boom"): + con._validate_operations_result(result, [{"id": "a", "type": "x"}])