Passed
Push — master ( bd4b5e...fa3bf6 )
by P.R.
01:57
created

Node.ensure_dependencies_output_port()   A

Complexity

Conditions 2

Size

Total Lines 9

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 5
CRAP Score 2

Importance

Changes 1
Bugs 0 Features 0
Metric Value
c 1
b 0
f 0
dl 0
loc 9
ccs 5
cts 5
cp 1
rs 9.6666
cc 2
crap 2
1
"""
2
Enarksh
3
4
Copyright 2015-2016 Set Based IT Consultancy
5
6
Licence MIT
7
"""
8 1
import abc
9 1
from xml.etree.ElementTree import SubElement
10
11 1
from enarksh_lib.xml_generator.port.InputPort import InputPort
12 1
from enarksh_lib.xml_generator.port.OutputPort import OutputPort
13
14
15 1
class Node:
16
    """
17
    Class for generating XML messages for elements of type 'NodeType'.
18
    """
19
20
    # ------------------------------------------------------------------------------------------------------------------
21 1
    ALL_PORT_NAME = 'all'
22
    """
23
    Token for 'all' input or output ports on a node.
24
25
    :type: str
26
    """
27
28 1
    NODE_SELF_NAME = '.'
29
    """
30
    Token for node self.
31
32
    :type: str
33
    """
34
35 1
    NODE_ALL_NAME = '*'
36
    """
37
    Token for all child nodes.
38
39
    :type: str
40
    """
41
42
    # ------------------------------------------------------------------------------------------------------------------
43 1
    def __init__(self, name):
44
        """
45
        Object constructor.
46
47
        :param str name: The name of the node.
48
        """
49 1
        self.name = name
50
        """
51
        The name of the node.
52
53
        :type: str
54
        """
55
56 1
        self.child_nodes = []
57
        """
58
        The child nodes of this node.
59
60
        :type: list[enarksh_lib.xml_generator.node.Node.Node]
61
        """
62
63 1
        self.consumptions = []
64
        """
65
        The consumptions.
66
67
        :type: list[enarksh_lib.xml_generator.consumption.Consumption.Consumption]
68
        """
69
70 1
        self.input_ports = []
71
        """
72
        The input ports of this node.
73
74
        :type: list[enarksh_lib.xml_generator.port.InputPort.InputPort]
75
        """
76
77 1
        self.output_ports = []
78
        """
79
        The output ports of this node.
80
81
        :type: list[enarksh_lib.xml_generator.port.OutputPort.OutputPort]
82
        """
83
84 1
        self.parent = None
85
        """
86
        The parent node of this node.
87
88
        :type: enarksh_lib.xml_generator.node.Node.Node
89
        """
90
91 1
        self.resources = []
92
        """
93
        The resources of this node.
94
95
        :type: list[enarksh_lib.xml_generator.resource.Resource.Resource]
96
        """
97
98 1
        self.username = ''
99 1
        """
100
        The user under which this node or its child nodes must run.
101
102
        :type: str
103
        """
104
105
    # ------------------------------------------------------------------------------------------------------------------
106 1
    def add_child_node(self, child_node):
107
        """
108
        Adds a node as a child node of this node.
109
110
        :param enarksh_lib.xml_generator.node.Node.Node child_node: The new child node.
111
        """
112
        # -- @todo Test node exists.
113
        # -- @todo Test node is not self.
114
        # -- @todo Test parent node is not set.
115
116 1
        self.child_nodes.append(child_node)
117 1
        child_node.parent = self
118
119
    # ------------------------------------------------------------------------------------------------------------------
120 1
    def add_dependency(self, successor_node_name, successor_port_name, predecessor_node_name, predecessor_port_name):
121
        """
122
        Adds a dependency between two child nodes are this node and a child node.
123
124
        :param str successor_node_name: The successor node (use NODE_SELF_NAME for the this node).
125
        :param str successor_port_name: The successor port.
126
        :param str predecessor_node_name: The predecessor node (use NODE_SELF_NAME for the this node).
127
        :param str predecessor_port_name: The predecessor port.
128
        """
129 1
        if not predecessor_port_name:
130 1
            predecessor_port_name = self.ALL_PORT_NAME
131 1
        if not successor_port_name:
132 1
            successor_port_name = self.ALL_PORT_NAME
133
134 1
        if successor_node_name == self.NODE_SELF_NAME:
135 1
            succ_port = self.get_output_port(successor_port_name)
136
        else:
137 1
            succ_node = self.get_child_node(successor_node_name)
