diff --git a/demo.py b/demo.py index 4cb36ec..12b7247 100755 --- a/demo.py +++ b/demo.py @@ -1,10 +1,12 @@ #!/usr/bin/env python +import datetime import pprint import random import string from flowroutenumbersandmessaging.configuration import Configuration from flowroutenumbersandmessaging.flowroutenumbersandmessaging_client import \ FlowroutenumbersandmessagingClient +from dateutil.relativedelta import relativedelta print("Number/Route Management v2 & Messaging v2.1 Demo") @@ -21,8 +23,11 @@ client = FlowroutenumbersandmessagingClient(Configuration.basic_auth_user_name, numbers_controller = client.numbers routes_controller = client.routes messages_controller = client.messages +porting_controller = client.porting -print("--List Available Area Codes") +# --------------------- Numbering ----------------------------------------- + +print("--List Available Area Codes, specifying a maximum setup cost.") max_setup_cost = 3.25 limit = 3 offset = None @@ -43,9 +48,9 @@ result = numbers_controller.list_available_exchange_codes(limit, pprint.pprint(result) print("--Search for Purchasable Phone Numbers") -starts_with = 646 +starts_with = 206 contains = 3 -ends_with = 7 +ends_with = None limit = 3 offset = None rate_center = None @@ -59,10 +64,13 @@ result = numbers_controller.search_for_purchasable_phone_numbers(starts_with, state) pprint.pprint(result) +purchasable_number = None if len(result['data']): print("--Purchase a Phone Number") - purchasable_number = result['data'][0]['id'] + print("NOTE: This demo has been disabled as it pulls credit from your account") + # purchasable_number = result['data'][0]['id'] # result = numbers_controller.purchase_a_phone_number(purchasable_number) + # pprint.pprint(result) print("--List Account Phone Numbers") starts_with = None @@ -74,17 +82,32 @@ result = numbers_controller.list_account_phone_numbers(starts_with, ends_with, contains, limit, offset) pprint.pprint(result) - print("--List Phone Number Details") number_id = result['data'][0]['id'] result = numbers_controller.list_phone_number_details(number_id) pprint.pprint(result) -print("---Create an Inbound Route") +if purchasable_number is not None: + print("--Release a DID") + result = numbers_controller.release_a_did(purchasable_number) + pprint.pprint(result) + +print("--Update an Alias for a DID") +result = numbers_controller.set_did_alias(number_id, "New Test") +pprint.pprint(result) +result = numbers_controller.list_phone_number_details(number_id) +pprint.pprint(result) -print("---First list all available edge strategies") -# result = routes_controller.list_edge_strategies() -# pprint.pprint(result) +print("--Set a callback for a DID") +result = numbers_controller.set_did_callback(number_id, + 'http://www.example.com/test') +pprint.pprint(result) + +# --------------------- Routes ----------------------------------------- + +print("---List all available edge strategies") +result = routes_controller.list_edge_strategies() +pprint.pprint(result) # Function to generate six-charac random string @@ -92,6 +115,7 @@ def id_generator(size=6, chars=string.ascii_lowercase + string.digits): return ''.join(random.choice(chars) for _ in range(size)) +print("---Create an Inbound Route") new_route = '{}.sonsofodin.com'.format(id_generator()) alias = id_generator() for i in range(10): @@ -111,17 +135,17 @@ result = routes_controller.create_an_inbound_route(request_body) pprint.pprint(result) print ("---List Inbound Routes") -limit = 3 +limit = 10 result = routes_controller.list_inbound_routes(limit) pprint.pprint(result) +primary_routeid = result['data'][1]['id'] +secondary_routeid = result['data'][2]['id'] -prirouteid = result['data'][1]['id'] -secrouteid = result['data'][2]['id'] request_body = '{ \ "data": { \ "type": "route", \ - "id": "' + str(prirouteid) + '" \ + "id": "' + str(primary_routeid) + '" \ } \ }' @@ -135,7 +159,7 @@ else: request_body = '{ \ "data": { \ "type": "route", \ - "id": "' + str(secrouteid) + '" \ + "id": "' + str(secondary_routeid) + '" \ } \ }' @@ -146,7 +170,9 @@ if result is None: else: print (result) -body_with_space = "hello there{}how are you?".format(chr(160)) +# --------------------- Messaging ----------------------------------------- + +body = "Hello there how are you?" request_body = '{ \ "data": { \ @@ -154,7 +180,7 @@ request_body = '{ \ "attributes": { \ "to": "' + str(mobile_number) + '", \ "from": "' + str(number_id) + '", \ - "body": "' + unicode(body_with_space, "utf-8") + '", \ + "body": "' + unicode(body, "utf-8") + '", \ "is_mms": "false" \ } \ } \ @@ -173,20 +199,65 @@ request_body_mms = '{ \ } \ }' +request_body_with_dlr = '{ \ + "data": { \ + "type": "message", \ + "attributes": { \ + "to": "' + str(mobile_number) + '", \ + "from": "' + str(number_id) + '", \ + "body": "' + unicode(body, "utf-8") + '", \ + "is_mms": "false", \ + "dlr_callback": "http://httpbin.org/status/:code" \ + } \ + } \ +}' + print("---Send A Message") result = messages_controller.send_a_message(request_body) pprint.pprint(result) +print("---Send A Message with a DLR") +sms_url = 'http://example.com/sms/special' +result = messages_controller.send_a_message(request_body_with_dlr) +pprint.pprint(result) + print("---Look Up A Set Of Messages") -start_date = "2017-12-01" -end_date = "2018-01-08" -limit = 2 -result = messages_controller.look_up_a_set_of_messages(start_date, - end_date, +start_date = datetime.datetime.now() - relativedelta(days=30) +end_date = datetime.datetime.now() + +start_date_string = start_date.strftime("%Y-%m-%d") +end_date_string = end_date.strftime("%Y-%m-%d") + +limit = 10 +result = messages_controller.look_up_a_set_of_messages(start_date_string, + end_date_string, limit) pprint.pprint(result) -print ("---Look Up A Message Detail Record") +print ("\n---Look Up A Message Detail Record") message_id = result['data'][0]['id'] result = messages_controller.look_up_a_message_detail_record(message_id) pprint.pprint(result) + +print("\n---Set an Account Level SMS Callback URL") +sms_url = 'http://example.com/sms' +result = messages_controller.set_account_level_sms_callback(sms_url) +pprint.pprint(result) + +print("\n---Set an Account Level MMS Callback URL") +mms_url = 'http://example.com/mms' +result = messages_controller.set_account_level_mms_callback(mms_url) +pprint.pprint(result) + +print("\n---Set an Account Level DLR Callback URL") +dlr_url = 'http://example.com/dlr' +result = messages_controller.set_account_level_dlr_callback(dlr_url) +pprint.pprint(result) + + +# --------------------- Porting ----------------------------------------- + +print("\n---Check Number Portability") +numbers = ['14254664444', '18827833439'] +result = porting_controller.checkPortability(numbers) +pprint.pprint(result) diff --git a/flowroutenumbersandmessaging/controllers/messages_controller.py b/flowroutenumbersandmessaging/controllers/messages_controller.py index 182b22c..d0f760a 100644 --- a/flowroutenumbersandmessaging/controllers/messages_controller.py +++ b/flowroutenumbersandmessaging/controllers/messages_controller.py @@ -14,11 +14,11 @@ from ..models.mdr_2 import MDR2 from ..exceptions.error_exception import ErrorException import json + class MessagesController(BaseController): """A Controller to access Endpoints in the flowroutenumbersandmessaging API.""" - def look_up_a_set_of_messages(self, start_date, end_date=None, @@ -192,3 +192,193 @@ class MessagesController(BaseController): # Return appropriate type return APIHelper.json_deserialize(_context.response.raw_body) + + def set_account_level_sms_callback(self, url): + """Does a PUT request to /v2.1/messages/sms_callback. + + Sets the callback url for all sms messages. + + Args: + url (string): The callback url to be hit. + + Returns: + mixed: Response from the API. ACCEPTED + + 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 + _query_builder = Configuration.base_uri + _query_builder += '/v2.1/messages/sms_callback' + _query_url = APIHelper.clean_url(_query_builder) + + # Prepare headers + _headers = { + 'accept': 'application/vnd.api+json', + 'content-type': 'application/vnd.api+json; charset=utf-8' + } + + body = { + 'data': { + 'attributes': { + 'callback_url': url + } + } + } + + # Prepare and execute request + _request = self.http_client.put(_query_url, + headers=_headers, + parameters=APIHelper.json_serialize(body)) + BasicAuth.apply(_request) + _context = self.execute_request(_request) + + # Endpoint and global error handling using HTTP status codes. + if _context.response.status_code == 401: + raise ErrorException('Unauthorized – There was an issue with your API credentials.', _context) + elif _context.response.status_code == 403: + raise ErrorException('Forbidden – You don\'t have permission to access this resource.', _context) + elif _context.response.status_code == 404: + raise ErrorException('The specified resource was not found', _context) + elif _context.response.status_code == 422: + raise ErrorException('Unprocessable Entity - You tried to enter an incorrect value.', _context) + self.validate_response(_context) + + # Return appropriate type + return APIHelper.json_deserialize(_context.response.raw_body) + + def set_account_level_mms_callback(self, url): + """Does a PUT request to /v2.1/messages/mms_callback. + + Sets the callback url for all mms messages. + + Args: + url (string): The callback url to be hit. + + Returns: + mixed: Response from the API. ACCEPTED + + 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 + _query_builder = Configuration.base_uri + _query_builder += '/v2.1/messages/mms_callback' + _query_url = APIHelper.clean_url(_query_builder) + + # Prepare headers + _headers = { + 'accept': 'application/vnd.api+json', + 'content-type': 'application/vnd.api+json; charset=utf-8' + } + + body = { + 'data': { + 'attributes': { + 'callback_url': url + } + } + } + + # Prepare and execute request + _request = self.http_client.put(_query_url, + headers=_headers, + parameters=APIHelper.json_serialize( + body)) + BasicAuth.apply(_request) + _context = self.execute_request(_request) + + # Endpoint and global error handling using HTTP status codes. + if _context.response.status_code == 401: + raise ErrorException( + 'Unauthorized – There was an issue with your API credentials.', + _context) + elif _context.response.status_code == 403: + raise ErrorException( + 'Forbidden – You don\'t have permission to access this resource.', + _context) + elif _context.response.status_code == 404: + raise ErrorException('The specified resource was not found', + _context) + elif _context.response.status_code == 422: + raise ErrorException( + 'Unprocessable Entity - You tried to enter an incorrect value.', + _context) + self.validate_response(_context) + + # Return appropriate type + return APIHelper.json_deserialize(_context.response.raw_body) + + def set_account_level_dlr_callback(self, url): + """Does a PUT request to /v2.1/messages/dlr_callback. + + Sets the callback url for all delivery receipts (dlrs) + + Args: + url (string): The callback url to be hit. + + Returns: + mixed: Response from the API. ACCEPTED + + 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 + _query_builder = Configuration.base_uri + _query_builder += '/v2.1/messages/dlr_callback' + _query_url = APIHelper.clean_url(_query_builder) + + # Prepare headers + _headers = { + 'accept': 'application/vnd.api+json', + 'content-type': 'application/vnd.api+json; charset=utf-8' + } + + body = { + 'data': { + 'attributes': { + 'callback_url': url + } + } + } + + # Prepare and execute request + _request = self.http_client.put(_query_url, + headers=_headers, + parameters=APIHelper.json_serialize( + body)) + BasicAuth.apply(_request) + _context = self.execute_request(_request) + + # Endpoint and global error handling using HTTP status codes. + if _context.response.status_code == 401: + raise ErrorException( + 'Unauthorized – There was an issue with your API credentials.', + _context) + elif _context.response.status_code == 403: + raise ErrorException( + 'Forbidden – You don\'t have permission to access this resource.', + _context) + elif _context.response.status_code == 404: + raise ErrorException('The specified resource was not found', + _context) + elif _context.response.status_code == 422: + raise ErrorException( + 'Unprocessable Entity - You tried to enter an incorrect value.', + _context) + self.validate_response(_context) + + # Return appropriate type + return APIHelper.json_deserialize(_context.response.raw_body) diff --git a/flowroutenumbersandmessaging/controllers/numbers_controller.py b/flowroutenumbersandmessaging/controllers/numbers_controller.py index d3b0dd9..4acc5c9 100644 --- a/flowroutenumbersandmessaging/controllers/numbers_controller.py +++ b/flowroutenumbersandmessaging/controllers/numbers_controller.py @@ -14,11 +14,11 @@ from ..models.number_26 import Number26 from ..exceptions.error_exception import ErrorException from ..exceptions.api_exception import APIException + class NumbersController(BaseController): """A Controller to access Endpoints in the flowroutenumbersandmessaging API.""" - def list_available_exchange_codes(self, limit=None, offset=None, @@ -392,3 +392,170 @@ class NumbersController(BaseController): # Return appropriate type return APIHelper.json_deserialize(_context.response.raw_body) + + def release_a_did(self, id): + """Does a DELETE request to /v2/numbers/{id}. + + Lets you release a phone number back to available Flowroute inventory. + + Args: + id (int): Phone number to purchase. Must be in 11-digit E.164 + format; e.g. 12061231234. + + Returns: + Number26: Response from the API. CREATED + + 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 + _query_builder = Configuration.base_uri + _query_builder += '/v2/numbers/{id}' + _query_builder = APIHelper.append_url_with_template_parameters(_query_builder, { + 'id': id + }) + + # Return appropriate type + _query_url = APIHelper.clean_url(_query_builder) + + # Prepare headers + _headers = { + 'accept': 'application/json' + } + + # Prepare and execute request + _request = self.http_client.delete(_query_url, headers=_headers) + BasicAuth.apply(_request) + _context = self.execute_request(_request) + + # Endpoint and global error handling using HTTP status codes. + if _context.response.status_code == 401: + raise ErrorException('Unauthorized – There was an issue with your API credentials.', _context) + elif _context.response.status_code == 404: + raise ErrorException('The specified resource was not found', _context) + self.validate_response(_context) + + # Return appropriate type + return APIHelper.json_deserialize(_context.response.raw_body) + + def set_did_alias(self, id, alias): + """Does a PATCH request to /v2/numbers/{id}. + + Lets you set an alias on one of your DIDs. + + Args: + id (int): Phone number to purchase. Must be in 11-digit E.164 + format; e.g. 12061231234. + alias (string): String to use as alias for this DID + + Returns: + Number26: Response from the API. CREATED + + 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 + _query_builder = Configuration.base_uri + _query_builder += '/v2/numbers/{id}' + _query_builder = APIHelper.append_url_with_template_parameters(_query_builder, { + 'id': id + }) + # Return appropriate type + _query_url = APIHelper.clean_url(_query_builder) + + # Prepare headers + _headers = { + 'accept': 'application/json' + } + + body = { + 'type': 'number', + 'alias': alias + } + + # Prepare and execute request + _request = self.http_client.patch(_query_url, headers=_headers, + parameters=APIHelper.json_serialize( + body)) + BasicAuth.apply(_request) + _context = self.execute_request(_request) + + # Endpoint and global error handling using HTTP status codes. + if _context.response.status_code == 401: + raise ErrorException('Unauthorized – There was an issue with your API credentials.', _context) + elif _context.response.status_code == 404: + raise ErrorException('The specified resource was not found', _context) + self.validate_response(_context) + + # Return appropriate type + return APIHelper.json_deserialize(_context.response.raw_body) + + def set_did_callback(self, id, url): + """Does a POST request to /v2/numbers/{id}/relationships/dlr_callback. + + Lets you set a dlr callback for a specific DID. + + Args: + id (int): Phone number to purchase. Must be in 11-digit E.164 + format; e.g. 12061231234. + url (string): String / URL to notify + + Returns: + 204 No Content + + 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 + _query_builder = Configuration.base_uri + _query_builder += '/v2/numbers/{id}/relationships/dlr_callback' + _query_builder = APIHelper.append_url_with_template_parameters(_query_builder, { + 'id': id + }) + # Return appropriate type + _query_url = APIHelper.clean_url(_query_builder) + + # Prepare headers + _headers = { + 'accept': 'application/json' + } + + body = { + 'data': { + 'attributes': { + 'callback_url': url + } + } + } + + # Prepare and execute request + _request = self.http_client.post(_query_url, headers=_headers, + parameters=APIHelper.json_serialize( + body)) + BasicAuth.apply(_request) + _context = self.execute_request(_request) + + # Endpoint and global error handling using HTTP status codes. + if _context.response.status_code == 401: + raise ErrorException('Unauthorized – There was an issue with your API credentials.', _context) + elif _context.response.status_code == 404: + raise ErrorException('The specified resource was not found', _context) + self.validate_response(_context) + + # Return appropriate type + return APIHelper.json_deserialize(_context.response.raw_body) diff --git a/flowroutenumbersandmessaging/controllers/porting_controller.py b/flowroutenumbersandmessaging/controllers/porting_controller.py new file mode 100644 index 0000000..9b37e49 --- /dev/null +++ b/flowroutenumbersandmessaging/controllers/porting_controller.py @@ -0,0 +1,155 @@ +# -*- coding: utf-8 -*- + +""" + flowroutenumbersandmessaging.controllers.porting_controller + + This file was automatically generated by APIMATIC v2.0 ( https://apimatic.io ). +""" + +from .base_controller import BaseController +from ..api_helper import APIHelper +from ..configuration import Configuration +from ..http.auth.basic_auth import BasicAuth +from ..exceptions.error_exception import ErrorException +from .numbers_controller import NumbersController + +class PortingController(BaseController): + + """A Controller to access Endpoints in the + flowroutenumbersandmessaging API.""" + + def checkPortability(self, numbers): + """Does a POST request to /v2/portorders/portability. + + Args: + numbers (list: comma delimited list of strings, required): + Phone numbers to check + + Returns: + mixed: Response from the API. A JSON object of the status of each + number specified + + 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. + + """ + body = { + "numbers": numbers + } + + # Prepare query URL + _query_builder = Configuration.base_uri + _query_builder += '/v2/portorders/portability' + _query_url = APIHelper.clean_url(_query_builder) + # Prepare headers + _headers = { + 'accept': 'application/json' + } + + # Prepare and execute request + _request = self.http_client.post(_query_url, headers=_headers, + parameters=body) + BasicAuth.apply(_request) + _context = self.execute_request(_request) + + # Endpoint and global error handling using HTTP status codes. + if _context.response.status_code == 401: + raise ErrorException('Unauthorized – There was an issue with your API credentials.', _context) + elif _context.response.status_code == 404: + raise ErrorException('The specified resource was not found', _context) + self.validate_response(_context) + + return APIHelper.json_deserialize(_context.response.raw_body) + + def associate_cnam(self, cnam_id, phone_number): + # first, verify the number belongs to the user + did = NumbersController().list_account_phone_numbers(contains=phone_number) + + if did is None: + error_string = "Error, this phone number does not belong to you." + return error_string + + did = did['data'][0]['id'] + + # Prepare query URL + _query_builder = Configuration.base_uri + _query_builder += '/v2/numbers/{}/relationships/cnam/{}'.format(did, cnam_id) + _query_url = APIHelper.clean_url(_query_builder) + # Prepare headers + _headers = { + 'accept': 'application/json' + } + + # Prepare and execute request + _request = self.http_client.patch(_query_url, headers=_headers) + BasicAuth.apply(_request) + _context = self.execute_request(_request) + + # Endpoint and global error handling using HTTP status codes. + if _context.response.status_code == 401: + raise ErrorException('Unauthorized – There was an issue with your API credentials.', _context) + elif _context.response.status_code == 404: + raise ErrorException('The specified resource was not found', _context) + self.validate_response(_context) + + return APIHelper.json_deserialize(_context.response.raw_body) + + def unassociate_cnam(self, phone_number): + # first, verify the number belongs to the user + did = NumbersController().list_account_phone_numbers(contains=phone_number) + + if did is None: + error_string = "Error, this phone number does not belong to you." + return error_string + + did = did['data'][0]['id'] + + # Prepare query URL + _query_builder = Configuration.base_uri + _query_builder += '/v2/numbers/{}/relationships/cnam'.format(did) + _query_url = APIHelper.clean_url(_query_builder) + # Prepare headers + _headers = { + 'accept': 'application/json' + } + + # Prepare and execute request + _request = self.http_client.delete(_query_url, headers=_headers) + BasicAuth.apply(_request) + _context = self.execute_request(_request) + + # Endpoint and global error handling using HTTP status codes. + if _context.response.status_code == 401: + raise ErrorException('Unauthorized – There was an issue with your API credentials.', _context) + elif _context.response.status_code == 404: + raise ErrorException('The specified resource was not found', _context) + self.validate_response(_context) + + return APIHelper.json_deserialize(_context.response.raw_body) + + def remove_cnam(self, cnam_id): + # Prepare query URL + _query_builder = Configuration.base_uri + _query_builder += '/v2/cnams/{}'.format(cnam_id) + _query_url = APIHelper.clean_url(_query_builder) + # Prepare headers + _headers = { + 'accept': 'application/json' + } + + # Prepare and execute request + _request = self.http_client.delete(_query_url, headers=_headers) + BasicAuth.apply(_request) + _context = self.execute_request(_request) + + # Endpoint and global error handling using HTTP status codes. + if _context.response.status_code == 401: + raise ErrorException('Unauthorized – There was an issue with your API credentials.', _context) + elif _context.response.status_code == 404: + raise ErrorException('The specified resource was not found', _context) + self.validate_response(_context) + + return APIHelper.json_deserialize(_context.response.raw_body) diff --git a/flowroutenumbersandmessaging/flowroutenumbersandmessaging_client.py b/flowroutenumbersandmessaging/flowroutenumbersandmessaging_client.py index dd619ab..6bd26ed 100644 --- a/flowroutenumbersandmessaging/flowroutenumbersandmessaging_client.py +++ b/flowroutenumbersandmessaging/flowroutenumbersandmessaging_client.py @@ -12,6 +12,7 @@ from .controllers.routes_controller import RoutesController from .controllers.messages_controller import MessagesController from .controllers.e911s_controller import E911sController from .controllers.cnams_controller import CNAMsController +from .controllers.porting_controller import PortingController class FlowroutenumbersandmessagingClient(object): @@ -38,6 +39,10 @@ class FlowroutenumbersandmessagingClient(object): def cnams(self): return CNAMsController() + @lazy_property + def porting(self): + return PortingController() + def __init__(self, basic_auth_user_name = None, basic_auth_password = None):