Errors & Exceptions

As with every software program, a task may crash or run into an error situation.

To help the task caller understand what's going on, we distinguish between different kinds of errors and try our best to report most of them to the task client. Details are explained in Error Reporting.

Within Python, when the task finds itself in a situation where it can't continue, it shall raise an exception. This is then forwarded to the client. In the following, we explain which information from the exception is made to the client program and how you as a developer should design your exceptions to help the user solve issues.

Error Type (Exception Name)

An error report in HQS Tasks includes a type which classifies the error, intended to allow some automated handling of the situation. When a task raises an exception, the exception's class name is used as the error type.

We recommend developers to define exception classes which reflect different error situations for their tasks.

Keep in mind that the error type, and hence the class name, is machine-processed. It shall identify the type of error which occurred. Therefore, other code may rely on the exact name chosen here, hence we advise to treat it as a breaking change to change this name between versions of the task. So please ensure that the class names for exceptions are chosen carefully.

Python's built in exception classes can be used explicitly (or indirectly) to express error cases. For example, assume your task has a dictionary as the input, and it expects some keys to be present. (Let's ignore for a moment that a proper schema could express this constraint very nicely.) Once it attemps to access that key, if it's not present, it will (indirectly) raise a KeyError even you did not write code to handle this case. You could however also check the key for being present in the given dictionary and (explicitly) raise a KeyError (or any other exception class) to explain this situation in more detail. In both cases the user will receive an error report with type = KeyError which they can then deal with accordingly. Since the KeyError that Python raises automatically for you already contains all information the user needs to solve the issue, there is not much benefit in adding the explicit code to check the key and raise an error.

Error Message

An error report in HQS Tasks includes a message which is intended to explain the situation to the (human) user. When a task raises an exception, the exception's string representation is used as the error message.

The built-in exception classes in Python provide a message argument as their first (and usually only) argument in their constructor. This message is returned when attempting to convert the exception object to a string.

>>> str(RuntimeError("Something bad happened."))
'Something bad happened.'

We recommend custom exception classes to be used in HQS Tasks to also have such a message argument which is forwarded to the constructor of the base class. This is the default behavior if you merely extend a built-in exception class without adding any constructor:

class MyErrorType(RuntimeError):
    pass
>>> str(MyErrorType("Something bad happened."))
'Something bad happened.'

You can also define the message in the class constructor, when there is only one "explanation" for each case where this error is raised:

class MyErrorType(RuntimeError):
    def __init__(self) -> None:
        super().__init__("Something bad happened.")
>>> str(MyErrorType())
'Something bad happened.'

Error Details (Arguments & Stacktrace)

An error report in HQS Tasks includes a defails dictionary which is intended to give technical details to be machine-processable.

Once you want an exception to carry more information, for example when the error arose from a parameter having a particular, invalid value, you can add that information to the error report. It is not recommended to (only) add this information in the error message, as this is not intended to be machine-processed, as parsing such a string is prone to failure in particular when the message layout changes.

In Python, we populate that with the exception's arguments as well as the stacktrace. Other (custom) details are not supported currently in the Python language interface.

Exception's Arguments

The arguments to the constructor of a built-in exception class are exposed in the details dictionary under the args key. We extract these by accessing the args property on the exception value. The requirement for this is that the base class of the exception class is based on BaseException, which is the base of all Python built-in errors. To add custom items to it, simply add values to the constructor call:

class MyErrorType(RuntimeError):
    def __init__(self, some_value: float, number_of_whatever: int) -> None:
        super().__init__("Something bad happened.", some_value, number_of_whatever)
>>> MyErrorType(3.141, 42).args
('Something bad happened.', 3.141, 42)

Exception's Stacktrace

We furthermore provide the stacktrace of the exception in the details dictionary under the key stacktrace. Similar to the arguments, this requires the class to be based on BaseException. However, this might get stripped for production environments targetting end-users, as it exposes the task's source code.

Custom Details

Note that there is currently no way to add details to the root level of the details dictionary, but that might be added in the future and probably then we also encourage developers to use this, as the args wrapper was just a "workaround" solution for the early prototype of the Python language interface.