email: make EmailClient compatible with mylib.mylib.Config

This commit is contained in:
Benjamin Renard 2022-01-19 17:31:00 +01:00
parent ba4e85be50
commit fdf68a8ee2

View file

@ -13,58 +13,98 @@ from email.encoders import encode_base64
from mako.template import Template as MakoTemplate from mako.template import Template as MakoTemplate
from mylib.config import ConfigurableObject
from mylib.config import BooleanOption
from mylib.config import IntegerOption
from mylib.config import PasswordOption
from mylib.config import StringOption
log = logging.getLogger(__name__) log = logging.getLogger(__name__)
class EmailClient(object): # pylint: disable=useless-object-inheritance,too-many-instance-attributes class EmailClient(ConfigurableObject): # pylint: disable=useless-object-inheritance,too-many-instance-attributes
""" """
Email client Email client
This class abstract all interactions with the SMTP server. This class abstract all interactions with the SMTP server.
""" """
smtp_host = None _config_name = 'email'
smtp_port = None _config_comment = 'Email'
smtp_ssl = None _defaults = {
smtp_tls = None 'smtp_host': 'localhost',
smtp_user = None 'smtp_port': 25,
smtp_password = None 'smtp_ssl': False,
smtp_debug = None 'smtp_tls': False,
'smtp_user': None,
sender_name = None 'smtp_password': None,
sender_email = None 'smtp_debug': False,
'sender_name': 'No reply',
catch_all_addr = False 'sender_email': 'noreply@localhost',
just_try = False 'encoding': 'utf-8',
'catch_all_addr': None,
encoding = 'utf-8' 'just_try': False,
}
templates = dict() templates = dict()
def __init__(self, smtp_host=None, smtp_port=None, smtp_ssl=None, smtp_tls=None, smtp_user=None, smtp_password=None, smtp_debug=None, def __init__(self, templates=None, **kwargs):
sender_name=None, sender_email=None, catch_all_addr=None, just_try=None, encoding=None, templates=None): super().__init__(**kwargs)
self.smtp_host = smtp_host if smtp_host else 'localhost'
self.smtp_port = smtp_port if smtp_port else 25
self.smtp_ssl = bool(smtp_ssl)
self.smtp_tls = bool(smtp_tls)
self.smtp_user = smtp_user if smtp_user else None
self.smtp_password = smtp_password if smtp_password else None
self.smtp_debug = bool(smtp_debug)
self.sender_name = sender_name if sender_name else "No reply"
self.sender_email = sender_email if sender_email else "noreply@localhost"
self.catch_all_addr = catch_all_addr if catch_all_addr else False
self.just_try = just_try if just_try else False
assert templates is None or isinstance(templates, dict) assert templates is None or isinstance(templates, dict)
self.templates = templates if templates else dict() self.templates = templates if templates else dict()
if encoding: def configure(self, use_smtp=True, just_try=True, ** kwargs): # pylint: disable=arguments-differ
self.encoding = encoding """ Configure options on registered mylib.Config object """
section = super().configure(**kwargs)
def forge_message(self, rcpt_to, subject=None, html_body=None, text_body=None, attachment_files=None, if use_smtp:
attachment_payloads=None, sender_name=None, sender_email=None, encoding=None, section.add_option(
template=None, **template_vars): # pylint: disable=too-many-arguments,too-many-locals StringOption, 'smtp_host', default=self._defaults['smtp_host'],
comment='SMTP server hostname/IP address')
section.add_option(
IntegerOption, 'smtp_port', default=self._defaults['smtp_port'],
comment='SMTP server port')
section.add_option(
BooleanOption, 'smtp_ssl', default=self._defaults['smtp_ssl'],
comment='Use SSL on SMTP server connection')
section.add_option(
BooleanOption, 'smtp_tls', default=self._defaults['smtp_tls'],
comment='Use TLS on SMTP server connection')
section.add_option(
StringOption, 'smtp_user', default=self._defaults['smtp_user'],
comment='SMTP authentication username')
section.add_option(
PasswordOption, 'smtp_password', default=self._defaults['smtp_password'],
comment='SMTP authentication password (set to "keyring" to use XDG keyring)',
username_option='smtp_user', keyring_value='keyring')
section.add_option(
BooleanOption, 'smtp_debug', default=self._defaults['smtp_debug'],
comment='Enable SMTP debugging')
section.add_option(
StringOption, 'sender_name', default=self._defaults['sender_name'],
comment='Sender name')
section.add_option(
StringOption, 'sender_email', default=self._defaults['sender_email'],
comment='Sender email address')
section.add_option(
StringOption, 'encoding', default=self._defaults['encoding'],
comment='Email encoding')
section.add_option(
StringOption, 'catch_all_addr', default=self._defaults['catch_all_addr'],
comment='Catch all sent emails to this specified email address')
if just_try:
section.add_option(
BooleanOption, 'just_try', default=self._defaults['just_try'],
comment='Just-try mode: do not really send emails')
return section
def forge_message(self, rcpt_to, subject=None, html_body=None, text_body=None, # pylint: disable=too-many-arguments,too-many-locals
attachment_files=None, attachment_payloads=None, sender_name=None,
sender_email=None, encoding=None, template=None, **template_vars):
""" """
Forge a message Forge a message
@ -83,11 +123,16 @@ class EmailClient(object): # pylint: disable=useless-object-inheritance,too-man
""" """
msg = MIMEMultipart('alternative') msg = MIMEMultipart('alternative')
msg['To'] = email.utils.formataddr(rcpt_to) if isinstance(rcpt_to, tuple) else rcpt_to msg['To'] = email.utils.formataddr(rcpt_to) if isinstance(rcpt_to, tuple) else rcpt_to
msg['From'] = email.utils.formataddr((sender_name or self.sender_name, sender_email or self.sender_email)) msg['From'] = email.utils.formataddr(
(
sender_name or self._get_option('sender_name'),
sender_email or self._get_option('sender_email')
)
)
if subject: if subject:
msg['Subject'] = subject.format(**template_vars) msg['Subject'] = subject.format(**template_vars)
msg['Date'] = email.utils.formatdate(None, True) msg['Date'] = email.utils.formatdate(None, True)
encoding = encoding if encoding else self.encoding encoding = encoding if encoding else self._get_option('encoding')
if template: if template:
assert template in self.templates, "Unknwon template %s" % template assert template in self.templates, "Unknwon template %s" % template
# Handle subject from template # Handle subject from template
@ -149,44 +194,54 @@ class EmailClient(object): # pylint: disable=useless-object-inheritance,too-man
""" """
msg = msg if msg else self.forge_message(rcpt_to, subject, **forge_args) msg = msg if msg else self.forge_message(rcpt_to, subject, **forge_args)
if just_try or self.just_try: if just_try or self._get_option('just_try'):
log.debug('Just-try mode: do not really send this email to %s (subject="%s")', rcpt_to, subject or msg.get('subject', 'No subject')) log.debug('Just-try mode: do not really send this email to %s (subject="%s")', rcpt_to, subject or msg.get('subject', 'No subject'))
return True return True
if self.catch_all_addr: catch_addr = self._get_option('catch_all_addr')
catch_addr = self.catch_all_addr if catch_addr:
log.debug('Catch email originaly send to %s to %s', rcpt_to, catch_addr) log.debug('Catch email originaly send to %s to %s', rcpt_to, catch_addr)
rcpt_to = catch_addr rcpt_to = catch_addr
smtp_host = self._get_option('smtp_host')
smtp_port = self._get_option('smtp_port')
try: try:
if self.smtp_ssl: if self._get_option('smtp_ssl'):
logging.info("Establish SSL connection to server %s:%s", self.smtp_host, self.smtp_port) logging.info("Establish SSL connection to server %s:%s", smtp_host, smtp_port)
server = smtplib.SMTP_SSL(self.smtp_host, self.smtp_port) server = smtplib.SMTP_SSL(smtp_host, smtp_port)
else: else:
logging.info("Establish connection to server %s:%s", self.smtp_host, self.smtp_port) logging.info("Establish connection to server %s:%s", smtp_host, smtp_port)
server = smtplib.SMTP(self.smtp_host, self.smtp_port) server = smtplib.SMTP(smtp_host, smtp_port)
if self.smtp_tls: if self._get_option('smtp_tls'):
logging.info('Start TLS on SMTP connection') logging.info('Start TLS on SMTP connection')
server.starttls() server.starttls()
except smtplib.SMTPException: except smtplib.SMTPException:
log.error('Error connecting to SMTP server %s:%s', self.smtp_host, self.smtp_port, exc_info=True) log.error('Error connecting to SMTP server %s:%s', smtp_host, smtp_port, exc_info=True)
return False return False
if self.smtp_debug: if self._get_option('smtp_debug'):
server.set_debuglevel(True) server.set_debuglevel(True)
if self.smtp_user and self.smtp_password: smtp_user = self._get_option('smtp_user')
smtp_password = self._get_option('smtp_password')
if smtp_user and smtp_password:
try: try:
log.info('Try to authenticate on SMTP connection as %s', self.smtp_user) log.info('Try to authenticate on SMTP connection as %s', smtp_user)
server.login(self.smtp_user, self.smtp_password) server.login(smtp_user, smtp_password)
except smtplib.SMTPException: except smtplib.SMTPException:
log.error('Error authenticating on SMTP server %s:%s with user %s', self.smtp_host, self.smtp_port, self.smtp_user, exc_info=True) log.error(
'Error authenticating on SMTP server %s:%s with user %s',
smtp_host, smtp_port, smtp_user, exc_info=True)
return False return False
error = False error = False
try: try:
log.info('Sending email to %s', rcpt_to) log.info('Sending email to %s', rcpt_to)
server.sendmail(self.sender_email, [rcpt_to[1] if isinstance(rcpt_to, tuple) else rcpt_to], msg.as_string()) server.sendmail(
self._get_option('sender_email'),
[rcpt_to[1] if isinstance(rcpt_to, tuple) else rcpt_to],
msg.as_string()
)
except smtplib.SMTPException: except smtplib.SMTPException:
error = True error = True
log.error('Error sending email to %s', rcpt_to, exc_info=True) log.error('Error sending email to %s', rcpt_to, exc_info=True)