Skip to content

Base Classes

All of the command/request/response classes for Broadworks are built on top of these base classes:

Broadworks OCI-P Interface Base Classes

Base classes used by the types, requests and responses as well as other components like ElementInfo that are used by those.

ElementInfo

ElementInfo - information on each element of a Broadsoft OCIType

Used to describe the element when serialising to/from XML

Attributes:

Name Type Description
name str

name of this element (in python snake case)

xmlname str

name of this element (in Java like camel case)

type

the type of the resulting element

is_complex bool

is this a complex element - containing another OCIType derived class

is_required bool

Is this required (True) or Optional (False)

is_array bool

Is this an array/list of element values

is_table bool

Is this a Broadworks table type - only seen in Responses

Source code in broadworks_ocip/base.py
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
@attr.s(slots=True, frozen=True)
class ElementInfo:
    """
    ElementInfo - information on each element of a Broadsoft OCIType

    Used to describe the element when serialising to/from XML

    Attributes:
        name: name of this element (in python snake case)
        xmlname: name of this element (in Java like camel case)
        type: the type of the resulting element
        is_complex: is this a complex element - containing another OCIType derived class
        is_required: Is this required (True) or Optional (False)
        is_array: Is this an array/list of element values
        is_table: Is this a Broadworks table type - only seen in Responses
    """

    name: str = attr.ib()
    xmlname: str = attr.ib()
    type = attr.ib()
    is_complex: bool = attr.ib(default=False)
    is_required: bool = attr.ib(default=False)
    is_array: bool = attr.ib(default=False)
    is_table: bool = attr.ib(default=False)
    is_abstract: bool = attr.ib(default=False)
    is_container: bool = attr.ib(default=False)

ErrorResponse

Bases: OCIResponse

The ErrorResponse is concrete response sent whenever a transaction fails and does not return any data.

As this an error, when it is created from an incoming command response, a OCIErrorResponse exception is raised in post_xml_decode_.

Attributes:

Name Type Description
error_code int

The error code

summary str

Summary of the error

summary_english str

Summary of the error in english!

detail str

Detail of the error

type str

Type of the error

Source code in broadworks_ocip/base.py
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
class ErrorResponse(OCIResponse):
    """
    The ErrorResponse is concrete response sent whenever a transaction fails
    and does not return any data.

    As this an error, when it is created from an incoming command response, a
    `OCIErrorResponse` exception is raised in `post_xml_decode_`.

    Attributes:
        error_code (int): The error code
        summary (str): Summary of the error
        summary_english (str): Summary of the error in english!
        detail (str): Detail of the error
        type (str): Type of the error

    """

    __slots__: List[str] = [
        "error_code",
        "summary",
        "summary_english",
        "detail",
        "type",
    ]

    @classmethod
    def _elements(cls) -> Tuple[ElementInfo, ...]:
        return (
            ElementInfo("error_code", "errorCode", int),
            ElementInfo("summary", "summary", str, is_required=True),
            ElementInfo("summary_english", "summaryEnglish", str, is_required=True),
            ElementInfo("detail", "detail", str),
            ElementInfo("type", "type", str),
        )

    def post_xml_decode_(self):
        """Raise an exception as this is an error"""
        raise OCIErrorResponse(
            object=self,
            message=f"{self.error_code}: {self.summary} - {self.detail}",
        )

    def build_xml_command_element_(self, root):
        return etree.SubElement(
            root,
            "command",
            {
                "type": "Error",
                "echo": "",
                XSD_TYPE: "c:" + self.type_,
            },
            nsmap=self._error_nsmap(),
        )

post_xml_decode_()

Raise an exception as this is an error

Source code in broadworks_ocip/base.py
669
670
671
672
673
674
def post_xml_decode_(self):
    """Raise an exception as this is an error"""
    raise OCIErrorResponse(
        object=self,
        message=f"{self.error_code}: {self.summary} - {self.detail}",
    )

OCICommand

Bases: OCIType

OCICommand - base class for all OCI Command (Request/Response) types

Attributes:

Name Type Description
session_id str

The session ID used for the command exchange. We use UUIDs by default, although this is not required. The default is fixed on here (but normally passed in from the containing API object) - do not use the default in production - its simply there to give a known value for testing.

