Skip to content

Commit f81ac4a

Browse files
committed
Initial restsession and exception modules
Create initial restsession.py and exception.py internal modules
1 parent c2bcb08 commit f81ac4a

File tree

3 files changed

+200
-0
lines changed

3 files changed

+200
-0
lines changed

ciscosparkapi/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
import exception
2+
import restsession

ciscosparkapi/exception.py

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
"""ciscosparkapi package-level exceptions."""
2+
3+
4+
class CiscoSparkApiException(Exception):
5+
"""Base class for all ciscosparkapi package exceptions."""
6+
7+
def __init__(self, *args, **kwargs):
8+
super(CiscoSparkApiException, self).__init__(*args, **kwargs)
9+
10+
11+
SPARK_RESPONSE_CODES = {
12+
200: "OK",
13+
400: "The request was invalid or cannot be otherwise served. An "
14+
"accompanying error message will explain further.",
15+
401: "Authentication credentials were missing or incorrect.",
16+
403: "The request is understood, but it has been refused or access is not "
17+
"allowed.",
18+
404: "The URI requested is invalid or the resource requested, such as a "
19+
"user, does not exist. Also returned when the requested format is "
20+
"not supported by the requested method.",
21+
409: "The request could not be processed because it conflicts with some "
22+
"established rule of the system. For example, a person may not be "
23+
"added to a room more than once.",
24+
500: "Something went wrong on the server.",
25+
503: "Server is overloaded with requests. Try again later."
26+
}
27+
28+
29+
class CiscoSparkApiError(CiscoSparkApiException):
30+
"""Errors returned by requests to the Cisco Spark cloud APIs."""
31+
32+
def __init__(self, response_code, request=None, response=None):
33+
assert isinstance(response_code, int)
34+
self.response_code = response_code
35+
self.request = request
36+
self.response = response
37+
response_text = SPARK_RESPONSE_CODES.get(response_code)
38+
if response_text:
39+
self.response_text = response_text
40+
error_message = "[%s]: %s" % (response_code, response_text)
41+
else:
42+
error_message = "[%s]: Unknown Response Code" % response_code
43+
super(CiscoSparkApiError, self).__init__(error_message)

ciscosparkapi/restsession.py

