computation-graph 68__tar.gz → 69__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 (38) hide show
  1. {computation_graph-68 → computation_graph-69}/PKG-INFO +1 -1
  2. {computation_graph-68 → computation_graph-69}/computation_graph/composers/__init__.py +29 -37
  3. {computation_graph-68 → computation_graph-69}/computation_graph/composers/duplication.py +2 -2
  4. {computation_graph-68 → computation_graph-69}/computation_graph/graph.py +31 -39
  5. {computation_graph-68 → computation_graph-69}/computation_graph/graph_runners.py +2 -2
  6. {computation_graph-68 → computation_graph-69}/computation_graph/run.py +6 -4
  7. {computation_graph-68 → computation_graph-69}/computation_graph/signature.py +36 -1
  8. {computation_graph-68 → computation_graph-69}/computation_graph.egg-info/PKG-INFO +1 -1
  9. {computation_graph-68 → computation_graph-69}/setup.py +1 -1
  10. {computation_graph-68 → computation_graph-69}/LICENSE.md +0 -0
  11. {computation_graph-68 → computation_graph-69}/README.md +0 -0
  12. {computation_graph-68 → computation_graph-69}/computation_graph/__init__.py +0 -0
  13. {computation_graph-68 → computation_graph-69}/computation_graph/base_types.py +0 -0
  14. {computation_graph-68 → computation_graph-69}/computation_graph/composers/composers_test.py +0 -0
  15. {computation_graph-68 → computation_graph-69}/computation_graph/composers/condition.py +0 -0
  16. {computation_graph-68 → computation_graph-69}/computation_graph/composers/condition_test.py +0 -0
  17. {computation_graph-68 → computation_graph-69}/computation_graph/composers/debug.py +0 -0
  18. {computation_graph-68 → computation_graph-69}/computation_graph/composers/lift.py +0 -0
  19. {computation_graph-68 → computation_graph-69}/computation_graph/composers/lift_test.py +0 -0
  20. {computation_graph-68 → computation_graph-69}/computation_graph/composers/logic.py +0 -0
  21. {computation_graph-68 → computation_graph-69}/computation_graph/composers/memory.py +0 -0
  22. {computation_graph-68 → computation_graph-69}/computation_graph/composers/memory_test.py +0 -0
  23. {computation_graph-68 → computation_graph-69}/computation_graph/graph_test.py +0 -0
  24. {computation_graph-68 → computation_graph-69}/computation_graph/infer_sink.py +0 -0
  25. {computation_graph-68 → computation_graph-69}/computation_graph/infer_sink_test.py +0 -0
  26. {computation_graph-68 → computation_graph-69}/computation_graph/legacy.py +0 -0
  27. {computation_graph-68 → computation_graph-69}/computation_graph/trace/__init__.py +0 -0
  28. {computation_graph-68 → computation_graph-69}/computation_graph/trace/ascii.py +0 -0
  29. {computation_graph-68 → computation_graph-69}/computation_graph/trace/graphviz.py +0 -0
  30. {computation_graph-68 → computation_graph-69}/computation_graph/trace/graphviz_test.py +0 -0
  31. {computation_graph-68 → computation_graph-69}/computation_graph/trace/mermaid.py +0 -0
  32. {computation_graph-68 → computation_graph-69}/computation_graph/trace/trace_utils.py +0 -0
  33. {computation_graph-68 → computation_graph-69}/computation_graph.egg-info/SOURCES.txt +0 -0
  34. {computation_graph-68 → computation_graph-69}/computation_graph.egg-info/dependency_links.txt +0 -0
  35. {computation_graph-68 → computation_graph-69}/computation_graph.egg-info/requires.txt +0 -0
  36. {computation_graph-68 → computation_graph-69}/computation_graph.egg-info/top_level.txt +0 -0
  37. {computation_graph-68 → computation_graph-69}/pyproject.toml +0 -0
  38. {computation_graph-68 → computation_graph-69}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: computation-graph
3
- Version: 68
3
+ Version: 69
4
4
  Requires-Python: >=3
5
5
  Description-Content-Type: text/markdown
6
6
  License-File: LICENSE.md
