From 04184c9f1fcd84d5e5c7d0b6fbe97bd648fb2537 Mon Sep 17 00:00:00 2001 From: AJ Rice <53190766+ajrice6713@users.noreply.github.com> Date: Wed, 28 Sep 2022 12:26:12 -0400 Subject: [PATCH 01/11] DX-2901 `` BXML Had to fix some bugs with the attributes in the `Verb` class Reordered arguments in the `Tag` class to match `Verb` --- bandwidth/model/bxml/verb.py | 22 ++++++-- bandwidth/model/bxml/verbs/phone_number.py | 64 ++++++++++++++++++++++ bandwidth/model/bxml/verbs/tag.py | 2 +- test/unit/bxml/test_phone_number.py | 40 ++++++++++++++ 4 files changed, 123 insertions(+), 5 deletions(-) create mode 100644 bandwidth/model/bxml/verbs/phone_number.py create mode 100644 test/unit/bxml/test_phone_number.py diff --git a/bandwidth/model/bxml/verb.py b/bandwidth/model/bxml/verb.py index 27fef5cc..bd5ef9de 100644 --- a/bandwidth/model/bxml/verb.py +++ b/bandwidth/model/bxml/verb.py @@ -48,14 +48,27 @@ def __getitem__(self, position) -> Verb: """ return self._nested_verbs[position] + def _set_attributes(self, root: ET.Element): + """Set XML attributes on an Element + + Args: + root (ET.Element): XML Element to add attributes to + """ + if self._attributes: + for key, value in self._attributes.items(): + if value: + root.set(key, value) + def _to_etree_element(self) -> ET.Element: + """Generate an ET.Element object from a Verb Object + + Returns: + ET.Element: ET.Element representation of Verb + """ root = ET.Element(self._tag) if self._content: root.text = self._content - if self._attributes: - for key, value in self._attributes: - if value: - root.set(key, value) + self._set_attributes(root) if self._nested_verbs: for verb in self._nested_verbs: root.append(verb._to_etree_element()) @@ -70,6 +83,7 @@ def _generate_xml(self) -> ET.ElementTree: root = ET.Element(self._tag) if self._content: root.text = self._content + self._set_attributes(root) if self._nested_verbs: for verb in self._nested_verbs: root.append(verb._to_etree_element()) diff --git a/bandwidth/model/bxml/verbs/phone_number.py b/bandwidth/model/bxml/verbs/phone_number.py new file mode 100644 index 00000000..25925d15 --- /dev/null +++ b/bandwidth/model/bxml/verbs/phone_number.py @@ -0,0 +1,64 @@ +""" +phone_number.py + +Bandwidth's PhoneNumber BXML verb + +@copyright Bandwidth INC +""" +from ..verb import Verb + + +class PhoneNumber(Verb): + + def __init__( + self, number, transfer_answer_url="", transfer_answer_method="", + transfer_answer_fallback_url="", transfer_answer_fallback_method="", + transfer_disconnect_url="", transfer_disconnect_method="", username="", + password="", fallback_username="", fallback_password="", tag="" + ): + """Initialize a verb + + Args: + phone_number (_type_): _description_ + transfer_answer_url (str, optional): _description_. Defaults to "". + transfer_answer_method (str, optional): _description_. Defaults to "". + transfer_answer_fallback_url (str, optional): _description_. Defaults to "". + transfer_answer_fallback_method (str, optional): _description_. Defaults to "". + transfer_disconnect_url (str, optional): _description_. Defaults to "". + transfer_disconnect_method (str, optional): _description_. Defaults to "". + username (str, optional): _description_. Defaults to "". + password (str, optional): _description_. Defaults to "". + fallback_username (str, optional): _description_. Defaults to "". + fallback_password (str, optional): _description_. Defaults to "". + tag (str, optional): _description_. Defaults to "". + """ + self.attributes = { + "transferAnswerUrl": transfer_answer_url, + "transferAnswerMethod": transfer_answer_method, + "transferAnswerFallbackUrl": transfer_answer_fallback_url, + "transferAnswerFallbackMethod": transfer_answer_fallback_method, + "transferDisconnectUrl": transfer_disconnect_url, + "transferDisconnectMethod": transfer_disconnect_method, + "username": username, + "password": password, + "fallbackUsername": fallback_username, + "fallbackPassword": fallback_password, + "tag": tag + } + super().__init__( + tag="PhoneNumber", + content=number, + attributes=self.attributes, + nested_verbs=None + ) + + def add_verb(self, verb: Verb): + """Adding verbs is not allowed for + + Args: + verb (Verb): BXML verb + + Raises: + AttributeError: This method is not allowed for + """ + raise AttributeError('Adding verbs is not supported by ') diff --git a/bandwidth/model/bxml/verbs/tag.py b/bandwidth/model/bxml/verbs/tag.py index 4ee9958c..8db67cd7 100644 --- a/bandwidth/model/bxml/verbs/tag.py +++ b/bandwidth/model/bxml/verbs/tag.py @@ -16,7 +16,7 @@ def __init__(self, content=""): Args: content (str, optional): Custom tag value. Defaults to "". """ - super().__init__(tag="Tag", attributes=None, content=content, nested_verbs=None) + super().__init__(tag="Tag", content=content, attributes=None, nested_verbs=None) def add_verb(self, verb: Verb): """Adding verbs is not allowed for diff --git a/test/unit/bxml/test_phone_number.py b/test/unit/bxml/test_phone_number.py new file mode 100644 index 00000000..848500ff --- /dev/null +++ b/test/unit/bxml/test_phone_number.py @@ -0,0 +1,40 @@ +""" +test_phone_number.py + +Unit tests for the BXML verb + +@copyright Bandwidth Inc. +""" +import pytest +import unittest + +from bandwidth.model.bxml.verb import Verb +from bandwidth.model.bxml.verbs.phone_number import PhoneNumber + + +class TestPhoneNumber(unittest.TestCase): + + def setUp(self): + self.phone_number = PhoneNumber( + number="+19195551234", + transfer_answer_url="https://example.com/webhooks/transfer_answer", + transfer_answer_method="POST", + transfer_answer_fallback_url="", + transfer_answer_fallback_method="", + transfer_disconnect_url="", + transfer_disconnect_method="", + username="", + password="", + fallback_username="", + fallback_password="", + tag="test" + ) + self.test_verb = Verb(tag="test") + + def test_to_bxml(self): + expected = '+19195551234' + assert(expected == self.phone_number.to_bxml()) + + def test_add_verb(self): + with pytest.raises(AttributeError): + self.phone_number.add_verb(self.test_verb) From c7ecbf21520858537ef9a00d2f6dffa513aa5916 Mon Sep 17 00:00:00 2001 From: AJ Rice <53190766+ajrice6713@users.noreply.github.com> Date: Wed, 28 Sep 2022 12:37:07 -0400 Subject: [PATCH 02/11] DX-2908 Refactor ` BXML --- bandwidth/model/bxml/verbs/sip_uri.py | 66 +++++++++++++++++++++++++++ test/unit/bxml/test_sip_uri.py | 41 +++++++++++++++++ 2 files changed, 107 insertions(+) create mode 100644 bandwidth/model/bxml/verbs/sip_uri.py create mode 100644 test/unit/bxml/test_sip_uri.py diff --git a/bandwidth/model/bxml/verbs/sip_uri.py b/bandwidth/model/bxml/verbs/sip_uri.py new file mode 100644 index 00000000..fc58e60f --- /dev/null +++ b/bandwidth/model/bxml/verbs/sip_uri.py @@ -0,0 +1,66 @@ +""" +sip_uri.py + +Bandwidth's SipUri BXML verb + +@copyright Bandwidth INC +""" +from ..verb import Verb + + +class SipUri(Verb): + + def __init__( + self, uri, uui="", transfer_answer_url="", transfer_answer_method="", + transfer_answer_fallback_url="", transfer_answer_fallback_method="", + transfer_disconnect_url="", transfer_disconnect_method="", username="", + password="", fallback_username="", fallback_password="", tag="" + ): + """Initialize a verb + + Args: + uri (_type_): The value of the User-To-User header to send within the initial INVITE. Must include the encoding parameter as specified in RFC 7433. Only base64 and jwt encoding are currently allowed. This value, including the encoding specifier, may not exceed 256 characters. + uui (str, optional): _description_. Defaults to "". + transfer_answer_url (str, optional): _description_. Defaults to "". + transfer_answer_method (str, optional): _description_. Defaults to "". + transfer_answer_fallback_url (str, optional): _description_. Defaults to "". + transfer_answer_fallback_method (str, optional): _description_. Defaults to "". + transfer_disconnect_url (str, optional): _description_. Defaults to "". + transfer_disconnect_method (str, optional): _description_. Defaults to "". + username (str, optional): _description_. Defaults to "". + password (str, optional): _description_. Defaults to "". + fallback_username (str, optional): _description_. Defaults to "". + fallback_password (str, optional): _description_. Defaults to "". + tag (str, optional): _description_. Defaults to "". + """ + self.attributes = { + "uui": uui, + "transferAnswerUrl": transfer_answer_url, + "transferAnswerMethod": transfer_answer_method, + "transferAnswerFallbackUrl": transfer_answer_fallback_url, + "transferAnswerFallbackMethod": transfer_answer_fallback_method, + "transferDisconnectUrl": transfer_disconnect_url, + "transferDisconnectMethod": transfer_disconnect_method, + "username": username, + "password": password, + "fallbackUsername": fallback_username, + "fallbackPassword": fallback_password, + "tag": tag + } + super().__init__( + tag="SipUri", + content=uri, + attributes=self.attributes, + nested_verbs=None + ) + + def add_verb(self, verb: Verb): + """Adding verbs is not allowed for + + Args: + verb (Verb): BXML verb + + Raises: + AttributeError: This method is not allowed for + """ + raise AttributeError('Adding verbs is not supported by ') diff --git a/test/unit/bxml/test_sip_uri.py b/test/unit/bxml/test_sip_uri.py new file mode 100644 index 00000000..fe9d97c6 --- /dev/null +++ b/test/unit/bxml/test_sip_uri.py @@ -0,0 +1,41 @@ +""" +test_sip_uri.py + +Unit tests for the BXML verb + +@copyright Bandwidth Inc. +""" +import pytest +import unittest + +from bandwidth.model.bxml.verb import Verb +from bandwidth.model.bxml.verbs.sip_uri import SipUri + + +class TestPhoneNumber(unittest.TestCase): + + def setUp(self): + self.phone_number = SipUri( + uri="sip:1-999-123-4567@voip-provider.example.net", + uui="abc123", + transfer_answer_url="https://example.com/webhooks/transfer_answer", + transfer_answer_method="POST", + transfer_answer_fallback_url="", + transfer_answer_fallback_method="", + transfer_disconnect_url="", + transfer_disconnect_method="", + username="", + password="", + fallback_username="", + fallback_password="", + tag="test" + ) + self.test_verb = Verb(tag="test") + + def test_to_bxml(self): + expected = 'sip:1-999-123-4567@voip-provider.example.net' + assert(expected == self.phone_number.to_bxml()) + + def test_add_verb(self): + with pytest.raises(AttributeError): + self.phone_number.add_verb(self.test_verb) From 09dc66e05cf7c02b482408fcb49ba2940d2dbe64 Mon Sep 17 00:00:00 2001 From: AJ Rice <53190766+ajrice6713@users.noreply.github.com> Date: Wed, 28 Sep 2022 12:58:57 -0400 Subject: [PATCH 03/11] DX-2918 Refactor `` BXML --- bandwidth/model/bxml/verbs/__init__.py | 3 ++ bandwidth/model/bxml/verbs/transfer.py | 63 ++++++++++++++++++++++++++ test/unit/bxml/test_transfer.py | 49 ++++++++++++++++++++ 3 files changed, 115 insertions(+) create mode 100644 bandwidth/model/bxml/verbs/transfer.py create mode 100644 test/unit/bxml/test_transfer.py diff --git a/bandwidth/model/bxml/verbs/__init__.py b/bandwidth/model/bxml/verbs/__init__.py index b1716479..9fa7c806 100644 --- a/bandwidth/model/bxml/verbs/__init__.py +++ b/bandwidth/model/bxml/verbs/__init__.py @@ -1 +1,4 @@ +from .phone_number import PhoneNumber +from .sip_uri import SipUri from .tag import Tag +from .transfer import Transfer diff --git a/bandwidth/model/bxml/verbs/transfer.py b/bandwidth/model/bxml/verbs/transfer.py new file mode 100644 index 00000000..2dbddd34 --- /dev/null +++ b/bandwidth/model/bxml/verbs/transfer.py @@ -0,0 +1,63 @@ +""" +transfer.py + +Bandwidth's Transfer BXML verb + +@copyright Bandwidth INC +""" +from typing import Union + +from ..verb import Verb +from ..verbs.phone_number import PhoneNumber +from ..verbs.sip_uri import SipUri + + +class Transfer(Verb): + + def __init__( + self, transfer_to: list[PhoneNumber, SipUri] = [], + transfer_caller_id="", call_timeout="", + transfer_complete_url="", transfer_complete_method="", + transfer_complete_fallback_url="", + transfer_complete_fallback_method="", username="", + password="", fallback_username="", fallback_password="", + tag="" + ): + """Initialize a verb + + Args: + transfer_to (list[PhoneNumber, SipUri], optional): _description_. Defaults to []. + transfer_caller_id (str, optional): _description_. Defaults to "". + call_timeout (str, optional): _description_. Defaults to "". + transfer_complete_url (str, optional): _description_. Defaults to "". + transfer_complete_method (str, optional): _description_. Defaults to "". + transfer_complete_fallback_url (str, optional): _description_. Defaults to "". + transfer_complete_fallback_method (str, optional): _description_. Defaults to "". + username (str, optional): _description_. Defaults to "". + password (str, optional): _description_. Defaults to "". + fallback_username (str, optional): _description_. Defaults to "". + fallback_password (str, optional): _description_. Defaults to "". + tag (str, optional): _description_. Defaults to "". + """ + self.attributes = { + "transferCallerId": transfer_caller_id, + "callTimeout": call_timeout, + "transferCompleteUrl": transfer_complete_url, + "transferCompleteMethod": transfer_complete_method, + "transferCompleteFallbackUrl": transfer_complete_fallback_url, + "transferCompleteFallbackMethod": transfer_complete_fallback_method, + "username": username, + "password": password, + "fallbackUsername": fallback_username, + "fallbackPassword": fallback_password, + "tag": tag + } + super().__init__( + tag="Transfer", + content=None, + attributes=self.attributes, + nested_verbs=transfer_to + ) + + def add_transfer_recipient(self, recipient: Union[PhoneNumber, SipUri]): + super().add_verb(recipient) diff --git a/test/unit/bxml/test_transfer.py b/test/unit/bxml/test_transfer.py new file mode 100644 index 00000000..471c676b --- /dev/null +++ b/test/unit/bxml/test_transfer.py @@ -0,0 +1,49 @@ +""" +test_transfer.py + +Unit tests for the BXML verb + +@copyright Bandwidth Inc. +""" +import pytest +import unittest + +from bandwidth.model.bxml.verbs.transfer import Transfer +from bandwidth.model.bxml.verbs.phone_number import PhoneNumber +from bandwidth.model.bxml.verbs.sip_uri import SipUri + + +class TestTransfer(unittest.TestCase): + + def setUp(self): + self.sip_uri = SipUri( + uri="sip@bw.com", + uui="test" + ) + self.phone_number = PhoneNumber( + number="+19195551234", + tag="test" + ) + self.transfer = Transfer( + transfer_to=[self.sip_uri], + transfer_caller_id = "+19195554321", + call_timeout = "15", + transfer_complete_url = "", + transfer_complete_method = "", + transfer_complete_fallback_url = "", + transfer_complete_fallback_method = "", + username = "", + password = "", + fallback_username = "", + fallback_password = "", + tag = "test" + ) + + def test_to_bxml(self): + expected = 'sip@bw.com' + assert(expected == self.transfer.to_bxml()) + + def test_add_verb(self): + expected = 'sip@bw.com+19195551234' + self.transfer.add_transfer_recipient(self.phone_number) + assert(expected == self.transfer.to_bxml()) From 42aca5c6a3c7f5ef9c11711141a2c3cd03f1ac47 Mon Sep 17 00:00:00 2001 From: AJ Rice <53190766+ajrice6713@users.noreply.github.com> Date: Wed, 28 Sep 2022 13:21:21 -0400 Subject: [PATCH 04/11] Cleanup and update descriptions in docstrings --- bandwidth/model/bxml/verb.py | 2 +- bandwidth/model/bxml/verbs/phone_number.py | 32 +++++------ bandwidth/model/bxml/verbs/sip_uri.py | 34 ++++++------ bandwidth/model/bxml/verbs/transfer.py | 62 +++++++++++++++------- test/unit/bxml/test_phone_number.py | 12 +---- test/unit/bxml/test_sip_uri.py | 8 --- test/unit/bxml/test_transfer.py | 8 --- 7 files changed, 79 insertions(+), 79 deletions(-) diff --git a/bandwidth/model/bxml/verb.py b/bandwidth/model/bxml/verb.py index bd5ef9de..7ac3a00d 100644 --- a/bandwidth/model/bxml/verb.py +++ b/bandwidth/model/bxml/verb.py @@ -56,7 +56,7 @@ def _set_attributes(self, root: ET.Element): """ if self._attributes: for key, value in self._attributes.items(): - if value: + if value is not None: root.set(key, value) def _to_etree_element(self) -> ET.Element: diff --git a/bandwidth/model/bxml/verbs/phone_number.py b/bandwidth/model/bxml/verbs/phone_number.py index 25925d15..d2de4c0d 100644 --- a/bandwidth/model/bxml/verbs/phone_number.py +++ b/bandwidth/model/bxml/verbs/phone_number.py @@ -11,26 +11,26 @@ class PhoneNumber(Verb): def __init__( - self, number, transfer_answer_url="", transfer_answer_method="", - transfer_answer_fallback_url="", transfer_answer_fallback_method="", - transfer_disconnect_url="", transfer_disconnect_method="", username="", - password="", fallback_username="", fallback_password="", tag="" + self, number: str, transfer_answer_url: str=None, transfer_answer_method: str=None, + transfer_answer_fallback_url: str=None, transfer_answer_fallback_method: str=None, + transfer_disconnect_url: str=None, transfer_disconnect_method: str=None, username: str=None, + password: str=None, fallback_username: str=None, fallback_password: str=None, tag: str=None ): """Initialize a verb Args: - phone_number (_type_): _description_ - transfer_answer_url (str, optional): _description_. Defaults to "". - transfer_answer_method (str, optional): _description_. Defaults to "". - transfer_answer_fallback_url (str, optional): _description_. Defaults to "". - transfer_answer_fallback_method (str, optional): _description_. Defaults to "". - transfer_disconnect_url (str, optional): _description_. Defaults to "". - transfer_disconnect_method (str, optional): _description_. Defaults to "". - username (str, optional): _description_. Defaults to "". - password (str, optional): _description_. Defaults to "". - fallback_username (str, optional): _description_. Defaults to "". - fallback_password (str, optional): _description_. Defaults to "". - tag (str, optional): _description_. Defaults to "". + phone_number (str): A phone number to transfer the call to. Value must be in E.164 format (e.g. +15555555555). + transfer_answer_url (str, optional): URL, if any, to send the Transfer Answer event to and request BXML to be executed for the called party before the call is bridged. May be a relative URL. Defaults to None. + transfer_answer_method (str, optional): The HTTP method to use for the request to transferAnswerUrl. GET or POST. Default value is POST. Defaults to None. + transfer_answer_fallback_url (str, optional): A fallback url which, if provided, will be used to retry the Transfer Answer callback delivery in case transferAnswerUrl fails to respond. Defaults to None. + transfer_answer_fallback_method (str, optional): The HTTP method to use to deliver the Transfer Answer callback to transferAnswerFallbackUrl. GET or POST. Default value is POST. Defaults to None. + transfer_disconnect_url (str, optional): URL, if any, to send the Transfer Disconnect event to. This event will be sent regardless of how the transfer ends and may not be responded to with BXML. May be a relative URL. Defaults to None. + transfer_disconnect_method (str, optional): The HTTP method to use for the request to transferDisconnectUrl. GET or POST. Default value is POST. Defaults to Defaults to Defaults to None. + username (str, optional): The username to send in the HTTP request to transferAnswerUrl and transferDisconnectUrl. Defaults to Defaults to None. + password (str, optional): The password to send in the HTTP request to transferAnswerUrl and transferDisconnectUrl. Defaults to Defaults to None. + fallback_username (str, optional): The username to send in the HTTP request to transferAnswerFallbackUrl. Defaults to None. + fallback_password (str, optional): The password to send in the HTTP request to transferAnswerFallbackUrl. Defaults to None. + tag (str, optional): A custom string that will be sent with these and all future callbacks unless overwritten by a future tag attribute or cleared. May be cleared by setting tag="" Max length 256 characters. Defaults to None. """ self.attributes = { "transferAnswerUrl": transfer_answer_url, diff --git a/bandwidth/model/bxml/verbs/sip_uri.py b/bandwidth/model/bxml/verbs/sip_uri.py index fc58e60f..f622e7c9 100644 --- a/bandwidth/model/bxml/verbs/sip_uri.py +++ b/bandwidth/model/bxml/verbs/sip_uri.py @@ -11,27 +11,27 @@ class SipUri(Verb): def __init__( - self, uri, uui="", transfer_answer_url="", transfer_answer_method="", - transfer_answer_fallback_url="", transfer_answer_fallback_method="", - transfer_disconnect_url="", transfer_disconnect_method="", username="", - password="", fallback_username="", fallback_password="", tag="" + self, uri: str, uui: str=None, transfer_answer_url: str=None, transfer_answer_method: str=None, + transfer_answer_fallback_url: str=None, transfer_answer_fallback_method: str=None, + transfer_disconnect_url: str=None, transfer_disconnect_method: str=None, username: str=None, + password: str=None, fallback_username: str=None, fallback_password: str=None, tag: str=None ): """Initialize a verb Args: - uri (_type_): The value of the User-To-User header to send within the initial INVITE. Must include the encoding parameter as specified in RFC 7433. Only base64 and jwt encoding are currently allowed. This value, including the encoding specifier, may not exceed 256 characters. - uui (str, optional): _description_. Defaults to "". - transfer_answer_url (str, optional): _description_. Defaults to "". - transfer_answer_method (str, optional): _description_. Defaults to "". - transfer_answer_fallback_url (str, optional): _description_. Defaults to "". - transfer_answer_fallback_method (str, optional): _description_. Defaults to "". - transfer_disconnect_url (str, optional): _description_. Defaults to "". - transfer_disconnect_method (str, optional): _description_. Defaults to "". - username (str, optional): _description_. Defaults to "". - password (str, optional): _description_. Defaults to "". - fallback_username (str, optional): _description_. Defaults to "". - fallback_password (str, optional): _description_. Defaults to "". - tag (str, optional): _description_. Defaults to "". + uri (str): A SIP URI to transfer the call to (e.g. sip:user@server.com) + uui (str, optional): he value of the User-To-User header to send within the initial INVITE. Must include the encoding parameter as specified in RFC 7433. Only base64 and jwt encoding are currently allowed. This value, including the encoding specifier, may not exceed 256 characters. Defaults to None. + transfer_answer_url (str, optional): URL, if any, to send the Transfer Answer event to and request BXML to be executed for the called party before the call is bridged. May be a relative URL. Defaults to None. + transfer_answer_method (str, optional): The HTTP method to use for the request to transferAnswerUrl. GET or POST. Default value is POST. Defaults to None. + transfer_answer_fallback_url (str, optional): A fallback url which, if provided, will be used to retry the Transfer Answer callback delivery in case transferAnswerUrl fails to respond. Defaults to None. + transfer_answer_fallback_method (str, optional): The HTTP method to use to deliver the Transfer Answer callback to transferAnswerFallbackUrl. GET or POST. Default value is POST. Defaults to None. + transfer_disconnect_url (str, optional): URL, if any, to send the Transfer Disconnect event to. This event will be sent regardless of how the transfer ends and may not be responded to with BXML. May be a relative URL. Defaults to None. + transfer_disconnect_method (str, optional): The HTTP method to use for the request to transferDisconnectUrl. GET or POST. Default value is POST. Defaults to Defaults to Defaults to None. + username (str, optional): The username to send in the HTTP request to transferAnswerUrl and transferDisconnectUrl. Defaults to Defaults to None. + password (str, optional): The password to send in the HTTP request to transferAnswerUrl and transferDisconnectUrl. Defaults to Defaults to None. + fallback_username (str, optional): The username to send in the HTTP request to transferAnswerFallbackUrl. Defaults to None. + fallback_password (str, optional): The password to send in the HTTP request to transferAnswerFallbackUrl. Defaults to None. + tag (str, optional): A custom string that will be sent with these and all future callbacks unless overwritten by a future tag attribute or cleared. May be cleared by setting tag="" Max length 256 characters. Defaults to None. """ self.attributes = { "uui": uui, diff --git a/bandwidth/model/bxml/verbs/transfer.py b/bandwidth/model/bxml/verbs/transfer.py index 2dbddd34..bdd27627 100644 --- a/bandwidth/model/bxml/verbs/transfer.py +++ b/bandwidth/model/bxml/verbs/transfer.py @@ -16,28 +16,50 @@ class Transfer(Verb): def __init__( self, transfer_to: list[PhoneNumber, SipUri] = [], - transfer_caller_id="", call_timeout="", - transfer_complete_url="", transfer_complete_method="", - transfer_complete_fallback_url="", - transfer_complete_fallback_method="", username="", - password="", fallback_username="", fallback_password="", - tag="" + transfer_caller_id: str=None, call_timeout: str=None, + transfer_complete_url: str=None, transfer_complete_method: str=None, + transfer_complete_fallback_url: str=None, + transfer_complete_fallback_method: str=None, username: str=None, + password: str=None, fallback_username: str=None, + fallback_password: str=None, tag: str=None, + diversion_treatment: str=None, diversion_reason: str=None ): """Initialize a verb Args: - transfer_to (list[PhoneNumber, SipUri], optional): _description_. Defaults to []. - transfer_caller_id (str, optional): _description_. Defaults to "". - call_timeout (str, optional): _description_. Defaults to "". - transfer_complete_url (str, optional): _description_. Defaults to "". - transfer_complete_method (str, optional): _description_. Defaults to "". - transfer_complete_fallback_url (str, optional): _description_. Defaults to "". - transfer_complete_fallback_method (str, optional): _description_. Defaults to "". - username (str, optional): _description_. Defaults to "". - password (str, optional): _description_. Defaults to "". - fallback_username (str, optional): _description_. Defaults to "". - fallback_password (str, optional): _description_. Defaults to "". - tag (str, optional): _description_. Defaults to "". + transfer_to (list[PhoneNumber, SipUri], optional): List of recipients to transfer a call to. Defaults to []. + transfer_caller_id (str, optional): The caller ID to use when the call is transferred, if different. Must be in E.164 format (e.g. +15555555555) or be one of the following strings Restricted, Anonymous, Private, or Unavailable. Leave as default to pass along the number of the remote party. Defaults to None. + call_timeout (str, optional):The timeout (in seconds) for the callee to answer the call after it starts ringing. If the call does not start ringing within 30s, the call will be cancelled regardless of this value. Range: decimal values between 1 - 300. Default value is 30 seconds. Defaults to None. + transfer_complete_url (str, optional): URL to send the Transfer Complete event to and request new BXML. Optional but recommended. See below for further details. May be a relative URL. Defaults to None. + transfer_complete_method (str, optional): The HTTP method to use for the request to transferCompleteUrl. GET or POST. Default value is POST. Defaults to None. + transfer_complete_fallback_url (str, optional): A fallback url which, if provided, will be used to retry the Transfer Complete callback delivery in case transferCompleteUrl fails to respond. Defaults to None. + transfer_complete_fallback_method (str, optional): The HTTP method to use to deliver the Transfer Complete callback to transferCompleteFallbackUrl. GET or POST. Default value is POST. Defaults to None. + username (str, optional): The username to send in the HTTP request to transferCompleteUrl. Defaults to None. + password (str, optional): The password to send in the HTTP request to transferCompleteUrl. Defaults to None. + fallback_username (str, optional): The username to send in the HTTP request to transferCompleteFallbackUrl. Defaults to None. + fallback_password (str, optional): The password to send in the HTTP request to transferCompleteFallbackUrl. Defaults to None. + tag (str, optional): A custom string that will be sent with this and all future callbacks unless overwritten by a future tag attribute or cleared. May be cleared by setting tag="" Max length 256 characters. Defaults to None. + diversion_treatment (str, optional): Can be any of the following: + none: No diversion headers are sent on the outbound leg of the transferred call. + propagate: Copy the Diversion header from the inbound leg to the outbound leg. Ignored if there is no Diversion header present on the inbound leg. + stack: After propagating any Diversion header from the inbound leg to the outbound leg, stack on top another Diversion header based on the Request-URI of the inbound call. + + Defaults to none. If diversionTreatment is not specified, no diversion header will be included for the transfer even if one came with the inbound call. Defaults to None. + diversion_reason (str, optional): Can be any of the following values: + unknown + user-busy + no-answer + unavailable + unconditional + time-of-day + do-not-disturb + deflection + follow-me + out-of-service + away + + This parameter is considered only when diversionTreatment is set to stack. Defaults is unknown. + Defaults to None. """ self.attributes = { "transferCallerId": transfer_caller_id, @@ -50,7 +72,9 @@ def __init__( "password": password, "fallbackUsername": fallback_username, "fallbackPassword": fallback_password, - "tag": tag + "tag": tag, + "diversionReason": diversion_reason, + "diversionTreatment": diversion_treatment } super().__init__( tag="Transfer", diff --git a/test/unit/bxml/test_phone_number.py b/test/unit/bxml/test_phone_number.py index 848500ff..4e24915f 100644 --- a/test/unit/bxml/test_phone_number.py +++ b/test/unit/bxml/test_phone_number.py @@ -19,20 +19,12 @@ def setUp(self): number="+19195551234", transfer_answer_url="https://example.com/webhooks/transfer_answer", transfer_answer_method="POST", - transfer_answer_fallback_url="", - transfer_answer_fallback_method="", - transfer_disconnect_url="", - transfer_disconnect_method="", - username="", - password="", - fallback_username="", - fallback_password="", - tag="test" + tag="" ) self.test_verb = Verb(tag="test") def test_to_bxml(self): - expected = '+19195551234' + expected = '+19195551234' assert(expected == self.phone_number.to_bxml()) def test_add_verb(self): diff --git a/test/unit/bxml/test_sip_uri.py b/test/unit/bxml/test_sip_uri.py index fe9d97c6..65f0c2a9 100644 --- a/test/unit/bxml/test_sip_uri.py +++ b/test/unit/bxml/test_sip_uri.py @@ -20,14 +20,6 @@ def setUp(self): uui="abc123", transfer_answer_url="https://example.com/webhooks/transfer_answer", transfer_answer_method="POST", - transfer_answer_fallback_url="", - transfer_answer_fallback_method="", - transfer_disconnect_url="", - transfer_disconnect_method="", - username="", - password="", - fallback_username="", - fallback_password="", tag="test" ) self.test_verb = Verb(tag="test") diff --git a/test/unit/bxml/test_transfer.py b/test/unit/bxml/test_transfer.py index 471c676b..ea431d8d 100644 --- a/test/unit/bxml/test_transfer.py +++ b/test/unit/bxml/test_transfer.py @@ -28,14 +28,6 @@ def setUp(self): transfer_to=[self.sip_uri], transfer_caller_id = "+19195554321", call_timeout = "15", - transfer_complete_url = "", - transfer_complete_method = "", - transfer_complete_fallback_url = "", - transfer_complete_fallback_method = "", - username = "", - password = "", - fallback_username = "", - fallback_password = "", tag = "test" ) From 73b15d6e746f33622e7458aa38acaf89fb475fb6 Mon Sep 17 00:00:00 2001 From: AJ Rice <53190766+ajrice6713@users.noreply.github.com> Date: Wed, 28 Sep 2022 13:28:16 -0400 Subject: [PATCH 05/11] Fix WebRTC Utilities Test --- bandwidth/utilities/web_rtc.py | 6 +++--- test/unit/test_webrtc_utilities.py | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/bandwidth/utilities/web_rtc.py b/bandwidth/utilities/web_rtc.py index 8358e09d..9943f3e0 100644 --- a/bandwidth/utilities/web_rtc.py +++ b/bandwidth/utilities/web_rtc.py @@ -15,11 +15,11 @@ def _generate_transfer_model(device_token: str, voice_call_id: str, sip_uri: str """ uui = "".join(voice_call_id.split("-")[1::]) sip_uri = SipUri( - uui=f"{uui};encoding=base64,{device_token};encoding=jwt", - uri=sip_uri + uri=sip_uri, + uui=f"{uui};encoding=base64,{device_token};encoding=jwt" ) transfer = Transfer( - sip_uris=[sip_uri] + transfer_to=[sip_uri] ) return transfer diff --git a/test/unit/test_webrtc_utilities.py b/test/unit/test_webrtc_utilities.py index 9a60d81d..9bc536d7 100644 --- a/test/unit/test_webrtc_utilities.py +++ b/test/unit/test_webrtc_utilities.py @@ -13,7 +13,7 @@ def test_generate_transfer_bxml(self): assert actual == expected def test_generate_transfer_bxml_document(self): - expected = 'sip:sipx.webrtc.bandwidth.com:5060' + expected = "\nsip:sipx.webrtc.bandwidth.com:5060" actual = generate_transfer_bxml( 'asdf', 'c-93d6f3c0-be584596-0b74-4fa2-8015-d8ede84bd1a4') assert actual == expected From 92f8e2dbb799dffded67b2ee4ef230fa171e36ad Mon Sep 17 00:00:00 2001 From: AJ Rice <53190766+ajrice6713@users.noreply.github.com> Date: Wed, 28 Sep 2022 13:51:10 -0400 Subject: [PATCH 06/11] import List type for Python < 3.9 https://stackoverflow.com/a/63460173/11633328 --- bandwidth/model/bxml/root.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/bandwidth/model/bxml/root.py b/bandwidth/model/bxml/root.py index 759a7c32..0ed28585 100644 --- a/bandwidth/model/bxml/root.py +++ b/bandwidth/model/bxml/root.py @@ -5,6 +5,7 @@ @copyright Bandwidth INC """ +from typing import List import xml.etree.ElementTree as ET from bandwidth.model.bxml.verb import Verb @@ -14,7 +15,7 @@ class Root: """Base class for BXML roots """ - def __init__(self, tag: str, nested_verbs: list[Verb] = None): + def __init__(self, tag: str, nested_verbs: List[Verb] = None): """Initialize instance of class Args: From 5df7c57b381f166ca20cafeae7065d7553ec40dd Mon Sep 17 00:00:00 2001 From: AJ Rice <53190766+ajrice6713@users.noreply.github.com> Date: Wed, 28 Sep 2022 13:59:28 -0400 Subject: [PATCH 07/11] Fix other `List` instances --- bandwidth/model/bxml/bxml.py | 4 +++- bandwidth/model/bxml/response.py | 4 +++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/bandwidth/model/bxml/bxml.py b/bandwidth/model/bxml/bxml.py index ca39d964..ed7b17b6 100644 --- a/bandwidth/model/bxml/bxml.py +++ b/bandwidth/model/bxml/bxml.py @@ -5,12 +5,14 @@ @copyright Bandwidth INC """ +from typing import List + from .root import Root from .verb import Verb class Bxml(Root): - def __init__(self, nested_verbs: list[Verb] = []): + def __init__(self, nested_verbs: List[Verb] = []): """Initialize an instance of the root Args: diff --git a/bandwidth/model/bxml/response.py b/bandwidth/model/bxml/response.py index 0849e039..f269377e 100644 --- a/bandwidth/model/bxml/response.py +++ b/bandwidth/model/bxml/response.py @@ -5,12 +5,14 @@ @copyright Bandwidth INC """ +from typing import List + from .root import Root from .verb import Verb class Response(Root): - def __init__(self, nested_verbs: list[Verb] = []): + def __init__(self, nested_verbs: List[Verb] = []): """Initialize an instance of the root Args: From 9531be539ee52ed9986838f31059cae41ca7334d Mon Sep 17 00:00:00 2001 From: AJ Rice <53190766+ajrice6713@users.noreply.github.com> Date: Wed, 28 Sep 2022 14:00:40 -0400 Subject: [PATCH 08/11] Missed one `List` --- bandwidth/model/bxml/verbs/transfer.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/bandwidth/model/bxml/verbs/transfer.py b/bandwidth/model/bxml/verbs/transfer.py index bdd27627..9421d41b 100644 --- a/bandwidth/model/bxml/verbs/transfer.py +++ b/bandwidth/model/bxml/verbs/transfer.py @@ -5,7 +5,7 @@ @copyright Bandwidth INC """ -from typing import Union +from typing import Union, List from ..verb import Verb from ..verbs.phone_number import PhoneNumber @@ -15,7 +15,7 @@ class Transfer(Verb): def __init__( - self, transfer_to: list[PhoneNumber, SipUri] = [], + self, transfer_to: List[PhoneNumber, SipUri] = [], transfer_caller_id: str=None, call_timeout: str=None, transfer_complete_url: str=None, transfer_complete_method: str=None, transfer_complete_fallback_url: str=None, From f5d428cb940925640df865b00247f6c25efdd2af Mon Sep 17 00:00:00 2001 From: AJ Rice <53190766+ajrice6713@users.noreply.github.com> Date: Wed, 28 Sep 2022 14:02:31 -0400 Subject: [PATCH 09/11] Add `Union` to support multiple types in List --- bandwidth/model/bxml/verbs/transfer.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bandwidth/model/bxml/verbs/transfer.py b/bandwidth/model/bxml/verbs/transfer.py index 9421d41b..8a43c75b 100644 --- a/bandwidth/model/bxml/verbs/transfer.py +++ b/bandwidth/model/bxml/verbs/transfer.py @@ -15,7 +15,7 @@ class Transfer(Verb): def __init__( - self, transfer_to: List[PhoneNumber, SipUri] = [], + self, transfer_to: List[Union[PhoneNumber, SipUri]] = [], transfer_caller_id: str=None, call_timeout: str=None, transfer_complete_url: str=None, transfer_complete_method: str=None, transfer_complete_fallback_url: str=None, From dd434c56be311d6db965733a5ed427e172cf3d52 Mon Sep 17 00:00:00 2001 From: AJ Rice <53190766+ajrice6713@users.noreply.github.com> Date: Wed, 28 Sep 2022 15:19:33 -0400 Subject: [PATCH 10/11] Construct verbs in alphabetical order to satisfy python@3.7 tests --- test/unit/bxml/test_phone_number.py | 6 +++--- test/unit/bxml/test_sip_uri.py | 8 ++++---- test/unit/bxml/test_transfer.py | 8 ++++---- 3 files changed, 11 insertions(+), 11 deletions(-) diff --git a/test/unit/bxml/test_phone_number.py b/test/unit/bxml/test_phone_number.py index 4e24915f..e30001f8 100644 --- a/test/unit/bxml/test_phone_number.py +++ b/test/unit/bxml/test_phone_number.py @@ -17,14 +17,14 @@ class TestPhoneNumber(unittest.TestCase): def setUp(self): self.phone_number = PhoneNumber( number="+19195551234", - transfer_answer_url="https://example.com/webhooks/transfer_answer", + tag="", transfer_answer_method="POST", - tag="" + transfer_answer_url="https://example.com/webhooks/transfer_answer" ) self.test_verb = Verb(tag="test") def test_to_bxml(self): - expected = '+19195551234' + expected = '+19195551234' assert(expected == self.phone_number.to_bxml()) def test_add_verb(self): diff --git a/test/unit/bxml/test_sip_uri.py b/test/unit/bxml/test_sip_uri.py index 65f0c2a9..212f25e4 100644 --- a/test/unit/bxml/test_sip_uri.py +++ b/test/unit/bxml/test_sip_uri.py @@ -17,15 +17,15 @@ class TestPhoneNumber(unittest.TestCase): def setUp(self): self.phone_number = SipUri( uri="sip:1-999-123-4567@voip-provider.example.net", - uui="abc123", - transfer_answer_url="https://example.com/webhooks/transfer_answer", + tag="test", transfer_answer_method="POST", - tag="test" + transfer_answer_url="https://example.com/webhooks/transfer_answer", + uui="abc123" ) self.test_verb = Verb(tag="test") def test_to_bxml(self): - expected = 'sip:1-999-123-4567@voip-provider.example.net' + expected = 'sip:1-999-123-4567@voip-provider.example.net' assert(expected == self.phone_number.to_bxml()) def test_add_verb(self): diff --git a/test/unit/bxml/test_transfer.py b/test/unit/bxml/test_transfer.py index ea431d8d..6c32d7d2 100644 --- a/test/unit/bxml/test_transfer.py +++ b/test/unit/bxml/test_transfer.py @@ -26,16 +26,16 @@ def setUp(self): ) self.transfer = Transfer( transfer_to=[self.sip_uri], - transfer_caller_id = "+19195554321", call_timeout = "15", - tag = "test" + tag = "test", + transfer_caller_id = "+19195554321" ) def test_to_bxml(self): - expected = 'sip@bw.com' + expected = 'sip@bw.com' assert(expected == self.transfer.to_bxml()) def test_add_verb(self): - expected = 'sip@bw.com+19195551234' + expected = 'sip@bw.com+19195551234' self.transfer.add_transfer_recipient(self.phone_number) assert(expected == self.transfer.to_bxml()) From 5c81d0c03b5b98f01c111b561b6b03bd4cf104a3 Mon Sep 17 00:00:00 2001 From: AJ Rice <53190766+ajrice6713@users.noreply.github.com> Date: Wed, 28 Sep 2022 15:31:33 -0400 Subject: [PATCH 11/11] Alphabetize attributes dict for python3.7 --- bandwidth/model/bxml/verbs/phone_number.py | 18 +++++++++--------- bandwidth/model/bxml/verbs/sip_uri.py | 18 +++++++++--------- bandwidth/model/bxml/verbs/transfer.py | 20 ++++++++++---------- test/unit/bxml/test_sip_uri.py | 2 +- 4 files changed, 29 insertions(+), 29 deletions(-) diff --git a/bandwidth/model/bxml/verbs/phone_number.py b/bandwidth/model/bxml/verbs/phone_number.py index d2de4c0d..d57decaf 100644 --- a/bandwidth/model/bxml/verbs/phone_number.py +++ b/bandwidth/model/bxml/verbs/phone_number.py @@ -33,17 +33,17 @@ def __init__( tag (str, optional): A custom string that will be sent with these and all future callbacks unless overwritten by a future tag attribute or cleared. May be cleared by setting tag="" Max length 256 characters. Defaults to None. """ self.attributes = { - "transferAnswerUrl": transfer_answer_url, - "transferAnswerMethod": transfer_answer_method, - "transferAnswerFallbackUrl": transfer_answer_fallback_url, + "fallbackPassword": fallback_password, + "fallbackUsername": fallback_username, + "password": password, + "tag": tag, "transferAnswerFallbackMethod": transfer_answer_fallback_method, - "transferDisconnectUrl": transfer_disconnect_url, + "transferAnswerFallbackUrl": transfer_answer_fallback_url, + "transferAnswerMethod": transfer_answer_method, + "transferAnswerUrl": transfer_answer_url, "transferDisconnectMethod": transfer_disconnect_method, - "username": username, - "password": password, - "fallbackUsername": fallback_username, - "fallbackPassword": fallback_password, - "tag": tag + "transferDisconnectUrl": transfer_disconnect_url, + "username": username } super().__init__( tag="PhoneNumber", diff --git a/bandwidth/model/bxml/verbs/sip_uri.py b/bandwidth/model/bxml/verbs/sip_uri.py index f622e7c9..a4273900 100644 --- a/bandwidth/model/bxml/verbs/sip_uri.py +++ b/bandwidth/model/bxml/verbs/sip_uri.py @@ -34,18 +34,18 @@ def __init__( tag (str, optional): A custom string that will be sent with these and all future callbacks unless overwritten by a future tag attribute or cleared. May be cleared by setting tag="" Max length 256 characters. Defaults to None. """ self.attributes = { - "uui": uui, - "transferAnswerUrl": transfer_answer_url, - "transferAnswerMethod": transfer_answer_method, - "transferAnswerFallbackUrl": transfer_answer_fallback_url, + "fallbackPassword": fallback_password, + "fallbackUsername": fallback_username, + "password": password, + "tag": tag, "transferAnswerFallbackMethod": transfer_answer_fallback_method, - "transferDisconnectUrl": transfer_disconnect_url, + "transferAnswerFallbackUrl": transfer_answer_fallback_url, + "transferAnswerMethod": transfer_answer_method, + "transferAnswerUrl": transfer_answer_url, "transferDisconnectMethod": transfer_disconnect_method, + "transferDisconnectUrl": transfer_disconnect_url, "username": username, - "password": password, - "fallbackUsername": fallback_username, - "fallbackPassword": fallback_password, - "tag": tag + "uui": uui } super().__init__( tag="SipUri", diff --git a/bandwidth/model/bxml/verbs/transfer.py b/bandwidth/model/bxml/verbs/transfer.py index 8a43c75b..35215e3f 100644 --- a/bandwidth/model/bxml/verbs/transfer.py +++ b/bandwidth/model/bxml/verbs/transfer.py @@ -62,19 +62,19 @@ def __init__( Defaults to None. """ self.attributes = { - "transferCallerId": transfer_caller_id, "callTimeout": call_timeout, - "transferCompleteUrl": transfer_complete_url, - "transferCompleteMethod": transfer_complete_method, - "transferCompleteFallbackUrl": transfer_complete_fallback_url, - "transferCompleteFallbackMethod": transfer_complete_fallback_method, - "username": username, - "password": password, - "fallbackUsername": fallback_username, + "diversionReason": diversion_reason, + "diversionTreatment": diversion_treatment, "fallbackPassword": fallback_password, + "fallbackUsername": fallback_username, + "password": password, "tag": tag, - "diversionReason": diversion_reason, - "diversionTreatment": diversion_treatment + "transferCallerId": transfer_caller_id, + "transferCompleteFallbackMethod": transfer_complete_fallback_method, + "transferCompleteFallbackUrl": transfer_complete_fallback_url, + "transferCompleteMethod": transfer_complete_method, + "transferCompleteUrl": transfer_complete_url, + "username": username } super().__init__( tag="Transfer", diff --git a/test/unit/bxml/test_sip_uri.py b/test/unit/bxml/test_sip_uri.py index 212f25e4..7e871e08 100644 --- a/test/unit/bxml/test_sip_uri.py +++ b/test/unit/bxml/test_sip_uri.py @@ -12,7 +12,7 @@ from bandwidth.model.bxml.verbs.sip_uri import SipUri -class TestPhoneNumber(unittest.TestCase): +class TestSipUri(unittest.TestCase): def setUp(self): self.phone_number = SipUri(