Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,16 @@ print(response.body)
print(response.headers)
```

## Timeout

By default, HTTP requests will time out after 30 seconds. You can change this by passing a `timeout` parameter (in seconds) when creating the client:

```python
sg = sendgrid.SendGridAPIClient(api_key=os.environ.get('SENDGRID_API_KEY'), timeout=60)
```

To disable the timeout, pass `None`.

## General v3 Web API Usage (With [Fluent Interface](https://sendgrid.com/blog/using-python-to-implement-a-fluent-interface-to-any-rest-api/))

```python
Expand Down
25 changes: 21 additions & 4 deletions sendgrid/base_interface.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,14 @@
import socket
import urllib.error

import python_http_client

from .helpers.mail.exceptions import SendGridTimeoutError

region_host_dict = {'eu':'https://api.eu.sendgrid.com','global':'https://api.sendgrid.com'}

class BaseInterface(object):
def __init__(self, auth, host, impersonate_subuser):
def __init__(self, auth, host, impersonate_subuser, timeout=30):
"""
Construct the Twilio SendGrid v3 API object.
Note that the underlying client is being set up during initialization,
Expand All @@ -20,18 +25,23 @@ def __init__(self, auth, host, impersonate_subuser):
:type impersonate_subuser: string
:param host: base URL for API calls
:type host: string
:param timeout: the timeout (in seconds) for HTTP requests.
Defaults to 30. Set to None to disable.
:type timeout: int
"""
from . import __version__
self.auth = auth
self.impersonate_subuser = impersonate_subuser
self.version = __version__
self.useragent = 'sendgrid/{};python'.format(self.version)
self.host = host
self.timeout = timeout

self.client = python_http_client.Client(
host=self.host,
request_headers=self._default_headers,
version=3)
version=3,
timeout=timeout)

@property
def _default_headers(self):
Expand Down Expand Up @@ -60,7 +70,13 @@ def send(self, message):
if not isinstance(message, dict):
message = message.get()

return self.client.mail.send.post(request_body=message)
try:
return self.client.mail.send.post(request_body=message)
except urllib.error.URLError as e:
if isinstance(e.reason, socket.timeout):
raise SendGridTimeoutError(
'The SendGrid API request timed out') from e
raise

def set_sendgrid_data_residency(self, region):
"""
Expand All @@ -78,6 +94,7 @@ def set_sendgrid_data_residency(self, region):
self.client = python_http_client.Client(
host=self.host,
request_headers=self._default_headers,
version=3)
version=3,
timeout=self.timeout)
else:
raise ValueError("region can only be \"eu\" or \"global\"")
5 changes: 5 additions & 0 deletions sendgrid/helpers/mail/exceptions.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,11 @@ class SendGridException(Exception):
pass


class SendGridTimeoutError(SendGridException):
"""Exception raised when an HTTP request to the SendGrid API times out"""
pass


class ApiKeyIncludedException(SendGridException):
"""Exception raised for when Twilio SendGrid API Key included in message text"""

Expand Down
8 changes: 6 additions & 2 deletions sendgrid/sendgrid.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,8 @@ def __init__(
self,
api_key=None,
host='https://api.sendgrid.com',
impersonate_subuser=None):
impersonate_subuser=None,
timeout=30):
"""
Construct the Twilio SendGrid v3 API object.
Note that the underlying client is being set up during initialization,
Expand All @@ -51,8 +52,11 @@ def __init__(
:type impersonate_subuser: string
:param host: base URL for API calls
:type host: string
:param timeout: the timeout (in seconds) for HTTP requests.
Defaults to 30. Set to None to disable.
:type timeout: int
"""
self.api_key = api_key or os.environ.get('SENDGRID_API_KEY')
auth = 'Bearer {}'.format(self.api_key)

super(SendGridAPIClient, self).__init__(auth, host, impersonate_subuser)
super(SendGridAPIClient, self).__init__(auth, host, impersonate_subuser, timeout=timeout)
8 changes: 6 additions & 2 deletions sendgrid/twilio_email.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,8 @@ def __init__(
username=None,
password=None,
host='https://email.twilio.com',
impersonate_subuser=None):
impersonate_subuser=None,
timeout=30):
"""
Construct the Twilio Email v3 API object.
Note that the underlying client is being set up during initialization,
Expand All @@ -59,6 +60,9 @@ def __init__(
:type impersonate_subuser: string
:param host: base URL for API calls
:type host: string
:param timeout: the timeout (in seconds) for HTTP requests.
Defaults to 30. Set to None to disable.
:type timeout: int
"""
self.username = username or \
os.environ.get('TWILIO_API_KEY') or \
Expand All @@ -70,4 +74,4 @@ def __init__(

auth = 'Basic ' + b64encode('{}:{}'.format(self.username, self.password).encode()).decode()

super(TwilioEmailAPIClient, self).__init__(auth, host, impersonate_subuser)
super(TwilioEmailAPIClient, self).__init__(auth, host, impersonate_subuser, timeout=timeout)
25 changes: 24 additions & 1 deletion test/unit/test_sendgrid.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
import socket
import unittest
from unittest.mock import patch
import urllib.error

import sendgrid
from sendgrid.helpers.mail.exceptions import SendGridTimeoutError

class UnitTests(unittest.TestCase):
def test_host_with_no_region(self):
Expand All @@ -24,4 +29,22 @@ def test_with_region_is_none(self):
def test_with_region_is_invalid(self):
sg = sendgrid.SendGridAPIClient(api_key='MY_API_KEY')
with self.assertRaises(ValueError):
sg.set_sendgrid_data_residency("abc")
sg.set_sendgrid_data_residency("abc")

def test_timeout_default(self):
sg = sendgrid.SendGridAPIClient(api_key='MY_API_KEY')
self.assertEqual(sg.timeout, 30)
self.assertEqual(sg.client.timeout, 30)

def test_timeout_set_via_constructor(self):
sg = sendgrid.SendGridAPIClient(api_key='MY_API_KEY', timeout=10)
self.assertEqual(sg.timeout, 10)
self.assertEqual(sg.client.timeout, 10)

@patch('python_http_client.Client')
def test_send_timeout_raises_sendgrid_timeout_error(self, MockClient):
sg = sendgrid.SendGridAPIClient(api_key='MY_API_KEY')
sg.client.mail.send.post.side_effect = urllib.error.URLError(
reason=socket.timeout('timed out'))
with self.assertRaises(SendGridTimeoutError):
sg.send({'key': 'value'})
10 changes: 10 additions & 0 deletions test/unit/test_twilio_email.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,3 +35,13 @@ def test_init_args(self):
self.assertEqual(mail_client.username, 'username')
self.assertEqual(mail_client.password, 'password')
self.assertEqual(mail_client.auth, 'Basic dXNlcm5hbWU6cGFzc3dvcmQ=')

def test_timeout_default(self):
mail_client = TwilioEmailAPIClient('username', 'password')
self.assertEqual(mail_client.timeout, 30)
self.assertEqual(mail_client.client.timeout, 30)

def test_timeout_set_via_constructor(self):
mail_client = TwilioEmailAPIClient('username', 'password', timeout=10)
self.assertEqual(mail_client.timeout, 10)
self.assertEqual(mail_client.client.timeout, 10)
Loading