Skip to content

Commit 598f90d

Browse files
committed
refact(jetsam): simpler API no contextlib...
but cannot etablish call correctnes, relying on recetn TCs.
1 parent b808c2c commit 598f90d

File tree

4 files changed

+161
-168
lines changed

4 files changed

+161
-168
lines changed

graphkit/base.py

Lines changed: 47 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
# Copyright 2016, Yahoo Inc.
22
# Licensed under the terms of the Apache License, Version 2.0. See the LICENSE file associated with the project for terms.
3-
import contextlib
43
import logging
54
from collections import namedtuple
65

@@ -15,18 +14,22 @@
1514
log = logging.getLogger(__name__)
1615

1716

18-
@contextlib.contextmanager
19-
## def jetsam(locs, *salvage_vars, annotation="graphkit_jetsam", **salvage_mappings): # bad PY2 syntax
20-
def jetsam(locs, *salvage_vars, **salvage_mappings):
17+
## def jetsam(ex, locs, *salvage_vars, annotation="graphkit_jetsam", **salvage_mappings): # bad PY2 syntax
18+
def jetsam(ex, locs, *salvage_vars, **salvage_mappings):
2119
"""
22-
Debug-aid to annotate exceptions with salvaged values from wrapped functions.
20+
Annotate exception with salvaged values from locals().
2321
22+
:param ex:
23+
the exception to annotate
2424
:param locs:
2525
``locals()`` from the context-manager's block containing vars
2626
to be salvaged in case of exception
2727
2828
ATTENTION: wrapped function must finally call ``locals()``, because
2929
*locals* dictionary only reflects local-var changes after call.
30+
:param str annotation:
31+
(a kwarg not seen in the signature due to PY2 compatibility)
32+
the name of the attribute to attach on the exception
3033
:param salvage_vars:
3134
local variable names to save as is in the salvaged annotations dictionary.
3235
:param salvage_mappings:
@@ -50,21 +53,19 @@ def jetsam(locs, *salvage_vars, **salvage_mappings):
5053
in case of errors::
5154
5255
53-
with jetsam(locals(), "a", b="salvaged_b", c_var="c"):
54-
try:
55-
a = 1
56-
b = 2
57-
raise Exception()
58-
finally:
59-
locals() # to update locals-dict handed to jetsam().
56+
try:
57+
a = 1
58+
b = 2
59+
raise Exception()
60+
exception Exception as ex:
61+
jetsam(ex, locals(), "a", b="salvaged_b", c_var="c")
6062
6163
And then from a REPL::
6264
6365
import sys
6466
sys.last_value.graphkit_jetsam
6567
{'a': 1, 'salvaged_b': 2, "c_var": None}
6668
67-
6869
** Reason:**
6970
7071
Graphs may become arbitrary deep. Debugging such graphs is notoriously hard.
@@ -80,6 +81,7 @@ def jetsam(locs, *salvage_vars, **salvage_mappings):
8081
#
8182
annotation = salvage_mappings.pop("annotation", "graphkit_jetsam")
8283

84+
assert isinstance(ex, Exception), ("Bad `ex`, not an exception dict:", ex)
8385
assert isinstance(locs, dict), ("Bad `locs`, not a dict:", locs)
8486
assert all(isinstance(i, str) for i in salvage_vars), (
8587
"Bad `salvage_vars`!",
@@ -97,30 +99,26 @@ def jetsam(locs, *salvage_vars, **salvage_mappings):
9799
salvage_mappings[var] = var
98100

99101
try:
100-
yield jetsam
101-
except Exception as ex_to_annotate:
102-
try:
103-
annotations = getattr(ex_to_annotate, annotation, None)
104-
if not isinstance(annotations, dict):
105-
annotations = {}
106-
setattr(ex_to_annotate, annotation, annotations)
107-
108-
## Salvage those asked
109-
for dst_key, src in salvage_mappings.items():
110-
try:
111-
salvaged_value = src(locs) if callable(src) else locs.get(src)
112-
annotations.setdefault(dst_key, salvaged_value)
113-
except Exception as ex:
114-
log.warning(
115-
"Supressed error while salvaging jetsam item (%r, %r): %r"
116-
% (dst_key, src, ex)
117-
)
118-
except Exception as ex:
119-
log.warning(
120-
"Supressed error while annotating exception: %r", ex, exc_info=1
121-
)
102+
annotations = getattr(ex, annotation, None)
103+
if not isinstance(annotations, dict):
104+
annotations = {}
105+
setattr(ex, annotation, annotations)
122106

123-
raise # re-raise without ex-arg, not to insert my frame
107+
## Salvage those asked
108+
for dst_key, src in salvage_mappings.items():
109+
try:
110+
salvaged_value = src(locs) if callable(src) else locs.get(src)
111+
annotations.setdefault(dst_key, salvaged_value)
112+
except Exception as ex:
113+
log.warning(
114+
"Supressed error while salvaging jetsam item (%r, %r): %r"
115+
% (dst_key, src, ex)
116+
)
117+
except Exception as ex2:
118+
log.warning("Supressed error while annotating exception: %r", ex2, exc_info=1)
119+
raise ex2
120+
121+
raise # noqa #re-raise without ex-arg, not to insert my frame
124122

125123

126124
class Data(object):
@@ -214,23 +212,22 @@ def compute(self, inputs):
214212
raise NotImplementedError("Define callable of %r!" % self)
215213

216214
def _compute(self, named_inputs, outputs=None):
217-
with jetsam(
218-
locals(), "outputs", "provides", "args", "results", operation="self"
219-
):
220-
try:
221-
provides = self.provides
222-
args = [named_inputs[d] for d in self.needs]
223-
results = self.compute(args)
215+
try:
216+
provides = self.provides
217+
args = [named_inputs[d] for d in self.needs]
218+
results = self.compute(args)
224219

225-
results = zip(provides, results)
220+
results = zip(provides, results)
226221

227-
if outputs:
228-
outs = set(outputs)
229-
results = filter(lambda x: x[0] in outs, results)
222+
if outputs:
223+
outs = set(outputs)
224+
results = filter(lambda x: x[0] in outs, results)
230225

231-
return dict(results)
232-
finally:
233-
locals() # to update locals-dict handed to jetsam()
226+
return dict(results)
227+
except Exception as ex:
228+
jetsam(
229+
ex, locals(), "outputs", "provides", "args", "results", operation="self"
230+
)
234231

235232
def _after_init(self):
236233
"""

graphkit/functional.py

Lines changed: 49 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -14,52 +14,55 @@ def __init__(self, **kwargs):
1414
Operation.__init__(self, **kwargs)
1515

1616
def _compute(self, named_inputs, outputs=None):
17-
with jetsam(
18-
locals(),
19-
"outputs",
20-
"provides",
21-
"results",
22-
operation="self",
23-
args=lambda locs: {"args": locs.get("args"), "kwargs": locs.get("kwargs")},
24-
):
25-
try:
26-
args = [
27-
named_inputs[n]
28-
for n in self.needs
29-
if not isinstance(n, optional) and not isinstance(n, sideffect)
30-
]
31-
32-
# Find any optional inputs in named_inputs. Get only the ones that
33-
# are present there, no extra `None`s.
34-
optionals = {
35-
n: named_inputs[n]
36-
for n in self.needs
37-
if isinstance(n, optional) and n in named_inputs
38-
}
39-
40-
# Combine params and optionals into one big glob of keyword arguments.
41-
kwargs = {k: v for d in (self.params, optionals) for k, v in d.items()}
42-
43-
# Don't expect sideffect outputs.
44-
provides = [n for n in self.provides if not isinstance(n, sideffect)]
45-
46-
results = self.fn(*args, **kwargs)
47-
48-
if not provides:
49-
# All outputs were sideffects.
50-
return {}
51-
52-
if len(provides) == 1:
53-
results = [results]
54-
55-
results = zip(provides, results)
56-
if outputs:
57-
outputs = set(n for n in outputs if not isinstance(n, sideffect))
58-
results = filter(lambda x: x[0] in outputs, results)
59-
60-
return dict(results)
61-
finally:
62-
locals() # to update locals-dict handed to jetsam()
17+
try:
18+
args = [
19+
named_inputs[n]
20+
for n in self.needs
21+
if not isinstance(n, optional) and not isinstance(n, sideffect)
22+
]
23+
24+
# Find any optional inputs in named_inputs. Get only the ones that
25+
# are present there, no extra `None`s.
26+
optionals = {
27+
n: named_inputs[n]
28+
for n in self.needs
29+
if isinstance(n, optional) and n in named_inputs
30+
}
31+
32+
# Combine params and optionals into one big glob of keyword arguments.
33+
kwargs = {k: v for d in (self.params, optionals) for k, v in d.items()}
34+
35+
# Don't expect sideffect outputs.
36+
provides = [n for n in self.provides if not isinstance(n, sideffect)]
37+
38+
results = self.fn(*args, **kwargs)
39+
40+
if not provides:
41+
# All outputs were sideffects.
42+
return {}
43+
44+
if len(provides) == 1:
45+
results = [results]
46+
47+
results = zip(provides, results)
48+
if outputs:
49+
outputs = set(n for n in outputs if not isinstance(n, sideffect))
50+
results = filter(lambda x: x[0] in outputs, results)
51+
52+
return dict(results)
53+
except Exception as ex:
54+
jetsam(
55+
ex,
56+
locals(),
57+
"outputs",
58+
"provides",
59+
"results",
60+
operation="self",
61+
args=lambda locs: {
62+
"args": locs.get("args"),
63+
"kwargs": locs.get("kwargs"),
64+
},
65+
)
6366

