Compare commits

..

2 commits

Author SHA1 Message Date
Benjamin Renard
e71fb28295
Email: add possibility to easily load templates from a directory 2023-03-13 18:58:20 +01:00
Benjamin Renard
b5df95a2dd
Config: add set_default() / set_defaults() methods 2023-03-13 18:53:29 +01:00
8 changed files with 122 additions and 47 deletions

View file

@ -116,6 +116,10 @@ class BaseOption: # pylint: disable=too-many-instance-attributes
self._set = True
def set_default(self, default_value):
"""Set option default value"""
self.default = default_value
@property
def parser_action(self):
"""Get action as accept by argparse.ArgumentParser"""
@ -511,6 +515,16 @@ class ConfigSection:
assert self.defined(option), f"Option {option} unknown"
return self.options[option].set(value)
def set_default(self, option, default_value):
"""Set default option value"""
assert self.defined(option), f"Option {option} unknown"
return self.options[option].set_default(default_value)
def set_defaults(self, **default_values):
"""Set default options value"""
for option, default_value in default_values.items():
self.set_default(option, default_value)
def add_options_to_parser(self, parser):
"""Add section to argparse.ArgumentParser"""
assert isinstance(parser, argparse.ArgumentParser)
@ -660,6 +674,18 @@ class Config: # pylint: disable=too-many-instance-attributes
self._init_config_parser()
self.sections[section].set(option, value)
def set_default(self, section, option, default_value):
"""Set default option value"""
assert self.defined(section, option), f"Unknown option {section}.{option}"
self._init_config_parser()
self.sections[section].set_default(option, default_value)
def set_defaults(self, section, **default_values):
"""Set default options value"""
assert section in self.sections, f"Unknown section {section}"
self._init_config_parser()
self.sections[section].set_defaults(**default_values)
def _init_config_parser(self, force=False):
"""Initialize ConfigParser object"""
if not self.config_parser or force:

View file

@ -46,15 +46,18 @@ class EmailClient(
"encoding": "utf-8",
"catch_all_addr": None,
"just_try": False,
"templates_path": None,
}
templates = {}
def __init__(self, templates=None, **kwargs):
def __init__(self, templates=None, initialize=False, **kwargs):
super().__init__(**kwargs)
assert templates is None or isinstance(templates, dict)
self.templates = templates if templates else {}
if initialize:
self.initialize()
# pylint: disable=arguments-differ,arguments-renamed
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",
)
section.add_option(
StringOption,
"templates_path",
comment="Path to templates directory",
)
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(
self,
rcpt_to,
@ -179,7 +214,11 @@ class EmailClient(
)
)
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)
encoding = encoding if encoding else self._get_option("encoding")
if template:
@ -189,7 +228,11 @@ class EmailClient(
assert self.templates[template].get(
"subject"
), 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
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 getpass
import logging
import os
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
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
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")
@ -31,6 +33,15 @@ def main(argv=None): # pylint: disable=too-many-locals,too-many-statements
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(
"-m",
"--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:
options.email_smtp_password = getpass.getpass("Please enter SMTP password: ")
email_client = init_email_client(
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>"
)
),
)
),
)
email_client = init_email_client(options)
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()):

View file

@ -1,10 +1,9 @@
""" Test Email client using mylib.config.Config for configuration """
import datetime
import logging
import os
import sys
from mako.template import Template as MakoTemplate
from mylib.config import Config
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.configure()
config.set_default(
"email",
"templates_path",
os.path.join(os.path.dirname(os.path.realpath(__file__)), "email_templates"),
)
# Options parser
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",
)
test_opts.add_argument(
"-T",
"--template",
action="store_true",
dest="template",
help="Template name to send (default: test)",
default="test",
)
test_opts.add_argument(
"-m",
"--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")
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)
if email_client.send(options.test_to, template="test", sent_date=datetime.datetime.now()):
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
def add_email_opts(parser, config=None):
def add_email_opts(parser, config=None, **defaults):
"""Add 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_email=f"{getpass.getuser()}@{socket.gethostname()}",
catch_all=False,
templates_path=None,
)
default_config.update(defaults)
email_opts.add_argument(
"--smtp-host",
@ -220,6 +222,18 @@ def add_email_opts(parser, config=None):
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):
"""Initialize email client from calling script options"""
@ -239,6 +253,8 @@ def init_email_client(options, **kwargs):
catch_all_addr=options.email_catch_all,
just_try=options.just_try if hasattr(options, "just_try") else False,
encoding=options.email_encoding,
templates_path=options.email_templates_path,
initialize=True,
**kwargs,
)