From 3f98235f97fc900daad08d80cf97530b4baaba88 Mon Sep 17 00:00:00 2001 From: Jakub Paczkowski Date: Thu, 11 Feb 2016 14:01:35 -0800 Subject: [PATCH] add asyncio support --- README.rst | 20 ++++++++++++ sendgrid/__init__.py | 4 ++- sendgrid/async.py | 76 ++++++++++++++++++++++++++++++++++++++++++++ sendgrid/client.py | 12 +++---- setup.py | 2 ++ 5 files changed, 105 insertions(+), 9 deletions(-) create mode 100644 sendgrid/async.py diff --git a/README.rst b/README.rst index e4b5ca567..2d6ca05db 100644 --- a/README.rst +++ b/README.rst @@ -541,6 +541,26 @@ Using Templates from the Template Engine message.add_filter('templates', 'template_id', 'TEMPLATE-ALPHA-NUMERIC-ID') message.add_substitution('key', 'value') +Asyncio- compatible version of client +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +You can use sendgrid-python also with asyncio library. It's enough to use `sendgrid.AsyncSendGridClient` +instead of `sendgrid.SendGridClient` or `sendgrid.AsyncSendGridAPIClient` instead of `sendgrid.SendGridAPIClient`. + +.. code:: python + + import sendgrid + + sg = sendgrid.AsyncSendGridClient('YOUR_SENDGRID_API_KEY') + + message = sendgrid.Mail() + message.add_to('John Doe ') + message.set_subject('Example') + message.set_html('Body') + message.set_text('Body') + message.set_from('Doe John ') + status, msg = yield from sg.send(message) + Tests ~~~~~ diff --git a/sendgrid/__init__.py b/sendgrid/__init__.py index 270c2895e..08bcb92e5 100644 --- a/sendgrid/__init__.py +++ b/sendgrid/__init__.py @@ -4,4 +4,6 @@ #v2 API from .message import Mail #v3 API -from .client import SendGridAPIClient \ No newline at end of file +from .client import SendGridAPIClient +#Async +from .async import AsyncSendGridClient, AsyncSendGridAPIClient \ No newline at end of file diff --git a/sendgrid/async.py b/sendgrid/async.py new file mode 100644 index 000000000..126a00d43 --- /dev/null +++ b/sendgrid/async.py @@ -0,0 +1,76 @@ +import warnings + +import asyncio +from urllib.error import HTTPError, URLError + +import aiohttp +from sendgrid import SendGridClientError, SendGridServerError, SendGridClient + +from sendgrid.client import SendGridAPIClient + + +class AsyncSendGridClient(SendGridClient): + @asyncio.coroutine + def _make_request(self, message): + connector = aiohttp.TCPConnector(conn_timeout=self.timeout) + if self.proxies: + if 'http' in self.proxies: + connector = aiohttp.ProxyConnector(proxy=self.proxies['http'], conn_timeout=self.timeout) + else: + warnings.warn("Only http proxies are supported for async connections") + session = aiohttp.ClientSession(connector=connector) + headers = { + 'User-Agent': self.useragent, + 'Accept': '*/*', + } + if self.username is None: + # Using API key + headers['Authorization'] = 'Bearer {}'.format(self.password) + data = self._build_body(message) + method = 'POST' if data else 'GET' + try: + resp = yield from session.request(method, self.mail_url, data=data, headers=headers) + except aiohttp.ClientTimeoutError: + session.close() + raise URLError('Request timeout') + resp_body = yield from resp.text() + session.close() + if resp.status == 200: + return resp.status, resp_body + elif 400 <= resp.status < 600: + raise HTTPError(resp.status, resp_body, None, None, None) + else: + assert False + + +class AsyncSendGridAPIClient(SendGridAPIClient): + @asyncio.coroutine + def _build_request(self, url, json_header=False, method='GET', data=None): + connector = aiohttp.TCPConnector(conn_timeout=self.timeout) + if self.proxies: + if 'http' in self.proxies: + connector = aiohttp.ProxyConnector(proxy=self.proxies['http'], conn_timeout=self.timeout) + else: + warnings.warn("Only http proxies are supported for async connections") + session = aiohttp.ClientSession(connector=connector) + headers = { + 'User-Agent': self.useragent, + 'Authorization': 'Bearer {}'.format(self.apikey) + } + if json_header: + headers['Content-Type'] = 'application/json' + try: + resp = yield from session.request(method, url, data=data, headers=headers) + except aiohttp.ClientTimeoutError: + session.close() + raise SendGridClientError(408, 'Request timeout') + resp_body = yield from resp.text() + session.close() + if resp.status == 200: + return resp.status, resp_body + elif 400 <= resp.status < 500: + raise SendGridClientError(resp.status, resp_body) + elif 500 <= resp.status < 600: + raise SendGridServerError(resp.status, resp_body) + else: + assert False diff --git a/sendgrid/client.py b/sendgrid/client.py index 6a389de29..8726b0cf8 100644 --- a/sendgrid/client.py +++ b/sendgrid/client.py @@ -82,20 +82,16 @@ def _build_request(self, url, json_header=False, method='GET', data=None): def get(self, api): url = self.host + api.endpoint - response, body = self._build_request(url, False, 'GET') - return response, body + return self._build_request(url, False, 'GET') def post(self, api, data): url = self.host + api.endpoint - response, body = self._build_request(url, True, 'POST', data) - return response, body + return self._build_request(url, True, 'POST', data) def delete(self, api): url = self.host + api.endpoint - response, body = self._build_request(url, False, 'DELETE') - return response, body + return self._build_request(url, False, 'DELETE') def patch(self, api, data): url = self.host + api.endpoint - response, body = self._build_request(url, True, 'PATCH', data) - return response, body + return self._build_request(url, True, 'PATCH', data) diff --git a/setup.py b/setup.py index 006e025bc..8a2eee0ba 100644 --- a/setup.py +++ b/setup.py @@ -12,6 +12,8 @@ def getRequires(): deps.append('unittest2') elif (3, 0) <= sys.version_info < (3, 2): deps.append('unittest2py3k') + if (3, 0) <= sys.version_info: + deps.append('aiohttp') return deps setup(