Skip to content

Commit 9b02d1b

Browse files
committed
changed edge signature: removed END parameter because of redundancy with None
1 parent 085064f commit 9b02d1b

7 files changed

Lines changed: 71 additions & 45 deletions

File tree

src/edgygraph/graph/branches.py

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,9 @@
44
from collections.abc import Hashable, Sequence
55
import asyncio
66

7-
from ..nodes import START, END, Node
87
from ..states import StateProtocol, SharedProtocol
98
from ..diff import Change
10-
from .types import NodeTupel, Edge, ErrorEdge, SingleNext, Entry, ErrorEntry, SingleSource, SingleErrorSource, Config, ErrorConfig, Types
9+
from .types import NodeTupel, Edge, ErrorEdge, Join, Entry, ErrorEntry, SingleSource, SingleErrorSource, Config, ErrorConfig, Types
1110

1211

1312
class Branch[T: StateProtocol, S: SharedProtocol]:
@@ -22,7 +21,7 @@ class Branch[T: StateProtocol, S: SharedProtocol]:
2221
"""
2322

2423

25-
def __init__(self, edges: Sequence[Edge[T, S] | ErrorEdge[T, S] | NodeTupel[T, S]], start: SingleSource[T, S], join: SingleNext[T, S] = None) -> None:
24+
def __init__(self, edges: Sequence[Edge[T, S] | ErrorEdge[T, S] | NodeTupel[T, S]], start: SingleSource[T, S], join: Join[T, S] = None) -> None:
2625

2726
self.edges = edges
2827
self.start = start
@@ -51,9 +50,8 @@ def index_edges(self) -> None:
5150
if Types[T, S].is_node_tupel(edge):
5251

5352
for source, next in zip(edge, edge[1:]):
54-
if isinstance(source, type): assert source is START, f"Unexpected type in node sequence: {source}"
55-
if isinstance(next, type): assert next is END, f"Unexpected type in node sequence: {next}"
56-
assert isinstance(source, (Node, type)), f"Unexpected source type in node sequence: {source}"
53+
assert Types[T, S].is_single_source(source), f"Unexpected source type in node sequence: {source}"
54+
assert Types[T, S].is_next(next), f"Unexpected next type in node sequence: {next}"
5755
self.edge_index[source].append(Entry[T, S](next=next, config=Config(), index=i))
5856

5957
continue

src/edgygraph/graph/graphs.py

Lines changed: 50 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
from ..states import StateProtocol, SharedProtocol
1111
from ..diff import Change, ChangeConflictException, Diff
1212
from ..nodes import Node, END, START
13-
from .types import SingleNext, NextNode, ErrorEntry, SingleErrorSource, Entries, BranchContainer, SingleSource, Source, Types, ResolvedNext
13+
from .types import SingleNext, NextNode, ErrorEntry, SingleErrorSource, Entries, BranchContainer, SingleSource, Source, Types, ResolvedNext, Join
1414
from .hooks import GraphHook
1515
from .branches import Branch
1616

@@ -35,7 +35,7 @@ class Graph[T: StateProtocol = StateProtocol, S: SharedProtocol = SharedProtocol
3535
3636
For the more flexible approach with better scaling use protocols to define the supported state types. Remember to always extend `typing.Protocol` in the child classes for typing.
3737
38-
This is recommended for scalable projects where many different state types need to be joined in one graph. See https://github.com/mathisxy/edgynodes/ for an example.
38+
This is recommended for scalable projects where many different state types need to be joined in one graph. See [edgynodes](https://github.com/mathisxy/edgynodes/) for an example.
3939
4040
### Disable Type Checking
4141
@@ -46,13 +46,50 @@ class Graph[T: StateProtocol = StateProtocol, S: SharedProtocol = SharedProtocol
4646
4747
The edges are defined as a list of tuples, where the first element is the source and the second element reveals the next node.
4848
49+
### Branches
50+
51+
The edges are contained in branches. A branch is a tuple with edges and a join parameter at the end.
52+
53+
#### Spawning
54+
55+
A branch is spawned when the source of the first edge of the branch is triggered.
56+
57+
In this example it would be on `START`:
58+
59+
```python
60+
edges=[(
61+
(START, node1),
62+
(node1, node2),
63+
END
64+
)]
65+
```
66+
67+
In this example it would be on `node1` and on `node2` each:
68+
69+
```python
70+
edges=[(
71+
([node1, node2], node3),
72+
node4
73+
)]
74+
```
75+
76+
#### Joining
77+
78+
The join parameter can be of the following types:
79+
80+
- `None`: The branch will not be joined.
81+
- `END`: The branch will be joined at the end of the graph.
82+
- A node instance: The branch will be joined directly before the given node is executed in any branch into this branch.
83+
84+
The process of joining describes waiting for the branches to finish wich aim to join and then applying the changes to the state of the whole finished branch to the state of the joining branch.
85+
4986
### Formats
5087
51-
The graph supports different formats for the edges.
88+
A branch supports different formats for the edges.
5289
5390
- `(source, target)`: A single edge from source to target.
5491
- `(START, target)`: A single edge from the start of the graph to target.
55-
- `(source, END)`: A single edge from source to the end of the graph. It equals to `(source, None)`. It is redundant but can be used for better readability.
92+
- `(source, None)`: A single edge from source to no target. `END` is not allowed here, since it would be redundant. It is only allowed in join parameters to distinct join at the end of the graph (`END`) from not joining (`None`)
5693
- `([source1, source2], target)`: Multiple edges from source1 and source2 to target.
5794
- `(source, [target1, target2])`: Multiple edges from source to target1 and target2.
5895
- `([source1, source2], [target1, target2])`: Multiple edges from source1 and source2 to target1 and target2. This will create 4 edges in total.
@@ -87,7 +124,7 @@ def __init__(self,
87124
self.hooks = hooks or []
88125

89126
self.branch_registry: dict[SingleSource[T, S], list[Branch[T, S]]] = defaultdict(list)
90-
self.join_registry: dict[SingleNext[T, S], list[Branch[T, S]]] = defaultdict(list)
127+
self.join_registry: dict[Join[T, S], list[Branch[T, S]]] = defaultdict(list)
91128

92129
self.index_branches()
93130

@@ -257,14 +294,6 @@ async def node_wrapper(self, state: T, shared: S, node: NextNode[T, S]):
257294
raise e
258295

259296

260-
261-
def spawn_branch(self, state: T, shared: S, branch: Branch[T, S]) -> None:
262-
263-
self.join_registry[branch.join].append(branch)
264-
265-
self.task_group.create_task(self.run_branch(state, shared, branch))
266-
267-
268297

269298
async def merge_states(self, current_state: T, result_states: list[T]) -> T:
270299
"""
@@ -356,6 +385,13 @@ async def spawn_branches(self, state: T, shared: S, next_nodes: list[NextNode[T,
356385

357386
for h in self.hooks: await h.on_spawn_branch_end(state, shared, branch, node, self.branch_registry, self.join_registry)
358387

388+
389+
390+
def spawn_branch(self, state: T, shared: S, branch: Branch[T, S]) -> None:
391+
392+
self.join_registry[branch.join].append(branch)
393+
394+
self.task_group.create_task(self.run_branch(state, shared, branch))
359395

360396
async def join_branches(self, state: T, next_nodes: list[NextNode[T, S]]) -> T:
361397
"""
@@ -569,11 +605,7 @@ def match(x: SingleNext[T, S]) -> None:
569605

570606
case None:
571607
print("None")
572-
pass # END
573-
574-
case type():
575-
print("END")
576-
assert next is END, "Only END is allowed as a type here"
608+
pass
577609

578610
case Node():
579611
print("Node")

src/edgygraph/graph/hooks.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
from ..states import StateProtocol, SharedProtocol
55
from ..diff import Change
66
from .branches import Branch
7-
from .types import NextNode, SingleSource, SingleNext
7+
from .types import NextNode, SingleSource, Join
88

99

1010

@@ -56,7 +56,7 @@ async def on_step_end(self, state: T, shared: S, nodes: list[NextNode[T, S]]) ->
5656
pass
5757

5858

59-
async def on_spawn_branch_start(self, state: T, shared: S, branch: Branch[T, S], trigger: NextNode[T, S], branch_registry: dict[SingleSource[T, S], list[Branch[T, S]]], join_registry: dict[SingleNext[T, S], list[Branch[T, S]]]):
59+
async def on_spawn_branch_start(self, state: T, shared: S, branch: Branch[T, S], trigger: NextNode[T, S], branch_registry: dict[SingleSource[T, S], list[Branch[T, S]]], join_registry: dict[Join[T, S], list[Branch[T, S]]]):
6060
"""
6161
Called before a branch is spawned.
6262
@@ -71,7 +71,7 @@ async def on_spawn_branch_start(self, state: T, shared: S, branch: Branch[T, S],
7171
pass
7272

7373

74-
async def on_spawn_branch_end(self, state: T, shared: S, branch: Branch[T, S], trigger: NextNode[T, S], branch_registry: dict[SingleSource[T, S], list[Branch[T, S]]], join_registry: dict[SingleNext[T, S], list[Branch[T, S]]]):
74+
async def on_spawn_branch_end(self, state: T, shared: S, branch: Branch[T, S], trigger: NextNode[T, S], branch_registry: dict[SingleSource[T, S], list[Branch[T, S]]], join_registry: dict[Join[T, S], list[Branch[T, S]]]):
7575
"""
7676
Called after a branch is spawned.
7777

src/edgygraph/graph/types.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,15 +15,16 @@
1515
type SingleErrorSource[T: StateProtocol, S: SharedProtocol] = type[Exception] | tuple[Node[T, S], type[Exception]]
1616
type ErrorSource[T: StateProtocol, S: SharedProtocol] = SingleErrorSource[T, S] | Sequence[SingleErrorSource[T, S]]
1717

18-
type SingleNext[T: StateProtocol, S: SharedProtocol] = Node[T, S] | type[END] | None
18+
type SingleNext[T: StateProtocol, S: SharedProtocol] = Node[T, S] | None
1919
type ResolvedNext[T: StateProtocol, S: SharedProtocol] = SingleNext[T, S] | Sequence[SingleNext[T, S]]
2020
type Next[T: StateProtocol, S: SharedProtocol] = ResolvedNext[T, S] | Callable[[T, S], ResolvedNext[T, S]] | Callable[[T, S], Awaitable[ResolvedNext[T, S]]]
2121

2222

2323
type Edge[T: StateProtocol, S: SharedProtocol] = tuple[Source[T, S], Next[T, S]] | tuple[Source[T, S], Next[T, S], Config]
2424
type ErrorEdge[T: StateProtocol, S: SharedProtocol] = tuple[ErrorSource[T, S], Next[T, S]] | tuple[ErrorSource[T, S], Next[T, S], ErrorConfig]
2525

26-
type BranchContainer[T: StateProtocol, S: SharedProtocol] = tuple[Edge[T, S] | NodeTupel[T, S], *tuple[Edge[T, S] | ErrorEdge[T, S] | NodeTupel[T, S], ...], SingleNext[T, S]]
26+
type Join[T: StateProtocol, S: SharedProtocol] = Next[T, S] | type[END]
27+
type BranchContainer[T: StateProtocol, S: SharedProtocol] = tuple[Edge[T, S] | NodeTupel[T, S], *tuple[Edge[T, S] | ErrorEdge[T, S] | NodeTupel[T, S], ...], Join[T, S]]
2728

2829

2930
class Types[T: StateProtocol, S: SharedProtocol]:

src/edgygraph/graph_hooks/node_print.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
from ..graph.hooks import GraphHook
44
from ..states import StateProtocol, SharedProtocol
5-
from ..graph.types import NextNode, SingleSource, SingleNext
5+
from ..graph.types import NextNode, SingleSource, Join
66
from .utils.rich_printing import GraphRenderer
77

88
class NodePrintHook[T: StateProtocol = StateProtocol, S: SharedProtocol = SharedProtocol](GraphHook[T, S]):
@@ -29,6 +29,6 @@ async def on_step_end(self, state: T, shared: S, nodes: list[NextNode[T, S]]) ->
2929
async def on_graph_end(self, state: T, shared: S) -> None:
3030
self.renderer.render_graph_end(state, shared)
3131

32-
async def on_spawn_branch_end(self, state: T, shared: S, branch: Branch[T, S], trigger: NextNode[T, S], branch_registry: dict[SingleSource[T, S], list[Branch[T, S]]], join_registry: dict[SingleNext[T, S], list[Branch[T, S]]]):
32+
async def on_spawn_branch_end(self, state: T, shared: S, branch: Branch[T, S], trigger: NextNode[T, S], branch_registry: dict[SingleSource[T, S], list[Branch[T, S]]], join_registry: dict[Join[T, S], list[Branch[T, S]]]):
3333
self.renderer.render_spawn_branch_end(branch, trigger)
3434
self.renderer.render_branch_overview(branch_registry, join_registry)

src/edgygraph/graph_hooks/utils/rich_printing.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
from rich.tree import Tree
1010

1111
from ...diff import Change, ChangeTypes
12-
from ...graph.types import NextNode, SingleNext, SingleSource
12+
from ...graph.types import NextNode, Join, SingleSource
1313
from ...graph.branches import Branch
1414
from ...states import StateProtocol as State, SharedProtocol as Shared
1515

@@ -225,7 +225,7 @@ def render_spawn_branch_end(
225225
def render_branch_overview(
226226
self,
227227
branch_registry: dict[SingleSource[T, S], list[Branch[T, S]]],
228-
join_registry: dict[SingleNext[T, S], list[Branch[T, S]]],
228+
join_registry: dict[Join[T, S], list[Branch[T, S]]],
229229
) -> None:
230230
"""
231231
Render a combined overview of branch_registry and join_registry.

tests/test_graph.py

Lines changed: 7 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -174,7 +174,7 @@ def noop(self):
174174
def test_single_node_increments_value(self):
175175
state = SimpleState(value=0)
176176
shared = SimpleShared()
177-
g = Graph[SimpleState, SimpleShared](edges=[((START, inc), (inc, END), END)])
177+
g = Graph[SimpleState, SimpleShared](edges=[((START, inc), END)])
178178
result_state, _ = asyncio.get_event_loop().run_until_complete(g(state, shared))
179179
assert result_state.value == 1
180180

@@ -183,14 +183,14 @@ def test_chain_of_two_nodes(self):
183183
n2 = IncrementNode()
184184
state = SimpleState(value=0)
185185
shared = SimpleShared()
186-
g = Graph(edges=[((START, n1), (n1, n2), (n2, END), END)])
186+
g = Graph(edges=[((START, n1), (n1, n2), (n2, None), END)])
187187
result_state, _ = asyncio.get_event_loop().run_until_complete(g(state, shared))
188188
assert result_state.value == 2
189189

190190
def test_empty_graph_returns_unchanged_state(self):
191191
state = SimpleState(value=42)
192192
shared = SimpleShared()
193-
g = Graph[SimpleState, SimpleShared](edges=[((START, END), END)])
193+
g = Graph[SimpleState, SimpleShared](edges=[((START, None), END)])
194194
result_state, _ = asyncio.get_event_loop().run_until_complete(g(state, shared))
195195
assert result_state.value == 42
196196

@@ -204,7 +204,7 @@ def test_no_edges_from_start_returns_unchanged(self):
204204
def test_shared_is_same_object(self):
205205
state = SimpleState()
206206
shared = SimpleShared()
207-
g = Graph[SimpleState, SimpleShared](edges=[((START, inc), (inc, END), END)])
207+
g = Graph[SimpleState, SimpleShared](edges=[((START, inc), END)])
208208
_, result_shared = asyncio.get_event_loop().run_until_complete(g(state, shared))
209209
assert result_shared is shared
210210

@@ -219,7 +219,7 @@ def test_conditional_next_based_on_state(self):
219219
noop = NoOpNode()
220220

221221
def router(state: SimpleState, shared: SimpleShared):
222-
return noop if state.value > 0 else END
222+
return noop if state.value > 0 else None
223223

224224
state = SimpleState(value=1)
225225
shared = SimpleShared()
@@ -232,7 +232,7 @@ def test_conditional_returns_end(self):
232232
inc = IncrementNode()
233233

234234
def router(state: SimpleState, shared: SimpleShared):
235-
return END
235+
return None
236236

237237
state = SimpleState(value=0)
238238
shared = SimpleShared()
@@ -249,7 +249,7 @@ async def async_router(state: SimpleState, shared: SimpleShared):
249249

250250
state = SimpleState(value=0)
251251
shared = SimpleShared()
252-
g = Graph(edges=[((START, inc), (inc, async_router), (noop, END), END)])
252+
g = Graph(edges=[((START, inc), (inc, async_router), END)])
253253
result_state, _ = asyncio.get_event_loop().run_until_complete(g(state, shared))
254254
assert result_state.value == 1
255255

@@ -279,7 +279,6 @@ async def __call__(self, state: SimpleState, shared: SimpleShared) -> None:
279279
g = Graph(edges=[(
280280
(START, [sv, sn]),
281281
([sv, sn], join),
282-
(join, END),
283282
END)
284283
])
285284
result_state, _ = asyncio.get_event_loop().run_until_complete(g(state, shared))
@@ -324,7 +323,6 @@ def test_error_edge_by_exception_type(self):
324323
g = Graph(edges=[(
325324
(START, raiser),
326325
(ValueError, recovery),
327-
(recovery, END),
328326
END)
329327
])
330328
result_state, _ = asyncio.get_event_loop().run_until_complete(g(state, shared))
@@ -339,7 +337,6 @@ def test_error_edge_by_node_and_exception_type(self):
339337
g = Graph(edges=[(
340338
(START, raiser),
341339
((raiser, RuntimeError), recovery),
342-
(recovery, END),
343340
END)
344341
])
345342
result_state, _ = asyncio.get_event_loop().run_until_complete(g(state, shared))
@@ -387,7 +384,6 @@ def test_instant_node_runs_in_same_step(self):
387384
g = Graph(edges=[(
388385
(START, inc),
389386
(inc, noop, Config(instant=True)),
390-
(noop, END),
391387
END)
392388
])
393389
result_state, _ = asyncio.get_event_loop().run_until_complete(g(state, shared))
@@ -412,7 +408,6 @@ def test_list_source_registers_for_each_node(self):
412408
(START, [n1, n3]),
413409
(n3, n2),
414410
([n1, n2], join),
415-
(join, END),
416411
END)
417412
])
418413
result_state, _ = asyncio.get_event_loop().run_until_complete(g(state, shared))

0 commit comments

Comments
 (0)