computation-graph 54__tar.gz → 56__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (35) hide show
  1. {computation-graph-54 → computation_graph-56}/PKG-INFO +1 -1
  2. {computation-graph-54 → computation_graph-56}/computation_graph/base_types.py +5 -1
  3. {computation-graph-54 → computation_graph-56}/computation_graph/composers/duplication.py +2 -1
  4. {computation-graph-54 → computation_graph-56}/computation_graph/graph.py +1 -1
  5. {computation-graph-54 → computation_graph-56}/computation_graph/graph_test.py +49 -0
  6. {computation-graph-54 → computation_graph-56}/computation_graph/run.py +269 -87
  7. {computation-graph-54 → computation_graph-56}/computation_graph.egg-info/PKG-INFO +1 -1
  8. {computation-graph-54 → computation_graph-56}/computation_graph.egg-info/top_level.txt +1 -0
  9. {computation-graph-54 → computation_graph-56}/setup.py +1 -1
  10. {computation-graph-54 → computation_graph-56}/LICENSE.md +0 -0
  11. {computation-graph-54 → computation_graph-56}/README.md +0 -0
  12. {computation-graph-54 → computation_graph-56}/computation_graph/__init__.py +0 -0
  13. {computation-graph-54 → computation_graph-56}/computation_graph/composers/__init__.py +0 -0
  14. {computation-graph-54 → computation_graph-56}/computation_graph/composers/composers_test.py +0 -0
  15. {computation-graph-54 → computation_graph-56}/computation_graph/composers/condition.py +0 -0
  16. {computation-graph-54 → computation_graph-56}/computation_graph/composers/condition_test.py +0 -0
  17. {computation-graph-54 → computation_graph-56}/computation_graph/composers/debug.py +0 -0
  18. {computation-graph-54 → computation_graph-56}/computation_graph/composers/lift.py +0 -0
  19. {computation-graph-54 → computation_graph-56}/computation_graph/composers/logic.py +0 -0
  20. {computation-graph-54 → computation_graph-56}/computation_graph/composers/memory.py +0 -0
  21. {computation-graph-54 → computation_graph-56}/computation_graph/composers/memory_test.py +0 -0
  22. {computation-graph-54 → computation_graph-56}/computation_graph/graph_runners.py +0 -0
  23. {computation-graph-54 → computation_graph-56}/computation_graph/legacy.py +0 -0
  24. {computation-graph-54 → computation_graph-56}/computation_graph/signature.py +0 -0
  25. {computation-graph-54 → computation_graph-56}/computation_graph/trace/__init__.py +0 -0
  26. {computation-graph-54 → computation_graph-56}/computation_graph/trace/ascii.py +0 -0
  27. {computation-graph-54 → computation_graph-56}/computation_graph/trace/graphviz.py +0 -0
  28. {computation-graph-54 → computation_graph-56}/computation_graph/trace/graphviz_test.py +0 -0
  29. {computation-graph-54 → computation_graph-56}/computation_graph/trace/mermaid.py +0 -0
  30. {computation-graph-54 → computation_graph-56}/computation_graph/trace/trace_utils.py +0 -0
  31. {computation-graph-54 → computation_graph-56}/computation_graph.egg-info/SOURCES.txt +0 -0
  32. {computation-graph-54 → computation_graph-56}/computation_graph.egg-info/dependency_links.txt +0 -0
  33. {computation-graph-54 → computation_graph-56}/computation_graph.egg-info/requires.txt +0 -0
  34. {computation-graph-54 → computation_graph-56}/pyproject.toml +0 -0
  35. {computation-graph-54 → computation_graph-56}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: computation-graph
3
- Version: 54
3
+ Version: 56
4
4
  Requires-Python: >=3
5
5
  Description-Content-Type: text/markdown
6
6
  License-File: LICENSE.md
@@ -96,9 +96,13 @@ class ComputationNode:
96
96
  func: Callable
97
97
  signature: NodeSignature
98
98
  is_terminal: bool
