-
Notifications
You must be signed in to change notification settings - Fork 62
/
__init__.py
182 lines (128 loc) · 5.47 KB
/
__init__.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
# encoding: utf-8
"""marrow.mailer mail delivery framework and MIME message abstraction."""
import warnings
import pkg_resources
from email import charset
from functools import partial
from marrow.mailer.message import Message
from marrow.mailer.exc import MailerNotRunning
from marrow.util.compat import basestring
from marrow.util.bunch import Bunch
from marrow.util.object import load_object
__all__ = ['Mailer', 'Delivery', 'Message']
log = __import__('logging').getLogger(__name__)
class Mailer(object):
"""The primary marrow.mailer interface.
Instantiate and configure marrow.mailer, then use the instance to initiate mail delivery.
Where managers and transports are defined in the configuration you may pass in the class,
an entrypoint name (simple string), or package-object notation ('foo.bar:baz').
"""
def __repr__(self):
return "Mailer(manager=%s, transport=%s)" % (self.Manager.__name__, self.Transport.__name__)
def __init__(self, config, prefix=None):
self.manager, self.Manager = None, None
self.Transport = None
self.running = False
self.config = config = Bunch(config)
if prefix is not None:
self.config = config = Bunch.partial(prefix, config)
if 'manager' in config and isinstance(config.manager, dict):
self.manager_config = manager_config = config.manager
elif 'manager' in config:
self.manager_config = manager_config = dict(manager_config)
else:
try:
self.manager_config = manager_config = Bunch.partial('manager', config)
except ValueError:
self.manager_config = manager_config = dict()
if 'manager' in config and isinstance(config.manager, basestring):
warnings.warn("Use of the manager directive is deprecated; use manager.use instead.", DeprecationWarning)
manager_config.use = config.manager
try:
if 'transport' in config and isinstance(config.transport, dict):
self.transport_config = transport_config = Bunch(config.transport)
else:
self.transport_config = transport_config = Bunch.partial('transport', config)
except (AttributeError, ValueError): # pragma: no cover
self.transport_config = transport_config = Bunch()
if 'transport' in config and isinstance(config.transport, basestring):
warnings.warn("Use of the transport directive is deprecated; use transport.use instead.", DeprecationWarning)
transport_config.use = config.transport
try:
if 'message' in config and isinstance(config.message, dict):
self.message_config = Bunch(config.message)
else:
self.message_config = Bunch.partial('message', config)
except (AttributeError, ValueError):
self.message_config = Bunch()
self.Manager = Manager = self._load(manager_config.use if 'use' in manager_config else 'immediate', 'marrow.mailer.manager')
if not Manager:
raise LookupError("Unable to determine manager from specification: %r" % (config.manager, ))
# Removed until marrow.interface is updated to use marrow.schema.
#if not isinstance(Manager, IManager):
# raise TypeError("Chosen manager does not conform to the manager API.")
self.Transport = Transport = self._load(transport_config.use, 'marrow.mailer.transport')
if not Transport:
raise LookupError("Unable to determine transport from specification: %r" % (config.transport, ))
# Removed until marrow.interface is updated to use marrow.schema.
#if not isinstance(Transport, ITransport):
# raise TypeError("Chosen transport does not conform to the transport API.")
self.manager = Manager(manager_config, partial(Transport, transport_config))
@staticmethod
def _load(spec, group):
if not isinstance(spec, basestring):
# It's already an object, just use it.
return spec
if ':' in spec:
# Load the Python package(s) and target object.
return load_object(spec)
# Load the entry point.
for entrypoint in pkg_resources.iter_entry_points(group, spec):
return entrypoint.load()
def start(self):
if self.running:
log.warning("Attempt made to start an already running Mailer service.")
return
log.info("Mail delivery service starting.")
self.manager.startup()
self.running = True
log.info("Mail delivery service started.")
return self
def stop(self):
if not self.running:
log.warning("Attempt made to stop an already stopped Mailer service.")
return
log.info("Mail delivery service stopping.")
self.manager.shutdown()
self.running = False
log.info("Mail delivery service stopped.")
return self
def send(self, message):
if not self.running:
raise MailerNotRunning("Mail service not running.")
log.info("Attempting delivery of message %s.", message.id)
try:
result = self.manager.deliver(message)
except:
log.error("Delivery of message %s failed.", message.id)
raise
log.debug("Message %s delivered.", message.id)
return result
def new(self, author=None, to=None, subject=None, **kw):
data = dict(self.message_config)
data['mailer'] = self
if author:
kw['author'] = author
if to:
kw['to'] = to
if subject:
kw['subject'] = subject
data.update(kw)
return Message(**data)
class Delivery(Mailer):
def __init__(self, *args, **kw):
warnings.warn("Use of the Delivery class is deprecated; use Mailer instead.", DeprecationWarning)
super(Delivery, self).__init__(*args, **kw)
# Import-time side-effect: un-fscking the default use of base-64 encoding for UTF-8 e-mail.
charset.add_charset('utf-8', charset.SHORTEST, charset.QP, 'utf-8')
charset.add_charset('utf8', charset.SHORTEST, charset.QP, 'utf8')