Source code in broadworks_ocip/base.py
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
class OCICommand(OCIType):
    """
    OCICommand - base class for all OCI Command (Request/Response) types

    Attributes:
        session_id (str): The session ID used for the command exchange.
            We use UUIDs by default, although this is not required.  The
            default is fixed on here (but normally passed in from the
            containing API object) - do not use the default in production -
            its simply there to give a known value for testing.
    """

    __slots__: List[str] = ["session_id"]

    def __init__(
        self,
        session_id: str = "00000000-1111-2222-3333-444444444444",
        **kwargs,
    ):
        self.session_id = session_id
        super().__init__(**kwargs)

    def build_xml_(self) -> bytes:
        """
        Build an XML document of the current Command (Request/Response)

        Returns:
            xml: byte string containing XML document
        """
        # document root element
        root = etree.Element(
            "{C}BroadsoftDocument",
            {"protocol": "OCI"},
            nsmap=self._document_nsmap(),
        )
        #
        # add the session
        session = etree.SubElement(root, "sessionId", nsmap=self._default_nsmap())
        session.text = self.session_id
        #
        # and the command
        element = self.build_xml_command_element_(root)
        self.etree_sub_components_(element)  # attach parameters etc
        #
        # wrap a tree around it
        tree = etree.ElementTree(root)
        return etree.tostring(
            tree,
            xml_declaration=True,
            encoding="ISO-8859-1",
            # standalone=False,
            # pretty_print=True,
        )

    def build_xml_command_element_(self, root: etree._Element) -> etree._Element:
        """
        Build the XML etree of the main command element of the current Command
        Intended to be overridden in a subclass for the few elements that do things
        a little differently (for example errors).

        Returns:
            wrapped_element: The wrapped commane element tree
        """
        return etree.SubElement(
            root,
            "command",
            {XSD_TYPE: self.type_},
            nsmap=self._default_nsmap(),
        )

    @classmethod
    def build_from_etree_non_parameters_(
        cls,
        element: etree._Element,
        initialiser: dict,
    ):
        """
        Pick up the session id from the command set

        Overrides the class method defined in OCIType.
        """
        node = element.find("sessionId")
        if node is not None:
            initialiser["session_id"] = node.text

    def to_dict(self) -> Dict[str, Any]:
        """
        Convert object to dict representation of itself

        This was provided as part of the Classforge system, which we have moved away
        from, so this is a local re-implementation.  This is only used within the test
        suite at present.
        """
        elements = super().to_dict()  # pick up the base object data
        return {"session_id": self.session_id, **elements}

build_from_etree_non_parameters_(element, initialiser) classmethod

Pick up the session id from the command set

Overrides the class method defined in OCIType.

Source code in broadworks_ocip/base.py
565
566
567
568
569
570
571
572
573
574
575
576
577
578
@classmethod
def build_from_etree_non_parameters_(
    cls,
    element: etree._Element,
    initialiser: dict,
):
    """
    Pick up the session id from the command set

    Overrides the class method defined in OCIType.
    """
    node = element.find("sessionId")
    if node is not None:
        initialiser["session_id"] = node.text

build_xml_()

Build an XML document of the current Command (Request/Response)

Returns:

Name Type Description
xml bytes

byte string containing XML document

Source code in broadworks_ocip/base.py
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
def build_xml_(self) -> bytes:
    """
    Build an XML document of the current Command (Request/Response)

    Returns:
        xml: byte string containing XML document
    """
    # document root element
    root = etree.Element(
        "{C}BroadsoftDocument",
        {"protocol": "OCI"},
        nsmap=self._document_nsmap(),
    )
    #
    # add the session
    session = etree.SubElement(root, "sessionId", nsmap=self._default_nsmap())
    session.text = self.session_id
    #
    # and the command
    element = self.build_xml_command_element_(root)
    self.etree_sub_components_(element)  # attach parameters etc
    #
    # wrap a tree around it
    tree = etree.ElementTree(root)
    return etree.tostring(
        tree,
        xml_declaration=True,
        encoding="ISO-8859-1",
        # standalone=False,
        # pretty_print=True,
    )

build_xml_command_element_(root)

Build the XML etree of the main command element of the current Command Intended to be overridden in a subclass for the few elements that do things a little differently (for example errors).

Returns:

Name Type Description
wrapped_element _Element

The wrapped commane element tree

Source code in broadworks_ocip/base.py
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
def build_xml_command_element_(self, root: etree._Element) -> etree._Element:
    """
    Build the XML etree of the main command element of the current Command
    Intended to be overridden in a subclass for the few elements that do things
    a little differently (for example errors).

    Returns:
        wrapped_element: The wrapped commane element tree
    """
    return etree.SubElement(
        root,
        "command",
        {XSD_TYPE: self.type_},
        nsmap=self._default_nsmap(),
    )

to_dict()

