refactor: implement email with stdlib and drop email dependency (#1746)
* implement email with stdlib and drop dependency * potentially provide in logs
This commit is contained in:
parent
d53e78317d
commit
0bb1b6500f
3 changed files with 71 additions and 115 deletions
|
@ -1,12 +1,55 @@
|
|||
import smtplib
|
||||
import typing
|
||||
from abc import ABC, abstractmethod
|
||||
from dataclasses import dataclass
|
||||
from email import message
|
||||
|
||||
import emails
|
||||
from emails.backend.response import SMTPResponse
|
||||
|
||||
from mealie.core.root_logger import get_logger
|
||||
from mealie.services._base_service import BaseService
|
||||
|
||||
logger = get_logger()
|
||||
|
||||
@dataclass(slots=True)
|
||||
class EmailOptions:
|
||||
host: str
|
||||
port: int
|
||||
username: str = None
|
||||
password: str = None
|
||||
tls: bool = False
|
||||
ssl: bool = False
|
||||
|
||||
|
||||
@dataclass(slots=True)
|
||||
class SMTPResponse:
|
||||
success: bool
|
||||
message: str
|
||||
errors: typing.Any
|
||||
|
||||
|
||||
@dataclass(slots=True)
|
||||
class Message:
|
||||
subject: str
|
||||
html: str
|
||||
mail_from: tuple[str, str]
|
||||
|
||||
def send(self, to: str, smtp: EmailOptions) -> SMTPResponse:
|
||||
msg = message.EmailMessage()
|
||||
msg["Subject"] = self.subject
|
||||
msg["From"] = self.mail_from
|
||||
msg["To"] = to
|
||||
msg.add_alternative(self.html, subtype="html")
|
||||
|
||||
if smtp.ssl:
|
||||
with smtplib.SMTP_SSL(smtp.host, smtp.port) as server:
|
||||
server.login(smtp.username, smtp.password)
|
||||
errors = server.send_message(msg)
|
||||
else:
|
||||
with smtplib.SMTP(smtp.host, smtp.port) as server:
|
||||
if smtp.tls:
|
||||
server.starttls()
|
||||
if smtp.username and smtp.password:
|
||||
server.login(smtp.username, smtp.password)
|
||||
errors = server.send_message(msg)
|
||||
|
||||
return SMTPResponse(errors == {}, "Message Sent", errors=errors)
|
||||
|
||||
|
||||
class ABCEmailSender(ABC):
|
||||
|
@ -16,26 +59,35 @@ class ABCEmailSender(ABC):
|
|||
|
||||
|
||||
class DefaultEmailSender(ABCEmailSender, BaseService):
|
||||
"""
|
||||
DefaultEmailSender is the default email sender for Mealie. It uses the SMTP settings
|
||||
from the config file to send emails via the python standard library. It supports
|
||||
both TLS and SSL connections.
|
||||
"""
|
||||
|
||||
def send(self, email_to: str, subject: str, html: str) -> bool:
|
||||
message = emails.Message(
|
||||
message = Message(
|
||||
subject=subject,
|
||||
html=html,
|
||||
mail_from=(self.settings.SMTP_FROM_NAME, self.settings.SMTP_FROM_EMAIL),
|
||||
)
|
||||
|
||||
smtp_options: dict[str, str | bool] = {"host": self.settings.SMTP_HOST, "port": self.settings.SMTP_PORT}
|
||||
if self.settings.SMTP_AUTH_STRATEGY.upper() == "TLS":
|
||||
smtp_options["tls"] = True
|
||||
if self.settings.SMTP_AUTH_STRATEGY.upper() == "SSL":
|
||||
smtp_options["ssl"] = True
|
||||
smtp_options = EmailOptions(
|
||||
self.settings.SMTP_HOST,
|
||||
int(self.settings.SMTP_PORT),
|
||||
tls=self.settings.SMTP_AUTH_STRATEGY.upper() == "TLS",
|
||||
ssl=self.settings.SMTP_AUTH_STRATEGY.upper() == "SSL",
|
||||
)
|
||||
|
||||
if self.settings.SMTP_USER:
|
||||
smtp_options["user"] = self.settings.SMTP_USER
|
||||
smtp_options.username = self.settings.SMTP_USER
|
||||
if self.settings.SMTP_PASSWORD:
|
||||
smtp_options["password"] = self.settings.SMTP_PASSWORD
|
||||
response: SMTPResponse = message.send(to=email_to, smtp=smtp_options)
|
||||
logger.info(f"send email result: {response}")
|
||||
smtp_options.password = self.settings.SMTP_PASSWORD
|
||||
|
||||
response = message.send(to=email_to, smtp=smtp_options)
|
||||
self.logger.info(f"send email result: {response}")
|
||||
|
||||
if not response.success:
|
||||
logger.error(f"send email error: {response.error}")
|
||||
self.logger.error(f"send email error: {response}")
|
||||
|
||||
return response.status_code in [250]
|
||||
return response.success
|
||||
|
|
99
poetry.lock
generated
99
poetry.lock
generated
|
@ -161,14 +161,6 @@ jupyter = ["ipython (>=7.8.0)", "tokenize-rt (>=3.2.0)"]
|
|||
python2 = ["typed-ast (>=1.4.3)"]
|
||||
uvloop = ["uvloop (>=0.15.2)"]
|
||||
|
||||
[[package]]
|
||||
name = "cachetools"
|
||||
version = "5.2.0"
|
||||
description = "Extensible memoizing collections and decorators"
|
||||
category = "main"
|
||||
optional = false
|
||||
python-versions = "~=3.7"
|
||||
|
||||
[[package]]
|
||||
name = "certifi"
|
||||
version = "2022.6.15"
|
||||
|
@ -196,14 +188,6 @@ category = "dev"
|
|||
optional = false
|
||||
python-versions = ">=3.6.1"
|
||||
|
||||
[[package]]
|
||||
name = "chardet"
|
||||
version = "5.0.0"
|
||||
description = "Universal encoding detector for Python 3"
|
||||
category = "main"
|
||||
optional = false
|
||||
python-versions = ">=3.6"
|
||||
|
||||
[[package]]
|
||||
name = "charset-normalizer"
|
||||
version = "2.1.0"
|
||||
|
@ -265,26 +249,6 @@ python-versions = ">=3.6.3,<4.0.0"
|
|||
click = ">=7.1.2"
|
||||
coverage = ">=5.5"
|
||||
|
||||
[[package]]
|
||||
name = "cssselect"
|
||||
version = "1.1.0"
|
||||
description = "cssselect parses CSS3 Selectors and translates them to XPath 1.0"
|
||||
category = "main"
|
||||
optional = false
|
||||
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
|
||||
|
||||
[[package]]
|
||||
name = "cssutils"
|
||||
version = "2.4.2"
|
||||
description = "A CSS Cascading Style Sheets library for Python"
|
||||
category = "main"
|
||||
optional = false
|
||||
python-versions = ">=3.7"
|
||||
|
||||
[package.extras]
|
||||
docs = ["jaraco.packaging (>=9)", "rst.linker (>=1.9)", "sphinx"]
|
||||
testing = ["cssselect", "importlib-resources", "jaraco.test (>=5.1)", "lxml", "mock", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=1.0.1)", "pytest-flake8", "pytest-mypy (>=0.9.1)"]
|
||||
|
||||
[[package]]
|
||||
name = "dill"
|
||||
version = "0.3.5.1"
|
||||
|
@ -319,22 +283,6 @@ six = ">=1.9.0"
|
|||
gmpy = ["gmpy"]
|
||||
gmpy2 = ["gmpy2"]
|
||||
|
||||
[[package]]
|
||||
name = "emails"
|
||||
version = "0.6"
|
||||
description = "Modern python library for emails."
|
||||
category = "main"
|
||||
optional = false
|
||||
python-versions = "*"
|
||||
|
||||
[package.dependencies]
|
||||
chardet = "*"
|
||||
cssutils = "*"
|
||||
lxml = "*"
|
||||
premailer = "*"
|
||||
python-dateutil = "*"
|
||||
requests = "*"
|
||||
|
||||
[[package]]
|
||||
name = "extruct"
|
||||
version = "0.13.0"
|
||||
|
@ -918,25 +866,6 @@ pyyaml = ">=5.1"
|
|||
toml = "*"
|
||||
virtualenv = ">=20.0.8"
|
||||
|
||||
[[package]]
|
||||
name = "premailer"
|
||||
version = "3.10.0"
|
||||
description = "Turns CSS blocks into style attributes"
|
||||
category = "main"
|
||||
optional = false
|
||||
python-versions = "*"
|
||||
|
||||
[package.dependencies]
|
||||
cachetools = "*"
|
||||
cssselect = "*"
|
||||
cssutils = "*"
|
||||
lxml = "*"
|
||||
requests = "*"
|
||||
|
||||
[package.extras]
|
||||
dev = ["black", "flake8", "therapist", "tox", "twine", "wheel"]
|
||||
test = ["mock", "nose"]
|
||||
|
||||
[[package]]
|
||||
name = "psycopg2-binary"
|
||||
version = "2.9.3"
|
||||
|
@ -1156,7 +1085,7 @@ testing = ["fields", "hunter", "process-tests", "pytest-xdist", "six", "virtuale
|
|||
name = "python-dateutil"
|
||||
version = "2.8.2"
|
||||
description = "Extensions to the standard Python datetime module"
|
||||
category = "main"
|
||||
category = "dev"
|
||||
optional = false
|
||||
python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7"
|
||||
|
||||
|
@ -1676,7 +1605,7 @@ pgsql = ["psycopg2-binary"]
|
|||
[metadata]
|
||||
lock-version = "1.1"
|
||||
python-versions = "^3.10"
|
||||
content-hash = "0beb3c09adcd4531955bc80e6d48a6cafd0b7c25d3310a78c568633a9acaafda"
|
||||
content-hash = "1d7f2c0315f317738d7976796e88f63468bc98068cf8db5772da8ca1b28fc0e4"
|
||||
|
||||
[metadata.files]
|
||||
aiofiles = [
|
||||
|
@ -1735,10 +1664,6 @@ black = [
|
|||
{file = "black-21.12b0-py3-none-any.whl", hash = "sha256:a615e69ae185e08fdd73e4715e260e2479c861b5740057fde6e8b4e3b7dd589f"},
|
||||
{file = "black-21.12b0.tar.gz", hash = "sha256:77b80f693a569e2e527958459634f18df9b0ba2625ba4e0c2d5da5be42e6f2b3"},
|
||||
]
|
||||
cachetools = [
|
||||
{file = "cachetools-5.2.0-py3-none-any.whl", hash = "sha256:f9f17d2aec496a9aa6b76f53e3b614c965223c061982d434d160f930c698a9db"},
|
||||
{file = "cachetools-5.2.0.tar.gz", hash = "sha256:6a94c6402995a99c3970cc7e4884bb60b4a8639938157eeed436098bf9831757"},
|
||||
]
|
||||
certifi = [
|
||||
{file = "certifi-2022.6.15-py3-none-any.whl", hash = "sha256:fe86415d55e84719d75f8b69414f6438ac3547d2078ab91b67e779ef69378412"},
|
||||
{file = "certifi-2022.6.15.tar.gz", hash = "sha256:84c85a9078b11105f04f3036a9482ae10e4621616db313fe045dd24743a0820d"},
|
||||
|
@ -1813,10 +1738,6 @@ cfgv = [
|
|||
{file = "cfgv-3.3.1-py2.py3-none-any.whl", hash = "sha256:c6a0883f3917a037485059700b9e75da2464e6c27051014ad85ba6aaa5884426"},
|
||||
{file = "cfgv-3.3.1.tar.gz", hash = "sha256:f5a830efb9ce7a445376bb66ec94c638a9787422f96264c98edc6bdeed8ab736"},
|
||||
]
|
||||
chardet = [
|
||||
{file = "chardet-5.0.0-py3-none-any.whl", hash = "sha256:d3e64f022d254183001eccc5db4040520c0f23b1a3f33d6413e099eb7f126557"},
|
||||
{file = "chardet-5.0.0.tar.gz", hash = "sha256:0368df2bfd78b5fc20572bb4e9bb7fb53e2c094f60ae9993339e8671d0afb8aa"},
|
||||
]
|
||||
charset-normalizer = [
|
||||
{file = "charset-normalizer-2.1.0.tar.gz", hash = "sha256:575e708016ff3a5e3681541cb9d79312c416835686d054a23accb873b254f413"},
|
||||
{file = "charset_normalizer-2.1.0-py3-none-any.whl", hash = "sha256:5189b6f22b01957427f35b6a08d9a0bc45b46d3788ef5a92e978433c7a35f8a5"},
|
||||
|
@ -1891,14 +1812,6 @@ coveragepy-lcov = [
|
|||
{file = "coveragepy-lcov-0.1.2.tar.gz", hash = "sha256:db6ad0d255d3a8041d30e797a9ec69c77c5963694a6b02a00907b56db7b882a3"},
|
||||
{file = "coveragepy_lcov-0.1.2-py3-none-any.whl", hash = "sha256:7c1e454ada324a1f47fd7cd2de2c6349b9822cc79691b313ed10370e7ce1b08b"},
|
||||
]
|
||||
cssselect = [
|
||||
{file = "cssselect-1.1.0-py2.py3-none-any.whl", hash = "sha256:f612ee47b749c877ebae5bb77035d8f4202c6ad0f0fc1271b3c18ad6c4468ecf"},
|
||||
{file = "cssselect-1.1.0.tar.gz", hash = "sha256:f95f8dedd925fd8f54edb3d2dfb44c190d9d18512377d3c1e2388d16126879bc"},
|
||||
]
|
||||
cssutils = [
|
||||
{file = "cssutils-2.4.2-py3-none-any.whl", hash = "sha256:17e5ba0de70a672cd1cd2de47fd756bd6bce12585acd91447bde7be1d7a6c5c2"},
|
||||
{file = "cssutils-2.4.2.tar.gz", hash = "sha256:877818bfa9668cc535773f46e6b6a46de28436191211741b3f7b4aaa64d9afbb"},
|
||||
]
|
||||
dill = [
|
||||
{file = "dill-0.3.5.1-py2.py3-none-any.whl", hash = "sha256:33501d03270bbe410c72639b350e941882a8b0fd55357580fbc873fba0c59302"},
|
||||
{file = "dill-0.3.5.1.tar.gz", hash = "sha256:d75e41f3eff1eee599d738e76ba8f4ad98ea229db8b085318aa2b3333a208c86"},
|
||||
|
@ -1911,10 +1824,6 @@ ecdsa = [
|
|||
{file = "ecdsa-0.18.0-py2.py3-none-any.whl", hash = "sha256:80600258e7ed2f16b9aa1d7c295bd70194109ad5a30fdee0eaeefef1d4c559dd"},
|
||||
{file = "ecdsa-0.18.0.tar.gz", hash = "sha256:190348041559e21b22a1d65cee485282ca11a6f81d503fddb84d5017e9ed1e49"},
|
||||
]
|
||||
emails = [
|
||||
{file = "emails-0.6-py2.py3-none-any.whl", hash = "sha256:72c1e3198075709cc35f67e1b49e2da1a2bc087e9b444073db61a379adfb7f3c"},
|
||||
{file = "emails-0.6.tar.gz", hash = "sha256:a4c2d67ea8b8831967a750d8edc6e77040d7693143fe280e6d2a367d9c36ff88"},
|
||||
]
|
||||
extruct = [
|
||||
{file = "extruct-0.13.0-py2.py3-none-any.whl", hash = "sha256:fe19b9aefdb4dfbf828c2b082b81a363a03a44c7591c2d6b62ca225cb8f8c0be"},
|
||||
{file = "extruct-0.13.0.tar.gz", hash = "sha256:50a5b5bac4c5e19ecf682bf63a28fde0b1bb57433df7057371f60b58c94a2c64"},
|
||||
|
@ -2421,10 +2330,6 @@ pre-commit = [
|
|||
{file = "pre_commit-2.19.0-py2.py3-none-any.whl", hash = "sha256:10c62741aa5704faea2ad69cb550ca78082efe5697d6f04e5710c3c229afdd10"},
|
||||
{file = "pre_commit-2.19.0.tar.gz", hash = "sha256:4233a1e38621c87d9dda9808c6606d7e7ba0e087cd56d3fe03202a01d2919615"},
|
||||
]
|
||||
premailer = [
|
||||
{file = "premailer-3.10.0-py2.py3-none-any.whl", hash = "sha256:021b8196364d7df96d04f9ade51b794d0b77bcc19e998321c515633a2273be1a"},
|
||||
{file = "premailer-3.10.0.tar.gz", hash = "sha256:d1875a8411f5dc92b53ef9f193db6c0f879dc378d618e0ad292723e388bfe4c2"},
|
||||
]
|
||||
psycopg2-binary = [
|
||||
{file = "psycopg2-binary-2.9.3.tar.gz", hash = "sha256:761df5313dc15da1502b21453642d7599d26be88bff659382f8f9747c7ebea4e"},
|
||||
{file = "psycopg2_binary-2.9.3-cp310-cp310-macosx_10_14_x86_64.macosx_10_9_intel.macosx_10_9_x86_64.macosx_10_10_intel.macosx_10_10_x86_64.whl", hash = "sha256:539b28661b71da7c0e428692438efbcd048ca21ea81af618d845e06ebfd29478"},
|
||||
|
|
|
@ -33,7 +33,6 @@ apprise = "^0.9.6"
|
|||
recipe-scrapers = "^14.20.0"
|
||||
psycopg2-binary = {version = "^2.9.1", optional = true}
|
||||
gunicorn = "^20.1.0"
|
||||
emails = "^0.6"
|
||||
python-ldap = "^3.3.1"
|
||||
pydantic = "^1.10.2"
|
||||
tzdata = "^2021.5"
|
||||
|
|
Loading…
Reference in a new issue