Passed
Push — master ( 4b3140...3dcfbb )
by P.R.
01:38
created

Node._generate_xml_common()   F

Complexity

Conditions 12

Size

Total Lines 45

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
c 0
b 0
f 0
dl 0
loc 45
rs 2.7855
cc 12

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
import abc
9
from xml.etree.ElementTree import SubElement
10
11
from enarksh_lib.xml_generator.port.InputPort import InputPort
12
from enarksh_lib.xml_generator.port.OutputPort import OutputPort
13
14
15
class Node:
16
    """
17
    Class for generating XML messages for elements of type 'NodeType'.
18
    """
19
20
    # ------------------------------------------------------------------------------------------------------------------
21
    ALL_PORT_NAME = 'all'
22
    """
23
    Token for 'all' input or output ports on a node.
24
25
    :type: str
26
    """
27
28
    NODE_SELF_NAME = '.'
29
    """
30
    Token for node self.
31
32
    :type: str
33
    """
34
35
    NODE_ALL_NAME = '*'
36
    """
37
    Token for all child nodes.
38
39
    :type: str
40
    """
41
42
    # ------------------------------------------------------------------------------------------------------------------
43
    def __init__(self, name):
44
        """
45
        Object constructor.
46
47
        :param str name: The name of the node.
48
        """
49
        self._name = name
50
        """
51
        The name of the node.
52
53
        :type: str
54
        """
55
56
        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
        self._consumptions = []
64
        """
65
        The consumptions.
66
67
        :type: list[enarksh_lib.xml_generator.consumption.Consumption.Consumption]
68
        """
69
70
        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
        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
        self._parent = None
85
        """
86
        The parent node of this node.
87
88
        :type: enarksh_lib.xml_generator.node.Node.Node
89
        """
90
91
        self._resources = []
92
        """
93
        The resources of this node.
94
95
        :type: list[enarksh_lib.xml_generator.resource.Resource.Resource]
96
        """
97
98
        self._username = None
99
        """
100
        The user under which this node or its child nodes must run.
101
102
        :type: str
103
        """
104
105
    # ------------------------------------------------------------------------------------------------------------------
106
    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
        self._child_nodes.append(child_node)
117
        child_node._parent = self
0 ignored issues
show
Coding Style Best Practice introduced by
It seems like _parent was declared protected and should not be accessed from this context.

Prefixing a member variable _ is usually regarded as the equivalent of declaring it with protected visibility that exists in other languages. Consequentially, such a member should only be accessed from the same class or a child class:

class MyParent:
    def __init__(self):
        self._x = 1;
        self.y = 2;

class MyChild(MyParent):
    def some_method(self):
        return self._x    # Ok, since accessed from a child class

class AnotherClass:
    def some_method(self, instance_of_my_child):
        return instance_of_my_child._x   # Would be flagged as AnotherClass is not
                                         # a child class of MyParent
Loading history...
118
119
    # ------------------------------------------------------------------------------------------------------------------
120
    def add_consumption(self, consumption):
121
        """
122
        Adds a consumption as a consumption of this node.
123
124
        :param enarksh_lib.xml_generator.node.consumption.Consumption.Consumption consumption: The consumption.
125
        """
126
        # -- @todo test consumption exists.
127
128
        self._consumptions.append(consumption)
129
130
    # ------------------------------------------------------------------------------------------------------------------
131
    def add_dependency(self, successor_node_name, successor_port_name, predecessor_node_name, predecessor_port_name):
132
        """
133
        Adds a dependency between two child nodes are this node and a child node.
134
135
        :param str successor_node_name: The successor node (use NODE_SELF_NAME for the this node).
136
        :param str successor_port_name: The successor port.
137
        :param str predecessor_node_name: The predecessor node (use NODE_SELF_NAME for the this node).
138
        :param str predecessor_port_name: The predecessor port.
139
        """
140
        if not predecessor_port_name:
141
            predecessor_port_name = self.ALL_PORT_NAME
142
        if not successor_port_name:
143
            successor_port_name = self.ALL_PORT_NAME
144
145
        if successor_node_name == self.NODE_SELF_NAME:
146
            succ_port = self.get_output_port(successor_port_name)
147
        else:
148
            succ_node = self.get_child_node(successor_node_name)
149
            succ_port = succ_node.get_input_port(successor_port_name)
150
151
        if predecessor_node_name == '.':
152
            pred_port = self.get_input_port(predecessor_port_name)
153
        else:
154
            pred_node = self.get_child_node(predecessor_node_name)
155
            pred_port = pred_node.get_output_port(predecessor_port_name)
156
157
        succ_port.add_dependency(pred_port)
158
159
    # ------------------------------------------------------------------------------------------------------------------
160
    def add_dependency_all_input_ports(self):
161
        """
162
        Add dependencies between the 'all' input port of this node and the 'all' input port of all this child nodes.
163
        """
164
        parent_port = self.get_input_port(self.ALL_PORT_NAME)
165
166
        for node in self._child_nodes:
167
            child_port = node.get_input_port(self.ALL_PORT_NAME)
168
            child_port.add_dependency(parent_port)
169
170
    # ------------------------------------------------------------------------------------------------------------------
171
    def add_dependency_all_output_ports(self):
172
        """
173
        Add dependencies between the 'all' output port of this node and the 'all' output of all this child nodes.
174
        """
175
        parent_port = self.get_output_port(self.ALL_PORT_NAME)
176
177
        for node in self._child_nodes:
178
            child_port = node.get_output_port(self.ALL_PORT_NAME)
179
            parent_port.add_dependency(child_port)
180
181
    # ------------------------------------------------------------------------------------------------------------------
182
    def add_resource(self, resource):
183
        """
184
        Adds resource 'resource' as a resource of this node.
185
186
        :param enarksh_lib.xml_generator.resource.Resource.Resource resource: The resource.
187
        """
188
        # -- @todo Test resource exists.
189
190
        self._resources.append(resource)
191
192
    # ------------------------------------------------------------------------------------------------------------------
193
    def finalize(self):
194
        """
195
        Ensures that all required dependencies between the 'all' input and output ports are present and removes
196
        redundant dependencies between ports and nodes.
197
        """
198
        self.ensure_dependencies()
199
        self.purge()
200
201
    # ------------------------------------------------------------------------------------------------------------------
202
    @abc.abstractmethod
203
    def generate_xml(self, parent):
204
        """
205
        Generates the XML element for this node.
206
207
        :param xml.etree.ElementTree.Element parent: The parent XML element.
208
209
        :rtype: None
210
        """
211
        raise NotImplementedError()
212
213
    # ------------------------------------------------------------------------------------------------------------------
214
    def _generate_xml_common(self, parent):
215
        """
216
        Generates the common XML elements of the XML element for this  node.
217
218
        :param xml.etree.ElementTree.Element parent: The parent XML element (i.e. the node XML element).
219
        """
220
        # Generate XML for the node name.
221
        node_name = SubElement(parent, 'NodeName')
222
        node_name.text = self._name
223
224
        # Generate XML for username.
225
        if self._username:
226
            username = SubElement(parent, 'UserName')
227
            username.text = self._username
228
229
        # Generate XML for input ports.
230
        if self._input_ports:
231
            input_ports = SubElement(parent, 'InputPorts')
232
            for port in self._input_ports:
233
                port.generate_xml(input_ports)
234
235
        # Generate XML for resources.
236
        if self._resources:
237
            resources = SubElement(parent, 'Resources')
238
            for resource in self._resources:
239
                resource.generate_xml(resources)
240
241
        # Generate XML for consumptions.
242
        if self._consumptions:
243
            consumptions = SubElement(parent, 'Consumptions')
244
            for consumption in self._consumptions:
245
                consumption.generate_xml(consumptions)
246
247
        # Generate XML for nodes.
248
        if self._child_nodes:
249
            child_nodes = SubElement(parent, 'Nodes')
250
            for node in self._child_nodes:
251
                node.pre_generate_xml()
252
                node.generate_xml(child_nodes)
253
254
        # Generate XML for output ports.
255
        if self._output_ports:
256
            output_ports = SubElement(parent, 'OutputPorts')
257
            for port in self._output_ports:
258
                port.generate_xml(output_ports)
259
260
    # ------------------------------------------------------------------------------------------------------------------
261
    def get_child_node(self, name):
262
        """
263
        Returns a child node by name. If no child node such name exists an exception is thrown.
264
265
        :param str name: The name of the child node.
266
267
        :rtype: enarksh_lib.xml_generator.node.Node.Node
268
        """
269
        ret = self.search_child_node(name)
270
        if not ret:
271
            raise ValueError("Child node with name '{0}' doesn't exists".format(name))
272
273
        return ret
274
275
    # ------------------------------------------------------------------------------------------------------------------
276
    def get_implicit_dependencies_input_ports(self, port_name, ports, level):
277
        """
278
        :param string port_name:
279
        :param list[] ports:
280
        :param int    level:
281
        """
282
        port = self.get_input_port(port_name)
283
284
        if port not in ports:
285
            if level:
286
                ports.append(port)
287
            port.get_dependencies_ports(ports, level + 1)
288
289
    # ------------------------------------------------------------------------------------------------------------------
290
    def get_implicit_dependencies_output_ports(self, port_name, ports, level):
291
        """
292
        :param string port_name:
293
        :param list[] ports:
294
        :param int    level:
295
        """
296
        port = self.get_output_port(port_name)
297
298
        if port not in ports:
299
            if level:
300
                ports.append(port)