Convert object to dict representation of itself

This was provided as part of the Classforge system, which we have moved away from, so this is a local re-implementation. This is only used within the test suite at present.

Source code in broadworks_ocip/base.py
580
581
582
583
584
585
586
587
588
589
def to_dict(self) -> Dict[str, Any]:
    """
    Convert object to dict representation of itself

    This was provided as part of the Classforge system, which we have moved away
    from, so this is a local re-implementation.  This is only used within the test
    suite at present.
    """
    elements = super().to_dict()  # pick up the base object data
    return {"session_id": self.session_id, **elements}

OCIRequest

Bases: OCICommand

OCIRequest - base class for all OCI Command Request types

Source code in broadworks_ocip/base.py
592
593
594
595
596
597
class OCIRequest(OCICommand):
    """
    OCIRequest - base class for all OCI Command Request types
    """

    pass

OCIResponse

Bases: OCICommand

OCIResponse - base class for all OCI Command Response types

Source code in broadworks_ocip/base.py
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
class OCIResponse(OCICommand):
    """
    OCIResponse - base class for all OCI Command Response types
    """

    def build_xml_command_element_(self, root: etree._Element) -> etree._Element:
        """
        Build the XML etree of the main command element of the current Command
        Responses have an echo attribute in the element.

        Returns:
            etree: The XML etree of the main command element
        """
        return etree.SubElement(
            root,
            "command",
            {"echo": "", XSD_TYPE: self.type_},
            nsmap=self._default_nsmap(),
        )

build_xml_command_element_(root)

Build the XML etree of the main command element of the current Command Responses have an echo attribute in the element.

Returns:

Name Type Description
etree _Element

The XML etree of the main command element

Source code in broadworks_ocip/base.py
605
606
607
608
609
610
611
612
613
614
615
616
617
618
def build_xml_command_element_(self, root: etree._Element) -> etree._Element:
    """
    Build the XML etree of the main command element of the current Command
    Responses have an echo attribute in the element.

    Returns:
        etree: The XML etree of the main command element
    """
    return etree.SubElement(
        root,
        "command",
        {"echo": "", XSD_TYPE: self.type_},
        nsmap=self._default_nsmap(),
    )

OCIType

OCIType - Base type for all the OCI-P component classes

There are no attributes of this base class (the _frozen attribute is used as a flag to lock the instance). The attributes are added in the various subclasses.