@@ -286,44 +286,36 @@ def _infer_composition_edges(
286
286
  unbound_signature = graph.unbound_signature(
287
287
  graph.get_incoming_edges_for_node(destination.edges)
288
288
  )
289
+ source_nodes = (
290
+ graph.get_all_nodes(source.edges)
291
+ if isinstance(source, GraphType)
292
+ else frozenset()
293
+ )
294
+ skip_cycle_check = isinstance(source, base_types.ComputationNode)
295
+ new_edges = set()
296
+ # The connectable-node set is order-independent (result is a frozenset), so we
297
+ # can iterate the cached node set instead of rescanning the edges.
298
+ for node in graph.get_all_nodes(destination.edges):
299
+ node_signature = unbound_signature(node)
300
+ if not (
301
+ key in node_signature.kwargs
302
+ or (key is None and signature.is_unary(node_signature))
303
+ ):
304
+ continue
305
+ # Do not add edges to nodes from source that are already present in destination (cycle).
306
+ if not skip_cycle_check and node in source_nodes:
307
+ continue
308
+ new_edges.add(
309
+ try_connect(destination=node, unbound_destination_signature=node_signature)
310
+ )
311
+ if not new_edges:
312
+ raise AssertionError(
313
+ f"Cannot compose, destination signature does not contain key '{key}'"
314
+ )
289
315
  return graph.merge_graphs(
290
- gamla.sync.pipe(
291
- destination,
292
- gamla.attrgetter("edges"),
293
- gamla.sync.mapcat(graph.get_edge_nodes),
294
- gamla.unique,
295
- gamla.sync.filter(
296
- gamla.sync.compose_left(
297
- unbound_signature,
298
- lambda sig: key in sig.kwargs
299
- or (key is None and signature.is_unary(sig)),
300
- )
301
- ),
302
- # Do not add edges to nodes from source that are already present in destination (cycle).
303
- gamla.sync.filter(
304
- lambda node: isinstance(source, base_types.ComputationNode)
305
- or node
306
- not in graph.get_all_nodes(
307
- source.edges if isinstance(source, GraphType) else source
308
- )
309
- ),
310
- gamla.sync.map(
311
- lambda destination_node: try_connect(
312
- destination=destination_node,
313
- unbound_destination_signature=unbound_signature(destination_node),
314
- )
315
- ),
316
- frozenset,
317
- gamla.sync.check(
318
- gamla.identity,
319
- AssertionError(
320
- f"Cannot compose, destination signature does not contain key '{key}'"
321
- ),
322
- ),
323
- # Sink is a placeholder here; `merge_graphs` below sets the real sink
324
- # via `sink_node_or_graph`.
325
- lambda edges: GraphType(edges, None), # type: ignore[arg-type]
326
- ),
316
+ # Sink is a placeholder here; `merge_graphs` below sets the real sink
317
+ # via `sink_node_or_graph`.
318
+ GraphType(frozenset(new_edges), None), # type: ignore[arg-type]
327
319
  _get_edges_from_node_or_graph(source),
328
320
  destination,
329
321
  sink_node_or_graph=_determine_sink(source, destination, is_future),
@@ -1,6 +1,6 @@
1
- import asyncio
2
1
  import dataclasses
3
2
  import functools
3
+ import inspect
4
4
  from typing import Dict
5
5
 
6
6
  import gamla
@@ -10,7 +10,7 @@ from computation_graph.composers import debug
10
10
 
11
11
 
12
12
  def duplicate_function(func):
13
- if asyncio.iscoroutinefunction(func):
13
+ if inspect.iscoroutinefunction(func):
14
14
 
15
15
  @functools.wraps(func)
16
16
  async def inner(*args, **kwargs):
@@ -1,6 +1,6 @@
1
1
  import dataclasses
2
2
  import functools
3
- from typing import Callable, Dict, FrozenSet, Optional, Tuple
3
+ from typing import Callable, Dict, FrozenSet, Iterable, Optional, Tuple
4
4
 
5
5
  import gamla
6
6
  from gamla.optimized import sync as opt_gamla
@@ -64,11 +64,17 @@ def make_computation_node(
64
64
  )
65
65
 
66
66
 
67
- get_incoming_edges_for_node = opt_gamla.compose_left(
68
- opt_gamla.groupby(base_types.edge_destination),
69
- opt_gamla.valmap(frozenset),
70
- gamla.dict_to_getter_with_default(frozenset()),
71
- )
67
+ _EMPTY_EDGE_SET: FrozenSet[base_types.ComputationEdge] = frozenset()
68
+
69
+
70
+ def get_incoming_edges_for_node(
71
+ edges: Iterable[base_types.ComputationEdge],
72
+ ) -> Callable[[base_types.ComputationNode], FrozenSet[base_types.ComputationEdge]]:
73
+ grouped: Dict[base_types.ComputationNode, list] = {}
74
+ for edge in edges:
75
+ grouped.setdefault(edge.destination, []).append(edge)
76
+ node_to_edges = {node: frozenset(group) for node, group in grouped.items()}
77
+ return lambda node: node_to_edges.get(node, _EMPTY_EDGE_SET)
72
78
 
73
79
 
74
80
  get_terminals = opt_gamla.compose_left(
@@ -95,30 +101,24 @@ def make_terminal(name: str, func: Callable) -> base_types.ComputationNode:
95
101
  )
96
102
 
97
103
 
98
- _keep_not_in_bound_kwargs = opt_gamla.compose_left(
99
- opt_gamla.map(base_types.edge_key),
100
- gamla.filter(gamla.identity),
101
- frozenset,
102
- gamla.contains,
103
- gamla.remove,
104
- )
105
-
106
-
107
104
  @gamla.curry
108
105
  def unbound_signature(
109
106
  node_to_incoming_edges, node: base_types.ComputationNode
110
107
  ) -> base_types.NodeSignature:
111
108
  """Computes the new signature of unbound variables after considering internal edges."""
112
109
  incoming_edges = node_to_incoming_edges(node)
113
- keep_not_in_bound_kwargs = _keep_not_in_bound_kwargs(incoming_edges)
110
+ bound_keys = {edge.key for edge in incoming_edges if edge.key}
111
+ node_signature = node.signature
114
112
  return base_types.NodeSignature(
115
- is_kwargs=node.signature.is_kwargs
116
- and "**kwargs" not in tuple(opt_gamla.map(base_types.edge_key)(incoming_edges))
117
- and signature.is_unary(node.signature),
118
- is_args=node.signature.is_args
113
+ is_kwargs=node_signature.is_kwargs
114
+ and all(edge.key != "**kwargs" for edge in incoming_edges)
115
+ and signature.is_unary(node_signature),
116
+ is_args=node_signature.is_args
119
117
  and not any(edge.args for edge in incoming_edges),
120
- kwargs=tuple(keep_not_in_bound_kwargs(node.signature.kwargs)),
121
- optional_kwargs=tuple(keep_not_in_bound_kwargs(node.signature.optional_kwargs)),
118
+ kwargs=tuple(k for k in node_signature.kwargs if k not in bound_keys),
119
+ optional_kwargs=tuple(
120
+ k for k in node_signature.optional_kwargs if k not in bound_keys
121
+ ),
122
122
  )
123
123
 
124
124
 
@@ -361,28 +361,20 @@ def merge_graphs(
361
361
  isinstance(x, base_types.GraphType) for x in graphs
362
362
  ), f"{graphs} not a graph"
363
363
  assert (
364
- not base_types.is_computation_graph(sink_node_or_graph)
364
+ not isinstance(sink_node_or_graph, base_types.GraphType)
365
+ or not sink_node_or_graph.edges
365
366
  or sink_node_or_graph in graphs
366
367
  ), "sink graph must be one of the graphs being merged"
367
368
 
368
- new_g = gamla.pipe(
369
- graphs,
370
- gamla.remove(gamla.equals(base_types.EMPTY_GRAPH)),
371
- tuple,
372
- gamla.pair_right(
373
- gamla.just(
374
- sink_node_or_graph.sink
375
- if isinstance(sink_node_or_graph, base_types.GraphType)
376
- else sink_node_or_graph
377
- )
369
+ new_g = base_types.GraphType(
370
+ base_types.merge_edges(
371
+ *(g.edges for g in graphs if g != base_types.EMPTY_GRAPH)
378
372
  ),
379
- gamla.packstack(
380
- gamla.compose_left(
381
- gamla.map(gamla.attrgetter("edges")), gamla.star(base_types.merge_edges)
382
- ),
383
- gamla.identity,
373
+ (
374
+ sink_node_or_graph.sink
375
+ if isinstance(sink_node_or_graph, base_types.GraphType)
376
+ else sink_node_or_graph
384
377
  ),
385
- gamla.star(base_types.GraphType),
386
378
  )
387
379
 
388
380
  if base_types.is_debug_mode():
@@ -1,4 +1,4 @@
1
- import asyncio
1
+ import inspect
2
2
  from typing import Callable, Union
3
3
 
4
4
  import gamla
@@ -77,7 +77,7 @@ def unary_with_state_and_expectations(
77
77
  def variadic_with_state_and_expectations(g, sink):
78
78
  f = run.to_callable_strict(g)
79
79
 
80
- if asyncio.iscoroutinefunction(f):
80
+ if inspect.iscoroutinefunction(f):
81
81
 
82
82
  async def inner(turns):
83
83
  prev = {}
@@ -109,7 +109,7 @@ _is_graph_async = opt_gamla.compose_left(
109
109
  opt_gamla.mapcat(lambda edge: (edge.source, *edge.args)),
110
110
  opt_gamla.remove(gamla.equals(None)),
111
111
  opt_gamla.map(base_types.node_implementation),
112
- gamla.anymap(asyncio.iscoroutinefunction),
112
+ gamla.anymap(inspect.iscoroutinefunction),
113
113
  )
114
114
 
115
115
 
@@ -452,7 +452,7 @@ def _make_get_node_executor(
452
452
  return result
453
453
 
454
454
  all_nodes = graph.get_all_nodes(edges)
455
- async_nodes = {n for n in all_nodes if asyncio.iscoroutinefunction(n.func)}
455
+ async_nodes = {n for n in all_nodes if inspect.iscoroutinefunction(n.func)}
456
456
  sync = all_nodes - async_nodes
457
457
  tf = graph.traverse_forward(edges)
458
458
  downstream_from_async = set(gamla.graph_traverse_many(async_nodes, tf))
@@ -572,7 +572,9 @@ to_callable = to_callable_with_side_effect(gamla.just(gamla.just(None)))
572
572
 
573
573
 
574
574
  def _node_is_properly_composed(
575
- node_to_incoming_edges: base_types.GraphType,
575
+ node_to_incoming_edges: Callable[
576
+ [base_types.ComputationNode], FrozenSet[base_types.ComputationEdge]
577
+ ]
576
578
  ) -> Callable[[base_types.ComputationNode], bool]:
577
579
  return gamla.compose_left(
578
580
  graph.unbound_signature(node_to_incoming_edges),
@@ -581,7 +583,7 @@ def _node_is_properly_composed(
581
583
  )
582
584
 
583
585
 
584
- def _assert_composition_is_valid(g: base_types.GraphType):
586
+ def _assert_composition_is_valid(g: typing.Iterable[base_types.ComputationEdge]):
585
587
  return opt_gamla.pipe(
586
588
  g,
587
589
  graph.get_all_nodes,
@@ -1,5 +1,6 @@
1
1
  import functools
2
2
  import inspect
3
+ import types
3
4
  from types import MappingProxyType
4
5
  from typing import Callable, FrozenSet
5
6
 
@@ -39,7 +40,7 @@ _func_parameters = gamla.compose_left(
39
40
  )
40
41
 
41
42
 
42
- def from_callable(func: Callable) -> base_types.NodeSignature:
43
+ def _from_callable_via_inspect(func: Callable) -> base_types.NodeSignature:
43
44
  function_parameters = _func_parameters(func)
44
45
  return base_types.NodeSignature(
45
46
  is_args=gamla.anymap(parameter_is_star)(function_parameters),
@@ -60,6 +61,40 @@ def from_callable(func: Callable) -> base_types.NodeSignature:
60
61
  )
61
62
 
62
63
 
64
+ def _as_plain_function(func: Callable):
65
+ """The `inspect.signature`-equivalent target when it is a plain function whose
66
+ signature is fully described by its code object, else None (wrappers with
67
+ `__signature__`, partials, methods, builtins and classes need `inspect`)."""
68
+ unwrapped = inspect.unwrap(func, stop=lambda f: hasattr(f, "__signature__"))
69
+ if not isinstance(unwrapped, types.FunctionType) or hasattr(
70
+ unwrapped, "__signature__"
71
+ ):
72
+ return None
73
+ return unwrapped
74
+
75
+
76
+ def from_callable(func: Callable) -> base_types.NodeSignature:
77
+ plain_function = _as_plain_function(func)
78
+ if plain_function is None:
79
+ return _from_callable_via_inspect(func)
80
+ code = plain_function.__code__
81
+ positional = code.co_varnames[: code.co_argcount]
82
+ keyword_only = code.co_varnames[
83
+ code.co_argcount : code.co_argcount + code.co_kwonlyargcount
84
+ ]
85
+ defaults = plain_function.__defaults__ or ()
86
+ keyword_defaults = plain_function.__kwdefaults__ or {}
87
+ return base_types.NodeSignature(
88
+ is_args=bool(code.co_flags & inspect.CO_VARARGS),
89
+ is_kwargs=bool(code.co_flags & inspect.CO_VARKEYWORDS),
90
+ kwargs=positional + keyword_only,
91
+ optional_kwargs=(
92
+ positional[len(positional) - len(defaults) :]
93
+ + tuple(name for name in keyword_only if name in keyword_defaults)
94
+ ),
95
+ )
96
+
97
+
63
98
  def parameters(signature: base_types.NodeSignature) -> FrozenSet[str]:
64
99
  return frozenset(
65
100
  {
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: computation-graph
3
- Version: 68
3
+ Version: 69
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="68",
10
+ version="69",
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