Node._generate_xml_common()   F
last analyzed

Complexity

Conditions 12

Size

Total Lines 45

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 17
CRAP Score 14.5573

Importance

Changes 2
Bugs 0 Features 0
Metric Value
c 2
b 0
f 0
dl 0
loc 45
ccs 17
cts 23
cp 0.7391
rs 2.7855
cc 12
crap 14.5573

How to fix   Complexity   

Complexity

Complex classes like Node._generate_xml_common() often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

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(metaclass=abc.ABCMeta):
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
        self.child_nodes.append(child_node)
113
        child_node.parent = self
114
115
    # ------------------------------------------------------------------------------------------------------------------
116 1
    def add_dependency(self, successor_node_name, successor_port_name, predecessor_node_name, predecessor_port_name):
117 1
        """
118
        Adds a dependency between two child nodes are this node and a child node.
119
120 1
        :param str successor_node_name: The successor node (use NODE_SELF_NAME for the this node).
121
        :param str successor_port_name: The successor port.
122
        :param str predecessor_node_name: The predecessor node (use NODE_SELF_NAME for the this node).
123
        :param str predecessor_port_name: The predecessor port.
124
        """
125
        if not predecessor_port_name:
126
            predecessor_port_name = self.ALL_PORT_NAME
127
        if not successor_port_name:
128
            successor_port_name = self.ALL_PORT_NAME
129 1
130 1
        if successor_node_name == self.NODE_SELF_NAME:
131 1
            succ_port = self.get_output_port(successor_port_name)
132 1
        else:
133
            succ_node = self.get_child_node(successor_node_name)
134 1
            succ_port = succ_node.get_input_port(successor_port_name)
135 1
136
        if predecessor_node_name == self.NODE_SELF_NAME:
137 1
            pred_port = self.get_input_port(predecessor_port_name)
138 1
        else:
139
            pred_node = self.get_child_node(predecessor_node_name)
140 1
            pred_port = pred_node.get_output_port(predecessor_port_name)
141 1
142
        succ_port.add_dependency(pred_port)
143 1
144 1
    # ------------------------------------------------------------------------------------------------------------------
145
    def finalize(self):
146 1
        """
147
        Ensures that all required dependencies between the 'all' input and output ports are present and removes
148
        redundant dependencies between ports and nodes.
149 1
        """
150
        self.ensure_dependencies()
151
        self.purge()
152
153
    # ------------------------------------------------------------------------------------------------------------------
154 1
    @abc.abstractmethod
155 1
    def generate_xml(self, parent):
156
        """
157
        Generates the XML element for this node.
158 1
159
        :param xml.etree.ElementTree.Element parent: The parent XML element.
160
161
        :rtype: None
162
        """
163
        raise NotImplementedError()
164
165
    # ------------------------------------------------------------------------------------------------------------------
166
    def _generate_xml_common(self, parent):
167
        """
168
        Generates the common XML elements of the XML element for this  node.
169
170 1
        :param xml.etree.ElementTree.Element parent: The parent XML element (i.e. the node XML element).
171
        """
172
        # Generate XML for the node name.
173
        node_name = SubElement(parent, 'NodeName')
174
        node_name.text = self.name
175
176
        # Generate XML for username.
177 1
        if self.username:
178 1
            username = SubElement(parent, 'UserName')
179
            username.text = self.username
180
181 1
        # Generate XML for input ports.
182 1
        if self.input_ports:
183 1
            input_ports = SubElement(parent, 'InputPorts')
184
            for port in self.input_ports:
185
                port.generate_xml(input_ports)
186 1
187 1
        # Generate XML for consumptions.
188 1
        if self.consumptions:
189 1
            consumptions = SubElement(parent, 'Consumptions')
190
            for consumption in self.consumptions:
191
                consumption.generate_xml(consumptions)
192 1
193
        # Generate XML for output ports.
194
        if self.output_ports:
195
            output_ports = SubElement(parent, 'OutputPorts')
196
            for port in self.output_ports:
197
                port.generate_xml(output_ports)
198 1
199
        # Generate XML for resources.
200
        if self.resources:
201
            resources = SubElement(parent, 'Resources')
202
            for resource in self.resources:
203
                resource.generate_xml(resources)
204 1
205 1
        # Generate XML for nodes.
206 1
        if self.child_nodes:
207 1
            child_nodes = SubElement(parent, 'Nodes')
208 1
            for node in self.child_nodes:
209
                node.pre_generate_xml()
210
                node.generate_xml(child_nodes)
211 1
212 1
    # ------------------------------------------------------------------------------------------------------------------
213 1
    def get_child_node(self, name):
214 1
        """
215
        Returns a child node by name. If no child node such name exists an exception is thrown.
216
217 1
        :param str name: The name of the child node.
218
219
        :rtype: enarksh_lib.xml_generator.node.Node.Node
220
        """
221
        ret = self.search_child_node(name)
222
        if not ret:
223
            raise ValueError("Child node with name '{0}' doesn't exists".format(name))
224
225 1
        return ret
226 1
227
    # ------------------------------------------------------------------------------------------------------------------
228
    def get_implicit_dependencies_input_ports(self, port_name, ports, level):
229 1
        """
230
        :param string port_name:
231
        :param list[] ports:
232 1
        :param int    level:
233
        """
234
        port = self.get_input_port(port_name)
235
236
        if port not in ports:
237
            if level:
238 1
                ports.append(port)
239
            port.get_dependencies_ports(ports, level + 1)
240 1
241 1
    # ------------------------------------------------------------------------------------------------------------------
242 1
    def get_implicit_dependencies_output_ports(self, port_name, ports, level):
243 1
        """
244
        :param string port_name:
245
        :param list[] ports:
246 1
        :param int    level:
247
        """
248
        port = self.get_output_port(port_name)
249
250
        if port not in ports:
251
            if level:
252 1
                ports.append(port)
253
            port.get_dependencies_ports(ports, level + 1)
254 1
255 1
    # ------------------------------------------------------------------------------------------------------------------
256
    def get_input_port(self, name):
257 1
        """
258
        Returns input port with 'name'. If no input port with 'name' exists an exception is thrown.
259
260 1
        :param string name: The name of the port.
261
262
        :rtype: enarksh_lib.xml_generator.port.Port.Port
263
        """
264
        ret = self.search_input_port(name)
265
266
        if not ret:
267
            if name == self.ALL_PORT_NAME:
268 1
                ret = self.make_input_port(name)
269
            else:
270 1
                raise Exception("Node '{0}' doesn't have input port '{1}'".format(self.name, name))
271 1
272 1
        return ret
273
274
    # ------------------------------------------------------------------------------------------------------------------
275
    def get_output_port(self, name):
276 1
        """
277
        Returns output port with 'name'. If no output port with 'name' exists, an exception is thrown.
278
279 1
        :param str name: The name of the output port.
280
281
        :rtype: enarksh_lib.xml_generator.port.Port.Port
282
        """
283
        ret = self.search_output_port(name)
284
285
        if not ret:
286
            if name == self.ALL_PORT_NAME:
287 1
                ret = self.make_output_port(name)
288
            else:
289 1
                raise Exception("Node '{0}' doesn't have output port '{1}'".format(self.name, name))
290 1
291 1
        return ret
292
293
    # ------------------------------------------------------------------------------------------------------------------
294
    def get_path(self):
295 1
        """
296
        Returns the path of this node.
297
298 1
        :rtype: str
299
        """
300
        path = self.parent.get_path() if self.parent else "/"
301
302
        return path + self.name
303
304
    # ------------------------------------------------------------------------------------------------------------------
305
    def make_input_port(self, name):
306
        """
307
        Creates an input port with name 'name' and returns that input port.
308
309
        :param str name: The name of port.
310 1
311
        :rtype: enarksh_lib.xml_generator.port.Port.Port
312
        """
313
        port = InputPort(self, name)
314
        self.input_ports.append(port)
315
316
        return port
317
318
    # ------------------------------------------------------------------------------------------------------------------
319
    def make_output_port(self, name):
320 1
        """
321 1
        Creates an output port with name 'name' and returns that output port.
322
323 1
        :param str name: The name of port.
324
325
        :rtype: enarksh_lib.xml_generator.port.Port.Port
326 1
        """
327
        port = OutputPort(self, name)
328
        self.output_ports.append(port)
329
330
        return port
331
332
    # ------------------------------------------------------------------------------------------------------------------
333
    def pre_generate_xml(self):
334
        """
335
        This function can be called before generation XML and is intended to be overloaded.
336 1
337 1
        :rtype: None
338
        """
339 1
        # Nothing to do.
340
        pass
341
342 1
    # ------------------------------------------------------------------------------------------------------------------
343
    def purge(self):
344
        """
345
        Removes duplicate dependencies and dependencies that are dependencies of predecessors.
346
        """
347
        for port in self.input_ports:
348
            port.purge()
349 1
350
        for node in self.child_nodes:
351
            node.purge()
352 1
353
        for port in self.output_ports:
354
            port.purge()
355
356 1
    # ------------------------------------------------------------------------------------------------------------------
357 1
    def remove_child_node(self, node_name):
358
        """
359 1
        Removes node 'node_name' as a child node. The dependencies of any successor of 'node' will be replaced
360 1
        with all dependencies of the removed node.
361
362 1
        :param str node_name:
363 1
        """
364
        node = None
365
366 1
        # Find and remove node 'node_name'.
367
        for tmp in self.child_nodes:
368
            if tmp.name == node_name:
369
                node = tmp
370
                self.child_nodes.remove(tmp)
371
                break
372
373
        if not node:
374
            raise Exception("Node '{0}' doesn't have child node '{1}'".format(self.get_path(), node_name))
375
376
        # Get all dependencies of the node.
377
        deps = []
378
        for port in node.input_ports:
379
            for dep in port.predecessors:
380
                deps.append(dep)
381
382
        for tmp in self.child_nodes:
383
            tmp.replace_node_dependency(node_name, deps)
384
385
        for port in self.output_ports:
386
            port.replace_node_dependency(node_name, deps)
387
388
    # ------------------------------------------------------------------------------------------------------------------
389
    def replace_node_dependency(self, node_name, dependencies):
390
        """
391
        Replaces any dependency of this node on node 'node_name' with dependencies 'dependencies'.
392
393
        :param str    node_name:
394
        :param list[] dependencies:
395
        """
396
        for port in self.input_ports:
397
            port.replace_node_dependency(node_name, dependencies)
398 1
399
    # ------------------------------------------------------------------------------------------------------------------
400
    def search_child_node(self, name):
401
        """
402
        If this node has a child node with name 'name' that child node will be returned.
403
        If no child node with 'name' exists, returns None.
404
405
        :param str name: The name of the child node.
406
407
        :rtype: None|enarksh_lib.xml_generator.node.Node.Node
408
        """
409 1
        ret = None
410
        for node in self.child_nodes:
411
            if node.name == name:
412
                ret = node
413
                break
414
415
        return ret
416
417
    # ------------------------------------------------------------------------------------------------------------------
418 1
    def search_input_port(self, name):
419 1
        """
420 1
        If this node has a input port with name 'name' that input port will be returned.
421 1
        If no input port with 'name' exists, returns None.
422 1
423
        :param str name: The name of the input port.
424 1
425
        :rtype: None|enarksh_lib.xml_generator.port.InputPort.InputPort
426
        """
427 1
        ret = None
428
        for port in self.input_ports:
429
            if port.port_name == name:
430
                ret = port
431
                break
432
433
        return ret
434
435
    # ------------------------------------------------------------------------------------------------------------------
436 1
    def search_output_port(self, name):
437 1
        """
438 1
        If this node has a output port with name 'name' that output port will be returned.
439 1
        If no output port with 'name' exists, returns None.
440 1
441
        :param str name: The name of the output port.
442 1
443
        :rtype: None|enarksh_lib.xml_generator.port.InputPort.InputPort
444
        """
445 1
        ret = None
446
        for port in self.output_ports:
447
            if port.port_name == name:
448
                ret = port
449
                break
450
451
        return ret
452
453
    # ------------------------------------------------------------------------------------------------------------------
454 1
    def ensure_dependencies_input_port(self):
455 1
        """
456 1
        Ensures that the input port 'all' of all child nodes depends on input port 'all' of this node.
457 1
        """
458 1
        parent_port = self.get_input_port(self.ALL_PORT_NAME)  # Ensure we have create the 'all' input port.
459
460 1
        if len(self.input_ports) > 1:
461
            raise ValueError("Compound node '{0}' has additional input ports therefore {1} must "
462
                             "override this method.".format(self.name, self.__class__))
463 1
464
        for node in self.child_nodes:
465
            child_port = node.get_input_port(self.ALL_PORT_NAME)
466
            child_port.add_dependency(parent_port)
467 1
468
    # ------------------------------------------------------------------------------------------------------------------
469 1
    def ensure_dependencies_output_port(self):
470
        """
471
        Ensures that output port 'all' of the node depends on all output ports 'all' of all child nodes.
472
        """
473 1
        parent_port = self.get_output_port(self.ALL_PORT_NAME)
474 1
475 1
        for node in self.child_nodes:
476
            child_port = node.get_output_port(self.ALL_PORT_NAME)
477
            parent_port.add_dependency(child_port)
478 1
479
    # ------------------------------------------------------------------------------------------------------------------
480
    def ensure_dependencies_self(self):
481
        """
482 1
        Ensures input port 'all' of this node depends on output 'all' of each predecessor of this node.
483
        """
484 1
        input_port_all = self.get_input_port(self.ALL_PORT_NAME)
485 1
        for input_port in self.input_ports:
486 1
            for port in input_port.predecessors:
487
                if port.node != self.parent:
488
                    input_port_all.add_dependency(port.node.get_output_port(self.ALL_PORT_NAME))
489 1
490
    # ------------------------------------------------------------------------------------------------------------------
491
    def ensure_dependencies(self):
492
        """
493 1
        Creates the following dependencies:
494 1
        - Dependencies between the input port 'all' and the input port 'all' of all the child nodes of this nodes.
495 1
        - Dependencies between all output ports 'all' of all child nodes and the output port 'all' of this node.
496 1
        - Dependencies between the input port 'all' of this node and the output ports 'all' of all predecessor nodes of
497 1
          this node.
498
        This is done recursively for all child node.
499
500 1
        Remember: Redundant and duplicate dependencies are removed by purge().
501
        """
502
        if self.child_nodes:
503
            # Apply this method recursively for all child node.
504
            for node in self.child_nodes:
505
                node.ensure_dependencies()
506
507
            self.ensure_dependencies_input_port()
508
            self.ensure_dependencies_output_port()
509
            self.ensure_dependencies_self()
510
511
# ----------------------------------------------------------------------------------------------------------------------
512