Skip to content

Commit 1bfb2ed

Browse files
committed
Add timeout parameter to SendGrid API clients
Default to a 30 second HTTP timeout to prevent hung processes. The timeout is passed through to python_http_client which already supports it. Callers can override or disable (via None) as needed. Timeouts via send() raise SendGridTimeoutError for clean handling.
1 parent 76788e7 commit 1bfb2ed

File tree

6 files changed

+57
-8
lines changed

6 files changed

+57
-8
lines changed

README.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -137,6 +137,16 @@ print(response.body)
137137
print(response.headers)
138138
```
139139

140+
## Timeout
141+
142+
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:
143+
144+
```python
145+
sg = sendgrid.SendGridAPIClient(api_key=os.environ.get('SENDGRID_API_KEY'), timeout=60)
146+
```
147+
148+
To disable the timeout, pass `None`.
149+
140150
## General v3 Web API Usage (With [Fluent Interface](https://sendgrid.com/blog/using-python-to-implement-a-fluent-interface-to-any-rest-api/))
141151

142152
```python

sendgrid/base_interface.py

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
region_host_dict = {'eu':'https://api.eu.sendgrid.com','global':'https://api.sendgrid.com'}
44

55
class BaseInterface(object):
6-
def __init__(self, auth, host, impersonate_subuser):
6+
def __init__(self, auth, host, impersonate_subuser, timeout=30):
77
"""
88
Construct the Twilio SendGrid v3 API object.
99
Note that the underlying client is being set up during initialization,
@@ -20,18 +20,23 @@ def __init__(self, auth, host, impersonate_subuser):
2020
:type impersonate_subuser: string
2121
:param host: base URL for API calls
2222
:type host: string
23+
:param timeout: the timeout (in seconds) for HTTP requests.
24+
Defaults to 30. Set to None to disable.
25+
:type timeout: int
2326
"""
2427
from . import __version__
2528
self.auth = auth
2629
self.impersonate_subuser = impersonate_subuser
2730
self.version = __version__
2831
self.useragent = 'sendgrid/{};python'.format(self.version)
2932
self.host = host
33+
self.timeout = timeout
3034

3135
self.client = python_http_client.Client(
3236
host=self.host,
3337
request_headers=self._default_headers,
34-
version=3)
38+
version=3,
39+
timeout=timeout)
3540

3641
@property
3742
def _default_headers(self):
@@ -78,6 +83,7 @@ def set_sendgrid_data_residency(self, region):
7883
self.client = python_http_client.Client(
7984
host=self.host,
8085
request_headers=self._default_headers,
81-
version=3)
86+
version=3,
87+
timeout=self.timeout)
8288
else:
8389
raise ValueError("region can only be \"eu\" or \"global\"")

sendgrid/sendgrid.py

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,8 @@ def __init__(
3333
self,
3434
api_key=None,
3535
host='https://api.sendgrid.com',
36-
impersonate_subuser=None):
36+
impersonate_subuser=None,
37+
timeout=30):
3738
"""
3839
Construct the Twilio SendGrid v3 API object.
3940
Note that the underlying client is being set up during initialization,
@@ -51,8 +52,11 @@ def __init__(
5152
:type impersonate_subuser: string
5253
:param host: base URL for API calls
5354
:type host: string
55+
:param timeout: the timeout (in seconds) for HTTP requests.
56+
Defaults to 30. Set to None to disable.
57+
:type timeout: int
5458
"""
5559
self.api_key = api_key or os.environ.get('SENDGRID_API_KEY')
5660
auth = 'Bearer {}'.format(self.api_key)
5761

58-
super(SendGridAPIClient, self).__init__(auth, host, impersonate_subuser)
62+
super(SendGridAPIClient, self).__init__(auth, host, impersonate_subuser, timeout=timeout)

sendgrid/twilio_email.py

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,8 @@ def __init__(
3535
username=None,
3636
password=None,
3737
host='https://email.twilio.com',
38-
impersonate_subuser=None):
38+
impersonate_subuser=None,
39+
timeout=30):
3940
"""
4041
Construct the Twilio Email v3 API object.
4142
Note that the underlying client is being set up during initialization,
@@ -59,6 +60,9 @@ def __init__(
5960
:type impersonate_subuser: string
6061
:param host: base URL for API calls
6162
:type host: string
63+
:param timeout: the timeout (in seconds) for HTTP requests.
64+
Defaults to 30. Set to None to disable.
65+
:type timeout: int
6266
"""
6367
self.username = username or \
6468
os.environ.get('TWILIO_API_KEY') or \
@@ -70,4 +74,4 @@ def __init__(
7074

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

73-
super(TwilioEmailAPIClient, self).__init__(auth, host, impersonate_subuser)
77+
super(TwilioEmailAPIClient, self).__init__(auth, host, impersonate_subuser, timeout=timeout)

test/unit/test_sendgrid.py

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,4 +24,19 @@ def test_with_region_is_none(self):
2424
def test_with_region_is_invalid(self):
2525
sg = sendgrid.SendGridAPIClient(api_key='MY_API_KEY')
2626
with self.assertRaises(ValueError):
27-
sg.set_sendgrid_data_residency("abc")
27+
sg.set_sendgrid_data_residency("abc")
28+
29+
def test_timeout_default(self):
30+
sg = sendgrid.SendGridAPIClient(api_key='MY_API_KEY')
31+
self.assertEqual(sg.timeout, 30)
32+
self.assertEqual(sg.client.timeout, 30)
33+
34+
def test_timeout_set_via_constructor(self):
35+
sg = sendgrid.SendGridAPIClient(api_key='MY_API_KEY', timeout=10)
36+
self.assertEqual(sg.timeout, 10)
37+
self.assertEqual(sg.client.timeout, 10)
38+
39+
def test_timeout_preserved_after_data_residency_change(self):
40+
sg = sendgrid.SendGridAPIClient(api_key='MY_API_KEY', timeout=10)
41+
sg.set_sendgrid_data_residency("eu")
42+
self.assertEqual(sg.client.timeout, 10)

test/unit/test_twilio_email.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,3 +35,13 @@ def test_init_args(self):
3535
self.assertEqual(mail_client.username, 'username')
3636
self.assertEqual(mail_client.password, 'password')
3737
self.assertEqual(mail_client.auth, 'Basic dXNlcm5hbWU6cGFzc3dvcmQ=')
38+
39+
def test_timeout_default(self):
40+
mail_client = TwilioEmailAPIClient('username', 'password')
41+
self.assertEqual(mail_client.timeout, 30)
42+
self.assertEqual(mail_client.client.timeout, 30)
43+
44+
def test_timeout_set_via_constructor(self):
45+
mail_client = TwilioEmailAPIClient('username', 'password', timeout=10)
46+
self.assertEqual(mail_client.timeout, 10)
47+
self.assertEqual(mail_client.client.timeout, 10)

0 commit comments

Comments
 (0)