From 18bb5b24fd5a1d591f5053be2d8fa2638be684b3 Mon Sep 17 00:00:00 2001 From: dandds Date: Wed, 10 Oct 2018 11:28:03 -0400 Subject: [PATCH 01/19] basic mailer implementation --- atst/app.py | 2 + atst/routes/dev.py | 24 ++++++++- atst/{utils.py => utils/__init__.py} | 0 atst/utils/mailer.py | 77 ++++++++++++++++++++++++++++ templates/dev/emails.html | 6 +++ 5 files changed, 108 insertions(+), 1 deletion(-) rename atst/{utils.py => utils/__init__.py} (100%) create mode 100644 atst/utils/mailer.py create mode 100644 templates/dev/emails.html diff --git a/atst/app.py b/atst/app.py index 884d2bee..c8795f61 100644 --- a/atst/app.py +++ b/atst/app.py @@ -22,6 +22,7 @@ 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.mailer import make_mailer ENV = os.getenv("FLASK_ENV", "dev") @@ -47,6 +48,7 @@ def make_app(config): register_filters(app) make_eda_client(app) make_upload_storage(app) + make_mailer(app) db.init_app(app) csrf.init_app(app) diff --git a/atst/routes/dev.py b/atst/routes/dev.py index e9b107e8..a087d061 100644 --- a/atst/routes/dev.py +++ b/atst/routes/dev.py @@ -1,4 +1,12 @@ -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 @@ -65,3 +73,17 @@ def login_dev(): session["user_id"] = user.id return redirect(redirect_after_login_url()) + + +@bp.route("/test-email") +def test_email(): + app.mailer.send( + [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) diff --git a/atst/utils.py b/atst/utils/__init__.py similarity index 100% rename from atst/utils.py rename to atst/utils/__init__.py diff --git a/atst/utils/mailer.py b/atst/utils/mailer.py new file mode 100644 index 00000000..32918bfa --- /dev/null +++ b/atst/utils/mailer.py @@ -0,0 +1,77 @@ +import smtplib +from email.message import EmailMessage +from collections import deque + + +class _HostConnection: + 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 + self.host = None + + def __enter__(self): + self.host = smtplib.SMTP_SSL(self.server, self.port) + if self.use_tls: + self.host.starttls() + self.host.login(self.username, self.password) + + return self.host + + def __exit__(self, *args): + if self.host: + self.host.quit() + + +class Mailer: + def __init__( + self, server, port, sender, username, password, use_tls=False, debug=False + ): + self.server = server + self.port = port + self.sender = sender + self.username = username + self.password = password + self.use_tls = use_tls + self.debug = debug + self.messages = deque(maxlen=50) + + def connection(self): + return _HostConnection(self.server, self.port, self.username, self.password) + + def _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._message(recipients, subject, body) + if self.debug: + self.messages.appendleft(message) + else: + with self.connection() as conn: + conn.send_message(message) + + +def _map_config(config): + return { + "server": config.get("MAIL_SERVER"), + "port": config.get("MAIL_PORT"), + "sender": config.get("MAIL_SENDER"), + "username": config.get("MAIL_USERNAME"), + "password": config.get("MAIL_PASSWORD"), + "use_tls": config.get("MAIL_TLS", False), + "debug": config.get("DEBUG", False), + } + + +def make_mailer(app): + config = _map_config(app.config) + mailer = Mailer(**config) + app.mailer = mailer diff --git a/templates/dev/emails.html b/templates/dev/emails.html new file mode 100644 index 00000000..b07f6b46 --- /dev/null +++ b/templates/dev/emails.html @@ -0,0 +1,6 @@ +{% for msg in messages %} +
+{{ msg }} +
+
+{% endfor %} From 0d9f1cd7e0f597ab45f57459f6cc69a64e6f2311 Mon Sep 17 00:00:00 2001 From: dandds Date: Thu, 11 Oct 2018 10:59:07 -0400 Subject: [PATCH 02/19] update mailer, add tests --- atst/app.py | 2 +- atst/utils/mailer.py | 8 ++---- tests/utils/test_mailer.py | 54 ++++++++++++++++++++++++++++++++++++++ 3 files changed, 57 insertions(+), 7 deletions(-) create mode 100644 tests/utils/test_mailer.py diff --git a/atst/app.py b/atst/app.py index c8795f61..36aca6d0 100644 --- a/atst/app.py +++ b/atst/app.py @@ -87,7 +87,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, diff --git a/atst/utils/mailer.py b/atst/utils/mailer.py index 32918bfa..e3a6a094 100644 --- a/atst/utils/mailer.py +++ b/atst/utils/mailer.py @@ -26,20 +26,17 @@ class _HostConnection: class Mailer: - def __init__( - self, server, port, sender, username, password, use_tls=False, debug=False - ): + def __init__(self, server, port, sender, password, use_tls=False, debug=False): self.server = server self.port = port self.sender = sender - self.username = username self.password = password self.use_tls = use_tls self.debug = debug self.messages = deque(maxlen=50) def connection(self): - return _HostConnection(self.server, self.port, self.username, self.password) + return _HostConnection(self.server, self.port, self.sender, self.password) def _message(self, recipients, subject, body): msg = EmailMessage() @@ -64,7 +61,6 @@ def _map_config(config): "server": config.get("MAIL_SERVER"), "port": config.get("MAIL_PORT"), "sender": config.get("MAIL_SENDER"), - "username": config.get("MAIL_USERNAME"), "password": config.get("MAIL_PASSWORD"), "use_tls": config.get("MAIL_TLS", False), "debug": config.get("DEBUG", False), diff --git a/tests/utils/test_mailer.py b/tests/utils/test_mailer.py new file mode 100644 index 00000000..1a820b6b --- /dev/null +++ b/tests/utils/test_mailer.py @@ -0,0 +1,54 @@ +import pytest +from atst.utils.mailer import Mailer + + +class MockHost: + def __init__(self): + self.messages = [] + + def __enter__(self): + return self + + def __exit__(self, *args): + pass + + def send_message(self, message): + self.messages.append(message) + + +@pytest.fixture +def mail_host(): + return MockHost() + + +def test_can_send_mail(monkeypatch, mail_host): + monkeypatch.setattr("atst.utils.mailer.Mailer.connection", lambda *args: mail_host) + mailer = Mailer("localhost", 456, "leia@rebellion.net", "droidsyourelookingfor") + message_data = { + "recipients": ["ben@tattoine.org"], + "subject": "help", + "body": "you're my only hope", + } + mailer.send(**message_data) + assert len(mail_host.messages) == 1 + message = mail_host.messages[0] + assert message["To"] == message_data["recipients"][0] + assert message["Subject"] == message_data["subject"] + assert message.get_content().strip() == message_data["body"] + + +def test_can_save_messages(): + mailer = Mailer( + "localhost", 456, "leia@rebellion.net", "droidsyourelookingfor", debug=True + ) + message_data = { + "recipients": ["ben@tattoine.org"], + "subject": "help", + "body": "you're my only hope", + } + mailer.send(**message_data) + assert len(mailer.messages) == 1 + message = mailer.messages[0] + assert message["To"] == message_data["recipients"][0] + assert message["Subject"] == message_data["subject"] + assert message.get_content().strip() == message_data["body"] From f7d87833498ea1905014ac8d68cd860a4e9b56ce Mon Sep 17 00:00:00 2001 From: dandds Date: Thu, 11 Oct 2018 14:58:33 -0400 Subject: [PATCH 03/19] implement mailer with task queue --- Pipfile | 1 + Pipfile.lock | 56 ++++++++++++++++++++++++-------------- atst/app.py | 33 ++++++++++++++++++---- atst/queue.py | 9 ++++++ atst/routes/dev.py | 3 +- atst/utils/mailer.py | 52 +++++++++++++++++------------------ tests/utils/test_mailer.py | 13 +++------ 7 files changed, 106 insertions(+), 61 deletions(-) create mode 100644 atst/queue.py diff --git a/Pipfile b/Pipfile index bc5ed901..937896a5 100644 --- a/Pipfile +++ b/Pipfile @@ -21,6 +21,7 @@ requests = "*" apache-libcloud = "*" lockfile = "*" defusedxml = "*" +"flask-rq2" = "*" [dev-packages] bandit = "*" diff --git a/Pipfile.lock b/Pipfile.lock index bf863dc6..581e1a7d 100644 --- a/Pipfile.lock +++ b/Pipfile.lock @@ -1,7 +1,7 @@ { "_meta": { "hash": { - "sha256": "1e5e6a695229166aaa5e6c427fed07a903766e9b3d24981a19cc8e5ada8db978" + "sha256": "7162a0e3c45d05aff99adde9d75128d1772cf030d1e2a722f441b21f251a4645" }, "pipfile-spec": 6, "requires": { @@ -41,10 +41,10 @@ }, "certifi": { "hashes": [ - "sha256:376690d6f16d32f9d1fe8932551d80b23e9d393a8578c5633a2ed39a64861638", - "sha256:456048c7e371c089d0a77a5212fb37a2c2dce1e24146e3b7e0261736aaeaa22a" + "sha256:339dc09518b07e2fa7eda5450740925974815557727d6bd35d319c1524a04a4c", + "sha256:6d58c986d22b038c8c0df30d639f23a3e6d172a05c3583e766f4c0b785c0986a" ], - "version": "==2018.8.24" + "version": "==2018.10.15" }, "cffi": { "hashes": [ @@ -97,6 +97,13 @@ ], "version": "==7.0" }, + "croniter": { + "hashes": [ + "sha256:64d5f8c719249694265190810ef2f051345007246c99a3879a35b393d593d668", + "sha256:8ce5e4edd6f1956e70c8a31211cf86a7859aa1f0ff256107723582d79238e002" + ], + "version": "==0.3.25" + }, "cryptography": { "hashes": [ "sha256:02602e1672b62e803e08617ec286041cc453e8d43f093a5f4162095506bc0beb", @@ -144,6 +151,14 @@ "index": "pypi", "version": "==0.12" }, + "flask-rq2": { + "hashes": [ + "sha256:83e28f0279828198e64e1ed52a43fd6b530d9192d9944f4dea30e99e5688c9de", + "sha256:d513aa8d3b91eda34091ed8e40d55655e3acff6d37d57197417b14839a066185" + ], + "index": "pypi", + "version": "==18.1" + }, "flask-session": { "hashes": [ "sha256:a31c27e0c3287f00c825b3d9625aba585f4df4cccedb1e7dd5a69a215881a731", @@ -313,6 +328,20 @@ "index": "pypi", "version": "==2.19.1" }, + "rq": { + "hashes": [ + "sha256:5dd83625ca64b0dbf668ee65a8d38f3f5132aa9b64de4d813ff76f97db194b60", + "sha256:7ac5989a27bdff713dd40517498c1b3bf720f8ebc47305055496f653a29da899" + ], + "version": "==0.12.0" + }, + "rq-scheduler": { + "hashes": [ + "sha256:6cad6b6d29eae55d4585e2ac9be3b8a36b3f18c87a494fc508a4fa19b9c845d6", + "sha256:fc51da3d4ad1a047cada3b97a96afea21a3102ea5aa5b79ed2ea97d8ffdf8821" + ], + "version": "==0.8.3" + }, "six": { "hashes": [ "sha256:70e8a77beed4562e7f14fe23a786b54f6296e34344c23bc42f07b15018ff98e9", @@ -453,7 +482,6 @@ "sha256:2105ee183c51fed27e2b6801029b3903f5c2774c78e3f53bd920ca468d0f5679", "sha256:236505d15af6c7b7bfe2a9485db4b2bdea21d9239351483326184314418c79a8", "sha256:237284425271db4f30d458b355decf388ab20b05278bdf8dc9a65de0973726c6", - "sha256:2619f0369412e22f01ad8f5bea503f15fb099a5eef3c31f1edb81dcb29221bf7", "sha256:26d8eea4c840b73c61a1081d68bceb57b21a2d4f7afda6cac8ac38cb05226b00", "sha256:39a3740f7721155f4269aedf67b211101c07bd2111b334dfd69b807156ab15d9", "sha256:4bd0c42db8efc8a60965769796d43a5570906a870bc819f7388860aa72779d1b", @@ -462,26 +490,19 @@ "sha256:5415cafb082dad78935b3045c2e5d8907f436d15ad24c3fdb8e1839e084e4961", "sha256:5631f1983074b33c35dbb84607f337b9d7e9808116d7f0f2cb7b9d6d4381d50e", "sha256:5e9249bc361cd22565fd98590a53fd25a3dd666b74791ed7237fa99de938bbed", - "sha256:61ad080b78287e8a10ae485a194fc552625d4ed4196ab32cc8987e61bdcceb0f", "sha256:6a48746154f1331f28ef9e889c625b5b15a36cb86dd8021b4bdd1180a2186aa5", "sha256:71d376dbac64855ed693bc1ca121794570fe603e8783cdfa304ec6825d4e768f", "sha256:749ebd8a615337747592bd1523dfc4af7199b2bf6403b55f96c728668aeff91f", - "sha256:8575f3e1a12eae8d2fd3935dcc6fad2d5a7cf32bc15150a69d3bede229e970d5", "sha256:8ec528b585b95234e9c0c31dcd0a89152d8ed82b4567aa62dbcb3e9a0600deee", "sha256:a1a9ccd879811437ca0307c914f136d6edb85bd0470e6d4966c6397927bcabd9", "sha256:abd956c334752776230b779537d911a5a12fcb69d8fd3fe332ae63a140301ae6", "sha256:ad18f836017f2e8881145795f483636564807aaed54223459915a0d4735300cf", "sha256:b07ac0b1533298ddbc54c9bf3464664895f22899fec027b8d6c8d3ac59023283", - "sha256:c3ae3527c72581595952977c1b391b9e7313d236216581099ee38e4240d997fe", - "sha256:d5309c5c6750ff882d47c0d4d5952d2384232e522db56d2bb63beb01dcb07f46", "sha256:d9385f1445e30e8e42b75a36a7899ea1fd0f5784233a626625d70f9b087de404", "sha256:db2d1fcd32dbeeb914b2660af1838e9c178b75173f95fd221b1f9410b5d3ef1d", "sha256:e1dec211147f1fd7cb7a0f9a96aeeca467a5af02d38911307b3b8c2324f9917e", - "sha256:e20f11023ab77ad08dcdbf3a740e2512f73ebfbbfcb4f08f0b8a8f65f98210a2", - "sha256:e2cc3fc55566990059afb0f06141e136095898b55e977af66d0b498415098792", "sha256:e96dffc1fa57bb8c1c238f3d989341a97302492d09cb11f77df031112621c35c", - "sha256:ed4d97eb0ecdee29d0748acd84e6380729f78ce5ba0c7fe3401801634c25a1c5", - "sha256:eecc9d908a22a97356a1033d756281cd8c37285430f047cb35458d1bc8e6f8de" + "sha256:ed4d97eb0ecdee29d0748acd84e6380729f78ce5ba0c7fe3401801634c25a1c5" ], "version": "==5.0a3" }, @@ -766,15 +787,11 @@ }, "pyyaml": { "hashes": [ - "sha256:1cbc199009e78f92d9edf554be4fe40fb7b0bef71ba688602a00e97a51909110", "sha256:254bf6fda2b7c651837acb2c718e213df29d531eebf00edb54743d10bcb694eb", "sha256:3108529b78577327d15eec243f0ff348a0640b0c3478d67ad7f5648f93bac3e2", "sha256:3c17fb92c8ba2f525e4b5f7941d850e7a48c3a59b32d331e2502a3cdc6648e76", - "sha256:6f89b5c95e93945b597776163403d47af72d243f366bf4622ff08bdfd1c950b7", "sha256:8d6d96001aa7f0a6a4a95e8143225b5d06e41b1131044913fecb8f85a125714b", - "sha256:be622cc81696e24d0836ba71f6272a2b5767669b0d79fdcf0295d51ac2e156c8", - "sha256:c8a88edd93ee29ede719080b2be6cb2333dfee1dccba213b422a9c8e97f2967b", - "sha256:f39411e380e2182ad33be039e8ee5770a5d9efe01a2bfb7ae58d9ba31c4a2a9d" + "sha256:c8a88edd93ee29ede719080b2be6cb2333dfee1dccba213b422a9c8e97f2967b" ], "version": "==4.2b4" }, @@ -823,8 +840,7 @@ "toml": { "hashes": [ "sha256:229f81c57791a41d65e399fc06bf0848bab550a9dfd5ed66df18ce5f05e73d5c", - "sha256:235682dd292d5899d361a811df37e04a8828a5b1da3115886b73cf81ebc9100e", - "sha256:f1db651f9657708513243e61e6cc67d101a39bad662eaa9b5546f789338e07a3" + "sha256:235682dd292d5899d361a811df37e04a8828a5b1da3115886b73cf81ebc9100e" ], "version": "==0.10.0" }, diff --git a/atst/app.py b/atst/app.py index 36aca6d0..d709fb5e 100644 --- a/atst/app.py +++ b/atst/app.py @@ -22,7 +22,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.mailer import make_mailer +from atst.utils.mailer import Mailer, RedisMailer +from atst.queue import queue ENV = os.getenv("FLASK_ENV", "dev") @@ -37,11 +38,11 @@ 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) @@ -49,6 +50,7 @@ def make_app(config): make_eda_client(app) make_upload_storage(app) make_mailer(app) + queue.init_app(app) db.init_app(app) csrf.init_app(app) @@ -95,6 +97,7 @@ def map_config(config): "PERMANENT_SESSION_LIFETIME": config.getint( "default", "PERMANENT_SESSION_LIFETIME" ), + "RQ_REDIS_URL": config["default"]["REDIS_URI"], } @@ -143,8 +146,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 +170,22 @@ def make_upload_storage(app): secret=app.config.get("STORAGE_SECRET"), ) app.uploader = uploader + + +def _map_config(config): + return { + "server": config.get("MAIL_SERVER"), + "port": config.get("MAIL_PORT"), + "sender": config.get("MAIL_SENDER"), + "password": config.get("MAIL_PASSWORD"), + "use_tls": config.get("MAIL_TLS", False), + } + + +def make_mailer(app): + config = _map_config(app.config) + if app.config["DEBUG"]: + mailer = RedisMailer(redis=app.redis, **config) + else: + mailer = Mailer(**config) + app.mailer = mailer diff --git a/atst/queue.py b/atst/queue.py new file mode 100644 index 00000000..8c94bf8f --- /dev/null +++ b/atst/queue.py @@ -0,0 +1,9 @@ +from flask_rq2 import RQ +from flask import current_app as app + +queue = RQ() + + +@queue.job +def send_mail(to, subject, body): + app.mailer.send(to, subject, body) diff --git a/atst/routes/dev.py b/atst/routes/dev.py index a087d061..862d602e 100644 --- a/atst/routes/dev.py +++ b/atst/routes/dev.py @@ -10,6 +10,7 @@ from flask import ( from . import redirect_after_login_url from atst.domain.users import Users +from atst.queue import send_mail bp = Blueprint("dev", __name__) @@ -77,7 +78,7 @@ def login_dev(): @bp.route("/test-email") def test_email(): - app.mailer.send( + send_mail.queue( [request.args.get("to")], request.args.get("subject"), request.args.get("body") ) diff --git a/atst/utils/mailer.py b/atst/utils/mailer.py index e3a6a094..7f1ca6cf 100644 --- a/atst/utils/mailer.py +++ b/atst/utils/mailer.py @@ -1,6 +1,5 @@ import smtplib from email.message import EmailMessage -from collections import deque class _HostConnection: @@ -25,18 +24,13 @@ class _HostConnection: self.host.quit() -class Mailer: - def __init__(self, server, port, sender, password, use_tls=False, debug=False): +class BaseMailer: + def __init__(self, server, port, sender, password, use_tls=False): self.server = server self.port = port self.sender = sender self.password = password self.use_tls = use_tls - self.debug = debug - self.messages = deque(maxlen=50) - - def connection(self): - return _HostConnection(self.server, self.port, self.sender, self.password) def _message(self, recipients, subject, body): msg = EmailMessage() @@ -47,27 +41,33 @@ class Mailer: return msg + def send(self, recipients, subject, body): + pass + + +class Mailer(BaseMailer): + def connection(self): + return _HostConnection(self.server, self.port, self.sender, self.password) + def send(self, recipients, subject, body): message = self._message(recipients, subject, body) - if self.debug: - self.messages.appendleft(message) - else: - with self.connection() as conn: - conn.send_message(message) + with self.connection() as conn: + conn.send_message(message) -def _map_config(config): - return { - "server": config.get("MAIL_SERVER"), - "port": config.get("MAIL_PORT"), - "sender": config.get("MAIL_SENDER"), - "password": config.get("MAIL_PASSWORD"), - "use_tls": config.get("MAIL_TLS", False), - "debug": config.get("DEBUG", False), - } +class RedisMailer(BaseMailer): + def __init__(self, redis, **kwargs): + super().__init__(**kwargs) + self.redis = redis + self._reset() + def _reset(self): + self.redis.delete("atat_inbox") -def make_mailer(app): - config = _map_config(app.config) - mailer = Mailer(**config) - app.mailer = mailer + @property + def messages(self): + return [msg.decode() for msg in self.redis.lrange("atat_inbox", 0, -1)] + + def send(self, recipients, subject, body): + message = self._message(recipients, subject, body) + self.redis.lpush("atat_inbox", str(message)) diff --git a/tests/utils/test_mailer.py b/tests/utils/test_mailer.py index 1a820b6b..f06d6ee9 100644 --- a/tests/utils/test_mailer.py +++ b/tests/utils/test_mailer.py @@ -1,5 +1,5 @@ import pytest -from atst.utils.mailer import Mailer +from atst.utils.mailer import Mailer, RedisMailer class MockHost: @@ -21,7 +21,7 @@ def mail_host(): return MockHost() -def test_can_send_mail(monkeypatch, mail_host): +def test_mailer_can_send_mail(monkeypatch, mail_host): monkeypatch.setattr("atst.utils.mailer.Mailer.connection", lambda *args: mail_host) mailer = Mailer("localhost", 456, "leia@rebellion.net", "droidsyourelookingfor") message_data = { @@ -37,10 +37,8 @@ def test_can_send_mail(monkeypatch, mail_host): assert message.get_content().strip() == message_data["body"] -def test_can_save_messages(): - mailer = Mailer( - "localhost", 456, "leia@rebellion.net", "droidsyourelookingfor", debug=True - ) +def test_redis_mailer_can_save_messages(app): + mailer = RedisMailer(app.redis, server=None, port=None, sender=None, password=None) message_data = { "recipients": ["ben@tattoine.org"], "subject": "help", @@ -49,6 +47,3 @@ def test_can_save_messages(): mailer.send(**message_data) assert len(mailer.messages) == 1 message = mailer.messages[0] - assert message["To"] == message_data["recipients"][0] - assert message["Subject"] == message_data["subject"] - assert message.get_content().strip() == message_data["body"] From ae909117f428e6a70c500491a5200c013c8b444e Mon Sep 17 00:00:00 2001 From: dandds Date: Thu, 11 Oct 2018 17:31:37 -0400 Subject: [PATCH 04/19] use honcho to manage multiple dev processes --- Pipfile | 1 + Pipfile.lock | 10 +++++++++- Procfile | 3 +++ script/server | 5 +---- 4 files changed, 14 insertions(+), 5 deletions(-) create mode 100644 Procfile diff --git a/Pipfile b/Pipfile index 937896a5..10d7ec29 100644 --- a/Pipfile +++ b/Pipfile @@ -36,6 +36,7 @@ pytest-flask = "*" pytest-env = "*" pytest-cov = "*" selenium = "*" +honcho = "*" [requires] python_version = "3.6.6" diff --git a/Pipfile.lock b/Pipfile.lock index 581e1a7d..cf9730d8 100644 --- a/Pipfile.lock +++ b/Pipfile.lock @@ -1,7 +1,7 @@ { "_meta": { "hash": { - "sha256": "7162a0e3c45d05aff99adde9d75128d1772cf030d1e2a722f441b21f251a4645" + "sha256": "c67f5a847351d9d6e8ef165c380dd97fdf623f87cf8299a64109e453027e2458" }, "pipfile-spec": 6, "requires": { @@ -556,6 +556,14 @@ ], "version": "==2.1.11" }, + "honcho": { + "hashes": [ + "sha256:af5806bf13e3b20acdcb9ff8c0beb91eee6fe07393c3448dfad89667e6ac7576", + "sha256:c189402ad2e337777283c6a12d0f4f61dc6dd20c254c9a3a4af5087fc66cea6e" + ], + "index": "pypi", + "version": "==1.0.1" + }, "ipdb": { "hashes": [ "sha256:7081c65ed7bfe7737f83fa4213ca8afd9617b42ff6b3f1daf9a3419839a2a00a" diff --git a/Procfile b/Procfile new file mode 100644 index 00000000..0101271f --- /dev/null +++ b/Procfile @@ -0,0 +1,3 @@ +assets: yarn watch +web: PORT=8000 python app.py +queue: flask rq worker diff --git a/script/server b/script/server index c03cfdd5..1fc46774 100755 --- a/script/server +++ b/script/server @@ -4,8 +4,5 @@ source "$(dirname "${0}")"/../script/include/global_header.inc.sh -# Compile js/css assets -yarn build - # Launch the app -run_command "./app.py ${LAUNCH_ARGS}" +run_command "honcho start" From ae7c644d9194b3c347b970ac0f79071fc9ab6909 Mon Sep 17 00:00:00 2001 From: dandds Date: Fri, 12 Oct 2018 09:15:56 -0400 Subject: [PATCH 05/19] update README, adjust email config function name --- README.md | 16 ++++++++++++++++ atst/app.py | 4 ++-- 2 files changed, 18 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index a0c82d63..f8c5429a 100644 --- a/README.md +++ b/README.md @@ -111,6 +111,22 @@ projects for all of the test users: `pipenv run python script/seed_sample.py` +### Email Notifications + +To send email, the following configuration values must be set: + +``` +MAIL_SERVER = +MAIL_PORT = +MAIL_SENDER = +MAIL_PASSWORD = +MAIL_TLS = +``` + +When the `DEBUG` environment variable is enabled and the app environment is not +set to production, sent email messages are available at the `/messages` endpoint. +Emails are not sent in development and test modes. + ## Testing Tests require a test database: diff --git a/atst/app.py b/atst/app.py index d709fb5e..68ebd331 100644 --- a/atst/app.py +++ b/atst/app.py @@ -172,7 +172,7 @@ def make_upload_storage(app): app.uploader = uploader -def _map_config(config): +def _map_email_config(config): return { "server": config.get("MAIL_SERVER"), "port": config.get("MAIL_PORT"), @@ -183,7 +183,7 @@ def _map_config(config): def make_mailer(app): - config = _map_config(app.config) + config = _map_email_config(app.config) if app.config["DEBUG"]: mailer = RedisMailer(redis=app.redis, **config) else: From df35725430894d8eb19c0c799970606913c925cd Mon Sep 17 00:00:00 2001 From: dandds Date: Fri, 12 Oct 2018 10:00:03 -0400 Subject: [PATCH 06/19] reformat files, add more tests --- tests/conftest.py | 2 ++ tests/test_queue.py | 16 ++++++++++++++++ tests/utils/test_mailer.py | 3 +++ 3 files changed, 21 insertions(+) create mode 100644 tests/test_queue.py diff --git a/tests/conftest.py b/tests/conftest.py index 947f054d..9c3c2296 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -10,6 +10,7 @@ from tempfile import TemporaryDirectory from atst.app import make_app, make_config from atst.database import db as _db from atst.domain.auth import logout +from atst.queue import queue import tests.factories as factories from tests.mocks import PDF_FILENAME @@ -22,6 +23,7 @@ def app(request): config = make_config() config.update({"STORAGE_CONTAINER": upload_dir.name}) + config.update({"RQ_QUEUES": ["test"]}) _app = make_app(config) diff --git a/tests/test_queue.py b/tests/test_queue.py new file mode 100644 index 00000000..ff634347 --- /dev/null +++ b/tests/test_queue.py @@ -0,0 +1,16 @@ +import pytest +from atst.queue import queue, send_mail + +# ensure queue is always empty for unit testing +@pytest.fixture(scope="function", autouse=True) +def reset_queue(): + queue.get_queue().empty() + yield + queue.get_queue().empty() + +def test_send_mail(): + assert len(queue.get_queue()) == 0 + send_mail.queue( + ["lordvader@geocities.net"], "death start", "how is it coming along?" + ) + assert len(queue.get_queue()) == 1 diff --git a/tests/utils/test_mailer.py b/tests/utils/test_mailer.py index f06d6ee9..0506cfdc 100644 --- a/tests/utils/test_mailer.py +++ b/tests/utils/test_mailer.py @@ -47,3 +47,6 @@ def test_redis_mailer_can_save_messages(app): mailer.send(**message_data) assert len(mailer.messages) == 1 message = mailer.messages[0] + assert message_data["recipients"][0] in message + assert message_data["subject"] in message + assert message_data["body"] in message From 4b8c017d49e97fb83c67a9d5cc3bf9b6cfec7c51 Mon Sep 17 00:00:00 2001 From: dandds Date: Fri, 12 Oct 2018 10:50:34 -0400 Subject: [PATCH 07/19] do not use SSL for email if using TLS --- atst/utils/mailer.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/atst/utils/mailer.py b/atst/utils/mailer.py index 7f1ca6cf..f98c7e5d 100644 --- a/atst/utils/mailer.py +++ b/atst/utils/mailer.py @@ -12,9 +12,11 @@ class _HostConnection: self.host = None def __enter__(self): - self.host = smtplib.SMTP_SSL(self.server, self.port) if self.use_tls: + self.host = smtplib.SMTP(self.server, self.port) self.host.starttls() + else: + self.host = smtplib.SMTP_SSL(self.server, self.port) self.host.login(self.username, self.password) return self.host @@ -31,6 +33,7 @@ class BaseMailer: self.sender = sender self.password = password self.use_tls = use_tls + self.messages = [] def _message(self, recipients, subject, body): msg = EmailMessage() @@ -47,7 +50,7 @@ class BaseMailer: class Mailer(BaseMailer): def connection(self): - return _HostConnection(self.server, self.port, self.sender, self.password) + return _HostConnection(self.server, self.port, self.sender, self.password, use_tls=self.use_tls) def send(self, recipients, subject, body): message = self._message(recipients, subject, body) From 95ad71605d1e9bed2070ddf9451c17ac653f4ca0 Mon Sep 17 00:00:00 2001 From: dandds Date: Fri, 12 Oct 2018 11:20:14 -0400 Subject: [PATCH 08/19] set default task queue name based on ATAT environment --- atst/app.py | 1 + atst/utils/mailer.py | 10 ++++++++-- tests/conftest.py | 1 - tests/test_queue.py | 1 + 4 files changed, 10 insertions(+), 3 deletions(-) diff --git a/atst/app.py b/atst/app.py index 68ebd331..8bfcb134 100644 --- a/atst/app.py +++ b/atst/app.py @@ -98,6 +98,7 @@ def map_config(config): "default", "PERMANENT_SESSION_LIFETIME" ), "RQ_REDIS_URL": config["default"]["REDIS_URI"], + "RQ_QUEUES": ["atat_{}".format(ENV.lower())], } diff --git a/atst/utils/mailer.py b/atst/utils/mailer.py index f98c7e5d..a2b7aeb2 100644 --- a/atst/utils/mailer.py +++ b/atst/utils/mailer.py @@ -33,7 +33,6 @@ class BaseMailer: self.sender = sender self.password = password self.use_tls = use_tls - self.messages = [] def _message(self, recipients, subject, body): msg = EmailMessage() @@ -47,10 +46,17 @@ class BaseMailer: def send(self, recipients, subject, body): pass + # do not collect messages by default + @property + def messages(self): + return [] + class Mailer(BaseMailer): def connection(self): - return _HostConnection(self.server, self.port, self.sender, self.password, use_tls=self.use_tls) + return _HostConnection( + self.server, self.port, self.sender, self.password, use_tls=self.use_tls + ) def send(self, recipients, subject, body): message = self._message(recipients, subject, body) diff --git a/tests/conftest.py b/tests/conftest.py index 9c3c2296..50a555e7 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -23,7 +23,6 @@ def app(request): config = make_config() config.update({"STORAGE_CONTAINER": upload_dir.name}) - config.update({"RQ_QUEUES": ["test"]}) _app = make_app(config) diff --git a/tests/test_queue.py b/tests/test_queue.py index ff634347..20331c1e 100644 --- a/tests/test_queue.py +++ b/tests/test_queue.py @@ -8,6 +8,7 @@ def reset_queue(): yield queue.get_queue().empty() + def test_send_mail(): assert len(queue.get_queue()) == 0 send_mail.queue( From 40317a06a475c7c899b24898937a8c1febab8292 Mon Sep 17 00:00:00 2001 From: dandds Date: Mon, 15 Oct 2018 11:35:06 -0400 Subject: [PATCH 09/19] use default queue for now --- atst/app.py | 1 - tests/test_queue.py | 4 ++-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/atst/app.py b/atst/app.py index 8bfcb134..68ebd331 100644 --- a/atst/app.py +++ b/atst/app.py @@ -98,7 +98,6 @@ def map_config(config): "default", "PERMANENT_SESSION_LIFETIME" ), "RQ_REDIS_URL": config["default"]["REDIS_URI"], - "RQ_QUEUES": ["atat_{}".format(ENV.lower())], } diff --git a/tests/test_queue.py b/tests/test_queue.py index 20331c1e..1754023e 100644 --- a/tests/test_queue.py +++ b/tests/test_queue.py @@ -10,8 +10,8 @@ def reset_queue(): def test_send_mail(): - assert len(queue.get_queue()) == 0 + initial = len(queue.get_queue()) send_mail.queue( ["lordvader@geocities.net"], "death start", "how is it coming along?" ) - assert len(queue.get_queue()) == 1 + assert len(queue.get_queue()) == initial + 1 From e5f588e03295c0238e42c749c0c87d465fed1bbd Mon Sep 17 00:00:00 2001 From: Patrick Smith Date: Mon, 15 Oct 2018 14:47:45 -0400 Subject: [PATCH 10/19] WIP of a queue --- atst/queue.py | 42 ++++++++++++++++++++++++++++++++++++++---- atst/routes/dev.py | 4 ++-- 2 files changed, 40 insertions(+), 6 deletions(-) diff --git a/atst/queue.py b/atst/queue.py index 8c94bf8f..0b92bf91 100644 --- a/atst/queue.py +++ b/atst/queue.py @@ -1,9 +1,43 @@ from flask_rq2 import RQ from flask import current_app as app -queue = RQ() + +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) + + """Instance methods to queue up application-specific jobs.""" + + def send_mail(self, to, subject, body): + self._queue_job(ATSTQueue._send_mail, to, subject, body) + + """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.job -def send_mail(to, subject, body): - app.mailer.send(to, subject, body) +queue = ATSTQueue() diff --git a/atst/routes/dev.py b/atst/routes/dev.py index 862d602e..0d90397f 100644 --- a/atst/routes/dev.py +++ b/atst/routes/dev.py @@ -10,7 +10,7 @@ from flask import ( from . import redirect_after_login_url from atst.domain.users import Users -from atst.queue import send_mail +from atst.queue import queue bp = Blueprint("dev", __name__) @@ -78,7 +78,7 @@ def login_dev(): @bp.route("/test-email") def test_email(): - send_mail.queue( + queue.send_mail( [request.args.get("to")], request.args.get("subject"), request.args.get("body") ) From 8a207b3bc64b5b32dba9264e4c095451ae8a6c3a Mon Sep 17 00:00:00 2001 From: dandds Date: Mon, 15 Oct 2018 16:11:14 -0400 Subject: [PATCH 11/19] restore environment-based queue names --- atst/app.py | 1 + atst/queue.py | 2 ++ tests/test_queue.py | 4 ++-- 3 files changed, 5 insertions(+), 2 deletions(-) diff --git a/atst/app.py b/atst/app.py index 68ebd331..8bfcb134 100644 --- a/atst/app.py +++ b/atst/app.py @@ -98,6 +98,7 @@ def map_config(config): "default", "PERMANENT_SESSION_LIFETIME" ), "RQ_REDIS_URL": config["default"]["REDIS_URI"], + "RQ_QUEUES": ["atat_{}".format(ENV.lower())], } diff --git a/atst/queue.py b/atst/queue.py index 0b92bf91..e036a642 100644 --- a/atst/queue.py +++ b/atst/queue.py @@ -24,11 +24,13 @@ class ATSTQueue(RQ): 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 diff --git a/tests/test_queue.py b/tests/test_queue.py index 1754023e..da6069d4 100644 --- a/tests/test_queue.py +++ b/tests/test_queue.py @@ -1,5 +1,5 @@ import pytest -from atst.queue import queue, send_mail +from atst.queue import queue # ensure queue is always empty for unit testing @pytest.fixture(scope="function", autouse=True) @@ -11,7 +11,7 @@ def reset_queue(): def test_send_mail(): initial = len(queue.get_queue()) - send_mail.queue( + queue.send_mail( ["lordvader@geocities.net"], "death start", "how is it coming along?" ) assert len(queue.get_queue()) == initial + 1 From 6a2a7545ae1849bf3cecb291e0fa372c01264ad7 Mon Sep 17 00:00:00 2001 From: dandds Date: Mon, 15 Oct 2018 16:52:12 -0400 Subject: [PATCH 12/19] remove dev server --- README.md | 8 ++------ script/dev_server | 32 -------------------------------- 2 files changed, 2 insertions(+), 38 deletions(-) delete mode 100755 script/dev_server diff --git a/README.md b/README.md index f8c5429a..f9cf66fd 100644 --- a/README.md +++ b/README.md @@ -77,13 +77,9 @@ virtualenvs for you when you enter and leave the directory. To start the app locally in the foreground and watch for changes: - script/dev_server + script/server -To watch for changes to any js/css assets: - - yarn watch - -After running `script/dev_server`, the application is available at +After running `script/server`, the application is available at [`http://localhost:8000`](http://localhost:8000). diff --git a/script/dev_server b/script/dev_server deleted file mode 100755 index 1f5f75a1..00000000 --- a/script/dev_server +++ /dev/null @@ -1,32 +0,0 @@ -#!/bin/bash - -# script/dev_server: Launch a local dev version of the server in the background - -# -# WIP -# - -source "$(dirname "${0}")"/../script/include/global_header.inc.sh - -# Create a function to run after a trap is triggered -reap() { - kill -s SIGTERM -- "-$$" - sleep 0.1 - exit -} - -# Register trapping of SIGTERM and SIGINT -trap reap SIGTERM SIGINT - -# Display the script PID, which will also be the process group ID for all -# child processes -echo "Process Group: $$" - -# Set server launch related environment variables -DEBUG=1 -LAUNCH_ARGS="$*" - - -# Launch the app -source ./script/server & -wait From f13cc03d24e59e56647dd0b38379628e13b7bfdb Mon Sep 17 00:00:00 2001 From: dandds Date: Tue, 16 Oct 2018 09:54:49 -0400 Subject: [PATCH 13/19] allow queue worker process to hot reload if entr is available --- Procfile | 2 +- README.md | 4 ++++ script/dev_queue | 11 +++++++++++ 3 files changed, 16 insertions(+), 1 deletion(-) create mode 100755 script/dev_queue diff --git a/Procfile b/Procfile index 0101271f..ba8ec7cf 100644 --- a/Procfile +++ b/Procfile @@ -1,3 +1,3 @@ assets: yarn watch web: PORT=8000 python app.py -queue: flask rq worker +queue: ./script/dev_queue diff --git a/README.md b/README.md index f9cf66fd..e5adfcfc 100644 --- a/README.md +++ b/README.md @@ -39,6 +39,10 @@ locally: running on the default port of 6379. You can ensure that Redis is running by executing `redis-cli` with no options and ensuring a connection is succesfully made. +* [`entr`](http://www.entrproject.org/) + This dependency is optional. If present, the queue worker process will hot + reload in development. + ### Cloning This project contains git submodules. Here is an example clone command that will automatically initialize and update those modules: diff --git a/script/dev_queue b/script/dev_queue new file mode 100755 index 00000000..db171e3e --- /dev/null +++ b/script/dev_queue @@ -0,0 +1,11 @@ +#!/bin/bash + +# script/dev_queue: Run the queue with entr if available + +set -e + +if [[ `command -v entr` ]]; then + find atst | entr -r flask rq worker +else + flask rq worker +fi From b930c85c3fae78803d131b545e9110645a5a60a3 Mon Sep 17 00:00:00 2001 From: richard-dds Date: Tue, 16 Oct 2018 13:04:30 -0400 Subject: [PATCH 14/19] Idea for using composition rather than inheritance --- atst/utils/mailer.py | 92 +++++++++++++++++++++----------------- tests/utils/test_mailer.py | 32 ++++++------- 2 files changed, 65 insertions(+), 59 deletions(-) diff --git a/atst/utils/mailer.py b/atst/utils/mailer.py index a2b7aeb2..0958f296 100644 --- a/atst/utils/mailer.py +++ b/atst/utils/mailer.py @@ -1,8 +1,19 @@ +from contextlib import contextmanager import smtplib from email.message import EmailMessage -class _HostConnection: +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 @@ -11,7 +22,10 @@ class _HostConnection: self.use_tls = use_tls self.host = None - def __enter__(self): + @contextmanager + def _host(self): + host = None + if self.use_tls: self.host = smtplib.SMTP(self.server, self.port) self.host.starttls() @@ -19,52 +33,20 @@ class _HostConnection: self.host = smtplib.SMTP_SSL(self.server, self.port) self.host.login(self.username, self.password) - return self.host + yield host - def __exit__(self, *args): - if self.host: - self.host.quit() + host.quit() - -class BaseMailer: - def __init__(self, server, port, sender, password, use_tls=False): - self.server = server - self.port = port - self.sender = sender - self.password = password - self.use_tls = use_tls - - def _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): - pass - - # do not collect messages by default @property def messages(self): return [] - -class Mailer(BaseMailer): - def connection(self): - return _HostConnection( - self.server, self.port, self.sender, self.password, use_tls=self.use_tls - ) - - def send(self, recipients, subject, body): - message = self._message(recipients, subject, body) - with self.connection() as conn: - conn.send_message(message) + def send(self, message): + with self._host() as host: + host.send_message(message) -class RedisMailer(BaseMailer): +class RedisConnection(MailConnection): def __init__(self, redis, **kwargs): super().__init__(**kwargs) self.redis = redis @@ -77,6 +59,34 @@ class RedisMailer(BaseMailer): 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 _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._message(recipients, subject, body) - self.redis.lpush("atat_inbox", str(message)) + self.connection.send(message) + + @property + def messages(self): + return self.connection.messages + + +class RedisMailer(object): + def __init__(self, *args, **kwargs): + pass diff --git a/tests/utils/test_mailer.py b/tests/utils/test_mailer.py index 0506cfdc..8b40ea48 100644 --- a/tests/utils/test_mailer.py +++ b/tests/utils/test_mailer.py @@ -1,44 +1,40 @@ import pytest -from atst.utils.mailer import Mailer, RedisMailer +from atst.utils.mailer import Mailer, Mailer, MailConnection, RedisConnection -class MockHost: +class MockConnection(MailConnection): def __init__(self): - self.messages = [] + self._messages = [] - def __enter__(self): - return self + def send(self, message): + self._messages.append(message) - def __exit__(self, *args): - pass - - def send_message(self, message): - self.messages.append(message) + @property + def messages(self): + return self._messages @pytest.fixture -def mail_host(): - return MockHost() +def mailer(): + return Mailer(MockConnection(), "test@atat.com") -def test_mailer_can_send_mail(monkeypatch, mail_host): - monkeypatch.setattr("atst.utils.mailer.Mailer.connection", lambda *args: mail_host) - mailer = Mailer("localhost", 456, "leia@rebellion.net", "droidsyourelookingfor") +def test_mailer_can_send_mail(mailer): message_data = { "recipients": ["ben@tattoine.org"], "subject": "help", "body": "you're my only hope", } mailer.send(**message_data) - assert len(mail_host.messages) == 1 - message = mail_host.messages[0] + assert len(mailer.messages) == 1 + message = mailer.messages[0] assert message["To"] == message_data["recipients"][0] assert message["Subject"] == message_data["subject"] assert message.get_content().strip() == message_data["body"] def test_redis_mailer_can_save_messages(app): - mailer = RedisMailer(app.redis, server=None, port=None, sender=None, password=None) + mailer = Mailer(RedisConnection(app.redis), "test@atat.com") message_data = { "recipients": ["ben@tattoine.org"], "subject": "help", From 408fe5dc54a093233601a2a55fa94ff4cf067954 Mon Sep 17 00:00:00 2001 From: richard-dds Date: Tue, 16 Oct 2018 13:21:11 -0400 Subject: [PATCH 15/19] Construct MailConnection --- atst/app.py | 25 ++++++++++--------------- 1 file changed, 10 insertions(+), 15 deletions(-) diff --git a/atst/app.py b/atst/app.py index 8bfcb134..a980fc7d 100644 --- a/atst/app.py +++ b/atst/app.py @@ -22,7 +22,7 @@ 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.mailer import Mailer, RedisMailer +from atst.utils import mailer from atst.queue import queue @@ -173,20 +173,15 @@ def make_upload_storage(app): app.uploader = uploader -def _map_email_config(config): - return { - "server": config.get("MAIL_SERVER"), - "port": config.get("MAIL_PORT"), - "sender": config.get("MAIL_SENDER"), - "password": config.get("MAIL_PASSWORD"), - "use_tls": config.get("MAIL_TLS", False), - } - - def make_mailer(app): - config = _map_email_config(app.config) if app.config["DEBUG"]: - mailer = RedisMailer(redis=app.redis, **config) + mailer_connection = mailer.RedisConnection(app.redis) else: - mailer = Mailer(**config) - app.mailer = mailer + mailer_connection = mailer.SMTPConnection( + server=app.config.get("MAIL_SERVER"), + port=app.config.get("MAIL_PORT"), + 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) From 79c3394e2cd4f0f560e7bb620c08a6c156bddd98 Mon Sep 17 00:00:00 2001 From: richard-dds Date: Tue, 16 Oct 2018 14:09:02 -0400 Subject: [PATCH 16/19] Use mail sender as username --- atst/app.py | 1 + 1 file changed, 1 insertion(+) diff --git a/atst/app.py b/atst/app.py index a980fc7d..9e772e56 100644 --- a/atst/app.py +++ b/atst/app.py @@ -180,6 +180,7 @@ def make_mailer(app): 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") ) From c893a06f376c7a7cfeadf40f9303c51c423e983b Mon Sep 17 00:00:00 2001 From: richard-dds Date: Tue, 16 Oct 2018 14:09:26 -0400 Subject: [PATCH 17/19] host is a local var --- atst/utils/mailer.py | 14 ++++---------- 1 file changed, 4 insertions(+), 10 deletions(-) diff --git a/atst/utils/mailer.py b/atst/utils/mailer.py index 0958f296..0e3995a1 100644 --- a/atst/utils/mailer.py +++ b/atst/utils/mailer.py @@ -20,18 +20,17 @@ class SMTPConnection(MailConnection): self.username = username self.password = password self.use_tls = use_tls - self.host = None @contextmanager def _host(self): host = None if self.use_tls: - self.host = smtplib.SMTP(self.server, self.port) - self.host.starttls() + host = smtplib.SMTP(self.server, self.port) + host.starttls() else: - self.host = smtplib.SMTP_SSL(self.server, self.port) - self.host.login(self.username, self.password) + host = smtplib.SMTP_SSL(self.server, self.port) + host.login(self.username, self.password) yield host @@ -85,8 +84,3 @@ class Mailer(object): @property def messages(self): return self.connection.messages - - -class RedisMailer(object): - def __init__(self, *args, **kwargs): - pass From c3f674008393dd5366a7ddf37f462b54d9eee300 Mon Sep 17 00:00:00 2001 From: richard-dds Date: Tue, 16 Oct 2018 14:10:15 -0400 Subject: [PATCH 18/19] Formatting --- atst/app.py | 2 +- atst/utils/mailer.py | 2 -- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/atst/app.py b/atst/app.py index 9e772e56..715e93ba 100644 --- a/atst/app.py +++ b/atst/app.py @@ -182,7 +182,7 @@ def make_mailer(app): 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") + use_tls=app.config.get("MAIL_TLS"), ) sender = app.config.get("MAIL_SENDER") app.mailer = mailer.Mailer(mailer_connection, sender) diff --git a/atst/utils/mailer.py b/atst/utils/mailer.py index 0e3995a1..52cd209c 100644 --- a/atst/utils/mailer.py +++ b/atst/utils/mailer.py @@ -4,7 +4,6 @@ from email.message import EmailMessage class MailConnection(object): - def send(self, message): raise NotImplementedError() @@ -63,7 +62,6 @@ class RedisConnection(MailConnection): class Mailer(object): - def __init__(self, connection, sender): self.connection = connection self.sender = sender From c81eab07f7fb0ed2e4ab0abea86c9b451bdb514d Mon Sep 17 00:00:00 2001 From: dandds Date: Wed, 17 Oct 2018 15:10:33 -0400 Subject: [PATCH 19/19] update some mailer method names, add space for clarity --- atst/utils/mailer.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/atst/utils/mailer.py b/atst/utils/mailer.py index 52cd209c..a67a851b 100644 --- a/atst/utils/mailer.py +++ b/atst/utils/mailer.py @@ -21,7 +21,7 @@ class SMTPConnection(MailConnection): self.use_tls = use_tls @contextmanager - def _host(self): + def _connected_host(self): host = None if self.use_tls: @@ -29,6 +29,7 @@ class SMTPConnection(MailConnection): host.starttls() else: host = smtplib.SMTP_SSL(self.server, self.port) + host.login(self.username, self.password) yield host @@ -40,7 +41,7 @@ class SMTPConnection(MailConnection): return [] def send(self, message): - with self._host() as host: + with self._connected_host() as host: host.send_message(message) @@ -66,7 +67,7 @@ class Mailer(object): self.connection = connection self.sender = sender - def _message(self, recipients, subject, body): + def _build_message(self, recipients, subject, body): msg = EmailMessage() msg.set_content(body) msg["From"] = self.sender @@ -76,7 +77,7 @@ class Mailer(object): return msg def send(self, recipients, subject, body): - message = self._message(recipients, subject, body) + message = self._build_message(recipients, subject, body) self.connection.send(message) @property