Skip to content

Commit 34878db

Browse files
committed
Creates more granular exceptions
This allows more fine control over which exceptions you can catch with (hopefully) less string introspection. These exceptions have both faultCode and faultString from the XML-RPC faults.. The Exception hierarchy is as follows: * SoftLayerError ** SoftLayerAPIError *** ParseError *** ServerError *** ApplicationError *** RemoteSystemError *** TransportError These exceptions are thrown in accordance to XML-RPC spec. http://xmlrpc-epi.sourceforge.net/specs/rfc.fault_codes.php
1 parent a779901 commit 34878db

7 files changed

Lines changed: 137 additions & 23 deletions

File tree

SoftLayer/__init__.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -46,8 +46,8 @@
4646
__author__ = 'SoftLayer Technologies, Inc.'
4747
__license__ = 'The BSD License'
4848
__copyright__ = 'Copyright 2013 SoftLayer Technologies, Inc.'
49-
__all__ = ['Client', 'SoftLayerError', 'API_PUBLIC_ENDPOINT',
50-
'API_PRIVATE_ENDPOINT']
49+
__all__ = ['Client', 'SoftLayerError', 'SoftLayerAPIError',
50+
'API_PUBLIC_ENDPOINT', 'API_PRIVATE_ENDPOINT']
5151

5252
from API import Client, API_PUBLIC_ENDPOINT, API_PRIVATE_ENDPOINT
53-
from SoftLayer.exceptions import SoftLayerError
53+
from SoftLayer.exceptions import *

SoftLayer/exceptions.py

Lines changed: 36 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,44 @@
11

22

33
class SoftLayerError(StandardError):
4+
" The base SoftLayer error. "
5+
6+
7+
class SoftLayerAPIError(SoftLayerError):
8+
""" SoftLayerAPIError is the exception that is raised whenever an error is
9+
returned from the API
10+
11+
Provides faultCode and faultString properties
412
"""
5-
The base SoftLayer error.
6-
"""
7-
def __init__(self, reason, *args):
8-
StandardError.__init__(self, reason, *args)
9-
self.reason = reason
13+
def __init__(self, faultCode, faultString, *args):
14+
SoftLayerError.__init__(self, faultString, *args)
15+
self.faultCode = faultCode
16+
self.reason = self.faultString = faultString
1017

1118
def __repr__(self):
12-
return 'SoftLayerError: %s' % self.reason
19+
return '<%s(%s): %s>' % \
20+
(self.__class__.__name__, self.faultCode, self.faultString)
1321

1422
def __str__(self):
15-
return 'SoftLayerError: %s' % self.reason
23+
return '<%s(%s): %s>' % \
24+
(self.__class__.__name__, self.faultCode, self.faultString)
25+
26+
27+
class ParseError(SoftLayerAPIError):
28+
" Parse Error "
29+
30+
31+
class ServerError(SoftLayerAPIError):
32+
" Server Error "
33+
34+
35+
class ApplicationError(SoftLayerAPIError):
36+
" Application Error "
37+
38+
39+
class RemoteSystemError(SoftLayerAPIError):
40+
" System Error "
41+
42+
43+
class TransportError(SoftLayerAPIError):
44+
" Transport Error "

SoftLayer/tests/basic_tests.py

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -224,3 +224,25 @@ def test_complex(self, make_api_call):
224224
'Content-Type': 'application/xml',
225225
'User-Agent': USER_AGENT,
226226
})
227+
228+
229+
class TestExceptions(unittest.TestCase):
230+
def test_softlayer_api_error(self):
231+
e = SoftLayer.SoftLayerAPIError('fault code', 'fault string')
232+
self.assertEquals(e.faultCode, 'fault code')
233+
self.assertEquals(e.faultString, 'fault string')
234+
self.assertEquals(e.reason, 'fault string')
235+
self.assertEquals(
236+
repr(e), "<SoftLayerAPIError(fault code): fault string>")
237+
self.assertEquals(
238+
str(e), "<SoftLayerAPIError(fault code): fault string>")
239+
240+
def test_parse_error(self):
241+
e = SoftLayer.ParseError('fault code', 'fault string')
242+
self.assertEquals(e.faultCode, 'fault code')
243+
self.assertEquals(e.faultString, 'fault string')
244+
self.assertEquals(e.reason, 'fault string')
245+
self.assertEquals(
246+
repr(e), "<ParseError(fault code): fault string>")
247+
self.assertEquals(
248+
str(e), "<ParseError(fault code): fault string>")

SoftLayer/tests/functional_tests.py

Lines changed: 34 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -28,17 +28,42 @@ def test_failed_auth(self):
2828
client = SoftLayer.Client(
2929
'SoftLayer_User_Customer', None, 'doesnotexist', 'issurelywrong',
3030
timeout=20)
31-
self.assertRaises(SoftLayer.SoftLayerError,
31+
self.assertRaises(SoftLayer.SoftLayerAPIError,
3232
client.getPortalLoginToken)
3333

3434
@patch('SoftLayer.API.make_api_call', xmlrpclib_transport.make_api_call)
3535
def test_with_xmlrpc_transport(self):
3636
client = SoftLayer.Client(
3737
'SoftLayer_User_Customer', None, 'doesnotexist', 'issurelywrong',
3838
timeout=20)
39-
self.assertRaises(SoftLayer.SoftLayerError,
39+
self.assertRaises(SoftLayer.SoftLayerAPIError,
4040
client.getPortalLoginToken)
4141

42+
def test_404(self):
43+
client = SoftLayer.Client(
44+
'SoftLayer_User_Customer', None, 'doesnotexist', 'issurelywrong',
45+
timeout=20, endpoint_url='http://httpbin.org/status/404')
46+
47+
try:
48+
client.doSomething()
49+
except SoftLayer.SoftLayerAPIError, e:
50+
self.assertEqual(e.faultCode, 404)
51+
self.assertEqual(e.faultString, 'NOT FOUND')
52+
self.assertEqual(e.faultString, 'NOT FOUND')
53+
54+
@patch('SoftLayer.API.make_api_call', xmlrpclib_transport.make_api_call)
55+
def test_404_with_xmlrpc_transport(self):
56+
client = SoftLayer.Client(
57+
'SoftLayer_User_Customer', None, 'doesnotexist', 'issurelywrong',
58+
timeout=20, endpoint_url='http://httpbin.org/status/404')
59+
60+
try:
61+
client.doSomething()
62+
except SoftLayer.SoftLayerAPIError, e:
63+
self.assertEqual(e.faultCode, 404)
64+
self.assertEqual(e.faultString, 'NOT FOUND')
65+
self.assertEqual(e.faultString, 'NOT FOUND')
66+
4267

4368
class AuthedUser(unittest.TestCase):
4469
def test_service_does_not_exist(self):
@@ -48,8 +73,13 @@ def test_service_does_not_exist(self):
4873
api_key=creds['api_key'],
4974
endpoint_url=creds['endpoint'],
5075
timeout=20)
51-
self.assertRaises(SoftLayer.SoftLayerError,
52-
client["SoftLayer_DOESNOTEXIST"].getObject)
76+
77+
try:
78+
client["SoftLayer_DOESNOTEXIST"].getObject()
79+
except SoftLayer.SoftLayerAPIError, e:
80+
self.assertEqual(e.faultCode, '-32601')
81+
self.assertEqual(e.faultString, 'Service does not exist')
82+
self.assertEqual(e.faultString, 'Service does not exist')
5383

5484
def test_dns(self):
5585
creds = get_creds()

SoftLayer/transport/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11

22
try:
33
from SoftLayer.transport.requests_transport import make_api_call
4-
except ImportError:
4+
except ImportError: # pragma: no cover
55
from SoftLayer.transport.xmlrpclib_transport import make_api_call

SoftLayer/transport/requests_transport.py

Lines changed: 20 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
1-
from SoftLayer.exceptions import SoftLayerError
1+
from SoftLayer.exceptions import (
2+
SoftLayerAPIError, ParseError, ServerError, ApplicationError,
3+
RemoteSystemError, TransportError)
24
import xmlrpclib
35
import requests
46

@@ -20,6 +22,21 @@ def make_api_call(uri, method, args, headers=None,
2022
return result
2123
else:
2224
# Some error occurred
23-
raise SoftLayerError(response.reason)
25+
raise SoftLayerAPIError(response.status_code, response.reason)
2426
except xmlrpclib.Fault, e:
25-
raise SoftLayerError(e.faultString)
27+
# These exceptions are formed from the XML-RPC spec
28+
# http://xmlrpc-epi.sourceforge.net/specs/rfc.fault_codes.php
29+
error_mapping = {
30+
'-32700': ParseError,
31+
'-32701': ParseError,
32+
'-32702': ParseError,
33+
'-32600': ServerError,
34+
'-32601': ServerError,
35+
'-32602': ServerError,
36+
'-32603': ServerError,
37+
'-32500': ApplicationError,
38+
'-32400': RemoteSystemError,
39+
'-32300': TransportError,
40+
}
41+
raise error_mapping.get(e.faultCode, SoftLayerAPIError)(
42+
e.faultCode, e.faultString)

SoftLayer/transport/xmlrpclib_transport.py

Lines changed: 21 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
1-
from SoftLayer.consts import USER_AGENT
2-
from SoftLayer.exceptions import SoftLayerError
1+
from SoftLayer.exceptions import (
2+
SoftLayerAPIError, ParseError, ServerError, ApplicationError,
3+
RemoteSystemError, TransportError)
34
from urlparse import urlparse
45
import xmlrpclib
56

@@ -26,13 +27,29 @@ def make_api_call(uri, method, args, headers=None, http_headers=None,
2627
verbose=verbose, allow_none=True)
2728
return getattr(proxy, method)({'headers': headers}, *args)
2829
except xmlrpclib.Fault, e:
29-
raise SoftLayerError(e.faultString)
30+
# These exceptions are formed from the XML-RPC spec
31+
# http://xmlrpc-epi.sourceforge.net/specs/rfc.fault_codes.php
32+
error_mapping = {
33+
'-32700': ParseError,
34+
'-32701': ParseError,
35+
'-32702': ParseError,
36+
'-32600': ServerError,
37+
'-32601': ServerError,
38+
'-32602': ServerError,
39+
'-32603': ServerError,
40+
'-32500': ApplicationError,
41+
'-32400': RemoteSystemError,
42+
'-32300': TransportError,
43+
}
44+
raise error_mapping.get(e.faultCode, SoftLayerAPIError)(
45+
e.faultCode, e.faultString)
46+
except xmlrpclib.ProtocolError, e:
47+
raise SoftLayerAPIError(e.errcode, e.errmsg)
3048
finally:
3149
socket.setdefaulttimeout(__prevDefaultTimeout)
3250

3351

3452
class ProxyTransport(xmlrpclib.Transport):
35-
user_agent = USER_AGENT
3653
__extra_headers = None
3754

3855
def set_raw_header(self, name, value):
@@ -47,5 +64,4 @@ def send_user_agent(self, connection):
4764

4865

4966
class SecureProxyTransport(xmlrpclib.SafeTransport, ProxyTransport):
50-
user_agent = USER_AGENT
5167
__extra_headers = {}

0 commit comments

Comments
 (0)