Skip to content

Commit d491605

Browse files
committed
Merge branch 'issue-46'
2 parents 1813aa9 + 4a98cc3 commit d491605

File tree

2 files changed

+127
-73
lines changed

2 files changed

+127
-73
lines changed

ciscosparkapi/exceptions.py

Lines changed: 96 additions & 73 deletions
Original file line numberDiff line numberDiff line change
@@ -12,12 +12,11 @@
1212
from builtins import *
1313
from past.builtins import basestring
1414

15-
import json
15+
import sys
16+
import textwrap
1617

1718
import requests
1819

19-
from collections import OrderedDict
20-
2120
from ciscosparkapi.responsecodes import SPARK_RESPONSE_CODES
2221

2322

@@ -27,41 +26,90 @@
2726
__license__ = "MIT"
2827

2928

30-
class ciscosparkapiException(Exception):
31-
"""Base class for all ciscosparkapi package exceptions."""
29+
# Helper functions
30+
def sanitize(header_tuple):
31+
"""Sanitize request headers.
3232
33-
def __init__(self, *error_message_args, **error_data):
34-
super(ciscosparkapiException, self).__init__()
35-
36-
self.error_message_args = error_message_args
37-
self.error_data = OrderedDict(error_data)
38-
39-
@property
40-
def error_message(self):
41-
"""The error message created from the error message arguments."""
42-
if not self.error_message_args:
43-
return ""
44-
elif len(self.error_message_args) == 1:
45-
return str(self.error_message_args[0])
46-
elif len(self.error_message_args) > 1 \
47-
and isinstance(self.error_message_args[0], basestring):
48-
return self.error_message_args[0] % self.error_message_args[1:]
49-
else:
50-
return "; ".join(self.error_message_args)
33+
Remove Spark authentication token.
34+
35+
"""
36+
header, value = header_tuple
37+
38+
if (header.lower().strip() == "Authorization".lower().strip()
39+
and "Bearer".lower().strip() in value.lower().strip()):
40+
return header, "Bearer <omitted>"
5141

52-
def __repr__(self):
53-
"""String representation of the exception."""
54-
arg_list = self.error_message_args
55-
kwarg_list = [str(key) + "=" + repr(value)
56-
for key, value in self.error_data.items()]
57-
arg_string = ", ".join(arg_list + kwarg_list)
42+
else:
43+
return header_tuple
5844

59-
return self.__class__.__name__ + "(" + arg_string + ")"
6045

61-
def __str__(self):
62-
"""Human readable string representation of the exception."""
63-
return self.error_message + '\n' + \
64-
json.dumps(self.error_data, indent=4)
46+
def to_unicode(string):
47+
"""Convert a string (bytes, str or unicode) to unicode."""
48+
assert isinstance(string, basestring)
49+
if sys.version_info[0] >= 3:
50+
if isinstance(string, bytes):
51+
return string.decode('utf-8')
52+
else:
53+
return string
54+
else:
55+
if isinstance(string, str):
56+
return string.decode('utf-8')
57+
else:
58+
return string
59+
60+
61+
def response_to_string(response):
62+
"""Render a response object as a human readable string."""
63+
assert isinstance(response, requests.Response)
64+
request = response.request
65+
66+
section_header = "{title:-^79}"
67+
68+
# Prepare request components
69+
req = textwrap.fill("{} {}".format(request.method, request.url),
70+
width=79,
71+
subsequent_indent=' ' * (len(request.method)+1))
72+
req_headers = [
73+
textwrap.fill("{}: {}".format(*sanitize(header)),
74+
width=79,
75+
subsequent_indent=' '*4)
76+
for header in request.headers.items()
77+
]
78+
req_body = (textwrap.fill(to_unicode(request.body), width=79)
79+
if request.body else "")
80+
81+
# Prepare response components
82+
resp = textwrap.fill("{} {}".format(response.status_code,
83+
response.reason
84+
if response.reason else ""),
85+
width=79,
86+
subsequent_indent=' ' * (len(request.method)+1))
87+
resp_headers = [
88+
textwrap.fill("{}: {}".format(*header), width=79,
89+
subsequent_indent=' '*4)
90+
for header in response.headers.items()
91+
]
92+
resp_body = textwrap.fill(response.text, width=79) if response.text else ""
93+
94+
# Return the combined string
95+
return "\n".join([
96+
section_header.format(title="Request"),
97+
req,
98+
"\n".join(req_headers),
99+
"",
100+
req_body,
101+
"",
102+
section_header.format(title="Response"),
103+
resp,
104+
"\n".join(resp_headers),
105+
"",
106+
resp_body,
107+
])
108+
109+
110+
class ciscosparkapiException(Exception):
111+
"""Base class for all ciscosparkapi package exceptions."""
112+
pass
65113