Source code in broadworks_ocip/base.py
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
class OCIType:
    """
    OCIType - Base type for all the OCI-P component classes

    There are no attributes of this base class (the `_frozen` attribute is
    used as a flag to lock the instance).  The attributes are added in the
    various subclasses.
    """

    __slots__: List[str] = ["_frozen"]

    def __init__(self, **kwargs):
        cname = type(self).__name__  # needed for error messages
        for elem in self._elements():
            if elem.name in kwargs:
                value = kwargs[elem.name]
                if elem.is_required and value is None:
                    raise OCIErrorAttributeMissing(
                        message=f"{cname}: Required attribute {elem.name} is missing",
                    )
                if value is Null:
                    # always allowed
                    pass
                elif elem.is_array:
                    if not isinstance(value, list):
                        raise TypeError(
                            f"{cname}: Expected {elem.name} to be a list but it is {type(value)}",
                        )
                    if len(value) > 0 and not isinstance(value[0], elem.type):
                        raise TypeError(
                            f"{cname}: Expected first element of {elem.name} to be type {elem.type} but it is {type(value[0])}",
                        )
                elif elem.is_table:
                    if not isinstance(value, list):
                        raise TypeError(
                            f"{cname}: Expected {elem.name} to be a table/list but it is {type(value)}",
                        )
                elif elem.is_container:
                    if value is not None and not isinstance(value, dict):
                        raise TypeError(
                            f"{cname}: Expected {elem.name} to be a dict of elements but it is {type(value)}",
                        )
                elif not isinstance(value, elem.type):
                    raise TypeError(
                        f"{cname}: Expected {elem.name} to be type {elem.type} but it is {type(value)}",
                    )
                setattr(self, elem.name, value)
                del kwargs[elem.name]
            elif elem.is_required:
                raise OCIErrorAttributeMissing(
                    message=f"{cname}: Required attribute {elem.name} is missing",
                )
            else:
                setattr(self, elem.name, None)
        if kwargs:
            raise OCIErrorUnexpectedAttribute(
                message=f"{cname}: Unexpected attribute(s) {kwargs.keys()}",
            )
        self._frozen = True

    def __delattr__(self, *args, **kwargs):
        if hasattr(self, "_frozen"):
            raise AttributeError("This object is frozen!")
        object.__delattr__(self, *args, **kwargs)

    def __setattr__(self, *args, **kwargs):
        if hasattr(self, "_frozen"):
            raise AttributeError("This object is frozen!")
        object.__setattr__(self, *args, **kwargs)

    # Namespace maps used for various XML build tasks
    @classmethod
    def _default_nsmap(cls):
        return {None: "", "xsi": "http://www.w3.org/2001/XMLSchema-instance"}

    @classmethod
    def _document_nsmap(cls):
        return {None: "C", "xsi": "http://www.w3.org/2001/XMLSchema-instance"}

    @classmethod
    def _error_nsmap(cls):
        return {
            "c": "C",
            None: "",
            "xsi": "http://www.w3.org/2001/XMLSchema-instance",
        }

    @classmethod
    def _elements(cls) -> Tuple[ElementInfo, ...]:
        raise OCIErrorAPISetup(message="_elements should be defined in the subclass.")

    @property
    def type_(self):
        """Return the typename of the class"""
        return self.__class__.__name__

    @classmethod
    def class_to_property_(self, name):
        """Map a XML class name to the associated property"""
        return name[0].lower() + name[1:]

    def post_xml_decode_(self):
        """
        Carry out any operations after the XML decode

        Intended for use by subclasses where they need to take actions immediately
        after they are created from an incoming XML document.
        """
        pass

    def etree_components_(self, name=None):
        """
        Build XML etree element tree for this OCIType

        Arguments:
            name: The name or tag of the element - defaults to the `type_`

        Returns:
            etree: etree.Element() for this class
        """
        if name is None:
            name = self.type_
        element = etree.Element(name, nsmap=self._default_nsmap())
        return self.etree_sub_components_(element)

    def etree_sub_components_(self, element: etree._Element) -> etree._Element:
        """
        Build XML etree subelements for the components within this OCIType

        Arguments:
            element: The parent element that the components are to be attached to

        Returns:
            etree: etree.Element() for this class
        """
        for sub_element in self._elements():
            value = getattr(self, sub_element.name)
            if sub_element.is_array:
                if value is not None:
                    if len(value) == 0:
                        element.attrib[XML_NIL] = "true"
                    else:
                        for subvalue in value:
                            self.etree_sub_element_(element, sub_element, subvalue)
            else:
                self.etree_sub_element_(element, sub_element, value)
        return element

    def etree_sub_element_(
        self,
        element: etree._Element,
        sub_element: ElementInfo,
        value,
    ) -> etree._Element:
        """
        Build XML etree subelement for one element within this OCIType

        Arguments:
            element: The parent element that the components are to be attached to
            sub_element: The definition of the sub element to be attached
            value: Value of the sub element - quite possibly None

        Returns:
            etree: etree.Element() for this class
        """
        # if value is Null or (value is None and sub_element.is_required):
        if value is Null or (value is None and sub_element.is_required):
            etree.SubElement(
                element,
                sub_element.xmlname,
                {XML_NIL: "true"},
                nsmap=self._default_nsmap(),
            )
        elif value is None:
            pass
        elif sub_element.is_table:
            # any table should be a list of namedtuple elements
            if isinstance(value, list) and len(value) > 0:
                elem = etree.SubElement(
                    element,
                    sub_element.xmlname,
                    nsmap=self._default_nsmap(),
                )
                first = value[0]
                for col in first._fields:
                    col_heading = etree.SubElement(elem, "colHeading")
                    col_heading.text = self.snake_case_to_column_header(col)
                for row in value:
                    row_item = etree.SubElement(elem, "row")
                    for col in row:
                        col_item = etree.SubElement(row_item, "col")
                        col_item.text = col
        elif sub_element.is_container:
            elem = etree.SubElement(
                element,
                sub_element.xmlname,
                nsmap=self._default_nsmap(),
            )
            for item in sub_element.type:
                if item.name in value:
                    self.etree_sub_element_(elem, item, value[item.name])
        elif sub_element.is_complex:
            if sub_element.is_abstract:
                elem = etree.SubElement(
                    element,
                    sub_element.xmlname,
                    {XSD_TYPE: value.type_},
                    nsmap=self._default_nsmap(),
                )
            else:
                elem = etree.SubElement(
                    element,
                    sub_element.xmlname,
                    nsmap=self._default_nsmap(),
                )
            value.etree_sub_components_(elem)
        else:
            elem = etree.SubElement(
                element,
                sub_element.xmlname,
                nsmap=self._default_nsmap(),
            )
            if sub_element.type is bool:
                elem.text = "true" if value else "false"
            elif sub_element.type is int:
                elem.text = str(value)
            else:
                elem.text = value
        return element

    @classmethod
    def column_header_snake_case_(cls, header: str) -> str:
        """
        Converts an XML name into a pythonic snake case name

        Arguments:
            header: The header name in space separated words

        Returns:
            snake: lower cased and underscore separated result name
        """
        return re.sub("[^A-Za-z0-9]+", r"_", header).lower()

    def snake_case_to_column_header(self, snake_str: str) -> str:
        """
        Converts a pythonic snake case name into a column header name

        Arguments:
            snake_str: The header name in snake lower case

        Returns:
            column_header: initial capital and space separated result name
        """
        components = snake_str.split("_")
        # We capitalize the first letter of each component except the first one
        # with the 'title' method and join them together.
        return " ".join(x.title() for x in components)

    @classmethod
    def decode_table_(cls, element: etree._Element) -> List[NamedTuple]:
        """
        Decode a table (used in a OCIResponse) into a list of named tuples

        Arguments:
            element: The OCITable XML element

        Returns:
            results: List of namedtuple elements, one for each table row
        """
        typename: str = element.tag
        results: List[NamedTuple] = []
        columns = [cls.column_header_snake_case_(b.text) for b in element.iterfind("colHeading")]
        type: NamedTuple = namedtuple(typename, columns)  # type: ignore
        for row in element.iterfind("row"):
            rowdata = [b.text for b in row.iterfind("col")]
            rowobj = type(*rowdata)
            results.append(rowobj)
        return results

    @classmethod
    def build_from_etree_non_parameters_(
        cls,
        element: etree._Element,
        initialiser: dict,
    ) -> None:
        """
        Handle any items outside the parameter set

        Intended for use by subclasses where they need to take actions immediately
        after they are created from an incoming XML document.
        """
        pass

    @classmethod
    def build_from_node_(
        cls,
        api: "BroadworksAPI",  # type: ignore # noqa
        elem: ElementInfo,
        node: etree._Element,
    ) -> Any:
        """
        Creates an OCI subelement from a single  XML etree node

        Arguments:
            elem: The subelement descriptor
            node: The OCITable XML element node

        Returns:
            results: Object instance for this class
        """
        if node is not None:
            if XML_NIL in node.attrib and node.attrib[XML_NIL] == "true":
                return Null
            elif elem.is_table:
                return cls.decode_table_(node)
            elif elem.is_complex:
                if elem.is_abstract:
                    if XSD_TYPE in node.attrib:
                        thisclass = api.get_type_class(
                            node.attrib[XSD_TYPE],
                        )
                        logger.error(f"thisclass={thisclass}")
                        return thisclass.build_from_etree_(api=api, element=node)
                    else:
                        return ValueError("Cannot decode abstract object")
                elif elem.is_container:
                    return cls.build_initialiser_from_etree_(
                        api=api,
                        element=node,
                        subelement_set=elem.type,
                    )
                else:
                    return elem.type.build_from_etree_(api=api, element=node)
            elif elem.type is bool:
                return elem.type(
                    True if node.text == "true" else False,
                )
            else:
                return elem.type(node.text)
        else:
            return None

    @classmethod
    def build_initialiser_from_etree_(
        cls,
        api: "BroadworksAPI",  # type: ignore # noqa
        element: etree._Element,
        subelement_set: List[ElementInfo],
        extras: Dict[str, Any] = {},
    ):
        initialiser = extras.copy()
        for elem in subelement_set:
            if elem.is_array:
                result = []
                nodes = element.findall(elem.xmlname)
                for node in nodes:
                    result.append(cls.build_from_node_(api=api, elem=elem, node=node))
                initialiser[elem.name] = result
            else:
                node = element.find(elem.xmlname)
                if node is not None:
                    initialiser[elem.name] = cls.build_from_node_(
                        api=api,
                        elem=elem,
                        node=node,
                    )
                # else:
                # I am inclined to thow an error here - at least after checking if
                # the thing is require, but the class builder should do that so lets
                # let it do its thing
        return initialiser

    @classmethod
    def build_from_etree_(
        cls,
        api: "BroadworksAPI",  # type: ignore # noqa
        element: etree._Element,
        extras: Dict[str, Any] = {},
    ):
        """
        Create an OciType based instance from an XML etree element

        Arguments:
            element: The OCIType XML element

        Returns:
            results: Object instance for this class
        """
        initialiser = cls.build_initialiser_from_etree_(
            api=api,
            element=element,
            subelement_set=list(cls._elements()),
            extras=extras,
        )
        # now have a dict with all the bits in it.
        # use that to build a new object
        return cls(**initialiser)

    def to_dict(self) -> Dict[str, Any]:
        """
        Convert object to dict representation of itself

        This was provided as part of the Classforge system, which we have moved away
        from, so this is a local re-implementation.  This is only used within the test
        suite at present.
        """
        elements = {}
        for elem in self._elements():
            value = getattr(self, elem.name)
            if elem.is_table:
                pass
            elif elem.is_complex:
                if elem.is_array:
                    value = [x.to_dict() for x in value]
                elif value is not None:
                    value = value.to_dict()
            elif elem.is_array:
                value = [x for x in value]
            elements[elem.name] = value
        return elements

    def __repr__(self) -> str:
        """Convert object to string representation of itself."""
        dict_form = self.to_dict()
        bits = [f"{key}={repr(dict_form[key])}" for key in dict_form.keys()]
        return f"{self.__class__.__name__}({', '.join(bits)})"

    def __str__(self) -> str:
        """Convert object to string representation of itself."""
        return repr(self)