138 1
            succ_port = succ_node.get_input_port(successor_port_name)
139
140 1
        if predecessor_node_name == '.':
141 1
            pred_port = self.get_input_port(predecessor_port_name)
142
        else:
143 1
            pred_node = self.get_child_node(predecessor_node_name)
144 1
            pred_port = pred_node.get_output_port(predecessor_port_name)
145
146 1
        succ_port.add_dependency(pred_port)
147
148
    # ------------------------------------------------------------------------------------------------------------------
149 1
    def finalize(self):
150
        """
151
        Ensures that all required dependencies between the 'all' input and output ports are present and removes
152
        redundant dependencies between ports and nodes.
153
        """
154 1
        self.ensure_dependencies()
155 1
        self.purge()
156
157
    # ------------------------------------------------------------------------------------------------------------------
158 1
    @abc.abstractmethod
159
    def generate_xml(self, parent):
160
        """
161
        Generates the XML element for this node.
162
163
        :param xml.etree.ElementTree.Element parent: The parent XML element.
164
165
        :rtype: None
166
        """
167
        raise NotImplementedError()
168
169
    # ------------------------------------------------------------------------------------------------------------------
170 1
    def _generate_xml_common(self, parent):
171
        """
172
        Generates the common XML elements of the XML element for this  node.
173
174
        :param xml.etree.ElementTree.Element parent: The parent XML element (i.e. the node XML element).
175
        """
176
        # Generate XML for the node name.
177 1
        node_name = SubElement(parent, 'NodeName')
178 1
        node_name.text = self.name
179
180
        # Generate XML for username.
181 1
        if self.username:
182 1
            username = SubElement(parent, 'UserName')
183 1
            username.text = self.username
184
185
        # Generate XML for input ports.
186 1
        if self.input_ports:
187 1
            input_ports = SubElement(parent, 'InputPorts')
188 1
            for port in self.input_ports:
189 1
                port.generate_xml(input_ports)
190
191
        # Generate XML for resources.
192 1
        if self.resources:
193
            resources = SubElement(parent, 'Resources')
194
            for resource in self.resources:
195
                resource.generate_xml(resources)
196
197
        # Generate XML for consumptions.
198 1
        if self.consumptions:
199
            consumptions = SubElement(parent, 'Consumptions')
200
            for consumption in self.consumptions:
201
                consumption.generate_xml(consumptions)
202
203
        # Generate XML for nodes.
204 1
        if self.child_nodes:
205 1
            child_nodes = SubElement(parent, 'Nodes')
206 1
            for node in self.child_nodes:
207 1
                node.pre_generate_xml()
208 1
                node.generate_xml(child_nodes)
209
210
        # Generate XML for output ports.
211 1
        if self.output_ports:
212 1
            output_ports = SubElement(parent, 'OutputPorts')
213 1
            for port in self.output_ports:
214 1
                port.generate_xml(output_ports)
215
216
    # ------------------------------------------------------------------------------------------------------------------
217 1
    def get_child_node(self, name):
218
        """
219
        Returns a child node by name. If no child node such name exists an exception is thrown.
220
221
        :param str name: The name of the child node.
222
223
        :rtype: enarksh_lib.xml_generator.node.Node.Node
224
        """
225 1
        ret = self.search_child_node(name)
226 1
        if not ret:
227
            raise ValueError("Child node with name '{0}' doesn't exists".format(name))
228
229 1
        return ret
230
231
    # ------------------------------------------------------------------------------------------------------------------
232 1
    def get_implicit_dependencies_input_ports(self, port_name, ports, level):
233
        """
234
        :param string port_name:
235
        :param list[] ports:
236
        :param int    level:
237
        """
238 1
        port = self.get_input_port(port_name)
239
240 1
        if port not in ports:
241 1
            if level:
242 1
                ports.append(port)
243 1
            port.get_dependencies_ports(ports, level + 1)
244
245
    # ------------------------------------------------------------------------------------------------------------------
246 1
    def get_implicit_dependencies_output_ports(self, port_name, ports, level):
247
        """
248
        :param string port_name:
249
        :param list[] ports:
250
        :param int    level:
251
        """
252 1
        port = self.get_output_port(port_name)
253
254 1
        if port not in ports:
255 1
            if level:
256
                ports.append(port)
257 1
            port.get_dependencies_ports(ports, level + 1)
258
259
    # ------------------------------------------------------------------------------------------------------------------
260 1
    def get_input_port(self, name):
