Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

GetSendStatistics support #9

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
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
135 changes: 79 additions & 56 deletions amazon_ses.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
#Copyright (c) 2011 Vladimir Pankratiev http://tagmask.com
#Copyright (c) 2011 Vladimir Pankratiev http://tagmask.com
#
#Permission is hereby granted, free of charge, to any person obtaining a copy
#of this software and associated documentation files (the "Software"), to deal
Expand All @@ -24,6 +24,7 @@
import hmac
import logging
import base64
import re
from datetime import datetime
from xml.etree.ElementTree import XML

Expand All @@ -38,7 +39,7 @@ def __init__(self, accessKeyID, secretAccessKey):
def _getSignature(self, dateValue):
h = hmac.new(key=self._secretAccessKey, msg=dateValue, digestmod=hashlib.sha256)
return base64.b64encode(h.digest()).decode()

def _getHeaders(self):
headers = { 'Content-type': 'application/x-www-form-urlencoded' }
d = datetime.utcnow()
Expand All @@ -47,11 +48,11 @@ def _getHeaders(self):
signature = self._getSignature(dateValue)
headers['X-Amzn-Authorization'] = 'AWS3-HTTPS AWSAccessKeyId=%s, Algorithm=HMACSHA256, Signature=%s' % (self._accessKeyID, signature)
return headers

def _performAction(self, actionName, params=None):
if not params:
params = {}
params['Action'] = actionName
params['Action'] = actionName
#https://email.us-east-1.amazonaws.com/
conn = httplib.HTTPSConnection('email.us-east-1.amazonaws.com')
params = urllib.urlencode(params)
Expand All @@ -60,24 +61,24 @@ def _performAction(self, actionName, params=None):
responseResult = response.read()
conn.close()
return self._responseParser.parse(actionName, response.status, response.reason, responseResult)

def verifyEmailAddress(self, emailAddress):
params = { 'EmailAddress': emailAddress }
return self._performAction('VerifyEmailAddress', params)

def deleteVerifiedEmailAddress(self, emailAddress):
params = { 'EmailAddress': emailAddress }
return self._performAction('DeleteVerifiedEmailAddress', params)

def getSendQuota(self):
return self._performAction('GetSendQuota')

def getSendStatistics(self):
return self._performAction('GetSendStatistics')

def listVerifiedEmailAddresses(self):
return self._performAction('ListVerifiedEmailAddresses')