type_ property

Return the typename of the class

__repr__()

Convert object to string representation of itself.

Source code in broadworks_ocip/base.py
484
485
486
487
488
def __repr__(self) -> str:
    """Convert object to string representation of itself."""
    dict_form = self.to_dict()
    bits = [f"{key}={repr(dict_form[key])}" for key in dict_form.keys()]
    return f"{self.__class__.__name__}({', '.join(bits)})"

__str__()

Convert object to string representation of itself.

Source code in broadworks_ocip/base.py
490
491
492
def __str__(self) -> str:
    """Convert object to string representation of itself."""
    return repr(self)

build_from_etree_(api, element, extras={}) classmethod

Create an OciType based instance from an XML etree element

Parameters:

Name Type Description Default
element _Element

The OCIType XML element

required

Returns:

Name Type Description
results

Object instance for this class

Source code in broadworks_ocip/base.py
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
@classmethod
def build_from_etree_(
    cls,
    api: "BroadworksAPI",  # type: ignore # noqa
    element: etree._Element,
    extras: Dict[str, Any] = {},
):
    """
    Create an OciType based instance from an XML etree element

    Arguments:
        element: The OCIType XML element

    Returns:
        results: Object instance for this class
    """
    initialiser = cls.build_initialiser_from_etree_(
        api=api,
        element=element,
        subelement_set=list(cls._elements()),
        extras=extras,
    )
    # now have a dict with all the bits in it.
    # use that to build a new object
    return cls(**initialiser)

