Email: add possibility to easily load templates from a directory

This commit is contained in:
Benjamin Renard 2023-03-13 18:58:20 +01:00
parent b5df95a2dd
commit e71fb28295
Signed by: bn8
GPG key ID: 3E2E1CE1907115BC
7 changed files with 96 additions and 47 deletions

View file

@ -46,15 +46,18 @@ class EmailClient(
"encoding": "utf-8", "encoding": "utf-8",
"catch_all_addr": None, "catch_all_addr": None,
"just_try": False, "just_try": False,
"templates_path": None,
} }
templates = {} templates = {}
def __init__(self, templates=None, **kwargs): def __init__(self, templates=None, initialize=False, **kwargs):
super().__init__(**kwargs) super().__init__(**kwargs)
assert templates is None or isinstance(templates, dict) assert templates is None or isinstance(templates, dict)
self.templates = templates if templates else {} self.templates = templates if templates else {}
if initialize:
self.initialize()
# pylint: disable=arguments-differ,arguments-renamed # pylint: disable=arguments-differ,arguments-renamed
def configure(self, use_smtp=True, just_try=True, **kwargs): def configure(self, use_smtp=True, just_try=True, **kwargs):
@ -137,8 +140,40 @@ class EmailClient(
comment="Just-try mode: do not really send emails", comment="Just-try mode: do not really send emails",
) )
section.add_option(
StringOption,
"templates_path",
comment="Path to templates directory",
)
return section return section
def initialize(self, *args, **kwargs): # pylint: disable=arguments-differ
"""Configuration initialized hook"""
super().initialize(*args, **kwargs)
self.load_templates_directory()
def load_templates_directory(self, templates_path=None):
"""Load templates from specified directory"""
if templates_path is None:
templates_path = self._get_option("templates_path")
if not templates_path:
return
log.debug("Load email templates from %s directory", templates_path)
for filename in os.listdir(templates_path):
filepath = os.path.join(templates_path, filename)
if not os.path.isfile(filepath):
continue
template_name, template_type = os.path.splitext(filename)
if template_type not in [".html", ".txt", ".subject"]:
continue
template_type = "text" if template_type == ".txt" else template_type[1:]
if template_name not in self.templates:
self.templates[template_name] = {}
log.debug("Load email template %s %s from %s", template_name, template_type, filepath)
with open(filepath, encoding="utf8") as file_desc:
self.templates[template_name][template_type] = MakoTemplate(file_desc.read())
def forge_message( def forge_message(
self, self,
rcpt_to, rcpt_to,
@ -179,7 +214,11 @@ class EmailClient(
) )
) )
if subject: if subject:
msg["Subject"] = subject.format(**template_vars) msg["Subject"] = (
subject.render(**template_vars)
if isinstance(subject, MakoTemplate)
else subject.format(**template_vars)
)
msg["Date"] = email.utils.formatdate(None, True) msg["Date"] = email.utils.formatdate(None, True)
encoding = encoding if encoding else self._get_option("encoding") encoding = encoding if encoding else self._get_option("encoding")
if template: if template:
@ -189,7 +228,11 @@ class EmailClient(
assert self.templates[template].get( assert self.templates[template].get(
"subject" "subject"
), f"No subject defined in template {template}" ), f"No subject defined in template {template}"
msg["Subject"] = self.templates[template]["subject"].format(**template_vars) msg["Subject"] = (
self.templates[template]["subject"].render(**template_vars)
if isinstance(self.templates[template]["subject"], MakoTemplate)
else self.templates[template]["subject"].format(**template_vars)
)
# Put HTML part in last one to prefered it # Put HTML part in last one to prefered it
parts = [] parts = []

View file

@ -0,0 +1 @@
<strong>Just a test email.</strong> <small>(sent at ${sent_date})</small>

View file

@ -0,0 +1 @@
Test email

View file

@ -0,0 +1 @@
Just a test email sent at ${sent_date}.

View file

@ -2,10 +2,9 @@
import datetime import datetime
import getpass import getpass
import logging import logging
import os
import sys import sys
from mako.template import Template as MakoTemplate
from mylib.scripts.helpers import add_email_opts, get_opts_parser, init_email_client, init_logging from mylib.scripts.helpers import add_email_opts, get_opts_parser, init_email_client, init_logging
log = logging.getLogger("mylib.scripts.email_test") log = logging.getLogger("mylib.scripts.email_test")
@ -18,7 +17,10 @@ def main(argv=None): # pylint: disable=too-many-locals,too-many-statements
# Options parser # Options parser
parser = get_opts_parser(just_try=True) parser = get_opts_parser(just_try=True)
add_email_opts(parser) add_email_opts(
parser,
templates_path=os.path.join(os.path.dirname(os.path.realpath(__file__)), "email_templates"),
)
test_opts = parser.add_argument_group("Test email options") test_opts = parser.add_argument_group("Test email options")
@ -31,6 +33,15 @@ def main(argv=None): # pylint: disable=too-many-locals,too-many-statements
help="Test email recipient", help="Test email recipient",
) )
test_opts.add_argument(
"-T",
"--template",
action="store_true",
dest="template",
help="Template name to send (default: test)",
default="test",
)
test_opts.add_argument( test_opts.add_argument(
"-m", "-m",
"--mako", "--mako",
@ -51,26 +62,7 @@ def main(argv=None): # pylint: disable=too-many-locals,too-many-statements
if options.email_smtp_user and not options.email_smtp_password: if options.email_smtp_user and not options.email_smtp_password:
options.email_smtp_password = getpass.getpass("Please enter SMTP password: ") options.email_smtp_password = getpass.getpass("Please enter SMTP password: ")
email_client = init_email_client( email_client = init_email_client(options)
options,
templates=dict(
test=dict(
subject="Test email",
text=(
"Just a test email sent at {sent_date}."
if not options.test_mako
else MakoTemplate("Just a test email sent at ${sent_date}.")
),
html=(
"<strong>Just a test email.</strong> <small>(sent at {sent_date})</small>"
if not options.test_mako
else MakoTemplate(
"<strong>Just a test email.</strong> <small>(sent at ${sent_date})</small>"
)
),
)
),
)
log.info("Send a test email to %s", options.test_to) log.info("Send a test email to %s", options.test_to)
if email_client.send(options.test_to, template="test", sent_date=datetime.datetime.now()): if email_client.send(options.test_to, template="test", sent_date=datetime.datetime.now()):

View file

@ -1,10 +1,9 @@
""" Test Email client using mylib.config.Config for configuration """ """ Test Email client using mylib.config.Config for configuration """
import datetime import datetime
import logging import logging
import os
import sys import sys
from mako.template import Template as MakoTemplate
from mylib.config import Config from mylib.config import Config
from mylib.email import EmailClient from mylib.email import EmailClient
@ -20,6 +19,11 @@ def main(argv=None): # pylint: disable=too-many-locals,too-many-statements
email_client = EmailClient(config=config) email_client = EmailClient(config=config)
email_client.configure() email_client.configure()
config.set_default(
"email",
"templates_path",
os.path.join(os.path.dirname(os.path.realpath(__file__)), "email_templates"),
)
# Options parser # Options parser
parser = config.get_arguments_parser(description=__doc__) parser = config.get_arguments_parser(description=__doc__)
@ -35,6 +39,15 @@ def main(argv=None): # pylint: disable=too-many-locals,too-many-statements
help="Test email recipient", help="Test email recipient",
) )
test_opts.add_argument(
"-T",
"--template",
action="store_true",
dest="template",
help="Template name to send (default: test)",
default="test",
)
test_opts.add_argument( test_opts.add_argument(
"-m", "-m",
"--mako", "--mako",
@ -49,24 +62,6 @@ def main(argv=None): # pylint: disable=too-many-locals,too-many-statements
parser.error("You must specify test email recipient using -t/--to parameter") parser.error("You must specify test email recipient using -t/--to parameter")
sys.exit(1) sys.exit(1)
email_client.templates = dict(
test=dict(
subject="Test email",
text=(
"Just a test email sent at {sent_date}."
if not options.test_mako
else MakoTemplate("Just a test email sent at ${sent_date}.")
),
html=(
"<strong>Just a test email.</strong> <small>(sent at {sent_date})</small>"
if not options.test_mako
else MakoTemplate(
"<strong>Just a test email.</strong> <small>(sent at ${sent_date})</small>"
)
),
)
)
logging.info("Send a test email to %s", options.test_to) logging.info("Send a test email to %s", options.test_to)
if email_client.send(options.test_to, template="test", sent_date=datetime.datetime.now()): if email_client.send(options.test_to, template="test", sent_date=datetime.datetime.now()):
logging.info("Test email sent") logging.info("Test email sent")

View file

@ -87,7 +87,7 @@ def get_opts_parser(desc=None, just_try=False, just_one=False, progress=False, c
return parser return parser
def add_email_opts(parser, config=None): def add_email_opts(parser, config=None, **defaults):
"""Add email options""" """Add email options"""
email_opts = parser.add_argument_group("Email options") email_opts = parser.add_argument_group("Email options")
@ -103,7 +103,9 @@ def add_email_opts(parser, config=None):
sender_name=getpass.getuser(), sender_name=getpass.getuser(),
sender_email=f"{getpass.getuser()}@{socket.gethostname()}", sender_email=f"{getpass.getuser()}@{socket.gethostname()}",
catch_all=False, catch_all=False,
templates_path=None,
) )
default_config.update(defaults)
email_opts.add_argument( email_opts.add_argument(
"--smtp-host", "--smtp-host",
@ -220,6 +222,18 @@ def add_email_opts(parser, config=None):
default=get_default_opt_value(config, default_config, "catch_all"), default=get_default_opt_value(config, default_config, "catch_all"),
) )
email_opts.add_argument(
"--templates-path",
action="store",
type=str,
dest="email_templates_path",
help=(
"Load templates from specify directory "
f'(default: {get_default_opt_value(config, default_config, "templates_path")})'
),
default=get_default_opt_value(config, default_config, "templates_path"),
)
def init_email_client(options, **kwargs): def init_email_client(options, **kwargs):
"""Initialize email client from calling script options""" """Initialize email client from calling script options"""
@ -239,6 +253,8 @@ def init_email_client(options, **kwargs):
catch_all_addr=options.email_catch_all, catch_all_addr=options.email_catch_all,
just_try=options.just_try if hasattr(options, "just_try") else False, just_try=options.just_try if hasattr(options, "just_try") else False,
encoding=options.email_encoding, encoding=options.email_encoding,
templates_path=options.email_templates_path,
initialize=True,
**kwargs, **kwargs,
) )