Richard Schulz
10/07/2024, 1:22 PMThierry Jean
10/07/2024, 2:46 PMDriver.display_all_functions())
There are 2 approaches to tackle the problem that comes to mind:
1. Use GracefulErrorAdapter (reference)
2. Use the new caching feature (tutorial)
Note that they can't be used together
## 1. GracefulErrorAdapter
This is a lightweight adapter that gives "fault tolerance"
dr = (
driver.Builder()
.with_modules(my_module)
.with_adapters(
default.GracefulErrorAdapter(
error_to_catch=KeyError,
sentinel_value=None
)
)
.build()
)
If the DAG execution encounters the exception specified in error_to_catch, the node will return the sentinel_value and continue execution. Setting error_to_cache=BaseException should catch all exceptions. This will ensure that Driver.execute() returns a result
## 2. Caching
Caching will store results on disk after each node is executed.
dr = (
driver.Builder()
.with_modules(my_module)
.with_cache()
.build()
)
If a task fails midway through, its intermediary results will be stored. However, I think if any task fails, it will stop execution (not fault tolerance). You will be able to inspect the cached results and the Driver object will store structure logs (which can also be stored to file directly via .with_cache(log_to_file=True)). Also, if you're to run the DAG a second time, it will skip the successful branches by using cached values
@Elijah Ben Izzy thoughts?Richard Schulz
10/07/2024, 2:55 PMThierry Jean
10/07/2024, 2:56 PMElijah Ben Izzy
10/07/2024, 4:27 PMNone for any failed task — you can add logging/whatnot here (or in the task).
Caching + rerunning is a pretty good way to do retries if you’re interested, but there are a few other ways to approach that as well if that’s what you’re going for. That said, I think this will get at what you want.
One trick — in this case we make the sentinel value None, as it should produce None when it fails and we can check it in the iterable. You could also make it 0 in this case, which would give it a default value! Then you don’t have to do the null check.
from hamilton import driver
from hamilton.ad_hoc_utils import create_temporary_module
from hamilton.htypes import Parallelizable, Collect
from hamilton.lifecycle import GracefulErrorAdapter
class MyException(Exception):
pass
def foo() -> Parallelizable[int]:
for i in range(10):
yield i
def process_foo(foo: int) -> int:
if foo % 2 == 0:
raise MyException("foo is even")
return foo + 1
def sum_process_foo(process_foo: Collect[int]) -> int:
return sum((item for item in process_foo if item is not None))
if __name__ == "__main__":
dr = (
driver
.Builder()
.with_modules(create_temporary_module(foo, process_foo, sum_process_foo))
.enable_dynamic_execution(allow_experimental_mode=True)
.with_adapter(GracefulErrorAdapter(error_to_catch=MyException, sentinel_value=None))
.build())
print(dr.execute(["sum_process_foo"]))