Skip to content

Commit 23fc0fb

Browse files
authored
Merge pull request #2423 from Avaiga/feature/#2322-log-error-when-sequence-scenario-inconsistent
Feature/#2322 - Log error when sequence or scenario is not consistent
2 parents ebefe2e + 82aab35 commit 23fc0fb

File tree

4 files changed

+82
-4
lines changed

4 files changed

+82
-4
lines changed

taipy/core/scenario/scenario.py

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
import networkx as nx
1818

1919
from taipy.common.config.common._validate_id import _validate_id
20+
from taipy.common.logger._taipy_logger import _TaipyLogger
2021

2122
from .._entity._entity import _Entity
2223
from .._entity._labeled import _Labeled
@@ -106,6 +107,8 @@ def by_two(x: int):
106107
id: ScenarioId
107108
"""The unique identifier of this scenario."""
108109

110+
_logger = _TaipyLogger._get_logger()
111+
109112
def __init__(
110113
self,
111114
config_id: str,
@@ -611,12 +614,32 @@ def _is_consistent(self) -> bool:
611614
if dag.number_of_nodes() == 0:
612615
return True
613616
if not nx.is_directed_acyclic_graph(dag):
617+
self._logger.error(f'The DAG of scenario "{self.id}" is not a directed acyclic graph')
614618
return False
615619
for left_node, right_node in dag.edges:
616620
if (isinstance(left_node, DataNode) and isinstance(right_node, Task)) or (
617621
isinstance(left_node, Task) and isinstance(right_node, DataNode)
618622
):
619623
continue
624+
625+
left_node_desc = (
626+
f'{left_node.__class__.__name__} "{left_node.get_label()}"'
627+
if isinstance(left_node, _Labeled)
628+
else left_node.__class__.__name__
629+
if left_node
630+
else "None"
631+
)
632+
right_node_desc = (
633+
f'{right_node.__class__.__name__} "{right_node.get_label()}"'
634+
if isinstance(right_node, _Labeled)
635+
else right_node.__class__.__name__
636+
if right_node
637+
else "None"
638+
)
639+
self._logger.error(
640+
f'Invalid edge detected in scenario "{self.id}": left node {left_node_desc} and right node '
641+
f"{right_node_desc} must connect a Task and a DataNode"
642+
)
620643
return False
621644
return True
622645

taipy/core/sequence/sequence.py

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
import networkx as nx
1717

1818
from taipy.common.config.common._validate_id import _validate_id
19+
from taipy.common.logger._taipy_logger import _TaipyLogger
1920

2021
from .._entity._entity import _Entity
2122
from .._entity._labeled import _Labeled
@@ -118,6 +119,8 @@ def planning(forecast, capacity):
118119
_MANAGER_NAME = "sequence"
119120
__CHECK_INIT_DONE_ATTR_NAME = "_init_done"
120121

122+
_logger = _TaipyLogger._get_logger()
123+
121124
id: SequenceId
122125
"""The unique identifier of the sequence."""
123126

@@ -343,15 +346,37 @@ def _is_consistent(self) -> bool:
343346
if dag.number_of_nodes() == 0:
344347
return True
345348
if not nx.is_directed_acyclic_graph(dag):
349+
self._logger.error(f'The DAG of sequence "{self.id}" is not a directed acyclic graph')
346350
return False
347351
if not nx.is_weakly_connected(dag):
352+
self._logger.error(f'The DAG of sequence "{self.id}" is not weakly connected')
348353
return False
349354
for left_node, right_node in dag.edges:
350355
if (isinstance(left_node, DataNode) and isinstance(right_node, Task)) or (
351356
isinstance(left_node, Task) and isinstance(right_node, DataNode)
352357
):
353358
continue
359+
360+
left_node_desc = (
361+
f'{left_node.__class__.__name__} "{left_node.get_label()}"'
362+
if isinstance(left_node, _Labeled)
363+
else left_node.__class__.__name__
364+
if left_node
365+
else "None"
366+
)
367+
right_node_desc = (
368+
f'{right_node.__class__.__name__} "{right_node.get_label()}"'
369+
if isinstance(right_node, _Labeled)
370+
else right_node.__class__.__name__
371+
if right_node
372+
else "None"
373+
)
374+
self._logger.error(
375+
f'Invalid edge detected in sequence "{self.id}": left node {left_node_desc} and right node '
376+
f"{right_node_desc} must connect a Task and a DataNode"
377+
)
354378
return False
379+
355380
return True
356381

357382
def _get_tasks(self) -> Dict[str, Task]:

tests/core/scenario/test_scenario.py

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1538,3 +1538,25 @@ def test_check_consistency():
15381538
task_5 = Task("bob", {}, print, [data_node_5], [data_node_3], TaskId("t5"))
15391539
scenario_9 = Scenario("scenario_9", [task_1, task_2, task_3, task_4, task_5], {}, [], scenario_id=ScenarioId("s8"))
15401540
assert scenario_9._is_consistent()
1541+
1542+
1543+
def test_check_inconsistency(caplog):
1544+
class FakeDataNode:
1545+
config_id = "config_id_of_a_fake_dn"
1546+
1547+
data_node_1 = InMemoryDataNode("foo", Scope.SCENARIO, "s1")
1548+
data_node_2 = InMemoryDataNode("bar", Scope.SCENARIO, "s2")
1549+
1550+
task_1 = Task("grault", {}, print, [data_node_1, data_node_2], [FakeDataNode()], TaskId("t1"))
1551+
task_2 = Task("garply", {}, print, [data_node_1], [data_node_2], id=TaskId("t2"))
1552+
scenario_1 = Scenario("scenario_1", [task_1, task_2], {}, [], scenario_id=ScenarioId("s1"))
1553+
assert not scenario_1._is_consistent()
1554+
assert (
1555+
'Invalid edge detected in scenario "s1": left node Task "grault" and right node FakeDataNode'
1556+
" must connect a Task and a DataNode" in caplog.text
1557+
)
1558+
1559+
task_3 = Task("waldo", {}, print, [data_node_2], [data_node_1], id=TaskId("t3"))
1560+
scenario_2 = Scenario("scenario_2", [task_2, task_3], {}, [], scenario_id=ScenarioId("s2"))
1561+
assert not scenario_2._is_consistent()
1562+
assert 'The DAG of scenario "s2" is not a directed acyclic graph' in caplog.text

tests/core/sequence/test_sequence.py

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,10 @@
3030
from taipy.core.task.task import Task, TaskId
3131

3232

33+
class FakeDataNode:
34+
config_id = "config_id_of_a_fake_dn"
35+
36+
3337
def test_sequence_equals():
3438
task_config = Config.configure_task("mult_by_3", print, [], None)
3539
scenario_config = Config.configure_scenario("scenario", [task_config])
@@ -143,7 +147,7 @@ def test_get_set_attribute():
143147
sequence.bar = "KeyAlreadyUsed"
144148

145149

146-
def test_check_consistency():
150+
def test_check_consistency(caplog):
147151
sequence_1 = Sequence({}, [], "name_1")
148152
assert sequence_1._is_consistent()
149153

@@ -157,23 +161,26 @@ def test_check_consistency():
157161
task_3 = Task("tfoo", {}, print, [data_node_3], [data_node_3], TaskId("task_id_3"))
158162
sequence_3 = Sequence({}, [task_3], "name_3")
159163
assert not sequence_3._is_consistent() # Not a dag
164+
assert 'The DAG of sequence "name_3" is not a directed acyclic graph' in caplog.text
160165

161166
input_4 = InMemoryDataNode("foo", Scope.SCENARIO)
162167
output_4 = InMemoryDataNode("bar", Scope.SCENARIO)
163168
task_4_1 = Task("tfoo", {}, print, [input_4], [output_4], TaskId("task_id_4_1"))
164169
task_4_2 = Task("tbar", {}, print, [output_4], [input_4], TaskId("task_id_4_2"))
165170
sequence_4 = Sequence({}, [task_4_1, task_4_2], "name_4")
166171
assert not sequence_4._is_consistent() # Not a Dag
167-
168-
class FakeDataNode:
169-
config_id = "config_id_of_a_fake_dn"
172+
assert 'The DAG of sequence "name_4" is not a directed acyclic graph' in caplog.text
170173

171174
input_6 = DataNode("foo", Scope.SCENARIO, "input_id_5")
172175
output_6 = DataNode("bar", Scope.SCENARIO, "output_id_5")
173176
task_6_1 = Task("tfoo", {}, print, [input_6], [output_6], TaskId("task_id_5_1"))
174177
task_6_2 = Task("tbar", {}, print, [output_6], [FakeDataNode()], TaskId("task_id_5_2"))
175178
sequence_6 = Sequence({}, [task_6_1, task_6_2], "name_5")
176179
assert not sequence_6._is_consistent() # Not a DataNode
180+
assert (
181+
'Invalid edge detected in sequence "name_5": left node Task "tbar" and right node FakeDataNode '
182+
"must connect a Task and a DataNode" in caplog.text
183+
)
177184

178185
intermediate_7 = DataNode("foo", Scope.SCENARIO, "intermediate_id_7")
179186
output_7 = DataNode("bar", Scope.SCENARIO, "output_id_7")
@@ -197,6 +204,7 @@ class FakeDataNode:
197204
task_9_2 = Task("tbar", {}, print, [input_9_2], [output_9_2], TaskId("task_id_9_2"))
198205
sequence_9 = Sequence({}, [task_9_1, task_9_2], "name_9")
199206
assert not sequence_9._is_consistent() # Not connected
207+
assert 'The DAG of sequence "name_9" is not weakly connected' in caplog.text
200208

201209
input_10_1 = DataNode("foo", Scope.SCENARIO, "output_id_10_1")
202210
intermediate_10_1 = DataNode("bar", Scope.SCENARIO, "intermediate_id_10_1")

0 commit comments

Comments
 (0)