diff --git a/HISTORY.md b/HISTORY.md index bef1b76..50e50ae 100644 --- a/HISTORY.md +++ b/HISTORY.md @@ -1 +1,7 @@ -# HISTORY \ No newline at end of file +# HISTORY + +0.3.0 Added support for APIStar version >= 0.4.0 + +0.2.1 Pinned APIStar requirement to 0.3.9 + +APIStar 0.4.0 introduced breaking changes to the framework. Since Components have completely changed we are pinning the requirement for this release. diff --git a/README.md b/README.md index 602d130..a671f88 100644 --- a/README.md +++ b/README.md @@ -17,22 +17,19 @@ Provides a simple interface to set up SMTP with your [APIStar](https://github.co ### Example Setup -To send mail messages from your view functions you must include the 'MAIL' dictionary in your settings, the mail_component in your component list, and the Mail component as a dependency in your view. Here we have a minimally viable app capable of sending an email message and returning a 204 response code: +To send mail messages from your view functions you must include a dictionary of mail options to the `MailComponent. Here we have a minimally viable app capable of sending an email message and returning a 204 response code: ```python -from apistar import Route -from apistar.frameworks.wsgi import WSGIApp as App -from apistar_mail import mail_component, Mail, Message - -settings = { - 'MAIL': { - 'MAIL_SERVER': 'smtp.example.com', - 'MAIL_USERNAME': 'me@example.com', - 'MAIL_PASSWORD': 'dontcommitthistoversioncontrol', - 'MAIL_PORT': 587, - 'MAIL_USE_TLS': True, - 'MAIL_DEFAULT_SENDER': 'me@example.com' - } +from apistar import App, Route +from apistar_mail import MailComponent, Mail, Message + +mail_options = { + 'MAIL_SERVER': 'smtp.example.com', + 'MAIL_USERNAME': 'me@example.com', + 'MAIL_PASSWORD': 'dontcommitthistoversioncontrol', + 'MAIL_PORT': 587, + 'MAIL_USE_TLS': True, + 'MAIL_DEFAULT_SENDER': 'me@example.com' } @@ -49,17 +46,16 @@ routes = [ ] components = [ - mail_component + MailComponent(**mail_options) ] app = App( - settings=settings, routes=routes, components=components ) if __name__ == '__main__': - app.main() + app.serve('127.0.0.1', 5000, debug=True) ``` diff --git a/apistar_mail/__init__.py b/apistar_mail/__init__.py index bc17249..1f3a665 100644 --- a/apistar_mail/__init__.py +++ b/apistar_mail/__init__.py @@ -4,4 +4,4 @@ __author__ = """Drew Bednar""" __email__ = 'drew@androiddrew.com' __version__ = '0.2.1' -from .mail import mail_component, Message, Mail # NOQA: F401 +from .mail import MailComponent, Message, Mail # NOQA: F401 diff --git a/apistar_mail/mail.py b/apistar_mail/mail.py index 4cf90db..6d8e60f 100644 --- a/apistar_mail/mail.py +++ b/apistar_mail/mail.py @@ -11,7 +11,7 @@ from email.mime.text import MIMEText from email.header import Header from email.utils import formatdate, formataddr, make_msgid, parseaddr -from apistar import Settings, Component +from apistar import Component from .exc import MailUnicodeDecodeError, BadHeaderError @@ -415,25 +415,25 @@ class Connection: class Mail: """Manages email messaging""" - def __init__(self, settings: Settings): + def __init__(self, **mail_options): """ Configure a new Mail manager Args: - settings: The application settings dictionary + mail_options: A components setting dictionary """ - mail_config = settings.get('MAIL') - self.mail_server = mail_config.get('MAIL_SERVER', 'localhost') - self.mail_user = mail_config.get('MAIL_USERNAME') - self.mail_password = mail_config.get('MAIL_PASSWORD') - self.mail_port = mail_config.get('MAIL_PORT', 25) - self.mail_use_tls = mail_config.get('MAIL_USE_TLS', False) - self.mail_use_ssl = mail_config.get('MAIL_USE_SSL', False) - self.mail_default_sender = mail_config.get('MAIL_DEFAULT_SENDER') - self.mail_debug = mail_config.get('MAIL_DEBUG', False) - self.mail_max_emails = mail_config.get('MAIL_MAX_EMAILS') - self.mail_suppress_send = mail_config.get('MAIL_SUPPRESS_SEND', False) - self.mail_ascii_attachments = mail_config.get('MAIL_ASCII_ATTACHMENTS', False) + + self.mail_server = mail_options.get('MAIL_SERVER', 'localhost') + self.mail_user = mail_options.get('MAIL_USERNAME') + self.mail_password = mail_options.get('MAIL_PASSWORD') + self.mail_port = mail_options.get('MAIL_PORT', 25) + self.mail_use_tls = mail_options.get('MAIL_USE_TLS', False) + self.mail_use_ssl = mail_options.get('MAIL_USE_SSL', False) + self.mail_default_sender = mail_options.get('MAIL_DEFAULT_SENDER') + self.mail_debug = mail_options.get('MAIL_DEBUG', False) + self.mail_max_emails = mail_options.get('MAIL_MAX_EMAILS') + self.mail_suppress_send = mail_options.get('MAIL_SUPPRESS_SEND', False) + self.mail_ascii_attachments = mail_options.get('MAIL_ASCII_ATTACHMENTS', False) def send(self, message): """ @@ -461,4 +461,11 @@ class Mail: return Connection(self) -mail_component = Component(Mail, preload=True) +class MailComponent(Component): + """A component that injects an instance of `Mail` for sending emails""" + + def __init__(self, **mail_options) -> None: + self.mail = Mail(**mail_options) + + def resolve(self) -> Mail: + return self.mail diff --git a/setup.py b/setup.py index bc635cc..3ae3ac4 100644 --- a/setup.py +++ b/setup.py @@ -7,7 +7,7 @@ with open('HISTORY.md') as history_file: history = history_file.read() requirements = [ - 'apistar~=0.3.9', + 'apistar>=0.4', ] test_requirements = [ @@ -21,6 +21,7 @@ setup( version='0.2.1', description="A simple email Component for APIStar", long_description=readme + '\n\n' + history, + long_description_content_type='text/markdown', author="Drew Bednar", author_email='drew@androiddrew.com', url='https://github.com/androiddrew/apistar-mail', diff --git a/tests/test_apistar_mail.py b/tests/test_apistar_mail.py index b6ac09c..a3f14ca 100644 --- a/tests/test_apistar_mail.py +++ b/tests/test_apistar_mail.py @@ -9,16 +9,14 @@ from apistar_mail.exc import MailUnicodeDecodeError, BadHeaderError import pytest -settings = { - 'MAIL': { - 'MAIL_SERVER': 'smtp.example.com', - 'MAIL_USERNAME': 'fake@example.com', - 'MAIL_PASSWORD': 'secret', - 'MAIL_PORT': 587, - 'MAIL_USE_TLS': True, - 'MAIL_SUPPRESS_SEND': True, - 'MAIL_DEFAULT_SENDER': 'fake@example.com' - } +test_mail_options = { + 'MAIL_SERVER': 'smtp.example.com', + 'MAIL_USERNAME': 'fake@example.com', + 'MAIL_PASSWORD': 'secret', + 'MAIL_PORT': 587, + 'MAIL_USE_TLS': True, + 'MAIL_SUPPRESS_SEND': True, + 'MAIL_DEFAULT_SENDER': 'fake@example.com' } @@ -400,7 +398,7 @@ def test_message_charset(): def test_empty_subject_header(): - mail = Mail(settings) + mail = Mail(**test_mail_options) msg = Message(sender="from@example.com", recipients=["foo@bar.com"]) msg.body = "normal ascii text" @@ -411,13 +409,13 @@ def test_empty_subject_header(): def test_message_default_sender(): msg = Message(recipients=["foo@bar.com"]) msg.body = "normal ascii text" - mail = Mail(settings) + mail = Mail(**test_mail_options) mail.send(msg) assert msg.sender == 'fake@example.com' def test_mail_send_message(): - mail = Mail(settings) + mail = Mail(**test_mail_options) mail.send = MagicMock() mail.send_message(sender="from@example.com", recipients=["foo@bar.com"], @@ -426,7 +424,7 @@ def test_mail_send_message(): def test_message_ascii_attachments_config(): - mail = Mail(settings) + mail = Mail(**test_mail_options) mail.mail_ascii_attachments = True msg = Message(sender="from@example.com", subject="subject", @@ -447,7 +445,7 @@ def test_message_as_bytes(): @patch('apistar_mail.mail.smtplib.SMTP') def test_connection_configure_host_non_ssl(mock_smtp): - mail = Mail(settings) + mail = Mail(**test_mail_options) mail.mail_suppress_send = False mail.mail_use_tls = True mock_smtp.return_value = MagicMock() @@ -459,7 +457,7 @@ def test_connection_configure_host_non_ssl(mock_smtp): @patch('apistar_mail.mail.smtplib.SMTP_SSL') def test_connection_configure_host_ssl(mock_smtp_ssl): - mail = Mail(settings) + mail = Mail(**test_mail_options) mail.mail_suppress_send = False mail.mail_use_tls = False mail.mail_use_ssl = True @@ -469,7 +467,7 @@ def test_connection_configure_host_ssl(mock_smtp_ssl): def test_connection_send_message(): - mail = Mail(settings) + mail = Mail(**test_mail_options) with mail.connect() as conn: conn.send = MagicMock() conn.send_message(sender="from@example.com", @@ -480,7 +478,7 @@ def test_connection_send_message(): @patch('apistar_mail.mail.smtplib.SMTP') def test_connection_send_single(mock_smtp): - mail = Mail(settings) + mail = Mail(**test_mail_options) mail.mail_suppress_send = False msg = Message(sender="from@example.com", recipients=["foo@bar.com"], @@ -494,7 +492,7 @@ def test_connection_send_single(mock_smtp): def test_connection_send_ascii_recipient_single(): - mail = Mail(settings) + mail = Mail(**test_mail_options) msg = Message(sender="from@example.com", recipients=["foo@bar.com"], body="normal ascii text") @@ -506,7 +504,7 @@ def test_connection_send_ascii_recipient_single(): def test_connection_send_non_ascii_recipient_single(): - mail = Mail(settings) + mail = Mail(**test_mail_options) with mail.connect() as conn: with patch.object(conn, 'host') as host: msg = Message(subject="testing", @@ -526,7 +524,7 @@ def test_connection_send_non_ascii_recipient_single(): @patch('apistar_mail.mail.smtplib.SMTP') def test_connection_send_many(mock_smtp): - mail = Mail(settings) + mail = Mail(**test_mail_options) mail.mail_suppress_send = False mail.mail_max_emails = 50 mock_smtp.return_value = MagicMock(spec=SMTP) @@ -545,7 +543,7 @@ def test_connection_send_many(mock_smtp): def test_bad_header_subject(): - mail = Mail(settings) + mail = Mail(**test_mail_options) msg = Message(subject="testing\r\n", body="testing", recipients=["to@example.com"]) @@ -555,7 +553,7 @@ def test_bad_header_subject(): def test_bad_header_subject_whitespace(): - mail = Mail(settings) + mail = Mail(**test_mail_options) msg = Message(subject="\t\r\n", body="testing", recipients=["to@example.com"]) @@ -570,7 +568,7 @@ def test_bad_header_subject_with_no_trailing_whitespace(): This is a bit of a strange test but we aren't changing the bad_header check from flask_mail """ - mail = Mail(settings) + mail = Mail(**test_mail_options) msg = Message(subject="testing\r\ntesting", body="testing", recipients=["to@example.com"]) @@ -580,7 +578,7 @@ def test_bad_header_subject_with_no_trailing_whitespace(): def test_bad_header_subject_trailing_whitespace(): - mail = Mail(settings) + mail = Mail(**test_mail_options) msg = Message(subject="testing\r\n\t", body="testing", recipients=["to@example.com"]) @@ -590,7 +588,7 @@ def test_bad_header_subject_trailing_whitespace(): def test_bad_header_with_a_newline(): - mail = Mail(settings) + mail = Mail(**test_mail_options) msg = Message(subject="\ntesting\r\ntesting", body="testing", recipients=["to@example.com"]) @@ -600,7 +598,7 @@ def test_bad_header_with_a_newline(): def test_bad_header_with_newline_in_sender(): - mail = Mail(settings) + mail = Mail(**test_mail_options) msg = Message(subject="testing", body="testing", sender='me\n@example.com',