301
            port.get_dependencies_ports(ports, level + 1)
302
303
    # ------------------------------------------------------------------------------------------------------------------
304
    def get_input_port(self, name):
305
        """
306
        Returns input port with 'name'. If no input port with 'name' exists an exception is thrown.
307
308
        :param string name: The name of the port.
309
310
        :rtype: enarksh_lib.xml_generator.port.Port.Port
311
        """
312
        ret = self.search_input_port(name)
313
314
        if not ret:
315
            if name == self.ALL_PORT_NAME:
316
                ret = self.make_input_port(name)
317
            else:
318
                raise Exception("Node '{0}' doesn't have input port '{1}'".format(self._name, name))
319
320
        return ret
321
322
    # ------------------------------------------------------------------------------------------------------------------
323
    def get_input_ports(self):
324
        """
325
        Returns all input ports of this node.
326
327
        :rtype: list[enarksh_lib.xml_generator.port.InputPort.InputPort]
328
        """
329
        return self._input_ports
330
331
    # ------------------------------------------------------------------------------------------------------------------
332
    def get_name(self):
333
        """
334
        Returns the name of this node.
335
336
        :rtype: str
337
        """
338
        return self._name
339
340
    # ------------------------------------------------------------------------------------------------------------------
341
    def get_output_port(self, name):
342
        """
343
        Returns output port with 'name'. If no output port with 'name' exists, an exception is thrown.
344
345
        :param str name: The name of the output port.
346
347
        :rtype: enarksh_lib.xml_generator.port.Port.Port
348
        """
349
        ret = self.search_output_port(name)
350
351
        if not ret:
352
            if name == self.ALL_PORT_NAME:
353
                ret = self.make_output_port(name)
354
            else:
355
                raise Exception("Node '{0}' doesn't have output port '{1}'".format(self._name, name))
356
357
        return ret
358
359
    # ------------------------------------------------------------------------------------------------------------------
360
    def get_output_ports(self):
361
        """
362
        Returns all output ports of this node.
363
364
        :rtype: list[enarksh_lib.xml_generator.port.OutputPort.OutputPort]
365
        """
366
        return self._output_ports
367
368
    # ------------------------------------------------------------------------------------------------------------------
369
    def get_parent(self):
370
        """
371
        Returns the parent node of this node.
372
373
        :rtype: enarksh_lib.xml_generator.node.Node.Node
374
        """
375
        return self._parent
376
377
    # ------------------------------------------------------------------------------------------------------------------
378
    def get_path(self):
379
        """
380
        Returns the path of this node.
381
382
        :rtype: str
383
        """
384
        # -- @todo detect recursion
385
        path = self._parent.get_path() if self._parent else "/"
386
387
        return path + self._name
388
389
    # ------------------------------------------------------------------------------------------------------------------
390
    def get_username(self):
391
        """
392
        Returns the username under which this node or its child nodes must run.
393
394
        :rtype: str
395
        """
396
        return self._username
397
398
    # ------------------------------------------------------------------------------------------------------------------
399
    def make_input_port(self, name):
400
        """
401
        Creates an input port with name 'name' and returns that input port.
402
403
        :param str name: The name of port.
404
405
        :rtype: enarksh_lib.xml_generator.port.Port.Port
406
        """
407
        # -- @todo test port already exists.
408
409
        port = InputPort(self, name)
410
        self._input_ports.append(port)
411
412
        return port
413
414
    # ------------------------------------------------------------------------------------------------------------------
415
    def make_output_port(self, name):
416
        """
417
        Creates an output port with name 'name' and returns that output port.
418
419
        :param str name: The name of port.
420
421
        :rtype: enarksh_lib.xml_generator.port.Port.Port
422
        """
423
        # -- @todo test port already exists.
424
425
        port = OutputPort(self, name)
426
        self._output_ports.append(port)
427
428
        return port
429
430
    # ------------------------------------------------------------------------------------------------------------------
431
    def pre_generate_xml(self):
432
        """
433
        This function can be called before generation XML and is intended to be overloaded.
434
435
        :rtype: None
436
        """
437
        # Nothing to do.
438
        pass
439
440
    # ------------------------------------------------------------------------------------------------------------------
441
    def purge(self):
442
        """
443
        Removes duplicate dependencies and dependencies that are dependencies of predecessors.
444
        """
445
        for port in self._input_ports:
446
            port.purge()
447
448
        for node in self._child_nodes:
449
            node.purge()
450
451
        for port in self._output_ports:
452
            port.purge()
453
454
    # ------------------------------------------------------------------------------------------------------------------
455
    def remove_child_node(self, node_name):
456
        """
457
        Removes node 'node_name' as a child node. The dependencies of any successor of 'node' will be replaced
458
        with all dependencies of the removed node.
459
460
        :param str node_name:
461
        """
