Message.send supports recipients list

This commit is contained in:
Sergey Lavrinenko 2015-07-24 22:09:06 +03:00
parent db3b772510
commit 53ebddd694
4 changed files with 85 additions and 15 deletions

View File

@ -0,0 +1,36 @@
# encoding: utf-8
from __future__ import unicode_literals
__all__ = ['InMemoryBackend', ]
import logging
class InMemoryBackend(object):
"""
InMemoryBackend store message in memory for testing purposes.
"""
def __init__(self, **kwargs):
self.kwargs = kwargs
self.messages = {}
def sendmail(self, from_addr, to_addrs, msg, **kwargs):
logging.debug('InMemoryBackend.sendmail(%s, %s, %r, %s)', from_addr, to_addrs, msg, kwargs)
if not to_addrs:
return None
if not isinstance(to_addrs, (list, tuple)):
to_addrs = [to_addrs, ]
for addr in to_addrs:
data = dict(from_addr=from_addr,
message=msg.as_string(),
source_message=msg,
**kwargs)
self.messages.setdefault(addr.lower(), []).append(data)
return True

View File

@ -7,7 +7,8 @@ from .compat import (string_types, is_callable, formataddr as compat_formataddr,
from .utils import (SafeMIMEText, SafeMIMEMultipart, sanitize_address,
parse_name_and_email, load_email_charsets,
encode_header as encode_header_,
renderable, format_date_header, parse_name_and_email_list)
renderable, format_date_header, parse_name_and_email_list,
cached_property)
from .exc import BadHeaderError
from .backend import ObjectFactory, SMTPBackend
from .store import MemoryFileStore, BaseFile
@ -73,6 +74,13 @@ class BaseMessage(object):
mail_to = property(get_mail_to, set_mail_to)
def get_recipients_emails(self):
"""
Returns message recipient's emails for actual sending.
:return:
"""
return [a[1] for a in self._mail_to]
def set_headers(self, headers):
self._headers = headers or {}
@ -312,12 +320,9 @@ class MessageSendMixin(object):
smtp_pool_factory = ObjectFactory
smtp_cls = SMTPBackend
@property
@cached_property
def smtp_pool(self):
pool = getattr(self, '_smtp_pool', None)
if pool is None:
pool = self._smtp_pool = self.smtp_pool_factory(cls=self.smtp_cls)
return pool
return self.smtp_pool_factory(cls=self.smtp_cls)
def send(self,
to=None,
@ -342,17 +347,17 @@ class MessageSendMixin(object):
raise ValueError(
"smtp must be a dict or an object with method 'sendmail'. got %s" % type(smtp))
mail_to = to
if mail_to:
mail_to = parse_name_and_email(mail_to)
to_addr = mail_to[1]
to_addrs = None
if to:
if set_mail_to:
self.set_mail_to(mail_to)
self.set_mail_to(to)
else:
to_addrs = [a[1] for a in parse_name_and_email_list(to)]
else:
to_addr = self._mail_to[0][1]
to_addrs = to_addrs or self.get_recipients_emails()
if not to_addr:
if not to_addrs:
raise ValueError('No to-addr')
if mail_from:
@ -368,7 +373,7 @@ class MessageSendMixin(object):
if not from_addr:
raise ValueError('No "from" addr')
params = dict(from_addr=from_addr, to_addrs=[to_addr, ], msg=self,
params = dict(from_addr=from_addr, to_addrs=to_addrs, msg=self,
mail_options=smtp_mail_options, rcpt_options=smtp_rcpt_options)
return smtp.sendmail(**params)

View File

@ -10,6 +10,7 @@ from emails import Message
import emails.exc
from emails.compat import to_unicode, StringIO, is_py2, is_py34_plus
from emails.utils import decode_header, MessageID
from emails.backend.inmemory import InMemoryBackend
from .helpers import common_email_data
@ -172,6 +173,9 @@ def test_message_id():
def test_several_recipients_in_to_header():
# Test multiple recipients in "To" header
params = dict(html='...', mail_from='a@b.c')
m = Message(mail_to=['d@e.f', 'g@h.i'], **params)
@ -180,3 +184,10 @@ def test_several_recipients_in_to_header():
m = Message(mail_to=[('', 'd@e.f'), ('', 'g@h.i')], **params)
assert m.as_message()['To'] == '=?utf-8?b?4pmh?= <d@e.f>, =?utf-8?b?7JuD?= <g@h.i>'
# Test sending to several emails
backend = InMemoryBackend()
m = Message(mail_to=[('', 'd@e.f'), ('', 'g@h.i')], **params)
m.send(smtp=backend)
assert len(backend.messages['d@e.f']) == 1
assert len(backend.messages['g@h.i']) == 1

View File

@ -41,6 +41,24 @@ def load_email_charsets():
charset)
class cached_property(object):
"""
A property that is only computed once per instance and then replaces itself
with an ordinary attribute. Deleting the attribute resets the property.
Source: https://github.com/bottlepy/bottle/commit/fa7733e075da0d790d809aa3d2f53071897e6f76
""" # noqa
def __init__(self, func):
self.__doc__ = getattr(func, '__doc__')
self.func = func
def __get__(self, obj, cls):
if obj is None:
return self
value = obj.__dict__[self.func.__name__] = self.func(obj)
return value
# Django's CachedDnsName:
# Cached the hostname, but do it lazily: socket.getfqdn() can take a couple of
# seconds, which slows down the restart of the server.