Skip to content
Snippets Groups Projects
Commit 4676d81f authored by Loic Huder's avatar Loic Huder
Browse files

Replace `inputs_complete` by `force_start_node` to forcefully mark nodes as start nodes

parent 375fee02
No related branches found
No related tags found
1 merge request!236Replace `inputs_complete` by `force_start_node` to forcefully mark nodes as start nodes
Pipeline #195378 passed
This commit is part of merge request !236. Comments created here will be created in the context of that merge request.
...@@ -151,8 +151,8 @@ Node attributes ...@@ -151,8 +151,8 @@ Node attributes
{ {
"default_inputs": [{"name":"a", "value":1}] "default_inputs": [{"name":"a", "value":1}]
} }
* *inputs_complete* (optional): set to `True` when the default inputs cover all required inputs * *force_start_node* (optional): when set to `True`, the node will be forcefully defined as a start node i.e. a node that should be executed before all others
(used for *method*, *script* and *notebook* as their required inputs are unknown) (to be used as an escape hatch when the graph analysis fails to correctly assert the start nodes).
* *conditions_else_value* (optional): value used in conditional links to indicate the *else* value (Default: `None`) * *conditions_else_value* (optional): value used in conditional links to indicate the *else* value (Default: `None`)
* *default_error_node* (optional): when set to `True` all nodes without error handler will be linked to this node. * *default_error_node* (optional): when set to `True` all nodes without error handler will be linked to this node.
* *default_error_attributes* (optional): when `default_error_node=True` this dictionary is used as attributes for the * *default_error_attributes* (optional): when `default_error_node=True` this dictionary is used as attributes for the
......
...@@ -257,12 +257,11 @@ def has_required_predecessors(graph: networkx.DiGraph, node_id: NodeIdType) -> b ...@@ -257,12 +257,11 @@ def has_required_predecessors(graph: networkx.DiGraph, node_id: NodeIdType) -> b
def has_required_static_inputs(graph: networkx.DiGraph, node_id: NodeIdType) -> bool: def has_required_static_inputs(graph: networkx.DiGraph, node_id: NodeIdType) -> bool:
"""Returns True when the default inputs cover all required inputs.""" """Returns True when the default inputs cover all required inputs."""
node_attrs = graph.nodes[node_id] node_attrs = graph.nodes[node_id]
inputs_complete = node_attrs.get("inputs_complete", None) if node_attrs.get("task_type", None) != "class":
if isinstance(inputs_complete, bool): # Tasks that are not `class` (e.g. `method` and `script`)
# method and script tasks always have an empty `required_input_names` # always have an empty `required_input_names`
# although they may have required input. This keyword is used the # although they may have required input.
# manually indicate that all required inputs are statically provided. return False
return inputs_complete
taskclass = get_task_class(node_id, node_attrs) taskclass = get_task_class(node_id, node_attrs)
static_inputs = {d["name"] for d in node_attrs.get("default_inputs", list())} static_inputs = {d["name"] for d in node_attrs.get("default_inputs", list())}
return not (set(taskclass.required_input_names()) - static_inputs) return not (set(taskclass.required_input_names()) - static_inputs)
...@@ -297,13 +296,23 @@ def node_has_noncovered_conditions( ...@@ -297,13 +296,23 @@ def node_has_noncovered_conditions(
return False return False
def node_is_start_node(graph: networkx.DiGraph, node_id: NodeIdType) -> bool:
node = graph.nodes[node_id]
if node.get("force_start_node", False):
return True
return not node_has_predecessors(graph, node_id)
def start_nodes(graph: networkx.DiGraph) -> Set[NodeIdType]: def start_nodes(graph: networkx.DiGraph) -> Set[NodeIdType]:
"""Nodes from which the graph execution starts""" """Nodes from which the graph execution starts"""
nodes = set(
node_id for node_id in graph.nodes if not node_has_predecessors(graph, node_id) start_nodes: Set[NodeIdType] = set(
node_id for node_id in graph.nodes if node_is_start_node(graph, node_id)
) )
if nodes: if start_nodes:
return nodes return start_nodes
return set( return set(
node_id node_id
for node_id in graph.nodes for node_id in graph.nodes
......
...@@ -112,7 +112,7 @@ def _get_subnode_attributes( ...@@ -112,7 +112,7 @@ def _get_subnode_attributes(
"""Update all input node attributes of the subgraph with the graph node attributes from the super graph""" """Update all input node attributes of the subgraph with the graph node attributes from the super graph"""
transfer_attributes = { transfer_attributes = {
"default_inputs", "default_inputs",
"inputs_complete", "start_node",
"conditions_else_value", "conditions_else_value",
"default_error_node", "default_error_node",
} }
......
from . import graph
@graph
def self_trigger():
task = "ewokscore.tests.examples.tasks.condsumtask.CondSumTask"
nodes = [
{
"id": "task1",
"default_inputs": [{"name": "a", "value": 1}],
"task_type": "class",
"task_identifier": task,
"force_start_node": True,
},
{
"id": "task2",
"default_inputs": [{"name": "b", "value": 1}],
"task_type": "class",
"task_identifier": task,
},
{
"id": "task3",
"task_type": "class",
"task_identifier": task,
},
]
links = [
{
"source": "task1",
"target": "task1",
"data_mapping": [{"source_output": "result", "target_input": "b"}],
"conditions": [{"source_output": "too_small", "value": True}],
},
{
"source": "task1",
"target": "task3",
"data_mapping": [{"source_output": "result", "target_input": "a"}],
},
{
"source": "task2",
"target": "task3",
"data_mapping": [{"source_output": "result", "target_input": "b"}],
},
]
# Cannot be executed with ewokscore execute_graph
expected = None
graph = {
"links": links,
"nodes": nodes,
}
return graph, expected
...@@ -63,6 +63,12 @@ def test_start_nodes(): ...@@ -63,6 +63,12 @@ def test_start_nodes():
ewoksgraph = load_graph(graph) ewoksgraph = load_graph(graph)
assert start_nodes(ewoksgraph.graph) == {"task1"} assert start_nodes(ewoksgraph.graph) == {"task1"}
graph, _ = get_graph("self_trigger")
ewoksgraph = load_graph(graph)
assert start_nodes(ewoksgraph.graph) == {"task1", "task2"}
ewoksgraph.graph.nodes["task1"].pop("force_start_node")
assert start_nodes(ewoksgraph.graph) == {"task2"}
@pytest.mark.parametrize("graph_name", graph_names()) @pytest.mark.parametrize("graph_name", graph_names())
@pytest.mark.parametrize( @pytest.mark.parametrize(
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment