Skip to content

Commit 4c56129

Browse files
committed
Surface server_error in the Python SDK
Summary: This diff changes the Python SDK to expose `server_error` when available. Test Plan: Mocked the error in the unit tests and wrote tests for all cases. Reviewers: emfree Reviewed By: emfree Projects: #python-sdk Differential Revision: https://phab.nylas.com/D2048
1 parent 716391f commit 4c56129

File tree

6 files changed

+93
-23
lines changed

6 files changed

+93
-23
lines changed

README.md

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -231,7 +231,20 @@ draft.to = [{'name': 'My Friend', 'email': '[email protected]'}]
231231
draft.subject = "Here's an attachment"
232232
draft.body = "Cheers mate!"
233233
draft.attach(myfile)
234-
draft.send()
234+
235+
# Send it
236+
try:
237+
draft.send()
238+
except nylas.client.errors.ConnectionError as e:
239+
print "Unable to connect to the SMTP server."
240+
except nylas.client.errors.MessageRejectedError as e:
241+
print "Message got rejected by the SMTP server!"
242+
print e.message
243+
244+
# Sometimes the API gives us the exact error message
245+
# returned by the server. Display it since it can be
246+
# helpful to know exactly why our message got rejected:
247+
print e.server_error
235248
```
236249

237250
### Working with Events

nylas/client/client.py

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -52,12 +52,15 @@ def _validate(response):
5252
cls = status_code_to_exc[status_code]
5353
try:
5454
response = json.loads(response.text)
55-
if 'message' in response:
56-
raise cls(url=url, status_code=status_code,
57-
data=data, message=response['message'])
58-
else:
59-
raise cls(url=url, status_code=status_code,
60-
data=data, message="N/A")
55+
kwargs = dict(url=url, status_code=status_code,
56+
data=data)
57+
58+
for key in ['message', 'server_error']:
59+
if key in response:
60+
kwargs[key] = response[key]
61+
62+
raise cls(**kwargs)
63+
6164
except (ValueError, TypeError):
6265
raise cls(url=url, status_code=status_code,
6366
data=data, message="Malformed")

nylas/client/errors.py

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,15 @@
33

44
class APIClientError(Exception):
55
def __init__(self, **kwargs):
6+
if 'message' in kwargs:
7+
Exception.__init__(self, kwargs['message'])
8+
else:
9+
Exception.__init__(self, '')
10+
611
self.attrs = kwargs.keys()
712
for k, v in kwargs.items():
813
setattr(self, k, v)
914

10-
Exception.__init__(self, str(kwargs))
11-
1215
def as_dict(self):
1316
resp = {}
1417
for attr in self.attrs:
@@ -61,3 +64,7 @@ class ServiceUnavailableError(APIClientError):
6164

6265
class ServerTimeoutError(APIClientError):
6366
pass
67+
68+
69+
class FileUploadError(APIClientError):
70+
pass

nylas/client/restful_models.py

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
from .restful_model_collection import RestfulModelCollection
2+
from .errors import FileUploadError
23
from six import StringIO
34
import base64
45
import json
@@ -367,8 +368,9 @@ def save(self):
367368
elif hasattr(self, 'data') and self.data is not None:
368369
data = {self.filename: StringIO(self.data)}
369370
else:
370-
raise Exception("File object not properly formatted, must provide"
371-
" either a stream or data.")
371+
raise FileUploadError(message=("File object not properly "
372+
"formatted, must provide "
373+
"either a stream or data."))
372374

373375
new_obj = self.api._create_resources(File, data)
374376
new_obj = new_obj[0]
@@ -378,7 +380,8 @@ def save(self):
378380

379381
def download(self):
380382
if not self.id:
381-
raise Exception("Can't download a file that hasn't been uploaded.")
383+
raise FileUploadError(message=("Can't download a file that "
384+
"hasn't been uploaded."))
382385

383386
return self.api._get_resource_data(File, self.id,
384387
extra='download')

tests/test_files.py

Lines changed: 20 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
import httpretty
55
from httpretty import Response
66
from conftest import API_URL
7-
from nylas.client.errors import InvalidRequestError
7+
from nylas.client.errors import InvalidRequestError, FileUploadError
88

99

1010
def test_file_upload(api_client):
@@ -23,13 +23,25 @@ def test_file_upload(api_client):
2323
httpretty.register_uri(httpretty.GET, API_URL + '/files/3qfe4k3siosfjtjpfdnon8zbn/download',
2424
body='test body')
2525

26-
myfile2 = api_client.files.create()
27-
myfile2.filename = 'test.txt'
28-
myfile2.data = "Hello World."
29-
myfile2.save()
26+
myfile = api_client.files.create()
27+
myfile.filename = 'test.txt'
28+
myfile.data = "Hello World."
29+
myfile.save()
3030

31-
assert myfile2.filename == 'a.txt'
32-
assert myfile2.size == 762878
31+
assert myfile.filename == 'a.txt'
32+
assert myfile.size == 762878
3333

34-
data = myfile2.download()
34+
data = myfile.download()
3535
assert data == 'test body'
36+
37+
38+
def test_file_upload_errors(api_client):
39+
myfile = api_client.files.create()
40+
myfile.filename = 'test.txt'
41+
myfile.data = "Hello World."
42+
43+
with pytest.raises(FileUploadError) as exc:
44+
myfile.download()
45+
46+
assert exc.value.message == ("Can't download a file that "
47+
"hasn't been uploaded.")

tests/test_send_error_handling.py

Lines changed: 35 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,12 +8,17 @@
88
API_URL = 'http://localhost:2222'
99

1010

11-
def mock_sending_error(http_code, message):
11+
def mock_sending_error(http_code, message, server_error=None):
1212
send_endpoint = re.compile(API_URL + '/send')
13-
response_body = json.dumps({
13+
response_body = {
1414
"type": "api_error",
1515
"message": message
16-
})
16+
}
17+
18+
if server_error is not None:
19+
response_body['server_error'] = server_error
20+
21+
response_body = json.dumps(response_body)
1722
responses.add(responses.POST, send_endpoint,
1823
content_type='application/json', status=http_code,
1924
body=response_body)
@@ -48,3 +53,30 @@ def test_handle_service_unavailable(api_client, mock_account,
4853
with pytest.raises(ServiceUnavailableError) as exc:
4954
draft.send()
5055
assert exc.value.message == error_message
56+
57+
58+
@responses.activate
59+
def test_returns_server_error(api_client, mock_account,
60+
mock_save_draft):
61+
draft = api_client.drafts.create()
62+
error_message = 'The server unexpectedly closed the connection'
63+
reason = 'Rejected potential SPAM'
64+
mock_sending_error(503, error_message,
65+
server_error=reason)
66+
with pytest.raises(ServiceUnavailableError) as exc:
67+
draft.send()
68+
69+
assert exc.value.message == error_message
70+
assert exc.value.server_error == reason
71+
72+
73+
@responses.activate
74+
def test_doesnt_return_server_error_if_not_defined(api_client, mock_account,
75+
mock_save_draft):
76+
draft = api_client.drafts.create()
77+
error_message = 'The server unexpectedly closed the connection'
78+
mock_sending_error(503, error_message)
79+
with pytest.raises(ServiceUnavailableError) as exc:
80+
draft.send()
81+
assert exc.value.message == error_message
82+
assert not hasattr(exc.value, 'server_error')

0 commit comments

Comments
 (0)