| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260 |
- from email.message import EmailMessage
- from typing import Callable, Dict, Union
- import jinja2
- from redmail.email.body import HTMLBody, TextBody
- from .address import EmailAddress
- from .envs import get_span, is_last_group_row
- import smtplib
- from pathlib import Path
- from platform import node
- from getpass import getuser
- import datetime
- class EmailSender:
- """Email sender
- Parameters
- ----------
- server : str
- SMTP server address.
- port : int
- Port to the SMTP server.
- user : str, callable
- User name to authenticate on the server.
- password : str, callable
- User password to authenticate on the server.
- Examples
- --------
- mymail = EmailSender(server="smtp.mymail.com", port=123)
- mymail.set_credentials(
- user=lambda: read_yaml("C:/config/email.yaml")["mymail"]["user"],
- password=lambda: read_yaml("C:/config/email.yaml")["mymail"]["password"]
- )
- mymail.send_email(
- subject="Important email",
- html_body="<h1>Important</h1><img src={{ nice_pic }}>",
- body_images={'nice_pic': 'path/to/pic.jpg'},
- )
- """
-
- default_html_theme = "modest.html"
- default_text_theme = "pandas.txt"
- templates_html = jinja2.Environment(loader=jinja2.FileSystemLoader(Path(__file__).parent / "templates/html"))
- templates_html_table = jinja2.Environment(loader=jinja2.FileSystemLoader(Path(__file__).parent / "templates/html/table"))
- templates_text = jinja2.Environment(loader=jinja2.FileSystemLoader(Path(__file__).parent / "templates/text"))
- templates_text_table = jinja2.Environment(loader=jinja2.FileSystemLoader(Path(__file__).parent / "templates/text/table"))
- # Set globals
- templates_html_table.globals["get_span"] = get_span
- templates_text_table.globals["get_span"] = get_span
-
- templates_html_table.globals["is_last_group_row"] = is_last_group_row
- templates_text_table.globals["is_last_group_row"] = is_last_group_row
- def __init__(self, server:str, port:int, user_name:str=None, password:str=None):
- self.server = server
- self.port = port
- self.user_name = user_name
- self.password = password
-
- def send_email(self, **kwargs):
- """Send an email message.
- Parameters
- ----------
- subject : str
- Subject of the email.
- receiver : list, optional
- Receivers of the email.
- sender : str, optional
- Sender of the email.
- cc : list, optional
- Cc or Carbon Copy of the email.
- Extra recipients of the email.
- bcc : list, optional
- Blind Carbon Copy of the email.
- Extra recipients of the email that
- don't see who else got the email.
- html_body : str, optional
- HTML body of the email. May contain
- Jinja templated variables of the
- tables, images and other variables.
- text_body : str, optional
- Text body of the email.
- body_images : dict of bytes, path-likes and figures, optional
- HTML images to embed with the html_body. The key should be
- as Jinja variables in the html_body and the values represent
- images (path to an image, bytes of an image or image object).
- body_tables : Dict[str, pd.DataFrame], optional
- HTML tables to embed with the html_body. The key should be
- as Jinja variables in the html_body and the values are Pandas
- DataFrames.
- html_params : dict, optional
- Extra parameters passed to html_table as Jinja parameters.
- Examples
- --------
- >>> sender = EmailSender(server="myserver", port=1234)
- >>> sender.send_email(
- sender="me@gmail.com",
- receiver="you@gmail.com",
- subject="Some news",
- html_body='<h1>Hi,</h1> Nice to meet you. Look at this: <img src="{{ my_image }}">',
- body_images={"my_image": Path("C:/path/to/img.png")}
- )
- >>> sender.send_email(
- sender="me@gmail.com",
- receiver="you@gmail.com",
- subject="Some news",
- html_body='<h1>Hi {{ name }},</h1> Nice to meet you. Look at this table: <img src="{{ my_table }}">',
- body_images={"my_image": Path("C:/path/to/img.png")},
- html_params={"name": "Jack"},
- )
- Returns
- -------
- EmailMessage
- Email message.
- """
- msg = self.get_message(**kwargs)
- self.send_message(msg)
- return msg
-
- def get_message(self,
- subject:str,
- receiver:list=None,
- sender:str=None,
- cc:list=None,
- bcc:list=None,
- html_body:str=None,
- text_body:str=None,
- html_template=None,
- text_template=None,
- body_images:Dict[str, str]=None,
- body_tables:Dict[str, str]=None,
- body_params:dict=None) -> EmailMessage:
- """Get the email message."""
-
- sender = sender or self.user_name
- msg = self._create_body(
- subject=subject,
- sender=sender,
- receiver=receiver,
- cc=cc,
- bcc=bcc,
- )
- jinja_params = self.get_template_params(sender=sender)
- jinja_params.update(body_params if body_params is not None else {})
- if text_body is not None or text_template is not None:
- body = TextBody(
- template=self.get_text_body_template(text_template),
- table_template=self.get_text_table_template(),
- )
- body.attach(
- msg,
- text_body,
- tables=body_tables,
- jinja_params=jinja_params,
- )
- if html_body is not None or html_template is not None:
- body = HTMLBody(
- template=self.get_html_body_template(html_template),
- table_template=self.get_html_table_template(),
- )
- body.attach(
- msg,
- html=html_body,
- images=body_images,
- tables=body_tables,
- jinja_params=jinja_params
- )
- return msg
- def _create_body(self, subject, sender, receiver=None, cc=None, bcc=None) -> EmailMessage:
- msg = EmailMessage()
- msg["from"] = sender
- msg["subject"] = subject
-
- # To whoom the email goes
- if receiver:
- msg["to"] = receiver
- if cc:
- msg['cc'] = cc
- if bcc:
- msg['bcc'] = bcc
- return msg
- def send_message(self, msg):
- "Send the created message"
- user = self.user_name
- password = self.password
-
- server = smtplib.SMTP(self.server, self.port)
- server.starttls()
- server.login(user, password)
- server.send_message(msg)
-
- server.quit()
-
- def get_template_params(self, sender:str):
- "Get Jinja parametes passed to template"
- # TODO: Add receivers to params
- return {
- "node": node(),
- "user": getuser(),
- "now": datetime.datetime.now(),
- "sender": EmailAddress(sender),
- }
- def get_html_table_template(self, layout=None) -> jinja2.Template:
- layout = self.default_html_theme if layout is None else layout
- if layout is None:
- return None
- return self.templates_html_table.get_template(layout)
- def get_html_body_template(self, layout=None) -> jinja2.Template:
- if layout is None:
- return None
- return self.templates_html.get_template(layout)
- def get_text_table_template(self, layout=None) -> jinja2.Template:
- layout = self.default_text_theme if layout is None else layout
- if layout is None:
- return None
- return self.templates_text_table.get_template(layout)
- def get_text_body_template(self, layout=None) -> jinja2.Template:
- if layout is None:
- return None
- return self.templates_text.get_template(layout)
- def set_template_paths(self, html=None, text=None, html_table=None, text_table=None):
- """Create Jinja envs for body templates using given paths
-
- This is a shortcut for manually setting them like:
- .. clode-block:: python
- sender.templates_html = jinja2.Environment(loader=jinja2.FileSystemLoader(...))
- sender.templates_text = jinja2.Environment(loader=jinja2.FileSystemLoader(...))
- ...
- """
- if html is not None:
- self.templates_html = jinja2.Environment(loader=jinja2.FileSystemLoader(html))
- if text is not None:
- self.templates_text = jinja2.Environment(loader=jinja2.FileSystemLoader(text))
- if html_table is not None:
- self.templates_html_table = jinja2.Environment(loader=jinja2.FileSystemLoader(html_table))
- if text_table is not None:
- self.templates_text_table = jinja2.Environment(loader=jinja2.FileSystemLoader(text_table))
|