Boto SES + Python Twisted GitHub
Boto SES + Python Twisted GitHub
israelshirk / aconnection.py
Last active 4 years ago
Star
aconnection.py
67 self.finished = finished
68 self.body = ""
69 self.callback = callback
70 self.response = response
71 self.data = data
72
73 def dataReceived(self, bytes):
74 self.body += bytes
75
76 def connectionLost(self, reason):
77 # print 'Finished receiving body:', reason.getErrorMessage
78
79 self.callback(self.response, self.body, self.data)
80
81 self.finished.callback(None)
82
83 class ASESConnection(SESConnection):
84 ResponseError = BotoServerError
85 DefaultRegionName = 'us-east-1'
86 DefaultRegionEndpoint = 'email.us-east-1.amazonaws.com'
87 # DefaultRegionEndpoint = 'coast.dev'
88 APIVersion = '2010-12-01'
89
90 host_override = None
91
92 def __init__(self, aws_access_key_id=None, aws_secret_access_k
93 is_secure=True, port=None, proxy=None, proxy_port
94 proxy_user=None, proxy_pass=None, debug=0,
95 https_connection_factory=None, region=None, path=
96 security_token=None, validate_certs=True, agent=N
97 if not region:
98 region = RegionInfo(self, self.DefaultRegionName,
99 self.DefaultRegionEndpoint)
100 self.region = region
101 SESConnection.__init__(self,
102 aws_access_key_id=aws_access_ke
103 is_secure=is_secure, port=port,
104 proxy_user=proxy_user, proxy_pa
105 https_connection_factory=None,
106 security_token=security_token,
107 validate_certs=validate_certs)
config.json
1
2 {
3 "database_host": "127.0.0.1",
4 "database_user": "sadlfkjasdlfkj",
5 "database_passwd": "asldkfjasdlkfj",
6 "database_db": "laskdjflaskdjf",
7
8 "rabbitmq_host": "127.0.0.1",
9 "rabbitmq_port": 5672,
10 "rabbitmq_vhost": "/",
11 "rabbitmq_username": "guest",
12 "rabbitmq_password": "guest",
13 "rabbitmq_queue": "queue",
14 "rabbitmq_exchange": "exchange",
15 "rabbitmq_routing_key": "routing_key",
16 "rabbitmq_rate_limit": 3
17 }
send_emails.py
1
2 from aconnection import ASESConnection
3 from pprint import pprint
4 from twisted.enterprise import adbapi
5 from twisted.internet import reactor, defer
6 from twisted.internet.defer import Deferred
7 from twisted.internet.ssl import ClientContextFactory
8 from twisted.web.client import Agent, HTTPConnectionPool
9 from twisted_queue import *
10 import functools
11 import json
12 import MySQLdb, MySQLdb.cursors
13 import re
14 import sys
15 import time
16 import traceback
17 import twisted.internet.task
18
19 # This class is used to enable SSL... Seems to just extend getCon
20 # accept extra arguments that HTTP might require.
21
22
23 class WebClientContextFactory(ClientContextFactory):
24 def getContext(self, hostname, port):
25 return ClientContextFactory.getContext(self)
26
27 class EmailSender:
28
29 def __init__(self, reactor, email_id):
30 contextFactory = WebClientContextFactory()
31
32 self.sent_count = 0
33 self.last_sent_count = 0
34 self.failed_count = 0
35 self.last_failed_count = 0
36
37 self.email_id = email_id
38 self.reactor = reactor
39
40 pool = HTTPConnectionPool(reactor, persistent=True
41 pool.maxPersistentPerHost = 50
42 pool.cachedConnectionTimeout = 10
43 https_agent = Agent(reactor, contextFactory, pool=
44
45 self.set_up_configuration()
46
47 self.db_pool = adbapi.ConnectionPool(
48 "MySQLdb",
49 host=self.configuration['database_host'],
50 user=self.configuration['database_user'],
51 passwd=self.configuration['database_passwd
52 db=self.configuration['database_db'],
53 cursorclass=MySQLdb.cursors.DictCursor,
54 )
55
56 # This probably should just get activated with a -
57 self.ses_connection = ASESConnection(
58 callback=self.sending_email_succeeded,
59 errback=self.boto_errback,
60 reactor=reactor,
61 agent=https_agent
62 )
63
64 if 'production' in self.configuration and self.con
65 print "WARNING: RUNNING PRODUCTION SEND.
66 time.sleep(10)
67 else:
68 self.ses_connection.host_override = 'ses.v
69
70 self.reactor.callWhenRunning(self.go)
71
72 def set_up_configuration(self):
73 try:
74 configuration = json.loads(open('config.js
75 except:
76 print "Failed to load manual configuration
77 configuration = {}
78
79 default_configuration = {
80 "database_host": "127.0.0.1",
81 "database_user": "asldkfjasldkfj",
82 "database_passwd": "asldkfjasldkfj",
83 "database_db": "asldkfjasldkfjasdlfkj",
84
85 "rabbitmq_host": "127.0.0.1",
86 "rabbitmq_port": 5672,
87 "rabbitmq_vhost": "/",
88 "rabbitmq_username": "guest",
89 "rabbitmq_password": "guest",
90 "rabbitmq_queue": "huge_email_address_queu
91 "rabbitmq_exchange": "huge_email_address_q
92 "rabbitmq_routing_key": "huge_email_addres
93 "rabbitmq_rate_limit": 1,
94
95 "source_address": '"MOJO Themes" <noreply@
96
97 "reporting_interval": 1
98 }
99
100 self.configuration = default_configuration
101
102 for (k, v) in configuration.items():
103 self.configuration[k] = v
104
105 pprint({'Configuration': self.configuration})
106
107 def go(self):
108 self.load_email_content()
109 self.send_emails()
110
111 @defer.inlineCallbacks
112 def load_email_content(self):
113 templates = yield self.db_pool.runQuery(
114 "SELECT subject, body FROM email_content W
115 (self.email_id,)
116 )
117
118 self.email_templates = []
119
120 if not templates:
121 self.reactor.stop()
122 print "No emails loaded"
123 sys.exit(1)
124
125 try:
126 for template in templates:
127 self.load_template(template)
128 except Exception, e:
129 print e
130 self.reactor.stop()
131 sys.exit(1)
132
133 if not self.email_templates:
134 print "No templates loaded!"
135 self.reactor.stop()
136 sys.exit(1)
137
179 errback=self.queue_errback,
180 host=self.configuration['rabbitmq_host'],
181 port=self.configuration['rabbitmq_port'],
182 vhost=self.configuration['rabbitmq_vhost']
183 username=self.configuration['rabbitmq_user
184 password=self.configuration['rabbitmq_pass
185 queue=self.configuration['rabbitmq_queue']
186 exchange=self.configuration['rabbitmq_exch
187 routing_key=self.configuration['rabbitmq_r
188 rate_limit=self.configuration['rabbitmq_ra
189 no_ack=False,
190 durable=True
191 )
192
193 def queue_errback(self, err):
194 print err
195 print "I should probably be killed because I am a
196 reactor.stop()
197 sys.exit(1)
198
199 @defer.inlineCallbacks
200 def send_email(self, channel, raw_message):
201 if not (channel or raw_message):
202 defer.returnValue(None)
203
204 try:
205 receiver_info = json.loads(raw_message.con
206 except ValueError, e:
207 channel.basic_ack(delivery_tag=raw_message
208
209 print "failed parsing", raw_message.conten
210 self.sending_email_failed(e, raw_message.c
211
212 defer.returnValue(None)
213
214 try:
215 template = self.get_template(receiver_info
216 except Exception, e:
217 channel.basic_ack(delivery_tag=raw_message
218
219 print "Missing email template:", receiver_
220
221 self.sending_email_failed(e, raw_message.c
222 defer.returnValue(None)
223
224 try:
225 templated_email = self.apply_template(temp
226 except ValueError, e:
227 channel.basic_ack(delivery_tag=raw_message
228 print "Couldn't template email:", receiver
229
230 self.sending_email_failed(e, raw_message.c
231 defer.returnValue(None)
232
233 email_subject = templated_email['subject']
234 email_content = templated_email['content']
235 email_source = templated_email['source']
236
237 try:
238 self.ses_connection.send_email(
239 source=email_source, subject=email
240 format='html', to_addresses=[recei
241 except Exception, e:
242 channel.basic_ack(delivery_tag=raw_message
243 print "Send_email exception:", e
244
245 print receiver_info['email']
246
247 self.sending_email_failed(e, raw_message.c
248 defer.returnValue(None)
249
250 try:
251 yield channel.basic_ack(delivery_tag=raw_m
252 except Exception, e:
253 print "Couldn't ack message because ???",
254 defer.returnValue(None)
255
256 def print_stats(self):
257 diff_sent_count = self.sent_count - self.last_sent
258 diff_failed_count = self.failed_count - self.last_
259
260 self.last_sent_count = self.sent_count
261 self.last_failed_count = self.failed_count
262
263 print
264 print "sent_count:", self.sent_count, "period rate
265 print "failed_count:", self.failed_count, "period
266 sys.stdout.flush()
267
268 def sending_email_succeeded(self, e, data):
269 # sys.stdout.write('.')
270
271 # Could stuff into a database here if desired
272
273 self.sent_count += 1
274
275 def boto_errback(self, data, error):
276 print "failed sending:", error, data
277
278 self.failed_count += 1
279
280 def sending_email_failed(self, e, data):
281
282 # Could stuff into a database or something else if
283 print "failed sending to", data, "because", e
284
285 self.failed_count += 1
286
287 if __name__ == '__main__':
288 sender = EmailSender(reactor, sys.argv[1])
289
290 reactor.run()
twisted_queue.py
46
47 d = ClientCreator(reactor, AMQClient, delegate=del
48
49 d.addCallback(self.gotConnection, username, passwo
50
51 d.addErrback(errback)
52
53 @inlineCallbacks
54 def gotConnection(self, conn, username, password):
55 print "Connected to broker."
56 yield conn.authenticate(username, password)
57
58 print "Authenticated. Ready to receive messages"
59 self.channel = yield conn.channel(1)
60 yield self.channel.channel_open()
61
62 yield self.channel.basic_qos(prefetch_count=self.p
63
64 yield self.channel.queue_declare(queue=self.queue,
65 yield self.channel.exchange_declare(exchange=self.
66
67 yield self.channel.queue_bind(queue=self.queue, ex
68
69 yield self.channel.basic_consume(queue=self.queue,
70
71 self.queue = yield conn.queue(self.routing_key)
72
73 self.getMessages()
74
75 @inlineCallbacks
76 def getMessages(self):
77 """
78 def RateLimited(maxPerSecond):
79 minInterval = 1.0 / float(maxPerSecond)
80 def decorate(func):
81 lastTimeCalled = [0.0]
82 def rateLimitedFunction(*args,**ka
83 elapsed = time.time() - la
84 leftToWait = minInterval -
85 if leftToWait>0:
86 time.sleep(leftToW
87 ret = func(*args,**kargs)
88 lastTimeCalled[0] = time.t
89 return ret
90 return rateLimitedFunction
91 return decorate
92 """
93
94 elapsed = time.time() - self.last_time_called
95 left_to_wait = self.min_interval - elapsed
96
97 if left_to_wait > 0:
98 yield reactor.callLater(left_to_wait, self
99 else:
100 self.last_time_called = time.time()
101
102 message = yield self.queue.get()
103 self.callback(self.channel, message)
104
105 elapsed = time.time() - self.last_time_cal
106 left_to_wait = self.min_interval - elapsed
107
108 if left_to_wait < 0:
109 left_to_wait = 0
110
111 #print "left_to_wait: ", left_to_wait
112
113 yield reactor.callLater(left_to_wait*1.01,
114
115 class twisted_queue_sender:
116
117 @inlineCallbacks
118 def gotConnection(self, conn, username, password):
119 print "Connected to broker."
120 yield conn.authenticate(username, password)
121
122 print "Authenticated. Ready to send messages"
123 self.channel = yield conn.channel(1)
124 yield self.channel.channel_open()
125
126 yield self.channel.queue_declare(queue=self.queue,
127 yield self.channel.exchange_declare(exchange=self.
128