462
        node = None
463
464
        # Find and remove node 'node_name'.
465
        for tmp in self._child_nodes:
466
            if tmp.get_name() == node_name:
467
                node = tmp
468
                self._child_nodes.remove(tmp)
469
                break
470
471
        if not node:
472
            raise Exception("Node '{0}' doesn't have child node '{1}'".format(self.get_path(), node_name))
473
474
        # Get all dependencies of the node.
475
        deps = []
476
        for port in node.get_input_ports():
477
            for dep in port.get_all_dependencies():
478
                deps.append(dep)
479
480
        for tmp in self._child_nodes:
481
            tmp.replace_node_dependency(node_name, deps)
482
483
        for port in self._output_ports:
484
            port.replace_node_dependency(node_name, deps)
485
486
    # ------------------------------------------------------------------------------------------------------------------
487
    def replace_node_dependency(self, node_name, dependencies):
488
        """
489
        Replaces any dependency of this node on node 'node_name' with dependencies 'dependencies'.
490
491
        :param str    node_name:
492
        :param list[] dependencies:
493
        """
494
        for port in self._input_ports:
495
            port.replace_node_dependency(node_name, dependencies)
496
497
    # ------------------------------------------------------------------------------------------------------------------
498
    def search_child_node(self, name):
499
        """
500
        If this node has a child node with name 'name' that child node will be returned.
501
        If no child node with 'name' exists, returns None.
502
503
        :param str name: The name of the child node.
504
505
        :rtype: None|enarksh_lib.xml_generator.node.Node.Node
506
        """
507
        ret = None
508
        for node in self._child_nodes:
509
            if node.get_name() == name:
510
                ret = node
511
                break
512
513
        return ret
514
515
    # ------------------------------------------------------------------------------------------------------------------
516
    def search_input_port(self, name):
517
        """
518
        If this node has a input port with name 'name' that input port will be returned.
519
        If no input port with 'name' exists, returns None.
520
521
        :param str name: The name of the input port.
522
523
        :rtype: None|enarksh_lib.xml_generator.port.InputPort.InputPort
524
        """
525
        ret = None
526
        for port in self._input_ports:
527
            if port.get_name() == name:
528
                ret = port
529
                break
530
531
        return ret
532
533
    # ------------------------------------------------------------------------------------------------------------------
534
    def search_output_port(self, name):
535
        """
536
        If this node has a output port with name 'name' that output port will be returned.
537
        If no output port with 'name' exists, returns None.
538
539
        :param str name: The name of the output port.
540
541
        :rtype: None|enarksh_lib.xml_generator.port.InputPort.InputPort
542
        """
543
        ret = None
544
        for port in self._output_ports:
545
            if port.get_name() == name:
546
                ret = port
547
                break
548
549
        return ret
550
551
    # ------------------------------------------------------------------------------------------------------------------
552
    def set_name(self, name):
553
        """
554
        Sets the name of this node to 'name'.
555
556
        :param str name:
557
        """
558
        self._name = name
559
560
    # ------------------------------------------------------------------------------------------------------------------
561
    def set_username(self, username):
562
        """
563
        Sets the name the account under which this node or its child nodes must run.
564
565
        :param str username: The name of the account.
566
        """
567
        # -- @todo Test username not empty of None.
568
        self._username = username
569
570
    # ------------------------------------------------------------------------------------------------------------------
571
    def ensure_dependencies(self):
572
        """
573
        Creates the following dependencies:
574
        - Dependencies between the input port 'all' and the input port 'all' of all the child nodes of this nodes.
575
        - Dependencies between all output ports 'all' of all child nodes and the output port 'all' of this node.
576
        - Dependencies between the input port 'all' of this node and the output ports 'all' of all predecessor nodes of
577
          this node.
578
        This is done recursively for all child node.
579
580
        Remember: Redundant and duplicate dependencies are removed by purge().
581
        """
582
        if self._child_nodes:
583
            # Apply this method recursively for all child node.
584
            for node in self._child_nodes:
585
                node.ensure_dependencies()
586
587
            # Ensure that the input port 'all' of all child nodes depends on input port 'all' of this node.
588
            self.add_dependency_all_input_ports()
589
590
            # Ensure that output port 'all' of the node depends on all output ports 'all' of all child nodes.
591
            self.add_dependency_all_output_ports()
592
593
            # Ensure input port 'all' of this node depends on output 'all' of each predecessor of this node.
594
            input_port_all = self.get_input_port(self.ALL_PORT_NAME)
595
            for input_port in self._input_ports:
596
                for port in input_port.get_all_dependencies():
597
                    if port.get_node() != self._parent:
598
                        input_port_all.add_dependency(port.get_node().get_output_port(self.ALL_PORT_NAME))
599
600
# ----------------------------------------------------------------------------------------------------------------------
601