I've tried getting this to work but there must be a better way, any input is welcome.
I'm trying to send scheduled emails in my python pyramid app using pyramid_mailer (settings stored in .ini file), and apscheduler to set the schedule.
I also use the SQLAlchemyJobStore so jobs can be restarted if the app restarts.
jobstores = {
'default': SQLAlchemyJobStore(url='mysql://localhost/lgmim')
}
scheduler = BackgroundScheduler(jobstores=jobstores)
@view_config(route_name='start_email_schedule')
def start_email_schedule(request):
# add the job and start the scheduler
scheduler.add_job(send_scheduled_email, 'interval', [request], weeks=1)
scheduler.start()
return HTTPOk()
def send_scheduled_email(request):
# compile message and recipients
# send mail
send_mail(request, subject, recipients, message)
def send_mail(request, subject, recipients, body):
mailer = request.registry['mailer']
message = Message(subject=subject,
recipients=recipients,
body=body)
mailer.send_immediately(message, fail_silently=False)
This is as far as I've gotten, now I'm getting an error, presumably because it can't pickle the request.
PicklingError: Can't pickle <type 'function'>: attribute lookup __builtin__.function failed
Using pyramid.threadlocal.get_current_registry().settings
to get the mailer works the first time, but thereafter I get an error. I'm advised not to use it in any case.
What else can I do?
Generally, you cannot pickle request
object as it contains references to things like open sockets and other liveful objects.
Some useful patterns here are that
You pregenerate email id in the database and then pass id (int, UUID) over scheduler
You generate template context (JSON dict) and then pass that over the scheduler and render the template inside a worker
You do all database fetching and related inside a scheduler and don't pass any arguments
Specifically, the problem how to generate a faux request
object inside a scheduler can be solved like this:
from pyramid import scripting
from pyramid.paster import bootstrap
def make_standalone_request():
bootstrap_env = bootstrap("your-pyramid-config.ini")
app = bootstrap_env["app"]
pyramid_env = scripting.prepare(registry=bootstrap_env["registry"])
request = pyramid_env["request"]
# Note that request.url will be always dummy,
# so if your email refers to site URL, you need to
# resolve request.route_url() calls before calling the scheduler
# or read the URLs from settings
return request
Some more inspiration can be found here (disclaimer: I am the author).