build_from_etree_non_parameters_(element, initialiser) classmethod

Handle any items outside the parameter set

Intended for use by subclasses where they need to take actions immediately after they are created from an incoming XML document.

Source code in broadworks_ocip/base.py
342
343
344
345
346
347
348
349
350
351
352
353
354
@classmethod
def build_from_etree_non_parameters_(
    cls,
    element: etree._Element,
    initialiser: dict,
) -> None:
    """
    Handle any items outside the parameter set

    Intended for use by subclasses where they need to take actions immediately
    after they are created from an incoming XML document.
    """
    pass

build_from_node_(api, elem, node) classmethod

Creates an OCI subelement from a single XML etree node

Parameters:

Name Type Description Default
elem ElementInfo

The subelement descriptor

required
node _Element

The OCITable XML element node

required

Returns:

Name Type Description
results Any

Object instance for this class

Source code in broadworks_ocip/base.py
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
@classmethod
def build_from_node_(
    cls,
    api: "BroadworksAPI",  # type: ignore # noqa
    elem: ElementInfo,
    node: etree._Element,
) -> Any:
    """
    Creates an OCI subelement from a single  XML etree node

    Arguments:
        elem: The subelement descriptor
        node: The OCITable XML element node

    Returns:
        results: Object instance for this class
    """
    if node is not None:
        if XML_NIL in node.attrib and node.attrib[XML_NIL] == "true":
            return Null
        elif elem.is_table:
            return cls.decode_table_(node)
        elif elem.is_complex:
            if elem.is_abstract:
                if XSD_TYPE in node.attrib:
                    thisclass = api.get_type_class(
                        node.attrib[XSD_TYPE],
                    )
                    logger.error(f"thisclass={thisclass}")
                    return thisclass.build_from_etree_(api=api, element=node)
                else:
                    return ValueError("Cannot decode abstract object")
            elif elem.is_container:
                return cls.build_initialiser_from_etree_(
                    api=api,
                    element=node,
                    subelement_set=elem.type,
                )
            else:
                return elem.type.build_from_etree_(api=api, element=node)
        elif elem.type is bool:
            return elem.type(
                True if node.text == "true" else False,
            )
        else:
            return elem.type(node.text)
    else:
        return None

