32
atst/app.py
32
atst/app.py
@@ -23,6 +23,8 @@ from atst.domain.authz import Authorization
|
||||
from atst.models.permissions import Permissions
|
||||
from atst.eda_client import MockEDAClient
|
||||
from atst.uploader import Uploader
|
||||
from atst.utils import mailer
|
||||
from atst.queue import queue
|
||||
|
||||
|
||||
ENV = os.getenv("FLASK_ENV", "dev")
|
||||
@@ -37,17 +39,19 @@ def make_app(config):
|
||||
template_folder=parent_dir.child("templates").absolute(),
|
||||
static_folder=parent_dir.child("static").absolute(),
|
||||
)
|
||||
redis = make_redis(config)
|
||||
make_redis(app, config)
|
||||
csrf = CSRFProtect()
|
||||
|
||||
app.config.update(config)
|
||||
app.config.update({"SESSION_REDIS": redis})
|
||||
app.config.update({"SESSION_REDIS": app.redis})
|
||||
|
||||
make_flask_callbacks(app)
|
||||
make_crl_validator(app)
|
||||
register_filters(app)
|
||||
make_eda_client(app)
|
||||
make_upload_storage(app)
|
||||
make_mailer(app)
|
||||
queue.init_app(app)
|
||||
|
||||
db.init_app(app)
|
||||
csrf.init_app(app)
|
||||
@@ -87,7 +91,7 @@ def map_config(config):
|
||||
return {
|
||||
**config["default"],
|
||||
"ENV": config["default"]["ENVIRONMENT"],
|
||||
"DEBUG": config["default"]["DEBUG"],
|
||||
"DEBUG": config["default"].getboolean("DEBUG"),
|
||||
"PORT": int(config["default"]["PORT"]),
|
||||
"SQLALCHEMY_DATABASE_URI": config["default"]["DATABASE_URI"],
|
||||
"SQLALCHEMY_TRACK_MODIFICATIONS": False,
|
||||
@@ -95,6 +99,8 @@ def map_config(config):
|
||||
"PERMANENT_SESSION_LIFETIME": config.getint(
|
||||
"default", "PERMANENT_SESSION_LIFETIME"
|
||||
),
|
||||
"RQ_REDIS_URL": config["default"]["REDIS_URI"],
|
||||
"RQ_QUEUES": ["atat_{}".format(ENV.lower())],
|
||||
}
|
||||
|
||||
|
||||
@@ -143,8 +149,9 @@ def make_config():
|
||||
return map_config(config)
|
||||
|
||||
|
||||
def make_redis(config):
|
||||
return redis.Redis.from_url(config["REDIS_URI"])
|
||||
def make_redis(app, config):
|
||||
r = redis.Redis.from_url(config["REDIS_URI"])
|
||||
app.redis = r
|
||||
|
||||
|
||||
def make_crl_validator(app):
|
||||
@@ -166,3 +173,18 @@ def make_upload_storage(app):
|
||||
secret=app.config.get("STORAGE_SECRET"),
|
||||
)
|
||||
app.uploader = uploader
|
||||
|
||||
|
||||
def make_mailer(app):
|
||||
if app.config["DEBUG"]:
|
||||
mailer_connection = mailer.RedisConnection(app.redis)
|
||||
else:
|
||||
mailer_connection = mailer.SMTPConnection(
|
||||
server=app.config.get("MAIL_SERVER"),
|
||||
port=app.config.get("MAIL_PORT"),
|
||||
username=app.config.get("MAIL_SENDER"),
|
||||
password=app.config.get("MAIL_PASSWORD"),
|
||||
use_tls=app.config.get("MAIL_TLS"),
|
||||
)
|
||||
sender = app.config.get("MAIL_SENDER")
|
||||
app.mailer = mailer.Mailer(mailer_connection, sender)
|
||||
|
||||
45
atst/queue.py
Normal file
45
atst/queue.py
Normal file
@@ -0,0 +1,45 @@
|
||||
from flask_rq2 import RQ
|
||||
from flask import current_app as app
|
||||
|
||||
|
||||
class ATSTQueue(RQ):
|
||||
|
||||
"""Internal helpers to get the queue that actually does the work.
|
||||
|
||||
The RQ object always uses the "default" queue, unless we explicitly request
|
||||
otherwise. These helpers allow us to use `.queue_name` to get the name of
|
||||
the configured queue and `_queue_job` will use the appropriate queue.
|
||||
|
||||
"""
|
||||
|
||||
@property
|
||||
def queue_name(self):
|
||||
return self.queues[0]
|
||||
|
||||
def get_queue(self, name=None):
|
||||
if not name:
|
||||
name = self.queue_name
|
||||
return super().get_queue(name)
|
||||
|
||||
def _queue_job(self, function, *args, **kwargs):
|
||||
self.get_queue().enqueue(function, *args, **kwargs)
|
||||
|
||||
# pylint: disable=pointless-string-statement
|
||||
"""Instance methods to queue up application-specific jobs."""
|
||||
|
||||
def send_mail(self, to, subject, body):
|
||||
self._queue_job(ATSTQueue._send_mail, to, subject, body)
|
||||
|
||||
# pylint: disable=pointless-string-statement
|
||||
"""Class methods to actually perform the work.
|
||||
|
||||
Must be a class method (or a module-level function) because we being able
|
||||
to pickle the class is more effort than its worth.
|
||||
"""
|
||||
|
||||
@classmethod
|
||||
def _send_mail(self, to, subject, body):
|
||||
app.mailer.send(to, subject, body)
|
||||
|
||||
|
||||
queue = ATSTQueue()
|
||||
@@ -1,7 +1,16 @@
|
||||
from flask import Blueprint, request, session, redirect
|
||||
from flask import (
|
||||
Blueprint,
|
||||
request,
|
||||
session,
|
||||
redirect,
|
||||
render_template,
|
||||
url_for,
|
||||
current_app as app,
|
||||
)
|
||||
|
||||
from . import redirect_after_login_url
|
||||
from atst.domain.users import Users
|
||||
from atst.queue import queue
|
||||
|
||||
bp = Blueprint("dev", __name__)
|
||||
|
||||
@@ -65,3 +74,17 @@ def login_dev():
|
||||
session["user_id"] = user.id
|
||||
|
||||
return redirect(redirect_after_login_url())
|
||||
|
||||
|
||||
@bp.route("/test-email")
|
||||
def test_email():
|
||||
queue.send_mail(
|
||||
[request.args.get("to")], request.args.get("subject"), request.args.get("body")
|
||||
)
|
||||
|
||||
return redirect(url_for("dev.messages"))
|
||||
|
||||
|
||||
@bp.route("/messages")
|
||||
def messages():
|
||||
return render_template("dev/emails.html", messages=app.mailer.messages)
|
||||
|
||||
85
atst/utils/mailer.py
Normal file
85
atst/utils/mailer.py
Normal file
@@ -0,0 +1,85 @@
|
||||
from contextlib import contextmanager
|
||||
import smtplib
|
||||
from email.message import EmailMessage
|
||||
|
||||
|
||||
class MailConnection(object):
|
||||
def send(self, message):
|
||||
raise NotImplementedError()
|
||||
|
||||
@property
|
||||
def messages(self):
|
||||
raise NotImplementedError()
|
||||
|
||||
|
||||
class SMTPConnection(MailConnection):
|
||||
def __init__(self, server, port, username, password, use_tls=False):
|
||||
self.server = server
|
||||
self.port = port
|
||||
self.username = username
|
||||
self.password = password
|
||||
self.use_tls = use_tls
|
||||
|
||||
@contextmanager
|
||||
def _connected_host(self):
|
||||
host = None
|
||||
|
||||
if self.use_tls:
|
||||
host = smtplib.SMTP(self.server, self.port)
|
||||
host.starttls()
|
||||
else:
|
||||
host = smtplib.SMTP_SSL(self.server, self.port)
|
||||
|
||||
host.login(self.username, self.password)
|
||||
|
||||
yield host
|
||||
|
||||
host.quit()
|
||||
|
||||
@property
|
||||
def messages(self):
|
||||
return []
|
||||
|
||||
def send(self, message):
|
||||
with self._connected_host() as host:
|
||||
host.send_message(message)
|
||||
|
||||
|
||||
class RedisConnection(MailConnection):
|
||||
def __init__(self, redis, **kwargs):
|
||||
super().__init__(**kwargs)
|
||||
self.redis = redis
|
||||
self._reset()
|
||||
|
||||
def _reset(self):
|
||||
self.redis.delete("atat_inbox")
|
||||
|
||||
@property
|
||||
def messages(self):
|
||||
return [msg.decode() for msg in self.redis.lrange("atat_inbox", 0, -1)]
|
||||
|
||||
def send(self, message):
|
||||
self.redis.lpush("atat_inbox", str(message))
|
||||
|
||||
|
||||
class Mailer(object):
|
||||
def __init__(self, connection, sender):
|
||||
self.connection = connection
|
||||
self.sender = sender
|
||||
|
||||
def _build_message(self, recipients, subject, body):
|
||||
msg = EmailMessage()
|
||||
msg.set_content(body)
|
||||
msg["From"] = self.sender
|
||||
msg["To"] = ", ".join(recipients)
|
||||
msg["Subject"] = subject
|
||||
|
||||
return msg
|
||||
|
||||
def send(self, recipients, subject, body):
|
||||
message = self._build_message(recipients, subject, body)
|
||||
self.connection.send(message)
|
||||
|
||||
@property
|
||||
def messages(self):
|
||||
return self.connection.messages
|
||||
Reference in New Issue
Block a user