Skip to content

Commit ebad47e

Browse files
committed
WIP: creating file to help debug an error
Proof of concept. TODOs: - python dependency logging - creating a notebook version of this - tests - extensions would be to serialize things more effectively
1 parent 65be67c commit ebad47e

File tree

4 files changed

+133
-1
lines changed

4 files changed

+133
-1
lines changed
+11
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
def input_function() -> int:
2+
return 2
3+
4+
5+
def output_function(input_function: int) -> int:
6+
return input_function + 1
7+
8+
9+
def error_function(input_function: int, output_function: int, input: int) -> int:
10+
raise ValueError("This is an error")
11+
return input_function + output_function + input
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
import inspect
2+
import logging
3+
from typing import Any, Callable, Dict, Optional
4+
5+
from hamilton import lifecycle
6+
7+
try:
8+
import cloudpickle as pickle
9+
except ImportError:
10+
import pickle
11+
12+
logger = logging.getLogger(__name__)
13+
14+
15+
template = """
16+
try:
17+
import cloudpickle as pickle
18+
except ImportError:
19+
import pickle
20+
21+
import {module_name} # we load this for imports
22+
23+
# let's load the inputs
24+
with open('{node_name}_inputs.pkl', 'rb') as f:
25+
inputs = pickle.load(f)
26+
27+
# the function that errored
28+
{function_to_debug}
29+
30+
31+
# run the function
32+
{func_name}(**inputs)
33+
"""
34+
35+
36+
class NotebookErrorDebugger(lifecycle.NodeExecutionHook):
37+
38+
def run_before_node_execution(
39+
self,
40+
*,
41+
node_name: str,
42+
node_tags: Dict[str, Any],
43+
node_kwargs: Dict[str, Any],
44+
node_return_type: type,
45+
task_id: Optional[str],
46+
run_id: str,
47+
node_input_types: Dict[str, Any],
48+
**future_kwargs: Any,
49+
):
50+
pass
51+
52+
def run_after_node_execution(
53+
self,
54+
*,
55+
node_name: str,
56+
node_tags: Dict[str, Any],
57+
node_kwargs: Dict[str, Any],
58+
node_return_type: type,
59+
result: Any,
60+
error: Optional[Exception],
61+
success: bool,
62+
task_id: Optional[str],
63+
run_id: str,
64+
originating_function: Callable,
65+
**future_kwargs: Any,
66+
):
67+
"""
68+
This function will create the follow in the case of a failure:
69+
70+
1. It will pickle of the inputs to the function.
71+
2. It will create a file with the following:
72+
a. it will import the module the function is from -- to cover any imports that need to exist.
73+
b. it will load the pickled inputs.
74+
c. it will have the code of the function that errored so you can debug it.
75+
d. it will then also list python version, hamilton version, and any other relevant package versions for
76+
the user to install / have.
77+
2. It will then print out where this data has been saved for the user to then debug.
78+
"""
79+
if not success:
80+
# pickle the inputs
81+
with open(f"{node_name}_inputs.pkl", "wb") as f:
82+
pickle.dump(node_kwargs, f)
83+
# create a file with the function and the inputs
84+
with open(f"{node_name}_debug.py", "w") as f:
85+
f.write(
86+
template.format(
87+
module_name=node_tags.get("module"),
88+
node_name=node_name,
89+
function_to_debug=inspect.getsource(originating_function),
90+
func_name=originating_function.__name__,
91+
)
92+
)
93+
# print out where the data has been saved
94+
message = (
95+
f"Inputs to {node_name} have been saved to {node_name}_inputs.pkl\n"
96+
f"The function that errored has been saved to {node_name}_debug.py\n"
97+
f"Please run the function in {node_name}_debug.py to debug the error."
98+
)
99+
logger.warning(message)
100+
# TODO: create file with python requirements for pickle to work...

examples/notebook_debug/run.py

+8
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
if __name__ == "__main__":
2+
import example_error
3+
from notebook_debugger_plugin import NotebookErrorDebugger
4+
5+
from hamilton import driver
6+
7+
dr = driver.Builder().with_modules(example_error).with_adapters(NotebookErrorDebugger()).build()
8+
dr.execute(["error_function"], inputs={"input": 4})

hamilton/lifecycle/api.py

+14-1
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,18 @@
11
import abc
22
from abc import ABC
33
from types import ModuleType
4-
from typing import TYPE_CHECKING, Any, Collection, Dict, List, Optional, Tuple, Type, final
4+
from typing import (
5+
TYPE_CHECKING,
6+
Any,
7+
Callable,
8+
Collection,
9+
Dict,
10+
List,
11+
Optional,
12+
Tuple,
13+
Type,
14+
final,
15+
)
516

617
from hamilton import graph_types, node
718

@@ -224,6 +235,7 @@ def run_after_node_execution(
224235
success: bool,
225236
task_id: Optional[str],
226237
run_id: str,
238+
originating_function: Callable,
227239
**future_kwargs: Any,
228240
):
229241
"""Hook that is executed post node execution.
@@ -265,6 +277,7 @@ def post_node_execute(
265277
task_id=task_id,
266278
success=success,
267279
run_id=run_id,
280+
originating_function=node_.originating_functions[0],
268281
)
269282

270283

0 commit comments

Comments
 (0)