def sendEmail(self, source, toAddresses, message, replyToAddresses=None, returnPath=None, ccAddresses=None, bccAddresses=None):
params = { 'Source': source }
for objName, addresses in zip(["ToAddresses", "CcAddresses", "BccAddresses"], [toAddresses, ccAddresses, bccAddresses]):
Expand All @@ -86,10 +87,10 @@ def sendEmail(self, source, toAddresses, message, replyToAddresses=None, returnP
for i, address in enumerate(addresses, 1):
params['Destination.%s.member.%d' % (objName, i)] = address
else:
params['Destination.%s.member.1' % objName] = addresses
params['Destination.%s.member.1' % objName] = addresses
if not returnPath:
returnPath = source
params['ReturnPath'] = returnPath
params['ReturnPath'] = returnPath
params['Message.Subject.Charset'] = message.charset
params['Message.Subject.Data'] = message.subject
if message.bodyText:
Expand All @@ -107,7 +108,7 @@ def __init__(self):
self.charset = 'UTF-8'
self.subject = None
self.bodyHtml = None
self.bodyText = None
self.bodyText = None



Expand All @@ -116,135 +117,157 @@ def __init__(self, errorType, code, message):
self.errorType = errorType
self.code = code
self.message = message

class AmazonAPIError(Exception):
def __init__(self, message):
self.message = message




class AmazonResult:
def __init__(self, requestId):
self.requestId = requestId

class AmazonSendEmailResult(AmazonResult):
def __init__(self, requestId, messageId):
self.requestId = requestId
self.messageId = messageId

class AmazonSendQuota(AmazonResult):
def __init__(self, requestId, max24HourSend, maxSendRate, sentLast24Hours):
self.requestId = requestId
self.max24HourSend = max24HourSend
self.maxSendRate = maxSendRate
self.sentLast24Hours = sentLast24Hours

class AmazonSendDataPoint:
def __init__(self, bounces, complaints, deliveryAttempts, rejects, timestamp):
self.bounces = bounces
self.complaints = complaints
self.deliveryAttempts = deliveryAttempts
self.rejects = rejects
self.timestamp = timestamp

self.deliveries = int(deliveryAttempts) - int(bounces) - int(rejects)

class AmazonSendStatistics(AmazonResult):
def __init__(self, requestId):
def __init__(self, requestId, datapoints):
self.requestId = requestId
self.members = []
self.datapoints = datapoints

class AmazonVerifiedEmails(AmazonSendStatistics):
pass

class AmazonResponseParser:
class XmlResponse:
def __init__(self, str):
self._rootElement = XML(str)
self._namespace = self._rootElement.tag[1:].split("}")[0]

def checkResponseName(self, name):
if self._rootElement.tag == self._fixTag(self._namespace, name):
return True
else:
raise AmazonAPIError('ErrorResponse is invalid.')
raise AmazonAPIError('ErrorResponse is invalid.')

def checkActionName(self, actionName):
if self._rootElement.tag == self._fixTag(self._namespace, ('%sResponse' % actionName)):
return True
else:
raise AmazonAPIError('Response of action "%s" is invalid.' % actionName)

def getChild(self, *itemPath):
node = self._findNode(self._rootElement, self._namespace, *itemPath)
if node != None:
return node
else:
raise AmazonAPIError('Node with the specified path was not found.')

def getChildText(self, *itemPath):
node = self.getChild(*itemPath)

def getChildElementTag(self, child): #strip off namespace
return re.sub('^\{.*\}', "", child.tag)

def getChildText(self, *itemPath):
node = self.getChild(*itemPath)
return node.text

def _fixTag(self, namespace, tag):
return '{%s}%s' % (namespace, tag)

def _findNode(self, rootElement, namespace, *args):
match = '.'
for s in args:
match += '/{%s}%s' % (namespace, s)
return rootElement.find(match)
match += '/{%s}%s' % (namespace, s)
return rootElement.find(match)


def __init__(self):
self._simpleResultActions = ['DeleteVerifiedEmailAddress', 'VerifyEmailAddress']
def _parseSimpleResult(self, actionName, xmlResponse):

def _parseSimpleResult(self, actionName, xmlResponse):
if xmlResponse.checkActionName(actionName):
requestId = xmlResponse.getChildText('ResponseMetadata', 'RequestId')
return AmazonResult(requestId)

def _parseSendQuota(self, actionName, xmlResponse):
if xmlResponse.checkActionName(actionName):
requestId = xmlResponse.getChildText('ResponseMetadata', 'RequestId')
value = xmlResponse.getChildText('GetSendQuotaResult', 'Max24HourSend')
value = xmlResponse.getChildText('GetSendQuotaResult', 'Max24HourSend')
max24HourSend = float(value)
value = xmlResponse.getChildText('GetSendQuotaResult', 'MaxSendRate')
maxSendRate = float(value)
value = xmlResponse.getChildText('GetSendQuotaResult', 'SentLast24Hours')
sentLast24Hours = float(value)
return AmazonSendQuota(requestId, max24HourSend, maxSendRate, sentLast24Hours)

#def _parseSendStatistics(self, actionName, xmlResponse):
# if xmlResponse.checkActionName(actionName):
# requestId = xmlResponse.getChildText('ResponseMetadata', 'RequestId')


def _parseSendStatistics(self, actionName, xmlResponse):
if xmlResponse.checkActionName(actionName):
requestId = xmlResponse.getChildText('ResponseMetadata', 'RequestId')
dp = xmlResponse.getChild("GetSendStatisticsResult", "SendDataPoints")
dps = []
for p in dp:
if xmlResponse.getChildElementTag(p) == "member":
for c in p:
tag = xmlResponse.getChildElementTag(c)
if tag == "DeliveryAttempts":
deliveryAttempts = c.text
elif tag == "Timestamp":
timestamp = datetime.strptime(c.text, "%Y-%m-%dT%H:%M:%SZ")
elif tag == "Rejects":
rejects = c.text
elif tag == "Bounces":
bounces = c.text
elif tag == "Complaints":
complaints = c.text
else:
raise AmazonAPIError("Unexpected data recieved in SendStatistics request")
dps.append(AmazonSendDataPoint(bounces, complaints, deliveryAttempts, rejects, timestamp))
return AmazonSendStatistics(requestId, dps)

def _parseListVerifiedEmails(self, actionName, xmlResponse):
if xmlResponse.checkActionName(actionName):
requestId = xmlResponse.getChildText('ResponseMetadata', 'RequestId')
node = xmlResponse.getChild('ListVerifiedEmailAddressesResult', 'VerifiedEmailAddresses')
result = AmazonVerifiedEmails(requestId)
for addr in node:
for addr in node:
result.members.append(addr.text)
return result

def _parseSendEmail(self, actionName, xmlResponse):
if xmlResponse.checkActionName(actionName):
requestId = xmlResponse.getChildText('ResponseMetadata', 'RequestId')
messageId = xmlResponse.getChildText('SendEmailResult', 'MessageId')
return AmazonSendEmailResult(requestId, messageId)

def _raiseError(self, xmlResponse):
if xmlResponse.checkResponseName('ErrorResponse'):
errorType = xmlResponse.getChildText('Error', 'Type')
code = xmlResponse.getChildText('Error', 'Code')
message = xmlResponse.getChildText('Error', 'Message')
raise AmazonError(errorType, code, message)
def parse(self, actionName, statusCode, reason, responseResult):

def parse(self, actionName, statusCode, reason, responseResult):
xmlResponse = self.XmlResponse(responseResult)
log.info('Response status code: %s, reason: %s', statusCode, reason)
log.debug(responseResult)
result = None

result = None
if statusCode != 200:
self._raiseError(xmlResponse)
else:
Expand All @@ -254,10 +277,10 @@ def parse(self, actionName, statusCode, reason, responseResult):
result = self._parseSendEmail(actionName, xmlResponse)
elif actionName == 'GetSendQuota':
result = self._parseSendQuota(actionName, xmlResponse)
#elif actionName == 'GetSendStatistics':
# result = self._parseSendStatistics(actionName, xmlResponse)
elif actionName == 'GetSendStatistics':
result = self._parseSendStatistics(actionName, xmlResponse)
elif actionName == 'ListVerifiedEmailAddresses':
result = self._parseListVerifiedEmails(actionName, xmlResponse)
result = self._parseListVerifiedEmails(actionName, xmlResponse)
else:
raise AmazonAPIError('Action %s is not supported. Please contact: [email protected]' % (actionName,))
return result