class_to_property_(name) classmethod

Map a XML class name to the associated property

Source code in broadworks_ocip/base.py
159
160
161
162
@classmethod
def class_to_property_(self, name):
    """Map a XML class name to the associated property"""
    return name[0].lower() + name[1:]

column_header_snake_case_(header) classmethod

Converts an XML name into a pythonic snake case name

Parameters:

Name Type Description Default
header str

The header name in space separated words

required

Returns:

Name Type Description
snake str

lower cased and underscore separated result name

Source code in broadworks_ocip/base.py
293
294
295
296
297
298
299
300
301
302
303
304
@classmethod
def column_header_snake_case_(cls, header: str) -> str:
    """
    Converts an XML name into a pythonic snake case name

    Arguments:
        header: The header name in space separated words

    Returns:
        snake: lower cased and underscore separated result name
    """
    return re.sub("[^A-Za-z0-9]+", r"_", header).lower()

decode_table_(element) classmethod

Decode a table (used in a OCIResponse) into a list of named tuples

Parameters:

Name Type Description Default
element _Element

The OCITable XML element

required

Returns:

Name Type Description
results List[NamedTuple]

List of namedtuple elements, one for each table row

Source code in broadworks_ocip/base.py
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
@classmethod
def decode_table_(cls, element: etree._Element) -> List[NamedTuple]:
    """
    Decode a table (used in a OCIResponse) into a list of named tuples

    Arguments:
        element: The OCITable XML element

    Returns:
        results: List of namedtuple elements, one for each table row
    """
    typename: str = element.tag
    results: List[NamedTuple] = []
    columns = [cls.column_header_snake_case_(b.text) for b in element.iterfind("colHeading")]
    type: NamedTuple = namedtuple(typename, columns)  # type: ignore
    for row in element.iterfind("row"):
        rowdata = [b.text for b in row.iterfind("col")]
        rowobj = type(*rowdata)
        results.append(rowobj)
    return results

etree_components_(name=None)

Build XML etree element tree for this OCIType

Parameters:

Name Type Description Default
name

The name or tag of the element - defaults to the type_

None

Returns:

Name Type Description
etree

etree.Element() for this class

Source code in broadworks_ocip/base.py
173
174
175
176
177
178
179
180
181
182
183
184
185
186
def etree_components_(self, name=None):
    """
    Build XML etree element tree for this OCIType

    Arguments:
        name: The name or tag of the element - defaults to the `type_`

    Returns:
        etree: etree.Element() for this class
    """
    if name is None:
        name = self.type_
    element = etree.Element(name, nsmap=self._default_nsmap())
    return self.etree_sub_components_(element)

etree_sub_components_(element)

Build XML etree subelements for the components within this OCIType

Parameters:

Name Type Description Default
element _Element

The parent element that the components are to be attached to

required

Returns:

Name Type Description
etree _Element

etree.Element() for this class

Source code in broadworks_ocip/base.py
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
def etree_sub_components_(self, element: etree._Element) -> etree._Element:
    """
    Build XML etree subelements for the components within this OCIType

    Arguments:
        element: The parent element that the components are to be attached to

    Returns:
        etree: etree.Element() for this class
    """
    for sub_element in self._elements():
        value = getattr(self, sub_element.name)
        if sub_element.is_array:
            if value is not None:
                if len(value) == 0:
                    element.attrib[XML_NIL] = "true"
                else:
                    for subvalue in value:
                        self.etree_sub_element_(element, sub_element, subvalue)
        else:
            self.etree_sub_element_(element, sub_element, value)
    return element

etree_sub_element_(element, sub_element, value)

Build XML etree subelement for one element within this OCIType

Parameters:

Name Type Description Default
element _Element

The parent element that the components are to be attached to

required
sub_element ElementInfo

The definition of the sub element to be attached

required
value

Value of the sub element - quite possibly None

required

Returns:

Name Type Description
etree _Element

etree.Element() for this class

