Skip to content

Commit b4beba2

Browse files
committed
Merge branch 'development'
2 parents 77c83a6 + 9488097 commit b4beba2

File tree

4 files changed

+97
-11
lines changed

4 files changed

+97
-11
lines changed

ciscosparkapi/api/messages.py

Lines changed: 39 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,11 @@
1212
from builtins import object
1313
from six import string_types
1414

15+
from requests_toolbelt import MultipartEncoder
16+
1517
from ciscosparkapi.exceptions import ciscosparkapiException
16-
from ciscosparkapi.helper import generator_container
18+
from ciscosparkapi.helper import generator_container, is_web_url, \
19+
is_local_file, open_local_file
1720
from ciscosparkapi.restsession import RestSession
1821
from ciscosparkapi.sparkdata import SparkData
1922

@@ -196,7 +199,11 @@ def create(self, roomId=None, toPersonId=None, toPersonEmail=None,
196199
specified this parameter may be optionally used to provide
197200
alternate text forUI clients that do not support rich text.
198201
markdown(string_types): The message, in markdown format.
199-
files(list): A list of URL references for the message attachments.
202+
files(list): A list containing local paths or URL references for
203+
the message attachment(s). The files attribute currently only
204+
takes a list containing one (1) filename or URL as an input.
205+
This is a Spark API limitation that may be lifted at a later
206+
date.
200207
201208
Returns:
202209
Message: With the details of the created message.
@@ -216,6 +223,7 @@ def create(self, roomId=None, toPersonId=None, toPersonEmail=None,
216223
assert markdown is None or isinstance(markdown, string_types)
217224
assert files is None or isinstance(files, list)
218225
post_data = {}
226+
# Where is message to be posted?
219227
if roomId:
220228
post_data['roomId'] = roomId
221229
elif toPersonId:
@@ -227,18 +235,45 @@ def create(self, roomId=None, toPersonId=None, toPersonEmail=None,
227235
"toPersonEmail to which you want to post a new " \
228236
"message."
229237
raise ciscosparkapiException(error_message)
238+
# Ensure some message 'content' is provided.
230239
if not text and not markdown and not files:
231240
error_message = "You must supply some message content (text, " \
232241
"markdown, files) when posting a message."
233242
raise ciscosparkapiException(error_message)
243+
# Process the content.
234244
if text:
235245
post_data['text'] = text
236246
if markdown:
237247
post_data['markdown'] = markdown
248+
upload_local_file = False
238249
if files:
239-
post_data['files'] = files
250+
if len(files) > 1:
251+
error_message = "The files attribute currently only takes a " \
252+
"list containing one (1) filename or URL as " \
253+
"an input. This is a Spark API limitation " \
254+
"that may be lifted at a later date."
255+
raise ciscosparkapiException(error_message)
256+
if is_web_url(files[0]):
257+
post_data['files'] = files
258+
elif is_local_file(files[0]):
259+
upload_local_file = True
260+
post_data['files'] = open_local_file(files[0])
261+
else:
262+
error_message = "The provided files argument does not " \
263+
"contain a valid URL or local file path."
264+
raise ciscosparkapiException(error_message)
240265
# API request
241-
json_obj = self.session.post('messages', json=post_data)
266+
if upload_local_file:
267+
try:
268+
multipart_data = MultipartEncoder(post_data)
269+
headers = {'Content-type': multipart_data.content_type}
270+
json_obj = self.session.post('messages',
271+
data=multipart_data,
272+
headers=headers)
273+
finally:
274+
post_data['files'].file_object.close()
275+
else:
276+
json_obj = self.session.post('messages', json=post_data)
242277
# Return a Message object created from the response JSON data
243278
return Message(json_obj)
244279

ciscosparkapi/helper.py

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,12 @@
55
from future import standard_library
66
standard_library.install_aliases()
77
from builtins import object
8+
from six import string_types
89

10+
from collections import namedtuple
911
import functools
12+
import mimetypes
13+
import os
1014
import urllib.parse
1115

1216
from ciscosparkapi import ciscosparkapiException, SparkApiError
@@ -21,6 +25,10 @@
2125
}
2226

2327

28+
EncodableFile = namedtuple('EncodableFile',
29+
['file_name', 'file_object', 'content_type'])
30+
31+
2432
def validate_base_url(base_url):
2533
"""Verify that base_url specifies a protocol and network location."""
2634
parsed_url = urllib.parse.urlparse(base_url)
@@ -32,6 +40,36 @@ def validate_base_url(base_url):
3240
raise ciscosparkapiException(error_message)
3341

3442

43+
def is_web_url(string):
44+
"""Check to see if string is an validly-formatted web url."""
45+
assert isinstance(string, string_types)
46+
parsed_url = urllib.parse.urlparse(string)
47+
if (parsed_url.scheme.lower() == 'http' \
48+
or parsed_url.scheme.lower() == 'https') \
49+
and parsed_url.netloc:
50+
return True
51+
else:
52+
return False
53+
54+
55+
def is_local_file(string):
56+
"""Check to see if string is a valid local file path."""
57+
assert isinstance(string, string_types)
58+
return os.path.isfile(string)
59+
60+
61+
def open_local_file(file_path):
62+
"""Open the file and return an EncodableFile tuple."""
63+
assert isinstance(file_path, string_types)
64+
assert is_local_file(file_path)
65+
file_name = os.path.basename(file_path)
66+
file_object = open(file_path, 'rb')
67+
content_type = mimetypes.guess_type(file_name)[0] or 'text/plain'
68+
return EncodableFile(file_name=file_name,
69+
file_object=file_object,
70+
content_type=content_type)
71+
72+
3573
def raise_if_extra_kwargs(kwargs):
3674
"""Raise a TypeError if kwargs is not empty."""
3775
if kwargs:

ciscosparkapi/restsession.py

Lines changed: 19 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -152,17 +152,31 @@ def get_items(self, url, params=None, **kwargs):
152152
for item in items:
153153
yield item
154154

155-
def post(self, url, json, **kwargs):
155+
def post(self, url, json=None, data=None, headers=None, **kwargs):
156156
# Process args
157157
assert isinstance(url, string_types)
158-
assert isinstance(json, dict)
159158
abs_url = self.urljoin(url)
160-
# Process kwargs
161-
timeout = kwargs.pop('timeout', self.timeout)
159+
# Process listed kwargs
160+
request_args = {}
161+
assert json is None or isinstance(json, dict)
162+
assert headers is None or isinstance(headers, dict)
163+
if json and data:
164+
raise TypeError("You must provide either a json or data argument, "
165+
"not both.")
166+
elif json:
167+
request_args['json'] = json
168+
elif data:
169+
request_args['data'] = data
170+
elif not json and not data:
171+
raise TypeError("You must provide either a json or data argument.")
172+
if headers:
173+
request_args['headers'] = headers
174+
# Process unlisted kwargs
175+
request_args['timeout'] = kwargs.pop('timeout', self.timeout)
162176
erc = kwargs.pop('erc', ERC['POST'])
163177
raise_if_extra_kwargs(kwargs)
164178
# API request
165-
response = self._req_session.post(abs_url, json=json, timeout=timeout)
179+
response = self._req_session.post(abs_url, **request_args)
166180
# Process response
167181
check_response_code(response, erc)
168182
return extract_and_parse_json(response)

setup.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,6 @@
4646
'Programming Language :: Python :: 2.6',
4747
'Programming Language :: Python :: 2.7',
4848
'Programming Language :: Python :: 3',
49-
'Programming Language :: Python :: 3.3',
5049
'Programming Language :: Python :: 3.4',
5150
'Programming Language :: Python :: 3.5',
5251
'Topic :: Communications',
@@ -56,5 +55,5 @@
5655
keywords='cisco spark api enterprise messaging',
5756

5857
packages=['ciscosparkapi', 'ciscosparkapi.api'],
59-
install_requires=['requests>=2.4.2', 'six', 'future'],
58+
install_requires=['requests>=2.4.2', 'requests_toolbelt', 'six', 'future'],
6059
)

0 commit comments

Comments
 (0)