Skip to content

Commit ae707cf

Browse files
committed
Add test on compose visitor
1 parent 21b7223 commit ae707cf

9 files changed

Lines changed: 318 additions & 32 deletions

File tree

CHANGELOG.md

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
# Changelogs
2+
3+
4+
## Version 1.0.0
5+
* First implementation:
6+
* Execution graph implementation
7+
* Visitor implementation
8+
* Composed visitor implementation
9+
* Documentation
10+
11+
12+
---
13+
14+
## TODO list for FreExGraph (next versions)
15+
16+
## Documentation todo
17+
18+
* Add doc about node / graph creation
19+
* Add documentation about graph nodes
20+
* Add documentation about custom hook
21+
22+
## Feature todo
23+
24+
* Delete node
25+
26+
## Testing todo
27+
28+
* ~~Test Composed Visitor~~
29+
* Custom hook

README.md

Lines changed: 25 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -130,7 +130,7 @@ It is possible to embed a graph into another thanks to a graph node. Any visitat
130130

131131
### Fork
132132

133-
FreExGraph provide a fork mechanism. It provides an easy way to duplicate a graph from a given node until the end of the graph (or to a specific node that would be used as a join: useful to implement).
133+
FreExGraph provide a fork mechanism. It provides an easy way to duplicate a graph from a given node until the end of the graph (or to a specific node that would be used as a join).
134134

135135
Here is an example:
136136
_Given the following graph in `execution_graph`:_
@@ -179,11 +179,11 @@ Join is do-able by adding the
179179
### Abstract Visitor hooks
180180

181181
Abstract visitor provide some default hooks that can be overridden from custom visitors in order to implement more complex logic depending on the graph visit.
182-
* `hook_start()` : This hook is called when the visitation of the graph start
183-
* `hook_end()` : This hook is called when the visitation of the graph end
184-
* `hook_fork_started(n: FreExNode, fork_id: str)` : This hook is called when a fork has been entered (when visiting the first node of a fork)
185-
* `hook_start_graph_node(gn: GraphNode)` : This hook is called when a graphnode recursion start (graph node given as parameter of the hook)
186-
* `hook_end_graph_node(gn: GraphNode)` : This hook is called when a graphnode visitation end (graph node given as parameter of the hook)
182+
* `hook_start()` : This hook is called when the visitation of the graph start (an interesting way to use this hook could be to reinitialize your visitor in case of re-use).
183+
* `hook_end()` : This hook is called when the visitation of the graph end.
184+
* `hook_fork_started(n: FreExNode, fork_id: str)` : This hook is called when a fork has been entered (when visiting the first node of a fork).
185+
* `hook_start_graph_node(gn: GraphNode)` : This hook is called when a graphnode recursion start (graph node given as parameter of the hook).
186+
* `hook_end_graph_node(gn: GraphNode)` : This hook is called when a graphnode visitation end (graph node given as parameter of the hook).
187187

188188
Custom hooks can be implemented if you must trigger a specific action that depend on the business data stored in your node.
189189
To do so, in the `__init__` of your custom visitor, use the method `register_custom_hook(predicate: Callable, hook: Callable)` : This method will trigger the provided hook if the given predicate return true for a node.
@@ -243,6 +243,25 @@ v = ValidateGraphIntegrity()
243243
# visitation does assertion to verify if the graph is correct
244244
v.visit(graph_above.root())
245245
```
246+
Those visitor implement the start hook that reinitialize their state as if they were new freshly created visitor. Which is why an instance of a standard visitor can be re-used. To implement this kind of behaviour in your own visitors, check out [visitor hooks](#abstract-visitor-hooks).
247+
248+
### Reverse visit
249+
It is possible to revert the order of visitation of your visitor by setting its attribute `is_reversed` of your visitor (works with any kind of visitor), example :
250+
```python
251+
# we assume a graph called `execution_graph` that represent the following graph:
252+
# id_1 ---> id_2 ---> id_3 ---> id4 ---> id3bis ---> id5
253+
254+
v = FindFirstVisitor(lambda node: node.id.startswith("id3"))
255+
256+
v.visit(execution_graph.root)
257+
assert v.found()
258+
assert v.result.id == "id3"
259+
260+
v.is_reversed = True
261+
v.visit(execution_graph.root)
262+
assert v.found()
263+
assert v.result.id == "id3bis"
264+
```
246265

247266
## Installation
248267

freexgraph/__init__.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,9 @@
2525
Module to manipulate an execution graph
2626
"""
2727

28+
from freexgraph._version import __version__
29+
2830
from freexgraph.freexgraph import FreExGraph, FreExNode, AnyVisitor
29-
from freexgraph.visitor import AbstractVisitor
31+
from freexgraph.visitor import AbstractVisitor, VisitorComposer
3032

3133
import freexgraph.standard_visitor

freexgraph/_version.py

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
# MIT License
2+
#
3+
# Copyright (c) 2021 Quentin Balland
4+
# Project : https://github.com/FreeYourSoul/FreExGraph
5+
#
6+
# Permission is hereby granted, free of charge, to any person obtaining a copy
7+
# of this software and associated documentation files (the "Software"), to deal
8+
# in the Software without restriction, including without limitation the rights
9+
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10+
# copies of the Software, and to permit persons to whom the Software is
11+
# furnished to do so, subject to the following conditions:
12+
#
13+
# The above copyright notice and this permission notice shall be included in all
14+
# copies or substantial portions of the Software.
15+
#
16+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17+
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18+
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19+
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20+
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21+
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22+
# SOFTWARE.
23+
24+
__version__ = "0.1.0"

freexgraph/visitor.py

Lines changed: 10 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@
2828

2929
from tqdm import tqdm
3030

31-
from freexgraph.freexgraph import FreExNode, GraphNode
31+
from freexgraph.freexgraph import FreExNode, GraphNode, AnyVisitor
3232

3333

3434
@contextmanager
@@ -168,18 +168,18 @@ def register_custom_hook(self, predicate: Callable, hook: Callable):
168168
class VisitorComposer:
169169
"""Class to compose visitor together"""
170170

171-
_sequential_before: List[AbstractVisitor]
172-
_action_composed: List[AbstractVisitor]
173-
_sequential_after: List[AbstractVisitor]
171+
_sequential_before: List[AnyVisitor]
172+
_action_composed: List[AnyVisitor]
173+
_sequential_after: List[AnyVisitor]
174174
_is_reversed: bool
175175
_with_progress_bar: bool
176176

177177
def __init__(
178178
self,
179-
actions: List[AbstractVisitor],
179+
actions: List[AnyVisitor],
180180
*,
181-
before: List[AbstractVisitor],
182-
after: List[AbstractVisitor],
181+
before: List[AnyVisitor] = None,
182+
after: List[AnyVisitor] = None,
183183
progress_bar_on_actions: bool = False,
184184
):
185185
"""
@@ -189,7 +189,6 @@ def __init__(
189189
:param after:
190190
:param progress_bar_on_actions:
191191
"""
192-
assert len(actions) > 1, "Composition of not at least 2 visitors is useless"
193192
reversed_action: List[bool] = [rev.is_reversed for rev in actions]
194193
assert all(reversed_action) or not any(
195194
reversed_action
@@ -198,8 +197,8 @@ def __init__(
198197
self._is_reversed = reversed_action[0]
199198
self._with_progress_bar = progress_bar_on_actions
200199
self._action_composed = actions
201-
self._sequential_before = before
202-
self._sequential_after = after
200+
self._sequential_before = before or []
201+
self._sequential_after = after or []
203202

204203
def visit(self, root: FreExNode) -> bool:
205204
to_continue = True
@@ -233,7 +232,7 @@ def _composed_visit(self, root: FreExNode) -> bool:
233232
for node_id in sorted_node_list:
234233
# do the visitation for the node on each action visitor
235234
for action_visitor in self._action_composed:
236-
if not root.graph_ref.nodes[node_id].apply_accept_(action_visitor):
235+
if not root.graph_ref.nodes[node_id]["content"].apply_accept_(action_visitor):
237236
return False
238237

239238
pbar.set_postfix({"node": node_id})

setup.py

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,10 +22,20 @@
2222
# SOFTWARE.
2323

2424
import setuptools
25+
import re
26+
27+
VERSIONFILE="freexgraph/_version.py"
28+
verstrline = open(VERSIONFILE, "rt").read()
29+
VSRE = r"^__version__ = ['\"]([^'\"]*)['\"]"
30+
mo = re.search(VSRE, verstrline, re.M)
31+
if mo:
32+
verstr = mo.group(1)
33+
else:
34+
raise RuntimeError("Unable to find version string in %s." % (VERSIONFILE,))
2535

2636
setuptools.setup(
2737
name="freexgraph",
28-
version="0.1.0",
38+
version=verstr,
2939
author="Quentin Balland",
3040
author_email="ballandFyS@protonmail.com",
3141
description="An execution graph library",

test/basic_freexgraph_test.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@
2424
import pytest
2525
import uuid
2626

27-
from typing import List, Optional
27+
from typing import Optional
2828

2929
from freexgraph.freexgraph import GraphNode
3030
from freexgraph import FreExNode, FreExGraph

0 commit comments

Comments
 (0)