Source code for flask_unchained.bundles.mail.utils

import inspect
import re

try:
    from bs4 import BeautifulSoup
except ImportError:
    BeautifulSoup = None
    from warnings import warn
    warn('BeautifulSoup4 is not installed. Will not automatically '
         'convert html email messages to plain text.')

try:
    import lxml  # skipcq
except ImportError:
    from warnings import warn
    warn('lxml is not installed. Will not automatically '
         'convert html email messages to plain text.')

from flask import render_template
from flask_mail import Message
from typing import *

from .extensions import mail

message_sig = inspect.signature(Message)
message_kwargs = {name for name, param in message_sig.parameters.items()
                  if (param.kind == param.POSITIONAL_OR_KEYWORD
                      or param.kind == param.KEYWORD_ONLY)}


def get_message_plain_text(msg: Message):
    """
    Converts an HTML message to plain text.

    :param msg: A :class:`~flask_mail.Message`
    :return: The plain text message.
    """
    if msg.body:
        return msg.body

    if BeautifulSoup is None or not msg.html:
        return msg.html

    plain_text = '\n'.join(line.strip() for line in
                           BeautifulSoup(msg.html, 'lxml').text.splitlines())
    return re.sub(r'\n\n+', '\n\n', plain_text).strip()


def make_message(subject_or_message: Union[str, Message],
                 to: Union[str, List[str]],
                 template: Optional[str] = None,
                 **kwargs):
    """
    Creates a new :class:`~flask_mail.Message` from the given arguments.

    :param subject_or_message: A subject string, or for backwards compatibility with
                               stock Flask-Mail, a :class:`~flask_mail.Message` instance
    :param to: An email address, or a list of email addresses
    :param template: Which template to render.
    :param kwargs: Extra kwargs to pass on to :class:`~flask_mail.Message`
    :return: The created :class:`~flask_mail.Message`
    """
    if isinstance(subject_or_message, Message):
        return subject_or_message

    if isinstance(to, tuple):
        to = list(to)
    elif not isinstance(to, list):
        to = [to]
    msg = Message(subject=subject_or_message, recipients=to, **{
        k: kwargs[k] for k in message_kwargs & set(kwargs)})

    if not msg.html and template:
        msg.html = render_template(template, **kwargs)
    if not msg.body:
        msg.body = get_message_plain_text(msg)
    return msg


def _send_mail(subject_or_message: Optional[Union[str, Message]] = None,
               to: Optional[Union[str, List[str]]] = None,
               template: Optional[str] = None,
               **kwargs):
    """
    The default function used for sending emails.

    :param subject_or_message: A subject string, or for backwards compatibility with
                               stock Flask-Mail, a :class:`~flask_mail.Message` instance
    :param to: An email address, or a list of email addresses
    :param template: Which template to render.
    :param kwargs: Extra kwargs to pass on to :class:`~flask_mail.Message`
    """
    subject_or_message = subject_or_message or kwargs.pop('subject')
    to = to or kwargs.pop('recipients', [])
    msg = make_message(subject_or_message, to, template, **kwargs)
    with mail.connect() as connection:
        connection.send(msg)