Source code in broadworks_ocip/base.py
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
def etree_sub_element_(
    self,
    element: etree._Element,
    sub_element: ElementInfo,
    value,
) -> etree._Element:
    """
    Build XML etree subelement for one element within this OCIType

    Arguments:
        element: The parent element that the components are to be attached to
        sub_element: The definition of the sub element to be attached
        value: Value of the sub element - quite possibly None

    Returns:
        etree: etree.Element() for this class
    """
    # if value is Null or (value is None and sub_element.is_required):
    if value is Null or (value is None and sub_element.is_required):
        etree.SubElement(
            element,
            sub_element.xmlname,
            {XML_NIL: "true"},
            nsmap=self._default_nsmap(),
        )
    elif value is None:
        pass
    elif sub_element.is_table:
        # any table should be a list of namedtuple elements
        if isinstance(value, list) and len(value) > 0:
            elem = etree.SubElement(
                element,
                sub_element.xmlname,
                nsmap=self._default_nsmap(),
            )
            first = value[0]
            for col in first._fields:
                col_heading = etree.SubElement(elem, "colHeading")
                col_heading.text = self.snake_case_to_column_header(col)
            for row in value:
                row_item = etree.SubElement(elem, "row")
                for col in row:
                    col_item = etree.SubElement(row_item, "col")
                    col_item.text = col
    elif sub_element.is_container:
        elem = etree.SubElement(
            element,
            sub_element.xmlname,
            nsmap=self._default_nsmap(),
        )
        for item in sub_element.type:
            if item.name in value:
                self.etree_sub_element_(elem, item, value[item.name])
    elif sub_element.is_complex:
        if sub_element.is_abstract:
            elem = etree.SubElement(
                element,
                sub_element.xmlname,
                {XSD_TYPE: value.type_},
                nsmap=self._default_nsmap(),
            )
        else:
            elem = etree.SubElement(
                element,
                sub_element.xmlname,
                nsmap=self._default_nsmap(),
            )
        value.etree_sub_components_(elem)
    else:
        elem = etree.SubElement(
            element,
            sub_element.xmlname,
            nsmap=self._default_nsmap(),
        )
        if sub_element.type is bool:
            elem.text = "true" if value else "false"
        elif sub_element.type is int:
            elem.text = str(value)
        else:
            elem.text = value
    return element

post_xml_decode_()

Carry out any operations after the XML decode

Intended for use by subclasses where they need to take actions immediately after they are created from an incoming XML document.

Source code in broadworks_ocip/base.py
164
165
166
167
168
169
170
171
def post_xml_decode_(self):
    """
    Carry out any operations after the XML decode

    Intended for use by subclasses where they need to take actions immediately
    after they are created from an incoming XML document.
    """
    pass

snake_case_to_column_header(snake_str)

Converts a pythonic snake case name into a column header name

Parameters:

Name Type Description Default
snake_str str

The header name in snake lower case

required

Returns:

Name Type Description
column_header str

initial capital and space separated result name

Source code in broadworks_ocip/base.py
306
307
308
309
310
311
312
313
314
315
316
317
318
319
def snake_case_to_column_header(self, snake_str: str) -> str:
    """
    Converts a pythonic snake case name into a column header name

    Arguments:
        snake_str: The header name in snake lower case

    Returns:
        column_header: initial capital and space separated result name
    """
    components = snake_str.split("_")
    # We capitalize the first letter of each component except the first one
    # with the 'title' method and join them together.
    return " ".join(x.title() for x in components)

to_dict()

Convert object to dict representation of itself

This was provided as part of the Classforge system, which we have moved away from, so this is a local re-implementation. This is only used within the test suite at present.

Source code in broadworks_ocip/base.py
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
def to_dict(self) -> Dict[str, Any]:
    """
    Convert object to dict representation of itself

    This was provided as part of the Classforge system, which we have moved away
    from, so this is a local re-implementation.  This is only used within the test
    suite at present.
    """
    elements = {}
    for elem in self._elements():
        value = getattr(self, elem.name)
        if elem.is_table:
            pass
        elif elem.is_complex:
            if elem.is_array:
                value = [x.to_dict() for x in value]
            elif value is not None:
                value = value.to_dict()
        elif elem.is_array:
            value = [x for x in value]
        elements[elem.name] = value
    return elements

SuccessResponse

Bases: OCIResponse

The SuccessResponse is concrete response sent whenever a transaction is successful and does not return any data.

Source code in broadworks_ocip/base.py
621
622
623
624
625
626
627
628
629
630
631
class SuccessResponse(OCIResponse):
    """
    The SuccessResponse is concrete response sent whenever a transaction is successful
    and does not return any data.
    """

    __slots__: List[str] = []

    @classmethod
    def _elements(cls) -> Tuple[ElementInfo, ...]:
        return ()