From 1d6214d3774b258e512b8f6a9e9172d6fcd8c82b Mon Sep 17 00:00:00 2001 From: Bjorn Post Date: Sun, 21 Dec 2014 19:39:10 +0100 Subject: [PATCH 1/3] Python3 fixes (relative imports, inconsistent use of tabs and spaces, urllib2) --- clockwork/clockwork.py | 270 +++++++++++++++--------------- clockwork/clockwork_exceptions.py | 34 ++-- clockwork/clockwork_http.py | 74 ++++---- 3 files changed, 190 insertions(+), 188 deletions(-) diff --git a/clockwork/clockwork.py b/clockwork/clockwork.py index 08b183d..e6041e3 100644 --- a/clockwork/clockwork.py +++ b/clockwork/clockwork.py @@ -1,6 +1,6 @@ from lxml import etree -import clockwork_http -import clockwork_exceptions +from . import clockwork_http +from . import clockwork_exceptions SMS_URL = 'https://api.clockworksms.com/xml/send.aspx' CREDIT_URL = 'https://api.clockworksms.com/xml/credit.aspx' @@ -8,144 +8,142 @@ class SMS(object): - """An SMS object""" + """An SMS object""" - def __init__(self, to, message, client_id = None, from_name = None, long = None, truncate = None, invalid_char_option = None): - self.client_id = client_id - self.from_name = from_name - self.long = long - self.truncate = truncate - self.invalid_char_option = invalid_char_option - self.to = to - self.message = message + def __init__(self, to, message, client_id = None, from_name = None, long = None, truncate = None, invalid_char_option = None): + self.client_id = client_id + self.from_name = from_name + self.long = long + self.truncate = truncate + self.invalid_char_option = invalid_char_option + self.to = to + self.message = message class SMSResponse(object): - """An wrapper around an SMS reponse""" + """An wrapper around an SMS reponse""" - def __init__(self, sms, id, error_code, error_message, success): - self.sms = sms - self.id = id - self.error_code = error_code - self.error_message = error_message - self.success = success + def __init__(self, sms, id, error_code, error_message, success): + self.sms = sms + self.id = id + self.error_code = error_code + self.error_message = error_message + self.success = success class API(object): - """Wraps the clockwork API""" - def __init__(self, apikey, from_name = 'Clockwork', concat = 3, - invalid_char_option = 'error', long = False, truncate = True, - use_ssl = True): - self.apikey = apikey - self.from_name = from_name - self.concat = concat - self.invalid_char_option = invalid_char_option - self.long = long - self.truncate = truncate - self.use_ssl = use_ssl - - def get_balance(self): - """Check the balance fot this account. - Returns a dictionary containing: - account_type: The account type - balance: The balance remaining on the account - currency: The currency used for the account balance. Assume GBP in not set""" - - xml_root = self.__init_xml('Balance') - - response = clockwork_http.request(BALANCE_URL,etree.tostring(xml_root, encoding='utf-8')) - data_etree = etree.fromstring(response['data']) - - err_desc = data_etree.find('ErrDesc') - if err_desc is not None: - raise clockwork_exceptions.ApiException(err_desc.text, data_etree.find('ErrNo').text) - - - result = {} - result['account_type'] = data_etree.find('AccountType').text - result['balance'] = data_etree.find('Balance').text - result['currency'] = data_etree.find('Currency').text - return result - - def send(self, messages): - """Send a SMS message, or an array of SMS messages""" - - tmpSms = SMS(to='', message='') - if str(type(messages)) == str(type(tmpSms)): - messages = [messages] - - xml_root = self.__init_xml('Message') - wrapper_id = 0 - - for m in messages: - m.wrapper_id = wrapper_id - msg = self.__build_sms_data(m) - sms = etree.SubElement(xml_root, 'SMS') - for sms_element in msg: - element = etree.SubElement(sms,sms_element) - element.text = msg[sms_element] - - # print etree.tostring(xml_root) - response = clockwork_http.request(SMS_URL,etree.tostring(xml_root, encoding='utf-8')) - response_data = response['data'] - - # print response_data - data_etree = etree.fromstring(response_data) - - # Check for general error - err_desc = data_etree.find('ErrDesc') - if err_desc is not None: - raise clockwork_exceptions.ApiException(err_desc.text, data_etree.find('ErrNo').text) - - # Return a consistent object - results = [] - for sms in data_etree: - matching_sms = next((s for s in messages if str(s.wrapper_id) == sms.find('WrapperID').text),None) - new_result = SMSResponse( - sms = matching_sms, - id = '' if sms.find('MessageID') is None else sms.find('MessageID').text, - error_code = 0 if sms.find('ErrNo') is None else sms.find('ErrNo').text, - error_message = '' if sms.find('ErrDesc') is None else sms.find('ErrDesc').text, - success = True if sms.find('ErrNo') is None else (sms.find('ErrNo').text == 0) - ) - results.append(new_result) - - if len(results) > 1: - return results - else: - return results[0] - - def __init_xml(self,rootElementTag): - """Init a etree element and pop a key in there""" - xml_root = etree.Element(rootElementTag) - key = etree.SubElement(xml_root, "Key") - key.text = self.apikey - return xml_root - - - def __build_sms_data(self, message): - """Build a dictionary of SMS message elements""" - - attributes = {} - - attributes_to_translate = { - 'to' : 'To', - 'message' : 'Content', - 'client_id' : 'ClientID', - 'concat' : 'Concat', - 'from_name': 'From', - 'invalid_char_option' : 'InvalidCharOption', - 'truncate' : 'Truncate', - 'wrapper_id' : 'WrapperId' - } - - for attr in attributes_to_translate: - val_to_use = None - if hasattr(message, attr): - val_to_use = getattr(message,attr) - if val_to_use == None and hasattr(self,attr): - val_to_use = getattr(self,attr) - if val_to_use != None: - attributes[attributes_to_translate[attr]] = unicode(val_to_use) - - return attributes - \ No newline at end of file + """Wraps the clockwork API""" + def __init__(self, apikey, from_name = 'Clockwork', concat = 3, + invalid_char_option = 'error', long = False, truncate = True, + use_ssl = True): + self.apikey = apikey + self.from_name = from_name + self.concat = concat + self.invalid_char_option = invalid_char_option + self.long = long + self.truncate = truncate + self.use_ssl = use_ssl + + def get_balance(self): + """Check the balance fot this account. + Returns a dictionary containing: + account_type: The account type + balance: The balance remaining on the account + currency: The currency used for the account balance. Assume GBP in not set""" + + xml_root = self.__init_xml('Balance') + + response = clockwork_http.request(BALANCE_URL,etree.tostring(xml_root, encoding='utf-8')) + data_etree = etree.fromstring(response['data']) + + err_desc = data_etree.find('ErrDesc') + if err_desc is not None: + raise clockwork_exceptions.ApiException(err_desc.text, data_etree.find('ErrNo').text) + + result = {} + result['account_type'] = data_etree.find('AccountType').text + result['balance'] = data_etree.find('Balance').text + result['currency'] = data_etree.find('Currency').text + return result + + def send(self, messages): + """Send a SMS message, or an array of SMS messages""" + + tmpSms = SMS(to='', message='') + if str(type(messages)) == str(type(tmpSms)): + messages = [messages] + + xml_root = self.__init_xml('Message') + wrapper_id = 0 + + for m in messages: + m.wrapper_id = wrapper_id + msg = self.__build_sms_data(m) + sms = etree.SubElement(xml_root, 'SMS') + for sms_element in msg: + element = etree.SubElement(sms,sms_element) + element.text = msg[sms_element] + + # print etree.tostring(xml_root) + response = clockwork_http.request(SMS_URL,etree.tostring(xml_root, encoding='utf-8')) + response_data = response['data'] + + # print response_data + data_etree = etree.fromstring(response_data) + + # Check for general error + err_desc = data_etree.find('ErrDesc') + if err_desc is not None: + raise clockwork_exceptions.ApiException(err_desc.text, data_etree.find('ErrNo').text) + + # Return a consistent object + results = [] + for sms in data_etree: + matching_sms = next((s for s in messages if str(s.wrapper_id) == sms.find('WrapperID').text),None) + new_result = SMSResponse( + sms = matching_sms, + id = '' if sms.find('MessageID') is None else sms.find('MessageID').text, + error_code = 0 if sms.find('ErrNo') is None else sms.find('ErrNo').text, + error_message = '' if sms.find('ErrDesc') is None else sms.find('ErrDesc').text, + success = True if sms.find('ErrNo') is None else (sms.find('ErrNo').text == 0) + ) + results.append(new_result) + + if len(results) > 1: + return results + else: + return results[0] + + def __init_xml(self,rootElementTag): + """Init a etree element and pop a key in there""" + xml_root = etree.Element(rootElementTag) + key = etree.SubElement(xml_root, "Key") + key.text = self.apikey + return xml_root + + + def __build_sms_data(self, message): + """Build a dictionary of SMS message elements""" + + attributes = {} + + attributes_to_translate = { + 'to' : 'To', + 'message' : 'Content', + 'client_id' : 'ClientID', + 'concat' : 'Concat', + 'from_name': 'From', + 'invalid_char_option' : 'InvalidCharOption', + 'truncate' : 'Truncate', + 'wrapper_id' : 'WrapperId' + } + + for attr in attributes_to_translate: + val_to_use = None + if hasattr(message, attr): + val_to_use = getattr(message,attr) + if val_to_use == None and hasattr(self,attr): + val_to_use = getattr(self,attr) + if val_to_use != None: + attributes[attributes_to_translate[attr]] = str(val_to_use) + + return attributes diff --git a/clockwork/clockwork_exceptions.py b/clockwork/clockwork_exceptions.py index 9f2ad76..edc1827 100644 --- a/clockwork/clockwork_exceptions.py +++ b/clockwork/clockwork_exceptions.py @@ -1,26 +1,26 @@ # Exception classes class HttpException(Exception): - def __init__(self, value): - self.value = value - def __str__(self): - return repr(self.value) + def __init__(self, value): + self.value = value + def __str__(self): + return repr(self.value) class AuthException(Exception): - def __init__(self, value): - self.value = value - def __str__(self): - return repr(self.value) + def __init__(self, value): + self.value = value + def __str__(self): + return repr(self.value) class GenericException(Exception): - def __init__(self, value): - self.value = value - def __str__(self): - return repr(self.value) + def __init__(self, value): + self.value = value + def __str__(self): + return repr(self.value) class ApiException(Exception): - def __init__(self, value, errNum): - self.value = value - self.errNum = errNum - def __str__(self): - return repr(self.value) + def __init__(self, value, errNum): + self.value = value + self.errNum = errNum + def __str__(self): + return repr(self.value) diff --git a/clockwork/clockwork_http.py b/clockwork/clockwork_http.py index 9608af4..80355b5 100644 --- a/clockwork/clockwork_http.py +++ b/clockwork/clockwork_http.py @@ -1,38 +1,42 @@ # coding: utf-8 -import urllib2 -import clockwork_exceptions +try: + import urllib.request as _urllib # py3 +except ImportError: + import urllib2 as _urllib # py2 -def request(url, xml): - """Make a http request to clockwork, using the XML provided - Sets sensible headers for the request. - - If there is a problem with the http connection a clockwork_exceptions.HttpException is raised - """ - - r = urllib2.Request(url, xml) - r.add_header('Content-Type','application/xml') - r.add_header('User-Agent','Clockwork Python wrapper/1.0') - - result = {} - try: - f = urllib2.urlopen(r) - except urllib2.URLError as error: - raise clockwork_exceptions.HttpException("Error connecting to clockwork server: %s" % error) +from . import clockwork_exceptions - result['data'] = f.read() - result['status'] = f.getcode() - - if hasattr(f, 'headers'): - result['etag'] = f.headers.get('ETag') - result['lastmodified'] = f.headers.get('Last-Modified') - if f.headers.get('content−encoding', '') == 'gzip': - result['data'] = gzip.GzipFile(fileobj=StringIO(result['data'])).read() - if hasattr(f, 'url'): - result['url'] = f.url - result['status'] = 200 - f.close() - - if result['status'] != 200: - raise clockwork_exceptions.HttpException("Error connecting to clockwork server - status code %s" % result['status']) - - return result +def request(url, xml): + """Make a http request to clockwork, using the XML provided + Sets sensible headers for the request. + + If there is a problem with the http connection a clockwork_exceptions.HttpException is raised + """ + + r = _urllib.Request(url, xml) + r.add_header('Content-Type', 'application/xml') + r.add_header('User-Agent', 'Clockwork Python wrapper/1.0') + + result = {} + try: + f = _urllib.urlopen(r) + except _urllib.URLError as error: + raise clockwork_exceptions.HttpException("Error connecting to clockwork server: %s" % error) + + result['data'] = f.read() + result['status'] = f.getcode() + + if hasattr(f, 'headers'): + result['etag'] = f.headers.get('ETag') + result['lastmodified'] = f.headers.get('Last-Modified') + if f.headers.get('content−encoding', '') == 'gzip': + result['data'] = gzip.GzipFile(fileobj=StringIO(result['data'])).read() + if hasattr(f, 'url'): + result['url'] = f.url + result['status'] = 200 + f.close() + + if result['status'] != 200: + raise clockwork_exceptions.HttpException("Error connecting to clockwork server - status code %s" % result['status']) + + return result From 5d058639ed0eafde4ce8fae6a4d29b8d468b4e3a Mon Sep 17 00:00:00 2001 From: Bjorn Post Date: Sun, 21 Dec 2014 19:53:01 +0100 Subject: [PATCH 2/3] URLError is moved to urllib.error --- clockwork/clockwork_http.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/clockwork/clockwork_http.py b/clockwork/clockwork_http.py index 80355b5..bcff4da 100644 --- a/clockwork/clockwork_http.py +++ b/clockwork/clockwork_http.py @@ -1,8 +1,10 @@ # coding: utf-8 try: import urllib.request as _urllib # py3 + from urllib.error import URLError except ImportError: import urllib2 as _urllib # py2 + from urllib2 import URLError from . import clockwork_exceptions @@ -20,7 +22,7 @@ def request(url, xml): result = {} try: f = _urllib.urlopen(r) - except _urllib.URLError as error: + except URLError as error: raise clockwork_exceptions.HttpException("Error connecting to clockwork server: %s" % error) result['data'] = f.read() From cd8abf014b33f9c34c4a5031826003a3f83c7755 Mon Sep 17 00:00:00 2001 From: Bjorn Post Date: Sun, 21 Dec 2014 20:10:19 +0100 Subject: [PATCH 3/3] Also fix the test-suite for py3 --- tests/clockwork_tests.py | 142 +++++++++++++++++++-------------------- 1 file changed, 71 insertions(+), 71 deletions(-) diff --git a/tests/clockwork_tests.py b/tests/clockwork_tests.py index f1f7ef6..b24659e 100644 --- a/tests/clockwork_tests.py +++ b/tests/clockwork_tests.py @@ -1,86 +1,86 @@ # -*- coding: utf-8 -*- import unittest -import clockwork -import clockwork_exceptions +from clockwork import clockwork +from clockwork import clockwork_exceptions class ApiTests(unittest.TestCase): - api_key = "YOUR_API_KEY_HERE" + api_key = "YOUR_API_KEY_HERE" - def test_should_send_single_message(self): - """Sending a single SMS with the minimum detail and no errors should work""" - api = clockwork.API(self.api_key) - sms = clockwork.SMS(to="441234567890", message="This is a test message") - response = api.send(sms) - self.assertTrue(response.success) + def test_should_send_single_message(self): + """Sending a single SMS with the minimum detail and no errors should work""" + api = clockwork.API(self.api_key) + sms = clockwork.SMS(to="441234567890", message="This is a test message") + response = api.send(sms) + self.assertTrue(response.success) - def test_should_send_single_unicode_message(self): - """Sending a single SMS with the full GSM character set (apart from ESC and form feed) should work""" - api = clockwork.API(self.api_key) - sms = clockwork.SMS( + def test_should_send_single_unicode_message(self): + """Sending a single SMS with the full GSM character set (apart from ESC and form feed) should work""" + api = clockwork.API(self.api_key) + sms = clockwork.SMS( to="441234567890", - #Message table copied from http://www.clockworksms.com/doc/reference/faqs/gsm-character-set/ + #Message table copied from http://www.clockworksms.com/doc/reference/faqs/gsm-character-set/ #Note, the "/f" (form feed) character does not work as lxml prohibits it. - message= u'''@£$¥èéùìòÇ\nØø\rÅåΔ_ΦΓΛΩΠΨΣΘΞÆæßÉ''' - u''' !“#¤%&‘()*+,-./''' - u'''0123456789:;<=>?''' - u'''¡ABCDEFGHIJKLMNO''' - u'''PQRSTUVWXYZÄÖÑܧ''' - u'''¿abcdefghijklmno''' - u'''pqrstuvwxyzäöñüà''' - u'''€[\]^{|}~''' - ,long=True) - response = api.send(sms) - self.assertTrue(response.success) - - - def test_should_fail_with_no_message(self): - """Sending a single SMS with no message should fail""" - api = clockwork.API(self.api_key) - sms = clockwork.SMS(to="441234567890", message="") - response = api.send(sms) - self.assertFalse(response.success) - - def test_should_fail_with_no_to(self): - """Sending a single SMS with no message should fail""" - api = clockwork.API(self.api_key) - sms = clockwork.SMS(to="", message="This is a test message") - response = api.send(sms) - self.assertFalse(response.success) - - def test_should_send_multiple_messages(self): - """Sending multiple sms messages should work""" - api = clockwork.API(self.api_key) - sms1 = clockwork.SMS(to="441234567890", message="This is a test message 1") - sms2 = clockwork.SMS(to="441234567890", message="This is a test message 2") - response = api.send([sms1,sms2]) - - for r in response: - self.assertTrue(r.success) - - def test_should_send_multiple_messages_with_erros(self): - """Sending multiple sms messages, one of which has an invalid message should work""" - api = clockwork.API(self.api_key) - sms1 = clockwork.SMS(to="441234567890", message="This is a test message 1") - sms2 = clockwork.SMS(to="441234567890", message="") - response = api.send([sms1,sms2]) - - self.assertTrue(response[0].success) - self.assertFalse(response[1].success) - - def test_should_fail_with_invalid_key(self): - api = clockwork.API("this_key_is_wrong") - sms = clockwork.SMS(to="441234567890", message="This is a test message 1") - self.assertRaises(clockwork_exceptions.ApiException, api.send, sms) - - def test_should_be_able_to_get_balance(self): - api = clockwork.API(self.api_key) - balance = api.get_balance() - self.assertEqual('PAYG',balance['account_type']) + message=u'''@£$¥èéùìòÇ\nØø\rÅåΔ_ΦΓΛΩΠΨΣΘΞÆæßÉ''' + u''' !“#¤%&‘()*+,-./''' + u'''0123456789:;<=>?''' + u'''¡ABCDEFGHIJKLMNO''' + u'''PQRSTUVWXYZÄÖÑܧ''' + u'''¿abcdefghijklmno''' + u'''pqrstuvwxyzäöñüà''' + u'''€[\]^{|}~''' + ,long=True) + response = api.send(sms) + self.assertTrue(response.success) + + + def test_should_fail_with_no_message(self): + """Sending a single SMS with no message should fail""" + api = clockwork.API(self.api_key) + sms = clockwork.SMS(to="441234567890", message="") + response = api.send(sms) + self.assertFalse(response.success) + + def test_should_fail_with_no_to(self): + """Sending a single SMS with no message should fail""" + api = clockwork.API(self.api_key) + sms = clockwork.SMS(to="", message="This is a test message") + response = api.send(sms) + self.assertFalse(response.success) + + def test_should_send_multiple_messages(self): + """Sending multiple sms messages should work""" + api = clockwork.API(self.api_key) + sms1 = clockwork.SMS(to="441234567890", message="This is a test message 1") + sms2 = clockwork.SMS(to="441234567890", message="This is a test message 2") + response = api.send([sms1,sms2]) + + for r in response: + self.assertTrue(r.success) + + def test_should_send_multiple_messages_with_erros(self): + """Sending multiple sms messages, one of which has an invalid message should work""" + api = clockwork.API(self.api_key) + sms1 = clockwork.SMS(to="441234567890", message="This is a test message 1") + sms2 = clockwork.SMS(to="441234567890", message="") + response = api.send([sms1,sms2]) + + self.assertTrue(response[0].success) + self.assertFalse(response[1].success) + + def test_should_fail_with_invalid_key(self): + api = clockwork.API("this_key_is_wrong") + sms = clockwork.SMS(to="441234567890", message="This is a test message 1") + self.assertRaises(clockwork_exceptions.ApiException, api.send, sms) + + def test_should_be_able_to_get_balance(self): + api = clockwork.API(self.api_key) + balance = api.get_balance() + self.assertEqual('PAYG',balance['account_type']) if __name__ == "__main__": - unittest.main() + unittest.main()