Skip to content

Commit 411c565

Browse files
committed
Merge pull request #94 from friedcell/tornado
More extensibility and Tornado integration
2 parents c4a194d + 9d7a191 commit 411c565

17 files changed

+433
-32
lines changed

AUTHORS.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,4 +15,5 @@ Patches and suggestions
1515
- Simeon Visser `@svisser <https://github.com/svisser>`_
1616
- `@gnarvaja <https://github.com/gnarvaja>`_
1717
- `@puttu <https://github.com/puttu>`_
18+
- Marko Mrdjenovic `@friedcell <https://github.com/friedcell>`_
1819
- ADD YOURSELF HERE (and link to your github page)

dev-requirements.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,3 +2,4 @@
22
wheel
33
twine
44
Django>=1.7,<1.10
5+
tornado>=3.2

sparkpost/__init__.py

Lines changed: 19 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import os
22

3+
from .base import RequestsTransport
34
from .exceptions import SparkPostException
45
from .metrics import Metrics
56
from .recipient_lists import RecipientLists
@@ -8,31 +9,37 @@
89
from .transmissions import Transmissions
910

1011

11-
__version__ = '1.0.5'
12-
13-
14-
def get_api_key():
15-
"Get API key from environment variable"
16-
return os.environ.get('SPARKPOST_API_KEY', None)
12+
__version__ = '1.0.6.dev1'
1713

1814

1915
class SparkPost(object):
16+
TRANSPORT_CLASS = RequestsTransport
17+
2018
def __init__(self, api_key=None, base_uri='https://api.sparkpost.com',
2119
version='1'):
2220
"Set up the SparkPost API client"
2321
if not api_key:
24-
api_key = get_api_key()
22+
api_key = self.get_api_key()
2523
if not api_key:
2624
raise SparkPostException("No API key. Improve message.")
2725

2826
self.base_uri = base_uri + '/api/v' + version
2927
self.api_key = api_key
3028

31-
self.metrics = Metrics(self.base_uri, self.api_key)
32-
self.recipient_lists = RecipientLists(self.base_uri, self.api_key)
33-
self.suppression_list = SuppressionList(self.base_uri, self.api_key)
34-
self.templates = Templates(self.base_uri, self.api_key)
35-
self.transmissions = Transmissions(self.base_uri, self.api_key)
29+
self.metrics = Metrics(self.base_uri, self.api_key,
30+
self.TRANSPORT_CLASS)
31+
self.recipient_lists = RecipientLists(self.base_uri, self.api_key,
32+
self.TRANSPORT_CLASS)
33+
self.suppression_list = SuppressionList(self.base_uri, self.api_key,
34+
self.TRANSPORT_CLASS)
35+
self.templates = Templates(self.base_uri, self.api_key,
36+
self.TRANSPORT_CLASS)
37+
self.transmissions = Transmissions(self.base_uri, self.api_key,
38+
self.TRANSPORT_CLASS)
3639
# Keeping self.transmission for backwards compatibility.
3740
# Will be removed in a future release.
3841
self.transmission = self.transmissions
42+
43+
def get_api_key(self):
44+
"Get API key from environment variable"
45+
return os.environ.get('SPARKPOST_API_KEY', None)

sparkpost/base.py

Lines changed: 20 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,28 @@
1-
import requests
21
import sparkpost
32

43
from .exceptions import SparkPostAPIException
54

65

6+
class RequestsTransport(object):
7+
def request(self, method, uri, headers, **kwargs):
8+
import requests
9+
response = requests.request(method, uri, headers=headers, **kwargs)
10+
if response.status_code == 204:
11+
return True
12+
if not response.ok:
13+
raise SparkPostAPIException(response)
14+
if 'results' in response.json():
15+
return response.json()['results']
16+
return response.json()
17+
18+
719
class Resource(object):
8-
def __init__(self, base_uri, api_key):
20+
key = ""
21+
22+
def __init__(self, base_uri, api_key, transport_class=RequestsTransport):
923
self.base_uri = base_uri
1024
self.api_key = api_key
25+
self.transport = transport_class()
1126

1227
@property
1328
def uri(self):
@@ -19,14 +34,9 @@ def request(self, method, uri, **kwargs):
1934
'Content-Type': 'application/json',
2035
'Authorization': self.api_key
2136
}
22-
response = requests.request(method, uri, headers=headers, **kwargs)
23-
if response.status_code == 204:
24-
return True
25-
if not response.ok:
26-
raise SparkPostAPIException(response)
27-
if 'results' in response.json():
28-
return response.json()['results']
29-
return response.json()
37+
response = self.transport.request(method, uri, headers=headers,
38+
**kwargs)
39+
return response
3040

3141
def get(self):
3242
raise NotImplementedError

