def _wrap_and_add(self, node: Union[Node, NodeContainable], id: str = None): """ Wraps NodeContainable to a Node if needed. Override node's id if an id provided. Then adds the node to the graph. :param node: a Node or ContainableNode :param id: id for the node :return: the newly added node """ if not isinstance(node, (Node, NodeContainable)): raise GraphException( 'object to add to a graph must be an instance of Node or NodeContainable' ) if isinstance(node, NodeContainable): containable = node node = Action(containable) id = id or node.id if id: # manual provided id self._used_node_ids[id] = 0 else: # automatic id id = self._generate_id(node) self._used_node_ids[id] = 1 node._id = id node.graph = self setattr(self.nodes, id, node) return node
def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) # create Workflow structure if all attributes have been initialized if hasattr(self, 'anomaly_detector') and \ hasattr(self, 'fault_classifier'): self.start() \ .add(Decision(self.anomaly_detector)) \ .add( yes=Action(self.fault_classifier) ) self.end()
def end(self) -> 'Graph': """ This method is required after adding all nodes to the graph. The end node with id='end' will be automatically added to the graph. All leaf nodes (without outgoing edges) will be automatically connected to the end node. Consolidate ids for nodes using the same NodeContainable type without provided id. Ids will be Xyz1, Xyz2, ... with class Xyz inherits from NodeContainable """ end_node = self.add(Action(id='end')) # connect all nodes without out-going edges to end node for id, node in self.nodes.__dict__.items(): if not node.edges and id != end_node.id: self._connect_nodes(node, end_node) return end_node
def start(self) -> 'Graph': """ Initial action to begin adding nodes to a (fresh) Graph. A node with the id='start' will be automatically added to the Graph. """ return self.add(Action(id='start'))