6467
def __call__(self, *args, **kwargs):
6568
return self.fn(*args, **kwargs)

graphkit/network.py

Lines changed: 30 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -259,11 +259,10 @@ def _pin_data_in_solution(self, value_name, solution, inputs, overwrites):
259259
def _call_operation(self, op, solution):
260260
# Although `plan` have added to jetsam in `compute()``,
261261
# add it again, in case compile()/execute is called separately.
262-
with jetsam(locals(), plan="self"):
263-
try:
264-
return op._compute(solution)
265-
finally:
266-
locals() # to update locals-dict handed to jetsam()
262+
try:
263+
return op._compute(solution)
264+
except Exception as ex:
265+
jetsam(ex, locals(), plan="self")
267266

268267
def _execute_thread_pool_barrier_method(
269268
self, inputs, solution, overwrites, thread_pool_size=10
@@ -717,30 +716,29 @@ def compute(self, named_inputs, outputs, method=None, overwrites_collector=None)
717716
718717
:returns: a dictionary of output data objects, keyed by name.
719718
"""
720-
with jetsam(locals(), "plan", "solution", "outputs", network="self"):
721-
try:
722-
if isinstance(outputs, str):
723-
outputs = [outputs]
724-
elif not isinstance(outputs, (list, tuple)) and outputs is not None:
725-
raise ValueError(
726-
"The outputs argument must be a list, was: %s", outputs
727-
)
728-
729-
# Build the execution plan.
730-
self.last_plan = plan = self.compile(named_inputs.keys(), outputs)
731-
732-
# start with fresh data solution.
733-
solution = dict(named_inputs)
734-
735-
plan.execute(solution, overwrites_collector, method)
736-
737-
if outputs:
738-
# Filter outputs to just return what's requested.
739-
# Otherwise, return the whole solution as output,
740-
# including input and intermediate data nodes.
741-
# Still needed with eviction to clean isolated given inputs.
742-
solution = dict(i for i in solution.items() if i[0] in outputs)
743-
744-
return solution
745-
finally:
746-
locals() # to update locals-dict handed to jetsam()
719+
try:
720+
if isinstance(outputs, str):
721+
outputs = [outputs]
722+
elif not isinstance(outputs, (list, tuple)) and outputs is not None:
723+
raise ValueError(
724+
"The outputs argument must be a list, was: %s", outputs
725+
)
726+
727+
# Build the execution plan.
728+
self.last_plan = plan = self.compile(named_inputs.keys(), outputs)
729+
730+
# start with fresh data solution.
731+
solution = dict(named_inputs)
732+
733+
plan.execute(solution, overwrites_collector, method)
734+
735+
if outputs:
736+
# Filter outputs to just return what's requested.
737+
# Otherwise, return the whole solution as output,
738+
# including input and intermediate data nodes.
739+
# Still needed with eviction to clean isolated given inputs.
740+
solution = dict(i for i in solution.items() if i[0] in outputs)
741+
742+
return solution
743+
except Exception as ex:
744+
jetsam(ex, locals(), "plan", "solution", "outputs", network="self")

0 commit comments

Comments
 (0)