Lines changed: 155 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,155 @@
1+
"""RestSession class for creating 'connections' to the Cisco Spark APIs."""
2+
3+
import urlparse
4+
import requests
5+
import exception
6+
7+
8+
GET_EXPECTED_RESPONSE_CODE = 200
9+
POST_EXPECTED_RESPONSE_CODE = 200
10+
PUT_EXPECTED_RESPONSE_CODE = 200
11+
DELETE_EXPECTED_RESPONSE_CODE = 204
12+
13+
14+
def _validate_base_url(base_url):
15+
parsed_url = urlparse.urlparse(base_url)
16+
if parsed_url.scheme and parsed_url.netloc:
17+
return parsed_url.geturl()
18+
else:
19+
error_message = "base_url must contain a valid scheme (protocol " \
20+
"specifier) and network location (hostname)"
21+
raise exception.CiscoSparkApiException(error_message)
22+
23+
24+
def _check_response(response, expected_response_code):
25+
if response.status_code != expected_response_code:
26+
raise exception.CiscoSparkApiError(response.status_code,
27+
request=response.request,
28+
response=response)
29+
30+
31+
def _extract_and_parse_json(response):
32+
return response.json()
33+
34+
35+
class RestSession(object):
36+
def __init__(self, base_url, access_token, timeout=None):
37+
super(RestSession, self).__init__()
38+
self._base_url = _validate_base_url(base_url)
39+
self._access_token = access_token
40+
self._req_session = requests.session()
41+
self._timeout = None
42+
self.update_headers({'Authorization': 'Bearer ' + access_token})
43+
self.timeout = timeout
44+
45+
@property
46+
def base_url(self):
47+
return self._base_url
48+
49+
@property
50+
def access_token(self):
51+
return self._access_token
52+
53+
@property
54+
def headers(self):
55+
return self._req_session.headers.copy()
56+
57+
def update_headers(self, headers):
58+
assert isinstance(headers, dict)
59+
self._req_session.headers.update(headers)
60+
61+
@property
62+
def timeout(self):
63+
return self._timeout
64+
65+
@timeout.setter
66+
def timeout(self, value):
67+
assert value is None or value > 0
68+
self._timeout = value
69+
70+
def urljoin(self, suffix_url):
71+
return urlparse.urljoin(self.base_url, suffix_url)
72+
73+
def get(self, url, **kwargs):
74+
timeout = kwargs.pop('timeout', self.timeout)
75+
expected_response_code = kwargs.pop('expected_response_code',
76+
GET_EXPECTED_RESPONSE_CODE)
77+
if kwargs:
78+
raise TypeError(' Unexpected **kwargs: %r' % kwargs)
79+
# API request
80+
response = self._req_session.get(self.urljoin(url), timeout=timeout)
81+
# Process response
82+
_check_response(response, expected_response_code)
83+
return _extract_and_parse_json(response)
84+
85+
def get_pages(self, url, **kwargs):
86+
timeout = kwargs.pop('timeout', self.timeout)
87+
expected_response_code = kwargs.pop('expected_response_code',
88+
GET_EXPECTED_RESPONSE_CODE)
89+
if kwargs:
90+
raise TypeError(' Unexpected **kwargs: %r' % kwargs)
91+
# API request - get first page
92+
response = self._req_session.get(self.urljoin(url), timeout=timeout)
93+
_check_response(response, expected_response_code)
94+
json_dict = _extract_and_parse_json(response)
95+
while True:
96+
# Yield response content
97+
yield json_dict
98+
# Get next page
99+
if response.links.get('next'):
100+
next_url = response.links.get('next').get('url')
101+
# API request - get next page
102+
response = self._req_session.get(next_url, timeout=timeout)
103+
_check_response(response, expected_response_code)
104+
json_dict = _extract_and_parse_json(response)
105+
else:
106+
raise StopIteration
107+
108+
def get_items(self, url, **kwargs):
109+
pages = self.get_pages(url, **kwargs)
110+
for json_dict in pages:
111+
assert isinstance(json_dict, dict)
112+
items = json_dict.get(u'items')
113+
if items:
114+
for item in items:
115+
yield item
116+
else:
117+
error_message = "'items' object not found in JSON data: %r" \
118+
% json_dict
119+
raise exception.CiscoSparkApiException(error_message)
120+
121+
def post(self, url, json_dict, **kwargs):
122+
timeout = kwargs.pop('timeout', self.timeout)
123+
expected_response_code = kwargs.pop('expected_response_code',
124+
POST_EXPECTED_RESPONSE_CODE)
125+
if kwargs:
126+
raise TypeError(' Unexpected **kwargs: %r' % kwargs)
127+
# API request
128+
response = self._req_session.post(self.urljoin(url),
129+
json=json_dict,
130+
timeout=timeout)
131+
_check_response(response, expected_response_code)
132+
return _extract_and_parse_json(response)
133+
134+
def put(self, url, json_dict, **kwargs):
135+
timeout = kwargs.pop('timeout', self.timeout)
136+
expected_response_code = kwargs.pop('expected_response_code',
137+
PUT_EXPECTED_RESPONSE_CODE)
138+
if kwargs:
139+
raise TypeError(' Unexpected **kwargs: %r' % kwargs)
140+
# API request
141+
response = self._req_session.put(self.urljoin(url),
142+
json=json_dict,
143+
timeout=timeout)
144+
_check_response(response, expected_response_code)
145+
return _extract_and_parse_json(response)
146+
147+
def delete(self, url, **kwargs):
148+
timeout = kwargs.pop('timeout', self.timeout)
149+
expected_response_code = kwargs.pop('expected_response_code',
150+
DELETE_EXPECTED_RESPONSE_CODE)
151+
if kwargs:
152+
raise TypeError(' Unexpected **kwargs: %r' % kwargs)
153+
# API request
154+
response = self._req_session.delete(self.urljoin(url), timeout=timeout)
155+
_check_response(response, expected_response_code)

0 commit comments

Comments
 (0)