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

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,
) )