261
        """
262
        Returns input port with 'name'. If no input port with 'name' exists an exception is thrown.
263
264
        :param string name: The name of the port.
265
266
        :rtype: enarksh_lib.xml_generator.port.Port.Port
267
        """
268 1
        ret = self.search_input_port(name)
269
270 1
        if not ret:
271 1
            if name == self.ALL_PORT_NAME:
272 1
                ret = self.make_input_port(name)
273
            else:
274
                raise Exception("Node '{0}' doesn't have input port '{1}'".format(self.name, name))
275
276 1
        return ret
277
278
    # ------------------------------------------------------------------------------------------------------------------
279 1
    def get_output_port(self, name):
280
        """
281
        Returns output port with 'name'. If no output port with 'name' exists, an exception is thrown.
282
283
        :param str name: The name of the output port.
284
285
        :rtype: enarksh_lib.xml_generator.port.Port.Port
286
        """
287 1
        ret = self.search_output_port(name)
288
289 1
        if not ret:
290 1
            if name == self.ALL_PORT_NAME:
291 1
                ret = self.make_output_port(name)
292
            else:
293
                raise Exception("Node '{0}' doesn't have output port '{1}'".format(self.name, name))
294
295 1
        return ret
296
297
    # ------------------------------------------------------------------------------------------------------------------
298 1
    def get_path(self):
299
        """
300
        Returns the path of this node.
301
302
        :rtype: str
303
        """
304
        # -- @todo detect recursion
305
        path = self.parent.get_path() if self.parent else "/"
306
307
        return path + self.name
308
309
    # ------------------------------------------------------------------------------------------------------------------
310 1
    def make_input_port(self, name):
311
        """
312
        Creates an input port with name 'name' and returns that input port.
313
314
        :param str name: The name of port.
315
316
        :rtype: enarksh_lib.xml_generator.port.Port.Port
317
        """
318
        # -- @todo test port already exists.
319
320 1
        port = InputPort(self, name)
321 1
        self.input_ports.append(port)
322
323 1
        return port
324
325
    # ------------------------------------------------------------------------------------------------------------------
326 1
    def make_output_port(self, name):
327
        """
328
        Creates an output port with name 'name' and returns that output port.
329
330
        :param str name: The name of port.
331
332
        :rtype: enarksh_lib.xml_generator.port.Port.Port
333
        """
334
        # -- @todo test port already exists.
335
336 1
        port = OutputPort(self, name)
337 1
        self.output_ports.append(port)
338
339 1
        return port
340
341
    # ------------------------------------------------------------------------------------------------------------------
342 1
    def pre_generate_xml(self):
343
        """
344
        This function can be called before generation XML and is intended to be overloaded.
345
346
        :rtype: None
347
        """
348
        # Nothing to do.
349 1
        pass
350
351
    # ------------------------------------------------------------------------------------------------------------------
352 1
    def purge(self):
353
        """
354
        Removes duplicate dependencies and dependencies that are dependencies of predecessors.
355
        """
356 1
        for port in self.input_ports:
357 1
            port.purge()
358
359 1
        for node in self.child_nodes:
360 1
            node.purge()
361
362 1
        for port in self.output_ports:
363 1
            port.purge()
364
365
    # ------------------------------------------------------------------------------------------------------------------
366 1
    def remove_child_node(self, node_name):
367
        """
368
        Removes node 'node_name' as a child node. The dependencies of any successor of 'node' will be replaced
369
        with all dependencies of the removed node.
370
371
        :param str node_name:
372
        """
373
        node = None
374
375
        # Find and remove node 'node_name'.
376
        for tmp in self.child_nodes:
377
            if tmp.name == node_name:
378
                node = tmp
379
                self.child_nodes.remove(tmp)
380
                break
381
382
        if not node:
383
            raise Exception("Node '{0}' doesn't have child node '{1}'".format(self.get_path(), node_name))
384
385
        # Get all dependencies of the node.
386
        deps = []
387
        for port in node.input_ports:
388
            for dep in port.predecessors:
389
                deps.append(dep)
390
391
        for tmp in self.child_nodes:
392
            tmp.replace_node_dependency(node_name, deps)
393
394
        for port in self.output_ports:
395
            port.replace_node_dependency(node_name, deps)
396
397
    # ------------------------------------------------------------------------------------------------------------------
398 1
    def replace_node_dependency(self, node_name, dependencies):
399
        """
400
        Replaces any dependency of this node on node 'node_name' with dependencies 'dependencies'.
401
402
        :param str    node_name:
403
        :param list[] dependencies:
404
        """
405
        for port in self.input_ports:
406
            port.replace_node_dependency(node_name, dependencies)
407
408
    # ------------------------------------------------------------------------------------------------------------------
409 1
    def search_child_node(self, name):
410
        """
411
        If this node has a child node with name 'name' that child node will be returned.
412
        If no child node with 'name' exists, returns None.
413
414
        :param str name: The name of the child node.
415
416
        :rtype: None|enarksh_lib.xml_generator.node.Node.Node
417
        """
418 1
        ret = None
419 1
        for node in self.child_nodes:
420 1
            if node.name == name:
421 1
                ret = node
422 1
                break
423
424 1
        return ret
425
426
    # ------------------------------------------------------------------------------------------------------------------
427 1
    def search_input_port(self, name):
428
        """
429
        If this node has a input port with name 'name' that input port will be returned.
430
        If no input port with 'name' exists, returns None.
431
432
        :param str name: The name of the input port.
433
434
        :rtype: None|enarksh_lib.xml_generator.port.InputPort.InputPort
435
        """
436 1
        ret = None
437 1
        for port in self.input_ports:
438 1
            if port.port_name == name:
439 1
                ret = port
440 1
                break
441
442 1
        return ret
443
444
    # ------------------------------------------------------------------------------------------------------------------
445 1
    def search_output_port(self, name):
446
        """
447
        If this node has a output port with name 'name' that output port will be returned.
448
        If no output port with 'name' exists, returns None.
449
450
        :param str name: The name of the output port.
451
452
        :rtype: None|enarksh_lib.xml_generator.port.InputPort.InputPort
453
        """
454 1
        ret = None
455 1
        for port in self.output_ports:
456 1
            if port.port_name == name:
457 1
                ret = port
458 1
                break
459
460 1
        return ret
461
462
    # ------------------------------------------------------------------------------------------------------------------
463 1
    def ensure_dependencies_input_port(self):
464
        """
465
        Ensures that the input port 'all' of all child nodes depends on input port 'all' of this node.
466
        """
467 1
        parent_port = self.get_input_port(self.ALL_PORT_NAME)  # Ensure we have create the 'all' input port.
468
469 1
        if len(self.input_ports) > 1:
470
            raise ValueError("Compound node '{0}' has additional input ports therefore {1} must "
471
                             "override this method.".format(self.name, self.__class__))
472
473 1
        for node in self.child_nodes:
474 1
            child_port = node.get_input_port(self.ALL_PORT_NAME)
475 1
            child_port.add_dependency(parent_port)
476
477
    # ------------------------------------------------------------------------------------------------------------------
478 1
    def ensure_dependencies_output_port(self):
479
        """
480
        Ensures that output port 'all' of the node depends on all output ports 'all' of all child nodes.
481
        """
482 1
        parent_port = self.get_output_port(self.ALL_PORT_NAME)
483
484 1
        for node in self.child_nodes:
485 1
            child_port = node.get_output_port(self.ALL_PORT_NAME)
486 1
            parent_port.add_dependency(child_port)
487
488
    # ------------------------------------------------------------------------------------------------------------------
489 1
    def ensure_dependencies_self(self):
490
        """
491
        Ensures input port 'all' of this node depends on output 'all' of each predecessor of this node.
492
        """
493 1
        input_port_all = self.get_input_port(self.ALL_PORT_NAME)
494 1
        for input_port in self.input_ports:
495 1
            for port in input_port.predecessors:
496 1
                if port.node != self.parent:
497 1
                    input_port_all.add_dependency(port.node.get_output_port(self.ALL_PORT_NAME))
498
499
    # ------------------------------------------------------------------------------------------------------------------
500 1
    def ensure_dependencies(self):
501
        """
502
        Creates the following dependencies:
503
        - Dependencies between the input port 'all' and the input port 'all' of all the child nodes of this nodes.
504
        - Dependencies between all output ports 'all' of all child nodes and the output port 'all' of this node.
505
        - Dependencies between the input port 'all' of this node and the output ports 'all' of all predecessor nodes of
506
          this node.
507
        This is done recursively for all child node.
508
509
        Remember: Redundant and duplicate dependencies are removed by purge().
510
        """
511 1
        if self.child_nodes:
512
            # Apply this method recursively for all child node.
513 1
            for node in self.child_nodes:
514 1
                node.ensure_dependencies()
515
516 1
            self.ensure_dependencies_input_port()
517 1
            self.ensure_dependencies_output_port()
518 1
            self.ensure_dependencies_self()
519
520
# ----------------------------------------------------------------------------------------------------------------------
521