From d7e3b21d98a2ee9716d10c3033b4f9a2cf7c136d Mon Sep 17 00:00:00 2001 From: jmulford-bw Date: Wed, 27 Jan 2021 20:39:24 +0000 Subject: [PATCH 1/3] New deploy --- .../messaging/controllers/api_controller.py | 140 ++++++++++++++++-- bandwidth/messaging/models/__init__.py | 3 + .../messaging/models/bandwidth_message.py | 26 ++-- .../models/bandwidth_message_item.py | 116 +++++++++++++++ .../models/bandwidth_messages_list.py | 70 +++++++++ bandwidth/messaging/models/media.py | 2 +- bandwidth/messaging/models/message_request.py | 18 ++- bandwidth/messaging/models/page_info.py | 74 +++++++++ bandwidth/voice/__init__.py | 5 +- .../call_engine_modify_conference_request.py | 103 ------------- setup.py | 4 +- tests/__init__.py | 0 tests/controllers/__init__.py | 0 tests/controllers/controller_test_base.py | 31 ++++ tests/http_response_catcher.py | 27 ++++ tests/test_helper.py | 126 ++++++++++++++++ 16 files changed, 603 insertions(+), 142 deletions(-) create mode 100644 bandwidth/messaging/models/bandwidth_message_item.py create mode 100644 bandwidth/messaging/models/bandwidth_messages_list.py create mode 100644 bandwidth/messaging/models/page_info.py delete mode 100644 bandwidth/voice/models/call_engine_modify_conference_request.py create mode 100644 tests/__init__.py create mode 100644 tests/controllers/__init__.py create mode 100644 tests/controllers/controller_test_base.py create mode 100644 tests/http_response_catcher.py create mode 100644 tests/test_helper.py diff --git a/bandwidth/messaging/controllers/api_controller.py b/bandwidth/messaging/controllers/api_controller.py index 764664ef..cfd9ef04 100644 --- a/bandwidth/messaging/controllers/api_controller.py +++ b/bandwidth/messaging/controllers/api_controller.py @@ -13,6 +13,7 @@ from bandwidth.messaging.controllers.base_controller import BaseController from bandwidth.http.auth.messaging_basic_auth import MessagingBasicAuth from bandwidth.messaging.models.media import Media +from bandwidth.messaging.models.bandwidth_messages_list import BandwidthMessagesList from bandwidth.messaging.models.bandwidth_message import BandwidthMessage from bandwidth.messaging.exceptions.messaging_exception import MessagingException @@ -32,9 +33,9 @@ def list_media(self, listMedia Args: - user_id (string): TODO: type description here. - continuation_token (string, optional): TODO: type description - here. + user_id (string): User's account ID + continuation_token (string, optional): Continuation token used to + retrieve subsequent media. Returns: ApiResponse: An object with the response value as well as other @@ -96,8 +97,8 @@ def get_media(self, getMedia Args: - user_id (string): TODO: type description here. - media_id (string): TODO: type description here. + user_id (string): User's account ID + media_id (string): Media ID to retrieve Returns: ApiResponse: An object with the response value as well as other @@ -158,13 +159,15 @@ def upload_media(self, uploadMedia Args: - user_id (string): TODO: type description here. - media_id (string): TODO: type description here. - content_length (long|int): TODO: type description here. + user_id (string): User's account ID + media_id (string): The user supplied custom media ID + content_length (long|int): The size of the entity-body body (typing.BinaryIO): TODO: type description here. - content_type (string, optional): TODO: type description here. - Example: application/octet-stream - cache_control (string, optional): TODO: type description here. + content_type (string, optional): The media type of the + entity-body + cache_control (string, optional): General-header field is used to + specify directives that MUST be obeyed by all caching + mechanisms along the request/response chain. Returns: ApiResponse: An object with the response value as well as other @@ -233,8 +236,8 @@ def delete_media(self, deleteMedia Args: - user_id (string): TODO: type description here. - media_id (string): TODO: type description here. + user_id (string): User's account ID + media_id (string): The media ID to delete Returns: ApiResponse: An object with the response value as well as other @@ -281,16 +284,121 @@ def delete_media(self, # Return appropriate type return ApiResponse(_response) + def get_messages(self, + user_id, + message_id=None, + source_tn=None, + destination_tn=None, + message_status=None, + error_code=None, + from_date_time=None, + to_date_time=None, + page_token=None, + limit=None): + """Does a GET request to /users/{userId}/messages. + + getMessages + + Args: + user_id (string): User's account ID + message_id (string, optional): The ID of the message to search + for. Special characters need to be encoded using URL encoding + source_tn (string, optional): The phone number that sent the + message + destination_tn (string, optional): The phone number that received + the message + message_status (string, optional): The status of the message. One + of RECEIVED, QUEUED, SENDING, SENT, FAILED, DELIVERED, + DLR_EXPIRED + error_code (int, optional): The error code of the message + from_date_time (string, optional): The start of the date range to + search in ISO 8601 format. Uses the message receive time. The + date range to search in is currently 14 days. + to_date_time (string, optional): The end of the date range to + search in ISO 8601 format. Uses the message receive time. The + date range to search in is currently 14 days. + page_token (string, optional): A base64 encoded value used for + pagination of results + limit (int, optional): The maximum records requested in search + result. Default 100. The sum of limit and after cannot be more + than 10000 + + Returns: + ApiResponse: An object with the response value as well as other + useful information such as status codes and headers. + successful operation + + Raises: + APIException: When an error occurs while fetching the data from + the remote API. This exception includes the HTTP Response + code, an error message, and the HTTP body that was received in + the request. + + """ + + # Prepare query URL + _url_path = '/users/{userId}/messages' + _url_path = APIHelper.append_url_with_template_parameters(_url_path, { + 'userId': {'value': user_id, 'encode': False} + }) + _query_builder = self.config.get_base_uri(Server.MESSAGINGDEFAULT) + _query_builder += _url_path + _query_parameters = { + 'messageId': message_id, + 'sourceTn': source_tn, + 'destinationTn': destination_tn, + 'messageStatus': message_status, + 'errorCode': error_code, + 'fromDateTime': from_date_time, + 'toDateTime': to_date_time, + 'pageToken': page_token, + 'limit': limit + } + _query_builder = APIHelper.append_url_with_query_parameters( + _query_builder, + _query_parameters + ) + _query_url = APIHelper.clean_url(_query_builder) + + # Prepare headers + _headers = { + 'accept': 'application/json' + } + + # Prepare and execute request + _request = self.config.http_client.get(_query_url, headers=_headers) + MessagingBasicAuth.apply(self.config, _request) + _response = self.execute_request(_request) + + # Endpoint and global error handling using HTTP status codes. + if _response.status_code == 400: + raise MessagingException('400 Request is malformed or invalid', _response) + elif _response.status_code == 401: + raise MessagingException('401 The specified user does not have access to the account', _response) + elif _response.status_code == 403: + raise MessagingException('403 The user does not have access to this API', _response) + elif _response.status_code == 404: + raise MessagingException('404 Path not found', _response) + elif _response.status_code == 415: + raise MessagingException('415 The content-type of the request is incorrect', _response) + elif _response.status_code == 429: + raise MessagingException('429 The rate limit has been reached', _response) + self.validate_response(_response) + + decoded = APIHelper.json_deserialize(_response.text, BandwidthMessagesList.from_dictionary) + _result = ApiResponse(_response, body=decoded) + return _result + def create_message(self, user_id, - body=None): + body): """Does a POST request to /users/{userId}/messages. createMessage Args: - user_id (string): TODO: type description here. - body (MessageRequest, optional): TODO: type description here. + user_id (string): User's account ID + body (MessageRequest): TODO: type description here. Returns: ApiResponse: An object with the response value as well as other diff --git a/bandwidth/messaging/models/__init__.py b/bandwidth/messaging/models/__init__.py index 46d2bfc0..bbc74dce 100644 --- a/bandwidth/messaging/models/__init__.py +++ b/bandwidth/messaging/models/__init__.py @@ -1,4 +1,7 @@ __all__ = [ + 'bandwidth_messages_list', + 'bandwidth_message_item', + 'page_info', 'media', 'tag', 'deferred_result', diff --git a/bandwidth/messaging/models/bandwidth_message.py b/bandwidth/messaging/models/bandwidth_message.py index 58098c98..04755a20 100644 --- a/bandwidth/messaging/models/bandwidth_message.py +++ b/bandwidth/messaging/models/bandwidth_message.py @@ -14,17 +14,21 @@ class BandwidthMessage(object): TODO: type model description here. Attributes: - id (string): TODO: type description here. - owner (string): TODO: type description here. - application_id (string): TODO: type description here. - time (string): TODO: type description here. - segment_count (int): TODO: type description here. - direction (string): TODO: type description here. - to (list of string): TODO: type description here. - mfrom (string): TODO: type description here. - media (list of string): TODO: type description here. - text (string): TODO: type description here. - tag (string): TODO: type description here. + id (string): The id of the message + owner (string): The Bandwidth phone number associated with the + message + application_id (string): The application ID associated with the + message + time (string): The datetime stamp of the message in ISO 8601 + segment_count (int): The number of segments the original message from + the user is broken into before sending over to carrier networks + direction (string): The direction of the message relative to + Bandwidth. Can be in or out + to (list of string): The phone number recipients of the message + mfrom (string): The phone number the message was sent from + media (list of string): The list of media URLs sent in the message + text (string): The contents of the message + tag (string): The custom string set by the user """ diff --git a/bandwidth/messaging/models/bandwidth_message_item.py b/bandwidth/messaging/models/bandwidth_message_item.py new file mode 100644 index 00000000..04b8f6b0 --- /dev/null +++ b/bandwidth/messaging/models/bandwidth_message_item.py @@ -0,0 +1,116 @@ +# -*- coding: utf-8 -*- + +""" +bandwidth + +This file was automatically generated by APIMATIC v2.0 ( https://apimatic.io ). +""" + + +class BandwidthMessageItem(object): + + """Implementation of the 'BandwidthMessageItem' model. + + TODO: type model description here. + + Attributes: + message_id (string): The message id + account_id (string): The account id of the message + source_tn (string): The source phone number of the message + destination_tn (string): The recipient phone number of the message + message_status (string): The status of the message + message_direction (string): The direction of the message relative to + Bandwidth. INBOUND or OUTBOUND + message_type (string): The type of message. sms or mms + segment_count (int): The number of segments the message was sent as + error_code (int): The numeric error code of the message + receive_time (string): The ISO 8601 datetime of the message + carrier_name (string): The name of the carrier. Not currently + supported for MMS, coming soon + + """ + + # Create a mapping from Model property names to API property names + _names = { + "message_id": 'messageId', + "account_id": 'accountId', + "source_tn": 'sourceTn', + "destination_tn": 'destinationTn', + "message_status": 'messageStatus', + "message_direction": 'messageDirection', + "message_type": 'messageType', + "segment_count": 'segmentCount', + "error_code": 'errorCode', + "receive_time": 'receiveTime', + "carrier_name": 'carrierName' + } + + def __init__(self, + message_id=None, + account_id=None, + source_tn=None, + destination_tn=None, + message_status=None, + message_direction=None, + message_type=None, + segment_count=None, + error_code=None, + receive_time=None, + carrier_name=None): + """Constructor for the BandwidthMessageItem class""" + + # Initialize members of the class + self.message_id = message_id + self.account_id = account_id + self.source_tn = source_tn + self.destination_tn = destination_tn + self.message_status = message_status + self.message_direction = message_direction + self.message_type = message_type + self.segment_count = segment_count + self.error_code = error_code + self.receive_time = receive_time + self.carrier_name = carrier_name + + @classmethod + def from_dictionary(cls, + dictionary): + """Creates an instance of this model from a dictionary + + Args: + dictionary (dictionary): A dictionary representation of the object + as obtained from the deserialization of the server's response. The + keys MUST match property names in the API description. + + Returns: + object: An instance of this structure class. + + """ + if dictionary is None: + return None + + # Extract variables from the dictionary + message_id = dictionary.get('messageId') + account_id = dictionary.get('accountId') + source_tn = dictionary.get('sourceTn') + destination_tn = dictionary.get('destinationTn') + message_status = dictionary.get('messageStatus') + message_direction = dictionary.get('messageDirection') + message_type = dictionary.get('messageType') + segment_count = dictionary.get('segmentCount') + error_code = dictionary.get('errorCode') + receive_time = dictionary.get('receiveTime') + carrier_name = dictionary.get('carrierName') + + # Return an object of this model + return cls(message_id, + account_id, + source_tn, + destination_tn, + message_status, + message_direction, + message_type, + segment_count, + error_code, + receive_time, + carrier_name) diff --git a/bandwidth/messaging/models/bandwidth_messages_list.py b/bandwidth/messaging/models/bandwidth_messages_list.py new file mode 100644 index 00000000..288bed39 --- /dev/null +++ b/bandwidth/messaging/models/bandwidth_messages_list.py @@ -0,0 +1,70 @@ +# -*- coding: utf-8 -*- + +""" +bandwidth + +This file was automatically generated by APIMATIC v2.0 ( https://apimatic.io ). +""" +from bandwidth.messaging.models.bandwidth_message_item import BandwidthMessageItem +from bandwidth.messaging.models.page_info import PageInfo + + +class BandwidthMessagesList(object): + + """Implementation of the 'BandwidthMessagesList' model. + + TODO: type model description here. + + Attributes: + total_count (int): Total number of messages matched by the search + page_info (PageInfo): TODO: type description here. + messages (list of BandwidthMessageItem): TODO: type description here. + + """ + + # Create a mapping from Model property names to API property names + _names = { + "total_count": 'totalCount', + "page_info": 'pageInfo', + "messages": 'messages' + } + + def __init__(self, + total_count=None, + page_info=None, + messages=None): + """Constructor for the BandwidthMessagesList class""" + + # Initialize members of the class + self.total_count = total_count + self.page_info = page_info + self.messages = messages + + @classmethod + def from_dictionary(cls, + dictionary): + """Creates an instance of this model from a dictionary + + Args: + dictionary (dictionary): A dictionary representation of the object + as obtained from the deserialization of the server's response. The + keys MUST match property names in the API description. + + Returns: + object: An instance of this structure class. + + """ + if dictionary is None: + return None + + # Extract variables from the dictionary + total_count = dictionary.get('totalCount') + page_info = PageInfo.from_dictionary(dictionary.get('pageInfo')) if dictionary.get('pageInfo') else None + messages = None + if dictionary.get('messages') is not None: + messages = [BandwidthMessageItem.from_dictionary(x) for x in dictionary.get('messages')] + + # Return an object of this model + return cls(total_count, + page_info, + messages) diff --git a/bandwidth/messaging/models/media.py b/bandwidth/messaging/models/media.py index 33f6f6c9..8309228e 100644 --- a/bandwidth/messaging/models/media.py +++ b/bandwidth/messaging/models/media.py @@ -21,7 +21,7 @@ class Media(object): content_length (string): TODO: type description here. content_type (string): TODO: type description here. tags (list of Tag): TODO: type description here. - user_id (string): TODO: type description here. + user_id (string): User's account ID media_name (string): TODO: type description here. media_id (string): TODO: type description here. cache_control (string): TODO: type description here. diff --git a/bandwidth/messaging/models/message_request.py b/bandwidth/messaging/models/message_request.py index 2af96789..5fabd7be 100644 --- a/bandwidth/messaging/models/message_request.py +++ b/bandwidth/messaging/models/message_request.py @@ -14,12 +14,18 @@ class MessageRequest(object): TODO: type model description here. Attributes: - application_id (string): TODO: type description here. - to (list of string): TODO: type description here. - mfrom (string): TODO: type description here. - text (string): TODO: type description here. - media (list of string): TODO: type description here. - tag (string): TODO: type description here. + application_id (string): The ID of the Application your from number is + associated with in the Bandwidth Phone Number Dashboard. + to (list of string): The phone number(s) the message should be sent to + in E164 format + mfrom (string): One of your telephone numbers the message should come + from in E164 format + text (string): The contents of the text message. Must be 2048 + characters or less. + media (list of string): A list of URLs to include as media attachments + as part of the message. + tag (string): A custom string that will be included in callback events + of the message. Max 1024 characters """ diff --git a/bandwidth/messaging/models/page_info.py b/bandwidth/messaging/models/page_info.py new file mode 100644 index 00000000..b13b7eab --- /dev/null +++ b/bandwidth/messaging/models/page_info.py @@ -0,0 +1,74 @@ +# -*- coding: utf-8 -*- + +""" +bandwidth + +This file was automatically generated by APIMATIC v2.0 ( https://apimatic.io ). +""" + + +class PageInfo(object): + + """Implementation of the 'PageInfo' model. + + TODO: type model description here. + + Attributes: + prev_page (string): The link to the previous page for pagination + next_page (string): The link to the next page for pagination + prev_page_token (string): The isolated pagination token for the + previous page + next_page_token (string): The isolated pagination token for the next + page + + """ + + # Create a mapping from Model property names to API property names + _names = { + "prev_page": 'prevPage', + "next_page": 'nextPage', + "prev_page_token": 'prevPageToken', + "next_page_token": 'nextPageToken' + } + + def __init__(self, + prev_page=None, + next_page=None, + prev_page_token=None, + next_page_token=None): + """Constructor for the PageInfo class""" + + # Initialize members of the class + self.prev_page = prev_page + self.next_page = next_page + self.prev_page_token = prev_page_token + self.next_page_token = next_page_token + + @classmethod + def from_dictionary(cls, + dictionary): + """Creates an instance of this model from a dictionary + + Args: + dictionary (dictionary): A dictionary representation of the object + as obtained from the deserialization of the server's response. The + keys MUST match property names in the API description. + + Returns: + object: An instance of this structure class. + + """ + if dictionary is None: + return None + + # Extract variables from the dictionary + prev_page = dictionary.get('prevPage') + next_page = dictionary.get('nextPage') + prev_page_token = dictionary.get('prevPageToken') + next_page_token = dictionary.get('nextPageToken') + + # Return an object of this model + return cls(prev_page, + next_page, + prev_page_token, + next_page_token) diff --git a/bandwidth/voice/__init__.py b/bandwidth/voice/__init__.py index 843fbc5d..f7f51aa9 100644 --- a/bandwidth/voice/__init__.py +++ b/bandwidth/voice/__init__.py @@ -1,7 +1,6 @@ -__all__ = [ - 'bxml', +__all__ = [ 'controllers', 'exceptions', 'models', - 'voice_client', + 'voice_client', ] diff --git a/bandwidth/voice/models/call_engine_modify_conference_request.py b/bandwidth/voice/models/call_engine_modify_conference_request.py deleted file mode 100644 index 8374370d..00000000 --- a/bandwidth/voice/models/call_engine_modify_conference_request.py +++ /dev/null @@ -1,103 +0,0 @@ -# -*- coding: utf-8 -*- - -""" -bandwidth - -This file was automatically generated by APIMATIC v2.0 ( https://apimatic.io ). -""" - - -class CallEngineModifyConferenceRequest(object): - - """Implementation of the 'CallEngineModifyConferenceRequest' model. - - TODO: type model description here. - - Attributes: - status (StatusEnum): TODO: type description here. - redirect_url (string): TODO: type description here. - redirect_fallback_url (string): TODO: type description here. - redirect_method (RedirectMethodEnum): TODO: type description here. - redirect_fallback_method (RedirectFallbackMethodEnum): TODO: type - description here. - username (string): TODO: type description here. - password (string): TODO: type description here. - fallback_username (string): TODO: type description here. - fallback_password (string): TODO: type description here. - - """ - - # Create a mapping from Model property names to API property names - _names = { - "redirect_url": 'redirectUrl', - "status": 'status', - "redirect_fallback_url": 'redirectFallbackUrl', - "redirect_method": 'redirectMethod', - "redirect_fallback_method": 'redirectFallbackMethod', - "username": 'username', - "password": 'password', - "fallback_username": 'fallbackUsername', - "fallback_password": 'fallbackPassword' - } - - def __init__(self, - redirect_url=None, - status=None, - redirect_fallback_url=None, - redirect_method=None, - redirect_fallback_method=None, - username=None, - password=None, - fallback_username=None, - fallback_password=None): - """Constructor for the CallEngineModifyConferenceRequest class""" - - # Initialize members of the class - self.status = status - self.redirect_url = redirect_url - self.redirect_fallback_url = redirect_fallback_url - self.redirect_method = redirect_method - self.redirect_fallback_method = redirect_fallback_method - self.username = username - self.password = password - self.fallback_username = fallback_username - self.fallback_password = fallback_password - - @classmethod - def from_dictionary(cls, - dictionary): - """Creates an instance of this model from a dictionary - - Args: - dictionary (dictionary): A dictionary representation of the object - as obtained from the deserialization of the server's response. The - keys MUST match property names in the API description. - - Returns: - object: An instance of this structure class. - - """ - if dictionary is None: - return None - - # Extract variables from the dictionary - redirect_url = dictionary.get('redirectUrl') - status = dictionary.get('status') - redirect_fallback_url = dictionary.get('redirectFallbackUrl') - redirect_method = dictionary.get('redirectMethod') - redirect_fallback_method = dictionary.get('redirectFallbackMethod') - username = dictionary.get('username') - password = dictionary.get('password') - fallback_username = dictionary.get('fallbackUsername') - fallback_password = dictionary.get('fallbackPassword') - - # Return an object of this model - return cls(redirect_url, - status, - redirect_fallback_url, - redirect_method, - redirect_fallback_method, - username, - password, - fallback_username, - fallback_password) diff --git a/setup.py b/setup.py index ae12a352..a86f39ab 100644 --- a/setup.py +++ b/setup.py @@ -12,7 +12,7 @@ setup( name='bandwidth-sdk', - version='7.0.0', + version='1.0.0', description='Bandwidth\'s set of APIs', long_description=long_description, long_description_content_type="text/markdown", @@ -28,4 +28,4 @@ 'enum34>=1.1.6', 'lxml>=4.3.4' ] -) +) \ No newline at end of file diff --git a/tests/__init__.py b/tests/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/tests/controllers/__init__.py b/tests/controllers/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/tests/controllers/controller_test_base.py b/tests/controllers/controller_test_base.py new file mode 100644 index 00000000..e5b88204 --- /dev/null +++ b/tests/controllers/controller_test_base.py @@ -0,0 +1,31 @@ +# -*- coding: utf-8 -*- + +""" +bandwidth + +This file was automatically generated by APIMATIC v2.0 ( https://apimatic.io ). +""" + +import unittest + +from bandwidth.configuration import Configuration +from bandwidth.configuration import Environment +from bandwidth.bandwidth_client import BandwidthClient + + +class ControllerTestBase(unittest.TestCase): + + """All test classes inherit from this base class. It abstracts out + common functionality and configuration variables set up.""" + + @classmethod + def setUpClass(cls): + """Class method called once before running tests in a test class.""" + cls.request_timeout = 30 + cls.assert_precision = 0.01 + cls.config = ControllerTestBase.create_configuration() + cls.client = BandwidthClient() + + @staticmethod + def create_configuration(): + return Configuration() diff --git a/tests/http_response_catcher.py b/tests/http_response_catcher.py new file mode 100644 index 00000000..9b245c69 --- /dev/null +++ b/tests/http_response_catcher.py @@ -0,0 +1,27 @@ +# -*- coding: utf-8 -*- + +""" +bandwidth + +This file was automatically generated by APIMATIC v2.0 ( https://apimatic.io ). +""" + +from bandwidth.http.http_call_back import HttpCallBack + +class HttpResponseCatcher(HttpCallBack): + + """A class used for catching the HttpResponse object from controllers. + + This class inherits HttpCallBack and implements the on_after_response + method to catch the HttpResponse object as returned by the HttpClient + after a request is executed. + + """ + def on_before_request(self, request): + pass + + def on_after_response(self, response): + self.response = response + + + diff --git a/tests/test_helper.py b/tests/test_helper.py new file mode 100644 index 00000000..06176bda --- /dev/null +++ b/tests/test_helper.py @@ -0,0 +1,126 @@ +# -*- coding: utf-8 -*- + +""" +bandwidth + +This file was automatically generated by APIMATIC v2.0 ( https://apimatic.io ). +""" + +import os, tempfile, requests + +class TestHelper(object): + + """A Helper Class for various functions associated with API testing. + + This class contains static and class methods for operations that need to be + performed during API testing. All of the methods inside this class are + static or class methods, there is no need to ever initialise an instance of this + class. + + Attributes: + cache (Set): Class variable which stores hashes of file URLs so we don't + download the same file twice in a test session. + + """ + + cache = {} + + @staticmethod + def match_headers(expected_headers, + received_headers, + allow_extra=True): + """Static method to compare the received headers with the expected headers. + + Args: + expected_headers (dict): A dictionary of expected headers (keys in lower case). + received_headers (dict): A dictionary of headers received. + allow_extra (Boolean, optional): A flag which determines if we + allow extra headers. + Returns: + Boolean: True if headers match, False otherwise. + + """ + if ((len(received_headers) < len(expected_headers)) or + ((allow_extra == False) and (len(expected_headers) != len(received_headers)))): + return False + + received_headers = {k.lower(): v for k, v in received_headers.items()} + for e_key in expected_headers: + if e_key not in received_headers: + return False + if((expected_headers[e_key] != None) and + (expected_headers[e_key] != received_headers[e_key])): + return False + + return True + + @staticmethod + def match_body(expected_body, + received_body, + check_values=False, + check_order=False, + check_count=False): + """Static method to compare the received body with the expected body. + + Args: + expected_body (dynamic): The expected body. + received_body (dynamic): The received body. + check_values (Boolean, optional): A flag which determines if we + check values in dictionaries. + check_order (Boolean, optional): A flag which determines if we + check the order of array elements. + check_count (Boolean, optional): A flag which determines if we + check the count of array elements. + Returns: + Boolean: True if bodies match, False otherwise. + + """ + if type(expected_body) == dict: + if type(received_body) != dict: + return False + for key in expected_body: + if key not in received_body: + return False + if check_values or type(expected_body[key]) == dict: + if TestHelper.match_body(expected_body[key], received_body[key], + check_values, check_order, check_count) == False: + return False + elif type(expected_body) == list: + if type(received_body) != list: + return False + if check_count == True and (len(expected_body) != len(received_body)): + return False + else: + previous_matches = [] + for i, expected_element in enumerate(expected_body): + matches = [j for j, received_element + in enumerate(received_body) + if TestHelper.match_body(expected_element, received_element, + check_values, check_order, check_count)] + if len(matches) == 0: + return False + if check_order == True: + if i != 0 and all([all(y > x for y in previous_matches) for x in matches]): + return False + previous_matches = matches + elif expected_body != received_body: + return False + return True + + @classmethod + def get_file(cls, url): + """Class method which takes a URL, downloads the file (if not + already downloaded for this test session) and returns a file object for + the file in read-binary mode. + + Args: + url (string): The URL of the required file. + Returns: + FileObject: The file object of the required file (opened with "rb"). + + """ + if url not in cls.cache: + cls.cache[url] = tempfile.TemporaryFile() + cls.cache[url].write(requests.get(url).content) + cls.cache[url].seek(0) + return cls.cache[url] \ No newline at end of file From 36e1c96032b6b213fb6e0d9fbaf1a2d19e1f94eb Mon Sep 17 00:00:00 2001 From: Jacob Mulford <39915377+jmulford-bw@users.noreply.github.com> Date: Wed, 27 Jan 2021 16:03:36 -0500 Subject: [PATCH 2/3] Update __init__.py --- bandwidth/voice/__init__.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/bandwidth/voice/__init__.py b/bandwidth/voice/__init__.py index f7f51aa9..843fbc5d 100644 --- a/bandwidth/voice/__init__.py +++ b/bandwidth/voice/__init__.py @@ -1,6 +1,7 @@ -__all__ = [ +__all__ = [ + 'bxml', 'controllers', 'exceptions', 'models', - 'voice_client', + 'voice_client', ] From bd0ccda26419c663a39aa9848e4456f216437ee6 Mon Sep 17 00:00:00 2001 From: Jacob Mulford <39915377+jmulford-bw@users.noreply.github.com> Date: Wed, 27 Jan 2021 16:04:20 -0500 Subject: [PATCH 3/3] Update setup.py --- setup.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/setup.py b/setup.py index a86f39ab..cea39701 100644 --- a/setup.py +++ b/setup.py @@ -12,7 +12,7 @@ setup( name='bandwidth-sdk', - version='1.0.0', + version='8.0.0', description='Bandwidth\'s set of APIs', long_description=long_description, long_description_content_type="text/markdown", @@ -28,4 +28,4 @@ 'enum34>=1.1.6', 'lxml>=4.3.4' ] -) \ No newline at end of file +)