sparkpost/django/message.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -65,4 +65,4 @@ def __init__(self, message):
6565
'type': mimetype
6666
})
6767

68-
return super(SparkPostMessage, self).__init__(formatted)
68+
super(SparkPostMessage, self).__init__(formatted)

sparkpost/exceptions.py

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,18 @@ class SparkPostException(Exception):
55
class SparkPostAPIException(SparkPostException):
66
"Handle 4xx and 5xx errors from the SparkPost API"
77
def __init__(self, response, *args, **kwargs):
8-
errors = response.json()['errors']
9-
errors = [e['message'] + ': ' + e.get('description', '')
10-
for e in errors]
8+
errors = None
9+
try:
10+
errors = response.json()['errors']
11+
errors = [e['message'] + ': ' + e.get('description', '')
12+
for e in errors]
13+
except:
14+
pass
15+
if not errors:
16+
errors = [response.text or ""]
17+
self.status = response.status_code
18+
self.response = response
19+
self.errors = errors
1120
message = """Call to {uri} returned {status_code}, errors:
1221
1322
{errors}

sparkpost/metrics.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,13 @@
1-
from .base import Resource
1+
from .base import Resource, RequestsTransport
22

33

44
class Metrics(object):
55
"Wrapper for sub-resources"
66

7-
def __init__(self, base_uri, api_key):
7+
def __init__(self, base_uri, api_key, transport_class=RequestsTransport):
88
self.base_uri = "%s/%s" % (base_uri, 'metrics')
9-
self.campaigns = Campaigns(self.base_uri, api_key)
10-
self.domains = Domains(self.base_uri, api_key)
9+
self.campaigns = Campaigns(self.base_uri, api_key, transport_class)
10+
self.domains = Domains(self.base_uri, api_key, transport_class)
1111

1212

1313
class Campaigns(Resource):

sparkpost/tornado/__init__.py

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
import sparkpost
2+
3+
from .exceptions import SparkPostAPIException
4+
from .base import TornadoTransport
5+
from .transmissions import Transmissions
6+
7+
__all__ = ["SparkPost", "TornadoTransport", "SparkPostAPIException",
8+
"Transmissions"]
9+
10+
11+
class SparkPost(sparkpost.SparkPost):
12+
TRANSPORT_CLASS = TornadoTransport
13+
14+
def __init__(self, *args, **kwargs):
15+
super(SparkPost, self).__init__(*args, **kwargs)
16+
self.transmissions = Transmissions(self.base_uri, self.api_key,
17+
self.TRANSPORT_CLASS)
18+
self.transmission = self.transmissions

sparkpost/tornado/base.py

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
import json
2+
from tornado import gen
3+
from tornado.httpclient import AsyncHTTPClient, HTTPError
4+
5+
from .exceptions import SparkPostAPIException
6+
7+
8+
class TornadoTransport(object):
9+
@gen.coroutine
10+
def request(self, method, uri, headers, **kwargs):
11+
if "data" in kwargs:
12+
kwargs["body"] = kwargs.pop("data")
13+
client = AsyncHTTPClient()
14+
try:
15+
response = yield client.fetch(uri, method=method, headers=headers,
16+
**kwargs)
17+
except HTTPError as ex:
18+
raise SparkPostAPIException(ex.response)
19+
if response.code == 204:
20+
raise gen.Return(True)
21+
if response.code == 200:
22+
result = None
23+
try:
24+
result = json.loads(response.body.decode("utf-8"))
25+
except:
26+
pass
27+
if result:
28+
if 'results' in result:
29+
raise gen.Return(result['results'])
30+
raise gen.Return(result)
31+
raise SparkPostAPIException(response)

sparkpost/tornado/exceptions.py

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
import json
2+
3+
from ..exceptions import SparkPostAPIException as RequestsSparkPostAPIException
4+
5+
6+
class SparkPostAPIException(RequestsSparkPostAPIException):
7+
def __init__(self, response, *args, **kwargs):
8+
errors = None
9+
try:
10+
data = json.loads(response.body.decode("utf-8"))
11+
if data:
12+
errors = data['errors']
13+
errors = [e['message'] + ': ' + e.get('description', '')
14+
for e in errors]
15+
except:
16+
pass
17+
if not errors:
18+
errors = [response.body.decode("utf-8") or ""]
19+
self.status = response.code
20+
self.response = response
21+
self.errors = errors
22+
message = """Call to {uri} returned {status_code}, errors:
23+
24+
{errors}
25+
""".format(
26+
uri=response.effective_url,
27+
status_code=response.code,
28+
errors='\n'.join(errors)
29+
)
30+
super(RequestsSparkPostAPIException, self).__init__(message, *args,
31+
**kwargs)

0 commit comments

Comments
 (0)