From f1a6044f78fdc4b4e37844045d5a40f6cb070f48 Mon Sep 17 00:00:00 2001 From: ryancustommade Date: Thu, 3 Feb 2011 20:18:05 -0500 Subject: [PATCH 01/10] added replyto to the sendMessage and a simple execption str so I know what's going on --- amazon_ses.py | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/amazon_ses.py b/amazon_ses.py index 046ed48..7d87d43 100644 --- a/amazon_ses.py +++ b/amazon_ses.py @@ -78,8 +78,11 @@ def getSendStatistics(self): def listVerifiedEmailAddresses(self): return self._performAction('ListVerifiedEmailAddresses') - def sendEmail(self, source, toAddresses, message, replyToAddresses=None, returnPath=None, ccAddresses=None, bccAddresses=None): + def sendEmail(self, source, toAddresses, message, replyToAddresses=[], returnPath=None, ccAddresses=None, bccAddresses=None): params = { 'Source': source } + for index, replyAddress in enumerate(replyToAddresses): + params.update({'ReplyToAddresses.member.%d' % (index+1) : replyAddress}) + for objName, addresses in zip(["ToAddresses", "CcAddresses", "BccAddresses"], [toAddresses, ccAddresses, bccAddresses]): if addresses: if not isinstance(addresses, basestring) and getattr(addresses, '__iter__', False): @@ -116,7 +119,10 @@ def __init__(self, errorType, code, message): self.errorType = errorType self.code = code self.message = message - + + def __str__(self): + return repr(self.message) + class AmazonAPIError(Exception): def __init__(self, message): self.message = message From 102f134cf7824a586ff015c0535fa7b1b7b254d2 Mon Sep 17 00:00:00 2001 From: Richard Kiss Date: Wed, 16 Mar 2011 19:47:57 -0700 Subject: [PATCH 02/10] Allow contents of EmailMessage to be set in constructor. --- amazon_ses.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/amazon_ses.py b/amazon_ses.py index 046ed48..4f5e31d 100644 --- a/amazon_ses.py +++ b/amazon_ses.py @@ -103,11 +103,11 @@ def sendEmail(self, source, toAddresses, message, replyToAddresses=None, returnP class EmailMessage: - def __init__(self): + def __init__(self, subject=None, bodyHtml=None, bodyText=None): self.charset = 'UTF-8' - self.subject = None - self.bodyHtml = None - self.bodyText = None + self.subject = subject + self.bodyHtml = bodyHtml + self.bodyText = bodyText From adba04057a0e5bea060083c5307ff552924267df Mon Sep 17 00:00:00 2001 From: Richard Kiss Date: Wed, 16 Mar 2011 19:53:10 -0700 Subject: [PATCH 03/10] Remove end of line whitespace. --- README.txt | 8 ++--- amazon_ses.py | 98 +++++++++++++++++++++++++-------------------------- 2 files changed, 53 insertions(+), 53 deletions(-) diff --git a/README.txt b/README.txt index d7241ab..f235f11 100644 --- a/README.txt +++ b/README.txt @@ -18,18 +18,18 @@ message = EmailMessage() message.subject = 'Hello from Amazon SES! Test subject' message.bodyText = 'This is body text of test message.' -result = amazonSes.sendEmail('username@yourdomaintest.com', 'testmail@yourdomaintest.com', message) +result = amazonSes.sendEmail('username@yourdomaintest.com', 'testmail@yourdomaintest.com', message) print result.requestId print result.messageId -I want you to notice that in case Amazon returns some error, the API will raise an exception AmazonError which will contain errorType, code and message. +I want you to notice that in case Amazon returns some error, the API will raise an exception AmazonError which will contain errorType, code and message. If your Amazon SES account is not switched to production use, you can send a message only from/to verified email addresses. Here is an example of how to verify the email using the API: result = amazonSes.verifyEmailAddress('username@yourdomaintest.com') print result.requestId -You will receive the confirmation with a link which you should click to verify your email. +You will receive the confirmation with a link which you should click to verify your email. An example of how to receive information about SendQuota: @@ -44,7 +44,7 @@ VerifyEmailAddress DeleteVerifiedEmailAddress GetSendQuota ListVerifiedEmailAddresses -Methods return instances of AmazonResult or derived class (for example, method amazonSes.getSendQuota() returns the instance of AmazonSendQuota). +Methods return instances of AmazonResult or derived class (for example, method amazonSes.getSendQuota() returns the instance of AmazonSendQuota). Author =============== diff --git a/amazon_ses.py b/amazon_ses.py index 4f5e31d..53b5118 100644 --- a/amazon_ses.py +++ b/amazon_ses.py @@ -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 @@ -38,7 +38,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() @@ -47,11 +47,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) @@ -60,24 +60,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]): @@ -86,10 +86,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: @@ -116,29 +116,29 @@ 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 @@ -146,105 +146,105 @@ def __init__(self, bounces, complaints, deliveryAttempts, rejects, timestamp): self.deliveryAttempts = deliveryAttempts self.rejects = rejects self.timestamp = timestamp - + class AmazonSendStatistics(AmazonResult): def __init__(self, requestId): self.requestId = requestId - self.members = [] - + self.members = [] + 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 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 _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: @@ -257,7 +257,7 @@ def parse(self, actionName, statusCode, reason, responseResult): #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: vladimir@tagmask.com' % (actionName,)) return result From 59903b85a7324bb8e0988def48aa28c7cee38597 Mon Sep 17 00:00:00 2001 From: Kiran Bandla Date: Fri, 8 Jul 2011 14:55:10 -0400 Subject: [PATCH 04/10] added setup.py --- setup.py | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) create mode 100644 setup.py diff --git a/setup.py b/setup.py new file mode 100644 index 0000000..4d5b384 --- /dev/null +++ b/setup.py @@ -0,0 +1,18 @@ +#!/usr/bin/env python + +try: + from setuptools import setup +except ImportError, excp: + from distutils.core import setup + +import amazon_ses +import os + +setup( name='amazon-ses', + description = 'Python API for Amazon Simple Email Service', + author= 'Vladimir Pankratiev', + url = 'http://tagmask.com/vladimir/profile', + download_url = 'https://github.com/kbandla/python-amazon-ses-api', + platforms = ['any'], + py_modules = ['amazon_ses'] + ) From a93e17838e27c01568622e065e7559d35d55a80c Mon Sep 17 00:00:00 2001 From: Kiran Bandla Date: Fri, 8 Jul 2011 14:56:54 -0400 Subject: [PATCH 05/10] Added a line for Installation --- README.txt | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/README.txt b/README.txt index d7241ab..59b6054 100644 --- a/README.txt +++ b/README.txt @@ -2,6 +2,11 @@ Python API for Amazon Simple Email Service http://aws.amazon.com/ses/ =============== +Installation: +============= +python setup.py build +python setup.py install + Description =============== Here is an example of how to send an email using the API: From 9c9b73ee203662743035ad011b04985d1cb50ab6 Mon Sep 17 00:00:00 2001 From: Kiran Bandla Date: Wed, 13 Jul 2011 11:22:54 -0400 Subject: [PATCH 06/10] Updated URL --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 4d5b384..a88b934 100644 --- a/setup.py +++ b/setup.py @@ -12,7 +12,7 @@ description = 'Python API for Amazon Simple Email Service', author= 'Vladimir Pankratiev', url = 'http://tagmask.com/vladimir/profile', - download_url = 'https://github.com/kbandla/python-amazon-ses-api', + download_url = 'https://github.com/pankratiev/python-amazon-ses-api', platforms = ['any'], py_modules = ['amazon_ses'] ) From dfd6e390a0da947e01103389ce97eb60a71ed59e Mon Sep 17 00:00:00 2001 From: Kiran Bandla Date: Wed, 13 Jul 2011 11:28:56 -0400 Subject: [PATCH 07/10] removed unnecessary imports --- setup.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/setup.py b/setup.py index a88b934..9a35c84 100644 --- a/setup.py +++ b/setup.py @@ -5,9 +5,6 @@ except ImportError, excp: from distutils.core import setup -import amazon_ses -import os - setup( name='amazon-ses', description = 'Python API for Amazon Simple Email Service', author= 'Vladimir Pankratiev', From 3f6436c0e0bc651e02a94afb1c87e237962c3990 Mon Sep 17 00:00:00 2001 From: Rubens Altimari Date: Mon, 22 Aug 2011 15:32:04 -0300 Subject: [PATCH 08/10] Merging kbandla and richardkiss branches, and eliminating a few more whitespaces --- amazon_ses.py | 48 ++++++++++++++++++++++++------------------------ setup.py | 21 +++++++++++---------- 2 files changed, 35 insertions(+), 34 deletions(-) diff --git a/amazon_ses.py b/amazon_ses.py index 53b5118..ea35780 100644 --- a/amazon_ses.py +++ b/amazon_ses.py @@ -34,11 +34,11 @@ def __init__(self, accessKeyID, secretAccessKey): self._accessKeyID = accessKeyID self._secretAccessKey = secretAccessKey self._responseParser = AmazonResponseParser() - + 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() @@ -47,7 +47,7 @@ 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 = {} @@ -60,24 +60,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]): @@ -160,48 +160,48 @@ 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.') - + 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) 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) - + def __init__(self): self._simpleResultActions = ['DeleteVerifiedEmailAddress', 'VerifyEmailAddress'] - + 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') @@ -212,11 +212,11 @@ def _parseSendQuota(self, actionName, xmlResponse): 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 _parseListVerifiedEmails(self, actionName, xmlResponse): if xmlResponse.checkActionName(actionName): requestId = xmlResponse.getChildText('ResponseMetadata', 'RequestId') @@ -225,25 +225,25 @@ def _parseListVerifiedEmails(self, actionName, xmlResponse): 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): xmlResponse = self.XmlResponse(responseResult) log.info('Response status code: %s, reason: %s', statusCode, reason) log.debug(responseResult) - + result = None if statusCode != 200: self._raiseError(xmlResponse) diff --git a/setup.py b/setup.py index 9a35c84..46a39a7 100644 --- a/setup.py +++ b/setup.py @@ -2,14 +2,15 @@ try: from setuptools import setup -except ImportError, excp: +except ImportError, e: from distutils.core import setup - -setup( name='amazon-ses', - description = 'Python API for Amazon Simple Email Service', - author= 'Vladimir Pankratiev', - url = 'http://tagmask.com/vladimir/profile', - download_url = 'https://github.com/pankratiev/python-amazon-ses-api', - platforms = ['any'], - py_modules = ['amazon_ses'] - ) + +setup( + name = 'amazon-ses', + description = 'Python API for Amazon Simple Email Service', + author = 'Vladimir Pankratiev', + url = 'http://tagmask.com/vladimir/profile', + download_url = 'https://github.com/pankratiev/python-amazon-ses-api', + platforms = ['any'], + py_modules = ['amazon_ses'], +) From bb6c5ba7fc96dc51c196a16673802f0181219d69 Mon Sep 17 00:00:00 2001 From: Rubens Altimari Date: Mon, 22 Aug 2011 17:21:11 -0300 Subject: [PATCH 09/10] Making package pip-friendly (so it can be installed with Django, for instance, with pip -e git+git://github.com/etc.) --- .gitignore | 1 + README.txt => README | 0 amazon_ses/__init__.py | 1 + amazon_ses.py => amazon_ses/amazon_ses.py | 0 setup.py | 8 +++----- 5 files changed, 5 insertions(+), 5 deletions(-) rename README.txt => README (100%) create mode 100644 amazon_ses/__init__.py rename amazon_ses.py => amazon_ses/amazon_ses.py (100%) diff --git a/.gitignore b/.gitignore index 1caf74b..5a11797 100644 --- a/.gitignore +++ b/.gitignore @@ -4,6 +4,7 @@ *.swp *.swo pip-log.txt* +amazon_ses.egg-info/ *.swn # Emacs backup files & locks \#* diff --git a/README.txt b/README similarity index 100% rename from README.txt rename to README diff --git a/amazon_ses/__init__.py b/amazon_ses/__init__.py new file mode 100644 index 0000000..0067e3d --- /dev/null +++ b/amazon_ses/__init__.py @@ -0,0 +1 @@ +from amazon_ses import * \ No newline at end of file diff --git a/amazon_ses.py b/amazon_ses/amazon_ses.py similarity index 100% rename from amazon_ses.py rename to amazon_ses/amazon_ses.py diff --git a/setup.py b/setup.py index 46a39a7..4eb8721 100644 --- a/setup.py +++ b/setup.py @@ -1,16 +1,14 @@ #!/usr/bin/env python -try: - from setuptools import setup -except ImportError, e: - from distutils.core import setup +from setuptools import setup, find_packages setup( name = 'amazon-ses', + version = '0.2', + packages = find_packages(), description = 'Python API for Amazon Simple Email Service', author = 'Vladimir Pankratiev', url = 'http://tagmask.com/vladimir/profile', download_url = 'https://github.com/pankratiev/python-amazon-ses-api', platforms = ['any'], - py_modules = ['amazon_ses'], ) From c34df40c41ec7b9f3a3c4482967dc43bbf4a1ce8 Mon Sep 17 00:00:00 2001 From: Rubens Altimari Date: Mon, 22 Aug 2011 20:49:36 -0300 Subject: [PATCH 10/10] Implementing GetSendStatistics --- amazon_ses/amazon_ses.py | 55 +++++++++++++++++++++++++++++----------- 1 file changed, 40 insertions(+), 15 deletions(-) diff --git a/amazon_ses/amazon_ses.py b/amazon_ses/amazon_ses.py index c41aa9f..8047bf5 100644 --- a/amazon_ses/amazon_ses.py +++ b/amazon_ses/amazon_ses.py @@ -24,8 +24,13 @@ import hmac import logging import base64 -from datetime import datetime -from xml.etree.ElementTree import XML +import datetime + +# Try to import the (much faster) C version of ElementTree +try: + from xml.etree import cElementTree as ET +except ImportError: + from xml.etree import ElementTree as ET log = logging.getLogger(__name__) @@ -41,7 +46,7 @@ def _getSignature(self, dateValue): def _getHeaders(self): headers = { 'Content-type': 'application/x-www-form-urlencoded' } - d = datetime.utcnow() + d = datetime.datetime.utcnow() dateValue = d.strftime('%a, %d %b %Y %H:%M:%S GMT') headers['Date'] = dateValue signature = self._getSignature(dateValue) @@ -145,12 +150,12 @@ def __init__(self, requestId, max24HourSend, maxSendRate, sentLast24Hours): 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 + def __init__(self, xmlResponse, member): + self.timestamp = datetime.datetime.strptime(xmlResponse.getChildTextFromNode(member, 'Timestamp'), '%Y-%m-%dT%H:%M:%SZ') + self.deliveryAttempts = int(xmlResponse.getChildTextFromNode(member, 'DeliveryAttempts')) + self.bounces = int(xmlResponse.getChildTextFromNode(member, 'Bounces')) + self.complaints = int(xmlResponse.getChildTextFromNode(member, 'Complaints')) + self.rejects = int(xmlResponse.getChildTextFromNode(member, 'Rejects')) class AmazonSendStatistics(AmazonResult): def __init__(self, requestId): @@ -161,9 +166,10 @@ class AmazonVerifiedEmails(AmazonSendStatistics): pass class AmazonResponseParser: + # FIXME: This XML structure is way too rigid and complex, tastes like Java - needs more flexibility class XmlResponse: def __init__(self, str): - self._rootElement = XML(str) + self._rootElement = ET.XML(str) self._namespace = self._rootElement.tag[1:].split("}")[0] def checkResponseName(self, name): @@ -189,6 +195,20 @@ def getChildText(self, *itemPath): node = self.getChild(*itemPath) return node.text + def getChildFromNode(self, node, *itemPath): + child = self._findNode(node, self._namespace, *itemPath) + if child is not None: + return child + else: + raise AmazonAPIError('Node with the specified path was not found.') + + def getChildTextFromNode(self, node, *itemPath): + child = self.getChildFromNode (node, *itemPath) + return child.text + + def getChildren(self, node): + return node.getchildren() + def _fixTag(self, namespace, tag): return '{%s}%s' % (namespace, tag) @@ -218,9 +238,14 @@ def _parseSendQuota(self, actionName, xmlResponse): 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') + result = AmazonSendStatistics(requestId) + send_data_points = xmlResponse.getChild('GetSendStatisticsResult', 'SendDataPoints') + for member in send_data_points: + result.members.append(AmazonSendDataPoint(xmlResponse, member)) + return result def _parseListVerifiedEmails(self, actionName, xmlResponse): if xmlResponse.checkActionName(actionName): @@ -259,8 +284,8 @@ 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) else: