From 12db9e96331d7ce4a7aacc0ea9513efe98d6ebe0 Mon Sep 17 00:00:00 2001 From: Sergey Lavrinenko Date: Sat, 28 Mar 2015 23:23:08 +0300 Subject: [PATCH] Support more than one address in Message.mail_to --- emails/message.py | 7 ++---- emails/testsuite/message/test_message.py | 27 +++++++++++++++++--- emails/testsuite/test_utils.py | 10 +++++++- emails/utils.py | 32 ++++++++++++++++++++++++ 4 files changed, 67 insertions(+), 9 deletions(-) diff --git a/emails/message.py b/emails/message.py index 532cf44..cbf0709 100644 --- a/emails/message.py +++ b/emails/message.py @@ -7,7 +7,7 @@ from .compat import (string_types, is_callable, to_bytes, formataddr as compat_f from .utils import (SafeMIMEText, SafeMIMEMultipart, sanitize_address, parse_name_and_email, load_email_charsets, encode_header as encode_header_, - renderable, format_date_header) + renderable, format_date_header, parse_name_and_email_list) from .exc import BadHeaderError from .backend import ObjectFactory, SMTPBackend from .store import MemoryFileStore, BaseFile @@ -66,10 +66,7 @@ class BaseMessage(object): mail_from = property(get_mail_from, set_mail_from) def set_mail_to(self, mail_to): - # Now we parse only one to-addr - # TODO: parse list of to-addrs - mail_to = mail_to and parse_name_and_email(mail_to) - self._mail_to = mail_to and [mail_to, ] or [] + self._mail_to = parse_name_and_email_list(mail_to) def get_mail_to(self): return self._mail_to diff --git a/emails/testsuite/message/test_message.py b/emails/testsuite/message/test_message.py index 61485aa..a2ec090 100644 --- a/emails/testsuite/message/test_message.py +++ b/emails/testsuite/message/test_message.py @@ -107,15 +107,36 @@ def test_sanitize_header(): emails.Message(html='...', **{header: value}).as_message() -def test_address_header_not_double_encoded(): - msg = {} - m = Message() +def test_headers_not_double_encoded(): + TEXT = '웃' + m = Message() m.mail_from = (TEXT, 'a@b.c') + m.mail_to = (TEXT, 'a@b.c') + m.subject = TEXT m.html = '...' msg = m.as_message() assert decode_header(parseaddr(msg['From'])[0]) == TEXT + assert decode_header(parseaddr(msg['To'])[0]) == TEXT + assert decode_header(msg['Subject']) == TEXT + + +def test_message_addresses(): + + m = Message() + + m.mail_from = "웃 " + assert m.mail_from == ("웃", "b@c.d") + + m.mail_from = ["웃", "b@c.d"] + assert m.mail_from == ("웃", "b@c.d") + + m.mail_to = ("웃", "b@c.d") + assert m.mail_to == [("웃", "b@c.d"), ] + + m.mail_to = [("웃", "b@c.d"), "e@f.g"] + assert m.mail_to == [("웃", "b@c.d"), (None, "e@f.g")] def test_message_policy(): diff --git a/emails/testsuite/test_utils.py b/emails/testsuite/test_utils.py index b584f85..b730952 100644 --- a/emails/testsuite/test_utils.py +++ b/emails/testsuite/test_utils.py @@ -4,9 +4,10 @@ import pytest import datetime import time from emails.utils import (parse_name_and_email, encode_header, decode_header, sanitize_address, fetch_url, - MessageID, format_date_header) + MessageID, format_date_header, parse_name_and_email_list) from emails.exc import HTTPLoaderError + def test_parse_name_and_email(): assert parse_name_and_email('john@smith.me') == (None, 'john@smith.me') assert parse_name_and_email('"John Smith" ') == \ @@ -19,6 +20,13 @@ def test_parse_name_and_email(): parse_name_and_email([42,]) +def test_parse_name_and_list(): + assert parse_name_and_email_list(['a@b.c', 'd@e.f']) == [(None, 'a@b.c'), (None, 'd@e.f')] + assert parse_name_and_email_list(('a@b.c', 'd@e.f')) == [('a@b.c', 'd@e.f'), ] + assert parse_name_and_email_list(['a@b.c']) == [(None, 'a@b.c')] + assert parse_name_and_email_list("♤ ") == [("♤", 'a@b.c'), ] + + def test_header_encode(): v = 'Мама мыла раму. ' * 30 assert decode_header(encode_header(v)).strip() == v.strip() diff --git a/emails/utils.py b/emails/utils.py index f279a3c..187e6a8 100644 --- a/emails/utils.py +++ b/emails/utils.py @@ -87,6 +87,38 @@ class MessageID: return "".join(['<', r, '@', self.domain, '>']) +def parse_name_and_email_list(elements, encoding='utf-8'): + """ + Parse a list of address-like elements, i.e.: + * "name " + * "email" + * (name, email) + + :param elements: one element or list of elements + :param encoding: element encoding, if bytes + :return: list of pairs (name, email) + """ + if not elements: + return [] + + if isinstance(elements, string_types): + return [parse_name_and_email(elements, encoding), ] + + if not isinstance(elements, (list, tuple)): + raise TypeError("Can not parse_name_and_email_list from %s" % elements.__repr__()) + + if len(elements) == 2: + # Oops, it may be pair (name, email) or pair of emails [email1, email2] + # Let's do some guesses + if isinstance(elements, tuple): + n, e = elements + if isinstance(e, string_types) and (not n or isinstance(e, string_types)): + # It is probably a pair (name, email) + return [parse_name_and_email(elements, encoding), ] + + return [parse_name_and_email(x, encoding) for x in elements] + + def parse_name_and_email(obj, encoding='utf-8'): # In: 'john@smith.me' or '"John Smith" ' or ('John Smith', 'john@smith.me') # Out: (u'John Smith', u'john@smith.me')