diff --git a/projects/vdk-core/src/vdk/internal/builtin_plugins/run/execution_results.py b/projects/vdk-core/src/vdk/internal/builtin_plugins/run/execution_results.py index d2d311fd0a..69ae309f0e 100644 --- a/projects/vdk-core/src/vdk/internal/builtin_plugins/run/execution_results.py +++ b/projects/vdk-core/src/vdk/internal/builtin_plugins/run/execution_results.py @@ -1,6 +1,9 @@ # Copyright 2021 VMware, Inc. # SPDX-License-Identifier: Apache-2.0 import json +import logging +import pprint +import sys from dataclasses import dataclass from datetime import datetime from typing import Any @@ -13,6 +16,8 @@ from vdk.internal.core.errors import PlatformServiceError from vdk.internal.core.errors import ResolvableBy +log = logging.getLogger(__name__) + @dataclass(frozen=True) class StepResult: @@ -146,4 +151,16 @@ def __repr__(self): def default_serialization(o: Any) -> Any: return o.__dict__ if "__dict__" in dir(o) else str(o) - return json.dumps(data, default=default_serialization, indent=2) + try: + result = json.dumps(data, default=default_serialization, indent=2) + except Exception as e: + log.debug(f"Failed to json.dumps executionResult: {e}. Fallback to pprint.") + # sort_dicts is supported since 3.8 + if sys.version_info[0] >= 3 and sys.version_info[1] >= 8: + result = pprint.pformat( + data, indent=2, depth=5, compact=False, sort_dicts=False + ) + else: + result = pprint.pformat(data, indent=2, depth=5, compact=False) + + return result diff --git a/projects/vdk-core/tests/vdk/internal/builtin_plugins/run/test_execution_results.py b/projects/vdk-core/tests/vdk/internal/builtin_plugins/run/test_execution_results.py index a9b8dabe9e..9e1512eedc 100644 --- a/projects/vdk-core/tests/vdk/internal/builtin_plugins/run/test_execution_results.py +++ b/projects/vdk-core/tests/vdk/internal/builtin_plugins/run/test_execution_results.py @@ -116,3 +116,31 @@ def test_serialization_non_serializable(): result_as_string = result.__repr__() assert json.loads(result_as_string) is not None + + +def test_serialization_circular_reference(): + exception = ArithmeticError("foo") + exception.__cause__ = exception + step_result = StepResult( + "step", + "type", + datetime.fromisoformat("2012-10-12 00:00:00"), + datetime.fromisoformat("2012-10-12 01:00:00"), + ExecutionStatus.ERROR, + "details", + exception, + ResolvableBy.USER_ERROR, + ) + result = ExecutionResult( + "job-name", + "exec-id", + datetime.fromisoformat("2012-10-12 00:00:00"), + datetime.fromisoformat("2012-10-12 01:00:00"), + ExecutionStatus.ERROR, + [step_result], + exception, + ResolvableBy.USER_ERROR, + ) + + result_as_string = result.__repr__() + assert result_as_string is not None