-
Notifications
You must be signed in to change notification settings - Fork 104
Minor exception-related updates #75
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -2,7 +2,7 @@ | |
|
||
import traceback | ||
from enum import IntEnum | ||
from typing import Any, Awaitable, Callable, Iterable, Optional | ||
from typing import Any, Awaitable, Callable, Iterable, Optional, Tuple | ||
|
||
import temporalio.api.common.v1 | ||
import temporalio.api.enums.v1 | ||
|
@@ -39,10 +39,13 @@ def __init__( | |
self, | ||
message: str, | ||
*, | ||
failure: Optional[temporalio.api.failure.v1.Failure] = None | ||
failure: Optional[temporalio.api.failure.v1.Failure] = None, | ||
exc_args: Optional[Tuple] = None, | ||
) -> None: | ||
"""Initialize a failure error.""" | ||
super().__init__(message) | ||
if exc_args is None: | ||
exc_args = (message,) | ||
super().__init__(*exc_args) | ||
self._message = message | ||
self._failure = failure | ||
|
||
|
@@ -65,10 +68,14 @@ def __init__( | |
message: str, | ||
*details: Any, | ||
type: Optional[str] = None, | ||
non_retryable: bool = False | ||
non_retryable: bool = False, | ||
) -> None: | ||
"""Initialize an application error.""" | ||
super().__init__(message) | ||
super().__init__( | ||
message, | ||
# If there is a type, prepend it to the message on the string repr | ||
exc_args=(message if not type else f"{type}: {message}",), | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why not override There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I want to alter the |
||
) | ||
self._details = details | ||
self._type = type | ||
self._non_retryable = non_retryable | ||
|
@@ -140,7 +147,7 @@ def __init__( | |
message: str, | ||
*, | ||
type: Optional[TimeoutType], | ||
last_heartbeat_details: Iterable[Any] | ||
last_heartbeat_details: Iterable[Any], | ||
) -> None: | ||
"""Initialize a timeout error.""" | ||
super().__init__(message) | ||
|
@@ -206,7 +213,7 @@ def __init__( | |
identity: str, | ||
activity_type: str, | ||
activity_id: str, | ||
retry_state: Optional[RetryState] | ||
retry_state: Optional[RetryState], | ||
) -> None: | ||
"""Initialize an activity error.""" | ||
super().__init__(message) | ||
|
@@ -261,7 +268,7 @@ def __init__( | |
workflow_type: str, | ||
initiated_event_id: int, | ||
started_event_id: int, | ||
retry_state: Optional[RetryState] | ||
retry_state: Optional[RetryState], | ||
) -> None: | ||
"""Initialize a child workflow error.""" | ||
super().__init__(message) | ||
|
@@ -404,7 +411,7 @@ def apply_error_to_failure( | |
|
||
# Set message, stack, and cause. Obtaining cause follows rules from | ||
# https://docs.python.org/3/library/exceptions.html#exception-context | ||
failure.message = str(error) | ||
failure.message = error.message | ||
if error.__traceback__: | ||
failure.stack_trace = "\n".join(traceback.format_tb(error.__traceback__)) | ||
if error.__cause__: | ||
|
@@ -490,6 +497,7 @@ def apply_exception_to_failure( | |
str(exception), type=exception.__class__.__name__ | ||
) | ||
failure_error.__traceback__ = exception.__traceback__ | ||
failure_error.__cause__ = exception.__cause__ | ||
apply_error_to_failure(failure_error, converter, failure) | ||
|
||
|
||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,71 @@ | ||
import logging | ||
import logging.handlers | ||
import traceback | ||
from typing import Optional | ||
|
||
import temporalio.converter | ||
from temporalio.api.failure.v1 import Failure | ||
from temporalio.exceptions import ( | ||
ApplicationError, | ||
FailureError, | ||
apply_exception_to_failure, | ||
failure_to_error, | ||
) | ||
|
||
|
||
# This is an example of appending the stack to every Temporal failure error | ||
def append_temporal_stack(exc: Optional[BaseException]) -> None: | ||
while exc: | ||
# Only append if it doesn't appear already there | ||
if ( | ||
isinstance(exc, FailureError) | ||
and exc.failure | ||
and exc.failure.stack_trace | ||
and len(exc.args) == 1 | ||
and "\nStack:\n" not in exc.args[0] | ||
): | ||
exc.args = (f"{exc}\nStack:\n{exc.failure.stack_trace.rstrip()}",) | ||
exc = exc.__cause__ | ||
|
||
|
||
def test_exception_format(): | ||
# Cause a nested exception | ||
actual_err: Exception | ||
try: | ||
try: | ||
raise ValueError("error1") | ||
except Exception as err: | ||
raise RuntimeError("error2") from err | ||
except Exception as err: | ||
actual_err = err | ||
assert actual_err | ||
|
||
# Convert to failure and back | ||
failure = Failure() | ||
apply_exception_to_failure( | ||
actual_err, temporalio.converter.default().payload_converter, failure | ||
) | ||
failure_error = failure_to_error( | ||
failure, temporalio.converter.default().payload_converter | ||
) | ||
# Confirm type is prepended | ||
assert isinstance(failure_error, ApplicationError) | ||
assert "RuntimeError: error2" == str(failure_error) | ||
assert isinstance(failure_error.cause, ApplicationError) | ||
assert "ValueError: error1" == str(failure_error.cause) | ||
|
||
# Append the stack and format the exception and check the output | ||
append_temporal_stack(failure_error) | ||
output = "".join( | ||
traceback.format_exception( | ||
type(failure_error), failure_error, failure_error.__traceback__ | ||
) | ||
) | ||
assert "temporalio.exceptions.ApplicationError: ValueError: error1" in output | ||
assert "temporalio.exceptions.ApplicationError: RuntimeError: error" in output | ||
assert output.count("\nStack:\n") == 2 | ||
|
||
# This shows how it might look for those with debugging on | ||
logging.getLogger(__name__).debug( | ||
"Showing appended exception", exc_info=failure_error | ||
) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Since we control the default value can we change this to
retryable: bool = True
to improve readability?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would like the init param names to match the property names. Currently we expose Java isNonRetryable, Go NonRetryable, TS nonRetryable, etc.