66114

67115
class SparkApiError(ciscosparkapiException):
@@ -70,47 +118,22 @@ class SparkApiError(ciscosparkapiException):
70118
def __init__(self, response):
71119
assert isinstance(response, requests.Response)
72120

73-
super(SparkApiError, self).__init__()
74-
75-
# Convenience data attributes
76-
self.request = response.request
121+
# Extended exception data attributes
77122
self.response = response
78-
self.response_code = response.status_code
79-
self.response_text = SPARK_RESPONSE_CODES.get(self.response_code,
80-
"Unknown Response Code")
81-
82-
# Error message and parameters
83-
self.error_message_args = [
84-
"Response Code [%s] - %s",
85-
self.response_code,
86-
self.response_text
87-
]
88-
89-
# Error Data
90-
self.error_data["response_code"] = self.response_code
91-
self.error_data["description"] = self.response_text
92-
if response.text:
93-
try:
94-
response_data = json.loads(response.text,
95-
object_pairs_hook=OrderedDict)
96-
except ValueError:
97-
self.error_data["response_body"] = response.text
98-
else:
99-
self.error_data["response_body"] = response_data
100-
101-
102-
class SparkRateLimitError(SparkApiError):
103-
"""Cisco Spark Rate-Limit exceeded Error."""
123+
self.request = response.request
104124

105-
def __init__(self, response):
106-
assert isinstance(response, requests.Response)
125+
# Error message
126+
response_code = response.status_code
127+
description = SPARK_RESPONSE_CODES.get(response_code,
128+
"Unknown Response Code")
129+
detail = response_to_string(response)
107130

108-
super(SparkRateLimitError, self).__init__(response)
131+
super(SparkApiError, self).__init__("Response Code [{}] - {}\n{}"
132+
"".format(response_code,
133+
description,
134+
detail))
109135

110-
retry_after = response.headers.get('Retry-After')
111-
if retry_after:
112-
# Convenience data attributes
113-
self.retry_after = float(retry_after)
114136

115-
# Error Data
116-
self.error_data["retry_after"] = self.retry_after
137+
class SparkRateLimitError(SparkApiError):
138+
"""Cisco Spark Rate-Limit exceeded Error."""
139+
pass

ciscosparkapi/utils.py

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
import functools
1616
import mimetypes
1717
import os
18+
import sys
1819
import urllib.parse
1920

2021
from collections import namedtuple
@@ -37,6 +38,36 @@
3738
['file_name', 'file_object', 'content_type'])
3839

3940

41+
def to_unicode(string):
42+
"""Convert a string (bytes, str or unicode) to unicode."""
43+
assert isinstance(string, basestring)
44+
if sys.version_info[0] >= 3:
45+
if isinstance(string, bytes):
46+
return string.decode('utf-8')
47+
else:
48+
return string
49+
else:
50+
if isinstance(string, str):
51+
return string.decode('utf-8')
52+
else:
53+
return string
54+
55+
56+
def to_bytes(string):
57+
"""Convert a string (bytes, str or unicode) to bytes."""
58+
assert isinstance(string, basestring)
59+
if sys.version_info[0] >= 3:
60+
if isinstance(string, str):
61+
return string.encode('utf-8')
62+
else:
63+
return string
64+
else:
65+
if isinstance(string, unicode):
66+
return string.encode('utf-8')
67+
else:
68+
return string
69+
70+
4071
def validate_base_url(base_url):
4172
"""Verify that base_url specifies a protocol and network location."""
4273
parsed_url = urllib.parse.urlparse(base_url)

0 commit comments

Comments
 (0)