diff --git a/flask_mail.py b/flask_mail.py index 6abc60f..8c9cda7 100644 --- a/flask_mail.py +++ b/flask_mail.py @@ -12,11 +12,11 @@ import re import smtplib -import sys import time import unicodedata from contextlib import contextmanager from email import charset +from email import policy from email.encoders import encode_base64 from email.header import Header from email.mime.base import MIMEBase @@ -30,21 +30,6 @@ import blinker from flask import current_app -PY3 = sys.version_info[0] == 3 - -PY34 = PY3 and sys.version_info[1] >= 4 - -if PY3: - string_types = (str,) - text_type = str - from email import policy - - message_policy = policy.SMTP -else: - string_types = (basestring,) - text_type = unicode - message_policy = None - charset.add_charset("utf-8", charset.SHORTEST, None, "utf-8") @@ -55,37 +40,30 @@ def __init__(self, obj, *args): def __str__(self): original = UnicodeDecodeError.__str__(self) - return "%s. You passed in %r (%s)" % (original, self.obj, type(self.obj)) + return f"{original}. You passed in {self.obj!r} ({type(self.obj)})" def force_text(s, encoding="utf-8", errors="strict"): """ Similar to smart_text, except that lazy instances are resolved to strings, rather than kept as lazy objects. - - If strings_only is True, don't convert (some) non-string-like objects. """ - if isinstance(s, text_type): + if isinstance(s, str): return s try: - if not isinstance(s, string_types): - if PY3: - if isinstance(s, bytes): - s = text_type(s, encoding, errors) - else: - s = text_type(s) - elif hasattr(s, "__unicode__"): - s = s.__unicode__() + if not isinstance(s, str): + if isinstance(s, bytes): + s = str(s, encoding, errors) else: - s = text_type(bytes(s), encoding, errors) + s = str(s) else: s = s.decode(encoding, errors) except UnicodeDecodeError as e: if not isinstance(s, Exception): - raise FlaskMailUnicodeDecodeError(s, *e.args) + raise FlaskMailUnicodeDecodeError(s, *e.args) from e else: - s = " ".join([force_text(arg, encoding, strings_only, errors) for arg in s]) + s = " ".join([force_text(arg, encoding, errors) for arg in s]) return s @@ -101,7 +79,7 @@ def sanitize_subject(subject, encoding="utf-8"): def sanitize_address(addr, encoding="utf-8"): - if isinstance(addr, string_types): + if isinstance(addr, str): addr = parseaddr(force_text(addr)) nm, addr = addr @@ -191,7 +169,7 @@ def send(self, message, envelope_from=None): self.host.sendmail( sanitize_address(envelope_from or message.sender), list(sanitize_addresses(message.send_to)), - message.as_bytes() if PY3 else message.as_string(), + message.as_bytes(), message.mail_options, message.rcpt_options, ) @@ -254,7 +232,8 @@ class Message: :param recipients: list of email addresses :param body: plain text message :param html: HTML message - :param alts: A dict or an iterable to go through dict() that contains multipart alternatives + :param alts: A dict or an iterable to go through dict() that contains multipart + alternatives :param sender: email sender address, or **MAIL_DEFAULT_SENDER** by default :param cc: CC list :param bcc: BCC list @@ -288,7 +267,7 @@ def __init__( sender = sender or current_app.extensions["mail"].default_sender if isinstance(sender, tuple): - sender = "%s <%s>" % sender + sender = "{} <{}>".format(*sender) self.recipients = recipients or [] self.subject = subject @@ -388,8 +367,6 @@ def _message(self): try: filename and filename.encode("ascii") except UnicodeEncodeError: - if not PY3: - filename = filename.encode("utf8") filename = ("UTF8", "", filename) f.add_header( @@ -400,8 +377,7 @@ def _message(self): f.add_header(key, value) msg.attach(f) - if message_policy: - msg.policy = message_policy + msg.policy = policy.SMTP return msg @@ -409,10 +385,7 @@ def as_string(self): return self._message().as_string() def as_bytes(self): - if PY34: - return self._message().as_bytes() - else: # fallback for old Python (3) versions - return self._message().as_string().encode(self.charset or "utf-8") + return self._message().as_bytes() def __str__(self): return self.as_string() @@ -539,10 +512,10 @@ def connect(self): app = getattr(self, "app", None) or current_app try: return Connection(app.extensions["mail"]) - except KeyError: + except KeyError as err: raise RuntimeError( "The current application was not configured with Flask-Mail" - ) + ) from err class _Mail(_MailMixin): diff --git a/scripts/release.py b/scripts/release.py deleted file mode 100755 index f60e57e..0000000 --- a/scripts/release.py +++ /dev/null @@ -1,193 +0,0 @@ -#!/usr/bin/env python -""" -make-release -~~~~~~~~~~~~ - -Helper script that performs a release. Does pretty much everything -automatically for us. - -:copyright: (c) 2011 by Armin Ronacher. -:license: BSD, see LICENSE for more details. -""" - -import os -import re -import sys -from datetime import date -from datetime import datetime -from subprocess import PIPE -from subprocess import Popen - -_date_clean_re = re.compile(r"(\d+)(st|nd|rd|th)") - - -def installed_libraries(): - return Popen(["pip", "freeze"], stdout=PIPE).communicate()[0] - - -def has_library_installed(library): - return library + "==" in installed_libraries() - - -def parse_changelog(): - with open("CHANGES") as f: - lineiter = iter(f) - for line in lineiter: - match = re.search(r"^Version\s+(.*)", line.strip()) - - if match is None: - continue - - version = match.group(1).strip() - - if lineiter.next().count("-") != len(line.strip()): - fail("Invalid hyphen count below version line: %s", line.strip()) - - while 1: - released = lineiter.next().strip() - if released: - break - - match = re.search(r"Released (\w+\s+\d+\w+\s+\d+)", released) - - if match is None: - fail("Could not find release date in version %s" % version) - - datestr = parse_date(match.group(1).strip()) - - return version, datestr - - -def bump_version(version): - try: - parts = map(int, version.split(".")) - except ValueError: - fail("Current version is not numeric") - parts[-1] += 1 - return ".".join(map(str, parts)) - - -def parse_date(string): - string = _date_clean_re.sub(r"\1", string) - return datetime.strptime(string, "%B %d %Y") - - -def set_filename_version(filename, version_number, pattern): - changed = [] - - def inject_version(match): - before, old, after = match.groups() - changed.append(True) - return before + version_number + after - - with open(filename) as f: - contents = re.sub( - r"^(\s*%s\s*=\s*')(.+?)(')(?sm)" % pattern, inject_version, f.read() - ) - - if not changed: - fail("Could not find %s in %s", pattern, filename) - - with open(filename, "w") as f: - f.write(contents) - - -def set_init_version(version): - info("Setting __init__.py version to %s", version) - set_filename_version("flask_mail.py", version, "__version__") - - -def set_setup_version(version): - info("Setting setup.py version to %s", version) - set_filename_version("setup.py", version, "version") - - -def set_docs_version(version): - info("Setting docs/conf.py version to %s", version) - set_filename_version("docs/conf.py", version, "version") - - -def build_and_upload(): - Popen( - [sys.executable, "setup.py", "sdist", "build_sphinx", "upload", "upload_sphinx"] - ).wait() - - -def fail(message, *args): - print >> sys.stderr, "Error:", message % args - sys.exit(1) - - -def info(message, *args): - print >> sys.stderr, message % args - - -def get_git_tags(): - return set(Popen(["git", "tag"], stdout=PIPE).communicate()[0].splitlines()) - - -def git_is_clean(): - return Popen(["git", "diff", "--quiet"]).wait() == 0 - - -def make_git_commit(message, *args): - message = message % args - Popen(["git", "commit", "-am", message]).wait() - - -def make_git_tag(tag): - info('Tagging "%s"', tag) - Popen(["git", "tag", "-a", tag, "-m", "%s release" % tag]).wait() - Popen(["git", "push", "--tags"]).wait() - - -def update_version(version): - for f in [set_init_version, set_setup_version, set_docs_version]: - f(version) - - -def get_branches(): - return set(Popen(["git", "branch"], stdout=PIPE).communicate()[0].splitlines()) - - -def branch_is(branch): - return "* " + branch in get_branches() - - -def main(): - os.chdir(os.path.join(os.path.dirname(__file__), "..")) - - rv = parse_changelog() - - if rv is None: - fail("Could not parse changelog") - - version, release_date = rv - - tags = get_git_tags() - - for lib in ["Sphinx", "Sphinx-PyPI-upload"]: - if not has_library_installed(lib): - fail("Build requires that %s be installed", lib) - - if version in tags: - fail('Version "%s" is already tagged', version) - if release_date.date() != date.today(): - fail("Release date is not today") - - if not branch_is("master"): - fail("You are not on the master branch") - - if not git_is_clean(): - fail("You have uncommitted changes in git") - - info("Releasing %s (release date %s)", version, release_date.strftime("%d/%m/%Y")) - - update_version(version) - make_git_commit("Bump version number to %s", version) - make_git_tag(version) - build_and_upload() - - -if __name__ == "__main__": - main() diff --git a/tests.py b/tests.py index 5b2d9c5..96452a9 100644 --- a/tests.py +++ b/tests.py @@ -11,7 +11,6 @@ from flask_mail import BadHeaderError from flask_mail import Mail from flask_mail import Message -from flask_mail import PY3 from flask_mail import sanitize_address from speaklater import make_lazy_string @@ -136,7 +135,9 @@ def test_reply_to(self): ) response = msg.as_string() - h = Header("Reply-To: %s" % sanitize_address("somebody ")) + h = Header( + "Reply-To: {}".format(sanitize_address("somebody ")) + ) self.assertIn(h.encode(), str(response)) def test_send_without_sender(self): @@ -326,8 +327,8 @@ def test_plain_message_with_unicode_attachment(self): self.assertIn( re.sub(r"\s+", " ", parsed.get_payload()[1].get("Content-Disposition")), [ - "attachment; filename*=\"UTF8''%C3%BCnic%C3%B6de%20%E2%86%90%E2%86%92%20%E2%9C%93.txt\"", - "attachment; filename*=UTF8''%C3%BCnic%C3%B6de%20%E2%86%90%E2%86%92%20%E2%9C%93.txt", + "attachment; filename*=\"UTF8''%C3%BCnic%C3%B6de%20%E2%86%90%E2%86%92%20%E2%9C%93.txt\"", # noqa: E501 + "attachment; filename*=UTF8''%C3%BCnic%C3%B6de%20%E2%86%90%E2%86%92%20%E2%9C%93.txt", # noqa: E501 ], ) @@ -343,7 +344,6 @@ def test_plain_message_with_ascii_converted_attachment(self): filename="ünicödeß ←.→ ✓.txt", ) - parsed = email.message_from_string(msg.as_string()) self.assertIn( 'Content-Disposition: attachment; filename="unicode . .txt"', msg.as_string(), @@ -460,10 +460,10 @@ def test_unicode_headers(self): response = msg.as_string() a1 = sanitize_address("Ä ") a2 = sanitize_address("Ü ") - h1_a = Header("To: %s, %s" % (a1, a2)) - h1_b = Header("To: %s, %s" % (a2, a1)) - h2 = Header("From: %s" % sanitize_address("ÄÜÖ → ✓ ")) - h3 = Header("Cc: %s" % sanitize_address("Ö ")) + h1_a = Header(f"To: {a1}, {a2}") + h1_b = Header(f"To: {a2}, {a1}") + h2 = Header("From: {}".format(sanitize_address("ÄÜÖ → ✓ "))) + h3 = Header("Cc: {}".format(sanitize_address("Ö "))) # Ugly, but there's no guaranteed order of the recipients in the header try: @@ -616,7 +616,6 @@ def test_send(self): self.mail.send(msg) self.assertIsNotNone(msg.date) self.assertEqual(len(outbox), 1) - sent_msg = outbox[0] self.assertEqual(msg.sender, self.app.extensions["mail"].default_sender) def test_send_message(self): @@ -664,7 +663,7 @@ def test_send_single(self): def test_send_many(self): with self.mail.record_messages() as outbox: with self.mail.connect() as conn: - for i in range(100): + for _i in range(100): msg = Message( subject="testing", recipients=["to@example.com"], body="testing" ) @@ -707,7 +706,7 @@ def test_sendmail_with_ascii_recipient(self): host.sendmail.assert_called_once_with( "from@example.com", ["to@example.com"], - msg.as_bytes() if PY3 else msg.as_string(), + msg.as_bytes(), msg.mail_options, msg.rcpt_options, ) @@ -726,7 +725,7 @@ def test_sendmail_with_non_ascii_recipient(self): host.sendmail.assert_called_once_with( "from@example.com", ["=?utf-8?b?w4TDnMOWIOKGkiDinJM=?= "], - msg.as_bytes() if PY3 else msg.as_string(), + msg.as_bytes(), msg.mail_options, msg.rcpt_options, ) @@ -745,7 +744,7 @@ def test_sendmail_with_ascii_body(self): host.sendmail.assert_called_once_with( "from@example.com", ["to@example.com"], - msg.as_bytes() if PY3 else msg.as_string(), + msg.as_bytes(), msg.mail_options, msg.rcpt_options, ) @@ -765,7 +764,7 @@ def test_sendmail_with_non_ascii_body(self): host.sendmail.assert_called_once_with( "from@example.com", ["to@example.com"], - msg.as_bytes() if PY3 else msg.as_string(), + msg.as_bytes(), msg.mail_options, msg.rcpt_options, )