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
@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)

__init__(self, name, xmlname, type, is_complex=False, is_required=False, is_array=False, is_table=False, is_abstract=False, is_container=False) special

Method generated by attrs for class ElementInfo.

Source code in broadworks_ocip/base.py
def __init__(self, name, xmlname, type, is_complex=attr_dict['is_complex'].default, is_required=attr_dict['is_required'].default, is_array=attr_dict['is_array'].default, is_table=attr_dict['is_table'].default, is_abstract=attr_dict['is_abstract'].default, is_container=attr_dict['is_container'].default):
    _setattr = _cached_setattr.__get__(self, self.__class__)
    _setattr('name', name)
    _setattr('xmlname', xmlname)
    _setattr('type', type)
    _setattr('is_complex', is_complex)
    _setattr('is_required', is_required)
    _setattr('is_array', is_array)
    _setattr('is_table', is_table)
    _setattr('is_abstract', is_abstract)
    _setattr('is_container', is_container)

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:

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
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(),
        )

build_xml_command_element_(self, root)

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

Returns:

Type Description
etree

The XML etree of the main command element

Source code in broadworks_ocip/base.py
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_(self)

Raise an exception as this is an error

Source code in broadworks_ocip/base.py
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 (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
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
@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_(self)

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

Returns:

Type Description
xml

byte string containing XML document

Source code in broadworks_ocip/base.py
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_(self, 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:

Type Description
wrapped_element

The wrapped commane element tree

Source code in broadworks_ocip/base.py
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(self)

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
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 (OCICommand)

OCIRequest - base class for all OCI Command Request types

Source code in broadworks_ocip/base.py
class OCIRequest(OCICommand):
    """
    OCIRequest - base class for all OCI Command Request types
    """

    pass

OCIResponse (OCICommand)

OCIResponse - base class for all OCI Command Response types

Source code in broadworks_ocip/base.py
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_(self, root)

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

Returns:

Type Description
etree

The XML etree of the main command element

Source code in broadworks_ocip/base.py
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
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 == bool:
                elem.text = "true" if value else "false"
            elif sub_element.type == 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 == 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 readonly

Return the typename of the class

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:

Type Description
results

Object instance for this class

Source code in broadworks_ocip/base.py
@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
@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:

Type Description
results

Object instance for this class

Source code in broadworks_ocip/base.py
@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 == 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
@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:

Type Description
snake

lower cased and underscore separated result name

Source code in broadworks_ocip/base.py
@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:

Type Description
results

List of namedtuple elements, one for each table row

Source code in broadworks_ocip/base.py
@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_(self, 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:

Type Description
etree

etree.Element() for this class

Source code in broadworks_ocip/base.py
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_(self, 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:

Type Description
etree

etree.Element() for this class

Source code in broadworks_ocip/base.py
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_(self, 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:

Type Description
etree

etree.Element() for this class

Source code in broadworks_ocip/base.py
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 == bool:
            elem.text = "true" if value else "false"
        elif sub_element.type == int:
            elem.text = str(value)
        else:
            elem.text = value
    return element

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.

Source code in broadworks_ocip/base.py
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(self, 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:

Type Description
column_header

initial capital and space separated result name

Source code in broadworks_ocip/base.py
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(self)

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
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 (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
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 ()