diff --git a/mylib/email.py b/mylib/email.py index edfa3a4..1c91e87 100644 --- a/mylib/email.py +++ b/mylib/email.py @@ -13,58 +13,98 @@ from email.encoders import encode_base64 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__) -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 This class abstract all interactions with the SMTP server. """ - smtp_host = None - smtp_port = None - smtp_ssl = None - smtp_tls = None - smtp_user = None - smtp_password = None - smtp_debug = None - - sender_name = None - sender_email = None - - catch_all_addr = False - just_try = False - - encoding = 'utf-8' + _config_name = 'email' + _config_comment = 'Email' + _defaults = { + 'smtp_host': 'localhost', + 'smtp_port': 25, + 'smtp_ssl': False, + 'smtp_tls': False, + 'smtp_user': None, + 'smtp_password': None, + 'smtp_debug': False, + 'sender_name': 'No reply', + 'sender_email': 'noreply@localhost', + 'encoding': 'utf-8', + 'catch_all_addr': None, + 'just_try': False, + } 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, - sender_name=None, sender_email=None, catch_all_addr=None, just_try=None, encoding=None, templates=None): - 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 + def __init__(self, templates=None, **kwargs): + super().__init__(**kwargs) assert templates is None or isinstance(templates, dict) self.templates = templates if templates else dict() - if encoding: - self.encoding = encoding + def configure(self, use_smtp=True, just_try=True, ** kwargs): # pylint: disable=arguments-differ + """ 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, - attachment_payloads=None, sender_name=None, sender_email=None, encoding=None, - template=None, **template_vars): # pylint: disable=too-many-arguments,too-many-locals + if use_smtp: + section.add_option( + 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 @@ -83,11 +123,16 @@ class EmailClient(object): # pylint: disable=useless-object-inheritance,too-man """ msg = MIMEMultipart('alternative') 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: msg['Subject'] = subject.format(**template_vars) 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: assert template in self.templates, "Unknwon template %s" % 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) - 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')) return True - if self.catch_all_addr: - catch_addr = self.catch_all_addr + catch_addr = self._get_option('catch_all_addr') + if catch_addr: log.debug('Catch email originaly send to %s to %s', rcpt_to, catch_addr) rcpt_to = catch_addr + smtp_host = self._get_option('smtp_host') + smtp_port = self._get_option('smtp_port') try: - if self.smtp_ssl: - logging.info("Establish SSL connection to server %s:%s", self.smtp_host, self.smtp_port) - server = smtplib.SMTP_SSL(self.smtp_host, self.smtp_port) + if self._get_option('smtp_ssl'): + logging.info("Establish SSL connection to server %s:%s", smtp_host, smtp_port) + server = smtplib.SMTP_SSL(smtp_host, smtp_port) else: - logging.info("Establish connection to server %s:%s", self.smtp_host, self.smtp_port) - server = smtplib.SMTP(self.smtp_host, self.smtp_port) - if self.smtp_tls: + logging.info("Establish connection to server %s:%s", smtp_host, smtp_port) + server = smtplib.SMTP(smtp_host, smtp_port) + if self._get_option('smtp_tls'): logging.info('Start TLS on SMTP connection') server.starttls() 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 - if self.smtp_debug: + if self._get_option('smtp_debug'): 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: - log.info('Try to authenticate on SMTP connection as %s', self.smtp_user) - server.login(self.smtp_user, self.smtp_password) + log.info('Try to authenticate on SMTP connection as %s', smtp_user) + server.login(smtp_user, smtp_password) 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 error = False try: 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: error = True log.error('Error sending email to %s', rcpt_to, exc_info=True)