99
+ computed_hash: int = dataclasses.field(init=False)
100
+
101
+ def __post_init__(self):
102
+ object.__setattr__(self, "computed_hash", hash(self.func))
99
103
 
100
104
  def __hash__(self):
101
- return hash(self.func)
105
+ return self.computed_hash
102
106
 
103
107
  def __repr__(self):
104
108
  return self.name
@@ -44,6 +44,7 @@ _duplicate_node = gamla.compose_left(
44
44
  graph.make_computation_node,
45
45
  )
46
46
 
47
+ duplicate_node = _duplicate_node
47
48
 
48
49
  _node_to_duplicated_node = gamla.compose_left(
49
50
  gamla.remove(base_types.node_is_terminal),
@@ -104,4 +105,4 @@ def safe_replace_sources(
104
105
  g = graph.replace_source(source, get_node_replacement(source), g)
105
106
  return g
106
107
 
107
- return gamla.reduce(lambda updated_cg, e: updated_cg + update_edge(e), (), cg)
108
+ return base_types.merge_graphs(*(update_edge(e) for e in cg))
@@ -162,7 +162,7 @@ traverse_forward: Callable[
162
162
  ),
163
163
  gamla.groupby_many_reduce(
164
164
  opt_gamla.compose_left(gamla.head, gamla.wrap_tuple),
165
- lambda destinations, e: (*(destinations if destinations else ()), e[1]),
165
+ lambda destinations, e: {*(destinations if destinations else ()), e[1]},
166
166
  ),
167
167
  gamla.dict_to_getter_with_default(()),
168
168
  gamla.before(make_computation_node),
@@ -63,6 +63,55 @@ def test_simple():
63
63
  )
64
64
 
65
65
 
66
+ async def test_async_run_as_soon_as_possible(capsys):
67
+ # This test is to make sure that the async node runs as soon as possible.
68
+ # nodes in different topological layers should run concurrently if not dependent on each other.
69
+ async def concurrent1(x):
70
+ print("start concurrent1") # noqa
71
+ await asyncio.sleep(0.1)
72
+ print("end concurrent1") # noqa
73
+ return "concurrent1"
74
+
75
+ async def concurrent2(x):
76
+ print("start concurrent2") # noqa
77
+ await asyncio.sleep(0.1)
78
+ print("end concurrent2") # noqa
79
+ return "concurrent2"
80
+
81
+ def sink(x, y):
82
+ return f"x={x}, y={y}"
83
+
84
+ g = base_types.merge_graphs(
85
+ composers.compose_unary(concurrent1, lambda: "x"),
86
+ composers.compose_unary(concurrent2, lambda y: y, lambda: "y"),
87
+ composers.compose_left(concurrent1, sink, key="x"),
88
+ composers.compose_left(concurrent2, sink, key="y"),
89
+ )
90
+
91
+ await graph_runners.nullary(g, sink)
92
+
93
+ # Capture the output
94
+ captured = capsys.readouterr()
95
+
96
+ # Assertions
97
+ assert "start concurrent1" in captured.out
98
+ assert "start concurrent2" in captured.out
99
+ assert "end concurrent1" in captured.out
100
+ assert "end concurrent2" in captured.out
101
+ assert captured.out.index("start concurrent1") < captured.out.index(
102
+ "end concurrent1"
103
+ )
104
+ assert captured.out.index("start concurrent2") < captured.out.index(
105
+ "end concurrent2"
106
+ )
107
+ assert captured.out.index("start concurrent1") < captured.out.index(
108
+ "end concurrent2"
109
+ )
110
+ assert captured.out.index("start concurrent2") < captured.out.index(
111
+ "end concurrent1"
112
+ )
113
+
114
+
66
115
  async def test_simple_async():
67
116
  assert (
68
117
  await graph_runners.unary(
@@ -1,24 +1,38 @@
1
1
  import asyncio
2
2
  import dataclasses
3
3
  import functools
4
+ import inspect
4
5
  import itertools
5
6
  import logging
6
7
  import os
7
8
  import time
8
9
  import typing
9
- from typing import Any, Callable, Dict, FrozenSet, Set, Tuple, Type
10
+ from typing import (
11
+ Any,
12
+ Awaitable,
13
+ Callable,
14
+ Dict,
15
+ FrozenSet,
16
+ Iterable,
17
+ Mapping,
18
+ Optional,
19
+ Set,
20
+ Tuple,
21
+ Type,
22
+ )
10
23
 
11
24
  import gamla
12
25
  import immutables
13
26
  import termcolor
14
27
  import toposort
15
28
  import typeguard
16
- from gamla.optimized import async_functions as opt_async_gamla
17
29
  from gamla.optimized import sync as opt_gamla
18
30
 
19
31
  from computation_graph import base_types, composers, graph, signature
20
32
  from computation_graph.composers import debug
21
33
 
34
+ CG_NO_RESULT = "CG_NO_RESULT"
35
+
22
36
 
23
37
  class _DepNotFoundError(Exception):
24
38
  pass
@@ -27,7 +41,6 @@ class _DepNotFoundError(Exception):
27
41
  _NodeToResults = Dict[base_types.ComputationNode, base_types.Result]
28
42
  _ComputationInput = Tuple[Tuple[base_types.Result, ...], Dict[str, base_types.Result]]
29
43
  _SingleNodeSideEffect = Callable[[base_types.ComputationNode, Any], None]
30
- _NodeToComputationInput = Callable[[base_types.ComputationNode], _ComputationInput]
31
44
  _ComputationInputSpec = Tuple[
32
45
  Tuple[base_types.ComputationNode, ...], Dict[str, base_types.ComputationNode]
33
46
  ]
@@ -74,34 +87,7 @@ def _profile(node, time_started: float):
74
87
  )
75
88
 
76
89
 
77
- def _make_get_node_input_and_apply(
78
- is_async: bool, side_effect: _SingleNodeSideEffect
79
- ) -> Callable[..., base_types.Result]:
80
- if is_async:
81
-
82
- @opt_async_gamla.star
83
- async def run_node(
84
- node_to_input: _NodeToComputationInput, node: base_types.ComputationNode
85
- ) -> base_types.Result:
86
- args, kwargs = node_to_input(node)
87
- before = time.perf_counter()
88
- result = await gamla.to_awaitable(node.func(*args, **kwargs))
89
- side_effect(node, result)
90
- _profile(node, before)
91
- return node, result
92
-
93
- else:
94
-
95
- @opt_gamla.star
96
- def run_node(node_to_input: _NodeToComputationInput, node: base_types.ComputationNode) -> base_types.Result: # type: ignore
97
- args, kwargs = node_to_input(node)
98
- before = time.perf_counter()
99
- result = node.func(*args, **kwargs)
100
- side_effect(node, result)
101
- _profile(node, before)
102
- return node, result
103
-
104
- return run_node
90
+ _group_by_is_async_result = opt_gamla.groupby(lambda k_v: inspect.isawaitable(k_v[1]))
105
91
 
106
92
 
107
93
  _is_graph_async = opt_gamla.compose_left(
@@ -205,29 +191,35 @@ def _to_callable_with_side_effect_for_single_and_multiple(
205
191
  placeholder_to_future_source = opt_gamla.pipe(
206
192
  future_source_to_placeholder, gamla.itemmap(lambda k_v: (k_v[1], k_v[0]))
207
193
  )
194
+ get_node_executor = _make_get_node_executor(
195
+ edges, handled_exceptions, single_node_side_effect
196
+ )
197
+
208
198
  topological_layers = opt_gamla.pipe(
209
199
  edges,
210
200
  _toposort_nodes,
211
201
  gamla.map_filter_empty(
212
202
  gamla.compose_left(
213
- gamla.remove(gamla.contains(placeholder_to_future_source)), frozenset
203
+ gamla.remove(gamla.contains(placeholder_to_future_source)),
204
+ opt_gamla.map(opt_gamla.pair_right(get_node_executor)),
205
+ frozenset,
214
206
  )
215
207
  ),
216
208
  tuple,
217
209
  )
210
+
218
211
  translate_source_to_placeholder = opt_gamla.compose_left(
219
212
  opt_gamla.keyfilter(gamla.contains(future_sources)),
220
213
  opt_gamla.keymap(future_source_to_placeholder.__getitem__),
221
214
  )
222
- all_node_side_effects_on_edges = all_nodes_side_effect(edges)
223
- reduce_layers = _make_reduce_layers(
224
- edges, handled_exceptions, is_async, single_node_side_effect
225
- )
215
+ all_node_side_effects_on_edges = gamla.side_effect(all_nodes_side_effect(edges))
216
+
217
+ reduce_layers = _make_reduce_layers(handled_exceptions)
226
218
 
227
219
  if is_async:
228
220
 
229
221
  async def final_runner(sources_to_values):
230
- return await gamla.pipe(
222
+ d = gamla.pipe(
231
223
  topological_layers,
232
224
  reduce_layers(
233
225
  immutables.Map(translate_source_to_placeholder(sources_to_values))
@@ -235,6 +227,26 @@ def _to_callable_with_side_effect_for_single_and_multiple(
235
227
  dict,
236
228
  gamla.side_effect(all_node_side_effects_on_edges),
237
229
  )
230
+ results_by_is_async = _group_by_is_async_result(d.items())
231
+ async_results = tuple(zip(*results_by_is_async.get(True, ())))
232
+ sync_results = dict(results_by_is_async.get(False, ()))
233
+ return all_node_side_effects_on_edges(
234
+ sync_results
235
+ | (
236
+ {
237
+ k: v
238
+ for (k, v) in zip(
239
+ async_results[0],
240
+ await asyncio.gather(
241
+ *async_results[1], return_exceptions=True
242
+ ),
243
+ )
244
+ if not isinstance(v, Exception)
245
+ }
246
+ if async_results
247
+ else {}
248
+ )
249
+ )
238
250
 
239
251
  else:
240
252
 
@@ -245,7 +257,7 @@ def _to_callable_with_side_effect_for_single_and_multiple(
245
257
  immutables.Map(translate_source_to_placeholder(sources_to_values))
246
258
  ),
247
259
  dict,
248
- gamla.side_effect(all_node_side_effects_on_edges),
260
+ all_node_side_effects_on_edges,
249
261
  )
250
262
 
251
263
  return (_async_graph_reducer if is_async else _graph_reducer)(final_runner)
@@ -280,10 +292,18 @@ def _node_incoming_edges_to_input_spec(
280
292
  )
281
293
 
282
294
 
283
- def _edges_to_accumulated_results_to_node_to_first_possible_input(
284
- edges,
285
- ) -> Callable[[_NodeToResults], _NodeToComputationInput]:
286
- node_to_incoming_edges = graph.get_incoming_edges_for_node(edges)
295
+ def _to_awaitable(v) -> Awaitable:
296
+ if inspect.isawaitable(v):
297
+ return v
298
+ f = asyncio.get_event_loop().create_future()
299
+ f.set_result(v)
300
+ return f
301
+
302
+
303
+ def _make_get_node_executor(
304
+ edges, handled_exceptions, single_node_side_effect: _SingleNodeSideEffect
305
+ ):
306
+ node_to_incoming_edges = functools.cache(graph.get_incoming_edges_for_node(edges))
287
307
  node_to_computation_input_spec_options: Callable[
288
308
  [base_types.ComputationNode], Tuple[_ComputationInputSpec]
289
309
  ] = functools.cache(
@@ -296,67 +316,229 @@ def _edges_to_accumulated_results_to_node_to_first_possible_input(
296
316
  opt_gamla.maptuple(_node_incoming_edges_to_input_spec),
297
317
  )
298
318
  )
299
- # TODO(eli): Add sync.translate_exception/except that avoids inspect.iscoroutinefunction so this can be inlined (ENG-5212)
300
- head_or_dep_not_found = gamla.translate_exception(
301
- gamla.head, StopIteration, _DepNotFoundError
302
- )
303
319
 
304
- def make_node_to_first_possible_input(
305
- accumulated_results: _NodeToResults,
306
- ) -> _NodeToComputationInput:
307
- return opt_gamla.compose_left(
308
- node_to_computation_input_spec_options,
309
- opt_gamla.map(
310
- gamla.excepts(
311
- KeyError,
312
- lambda _: None,
313
- opt_gamla.packstack(
314
- opt_gamla.maptuple(accumulated_results.__getitem__),
315
- opt_gamla.valmap(accumulated_results.__getitem__),
316
- ),
320
+ async def gather(
321
+ args: Tuple[Awaitable, ...], kwargs: Mapping[str, Awaitable]
322
+ ) -> tuple[tuple[Any, ...], dict[str, Any]]:
323
+ if args or kwargs:
324
+ try:
325
+ gathered = await asyncio.gather(*args, *kwargs.values())
326
+ return gathered[: len(args)], dict(
327
+ zip(kwargs.keys(), gathered[len(args) :])
317
328
  )
318
- ),
319
- opt_gamla.remove(gamla.equals(None)),
320
- head_or_dep_not_found,
321
- tuple,
322
- )
329
+ except (
330
+ _DepNotFoundError,
331
+ base_types.SkipComputationError,
332
+ *handled_exceptions,
333
+ ):
334
+ # We delete the references to the upstream tasks to avoid circular reference (task->exception->traceback->task) and improve memory performance
335
+ del args, kwargs
336
+ raise _DepNotFoundError() from None
337
+ return (), {}
323
338
 
324
- return make_node_to_first_possible_input
339
+ def node_to_input_sync(
340
+ accumulated_results: Mapping[base_types.ComputationNode, base_types.Result],
341
+ input_options: Iterable[_ComputationInputSpec],
342
+ ) -> Optional[_ComputationInput]:
343
+ for input_spec in input_options:
344
+ args, kwargs = input_spec
345
+ try:
346
+ return tuple(accumulated_results[arg] for arg in args), {
347
+ k: accumulated_results[v] for k, v in kwargs.items()
348
+ }
349
+ except KeyError:
350
+ ...
351
+ return None
352
+
353
+ async def node_to_input_async(
354
+ accumulated_results: Mapping[
355
+ base_types.ComputationNode, base_types.Result | Awaitable[base_types.Result]
356
+ ],
357
+ input_options: Iterable[_ComputationInputSpec],
358
+ ) -> Optional[_ComputationInput]:
359
+ for input_spec in input_options:
360
+ args_spec, kwargs_spec = input_spec
361
+ if all(
362
+ accumulated_results.get(arg, CG_NO_RESULT) is not CG_NO_RESULT
363
+ for arg in args_spec
364
+ ) and all(
365
+ accumulated_results.get(kwarg, CG_NO_RESULT) is not CG_NO_RESULT
366
+ for kwarg in kwargs_spec.values()
367
+ ):
368
+ try:
369
+ return await gather(
370
+ tuple(_to_awaitable(accumulated_results[a]) for a in args_spec),
371
+ {
372
+ k: _to_awaitable(accumulated_results[v])
373
+ for k, v in kwargs_spec.items()
374
+ },
375
+ )
376
+ except (
377
+ _DepNotFoundError,
378
+ base_types.SkipComputationError,
379
+ *handled_exceptions,
380
+ ):
381
+ ...
382
+ return None
383
+
384
+ @opt_gamla.after(asyncio.create_task)
385
+ async def await_deps_and_apply(
386
+ accumulated_results: Mapping[
387
+ base_types.ComputationNode, base_types.Result | Awaitable[base_types.Result]
388
+ ],
389
+ node: base_types.ComputationNode,
390
+ ) -> base_types.Result:
391
+ args_kwargs = await node_to_input_async(
392
+ accumulated_results, node_to_computation_input_spec_options(node)
393
+ )
394
+ # We delete the references to the upstream tasks to avoid circular reference (task->exception->traceback->task) and improve memory performance
395
+ del accumulated_results
396
+ if args_kwargs is None:
397
+ raise _DepNotFoundError()
398
+
399
+ args, kwargs = args_kwargs
400
+ before = time.perf_counter()
401
+ result = node.func(*args, **kwargs)
402
+ single_node_side_effect(node, result)
403
+ if inspect.isawaitable(result):
404
+ raise Exception(
405
+ f"{node} returned an awaitable result but is not an async function"
406
+ )
407
+ _profile(node, before)
408
+ return result
409
+
410
+ @opt_gamla.after(asyncio.create_task)
411
+ async def await_deps_and_await(
412
+ accumulated_results: Mapping[
413
+ base_types.ComputationNode, base_types.Result | Awaitable[base_types.Result]
414
+ ],
415
+ node: base_types.ComputationNode,
416
+ ) -> base_types.Result:
417
+ args_kwargs = await node_to_input_async(
418
+ accumulated_results, node_to_computation_input_spec_options(node)
419
+ )
420
+ # We delete the references to the upstream tasks to avoid circular reference (task->exception->traceback->task) and improve memory performance
421
+ del accumulated_results
422
+ if args_kwargs is None:
423
+ raise _DepNotFoundError()
424
+
425
+ args, kwargs = args_kwargs
426
+ before = time.perf_counter()
427
+ result = await node.func(*args, **kwargs)
428
+ single_node_side_effect(node, result)
429
+ _profile(node, before)
430
+ return result
431
+
432
+ @opt_gamla.after(asyncio.create_task)
433
+ async def get_deps_and_await(
434
+ accumulated_results: Mapping[base_types.ComputationNode, base_types.Result],
435
+ node: base_types.ComputationNode,
436
+ ) -> base_types.Result:
437
+ args_kwargs = node_to_input_sync(
438
+ accumulated_results, node_to_computation_input_spec_options(node)
439
+ )
440
+ # We delete the references to the upstream tasks to avoid circular reference (task->exception->traceback->task) and improve memory performance
441
+ del accumulated_results
442
+ if args_kwargs is None:
443
+ raise _DepNotFoundError()
444
+
445
+ args, kwargs = args_kwargs
446
+ before = time.perf_counter()
447
+ result = await node.func(*args, **kwargs)
448
+ single_node_side_effect(node, result)
449
+ _profile(node, before)
450
+ return result
451
+
452
+ def get_deps_and_apply(
453
+ accumulated_results: Mapping[base_types.ComputationNode, base_types.Result],
454
+ node: base_types.ComputationNode,
455
+ ) -> base_types.Result:
456
+ args_kwargs = node_to_input_sync(
457
+ accumulated_results, node_to_computation_input_spec_options(node)
458
+ )
459
+ # We delete the references to the upstream tasks to avoid circular reference (task->exception->traceback->task) and improve memory performance
460
+ del accumulated_results
461
+ if args_kwargs is None:
462
+ raise _DepNotFoundError()
463
+
464
+ args, kwargs = args_kwargs
465
+ before = time.perf_counter()
466
+ result = node.func(*args, **kwargs)
467
+ single_node_side_effect(node, result)
468
+ if inspect.isawaitable(result):
469
+ raise Exception(
470
+ f"{node} returned an awaitable result but is not an async function"
471
+ )
472
+ _profile(node, before)
473
+ return result
474
+
475
+ all_nodes = graph.get_all_nodes(edges)
476
+ async_nodes = {n for n in all_nodes if asyncio.iscoroutinefunction(n.func)}
477
+ sync = all_nodes - async_nodes
478
+ tf = graph.traverse_forward(edges)
479
+ downstream_from_async = set(gamla.graph_traverse_many(async_nodes, tf))
480
+
481
+ async_and_downstream = async_nodes & downstream_from_async
482
+ async_not_downstream = async_nodes - downstream_from_async
483
+ sync_and_downstream = sync & downstream_from_async
484
+ sync_not_downstream = sync - downstream_from_async
485
+
486
+ def get_executor(
487
+ node: base_types.ComputationNode,
488
+ ) -> Callable[
489
+ [
490
+ Mapping[
491
+ base_types.ComputationNode,
492
+ base_types.Result | Awaitable[base_types.Result],
493
+ ],
494
+ base_types.ComputationNode,
495
+ ],
496
+ base_types.Result,
497
+ ]:
498
+ if node in async_and_downstream:
499
+ return await_deps_and_await
500
+ if node in async_not_downstream:
501
+ return get_deps_and_await
502
+ if node in sync_and_downstream:
503
+ return await_deps_and_apply
504
+ if node in sync_not_downstream:
505
+ # This is fully sync so it only uses sync results from the mapping, its typing says the whole mapping is sync.
506
+ return get_deps_and_apply # type: ignore
507
+ raise Exception("no executor found")
508
+
509
+ return get_executor
325
510
 
326
511
 
327
512
  def _make_reduce_layers(
328
- edges: base_types.GraphType,
329
- handled_exceptions: Tuple[Type[Exception], ...],
330
- is_async: bool,
331
- single_node_side_effect: _SingleNodeSideEffect,
513
+ handled_exceptions: Tuple[Type[Exception], ...]
332
514
  ) -> Callable[[immutables.Map], Callable[[Tuple[FrozenSet, ...]], immutables.Map]]:
333
- get_node_input_and_apply = _make_get_node_input_and_apply(
334
- is_async, single_node_side_effect
335
- )
336
- accumulated_results_to_node_to_input = (
337
- _edges_to_accumulated_results_to_node_to_first_possible_input(edges)
338
- )
515
+ def apply_executor(accumulated_results, node, executor):
516
+ try:
517
+ return node, executor(accumulated_results, node)
518
+ except (
519
+ _DepNotFoundError,
520
+ base_types.SkipComputationError,
521
+ *handled_exceptions,
522
+ ):
523
+ # We delete the references to the upstream tasks to avoid circular reference (task->exception->traceback->task) and improve memory performance
524
+ del accumulated_results
525
+ return None
526
+
339
527
  single_layer_reducer = debug.name_callable(
340
528
  gamla.compose_left(
341
529
  gamla.pack,
342
530
  gamla.juxt(
343
531
  gamla.head,
344
532
  gamla.compose_left(
345
- opt_gamla.packstack(
346
- accumulated_results_to_node_to_input, gamla.identity
347
- ),
348
533
  gamla.explode(1),
349
- gamla.map_filter_empty(
350
- gamla.first(
351
- get_node_input_and_apply,
352
- gamla.just(None),
353
- exception_type=(
354
- *handled_exceptions,
355
- base_types.SkipComputationError,
356
- _DepNotFoundError,
357
- ),
534
+ opt_gamla.map(
535
+ lambda results__node_executor: apply_executor(
536
+ results__node_executor[0],
537
+ results__node_executor[1][0],
538
+ results__node_executor[1][1],
358
539
  )
359
540
  ),
541
+ opt_gamla.filter(bool),
360
542
  tuple,
361
543
  ),
362
544
  ),
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: computation-graph
3
- Version: 54
3
+ Version: 56
4
4
  Requires-Python: >=3
5
5
  Description-Content-Type: text/markdown
6
6
  License-File: LICENSE.md
@@ -7,7 +7,7 @@ with open("README.md", "r") as fh:
7
7
  setuptools.setup(
8
8
  name="computation-graph",
9
9
  python_requires=">=3",
10
- version="54",
10
+ version="56",
11
11
  long_description=_LONG_DESCRIPTION,
12
12
  long_description_content_type="text/markdown",
13
13
  packages=setuptools.find_namespace_packages(),
File without changes
File without changes