From b26ce25719f9478cbc02cd0f0ec7e63c50ec62d3 Mon Sep 17 00:00:00 2001 From: grillazz Date: Fri, 20 Dec 2024 18:06:21 +0100 Subject: [PATCH 1/3] add singleton meta with no args --- app/utils/singleton.py | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/app/utils/singleton.py b/app/utils/singleton.py index 0c94176..85df17e 100644 --- a/app/utils/singleton.py +++ b/app/utils/singleton.py @@ -16,3 +16,21 @@ def __call__(cls, *args, **kwargs): instance = super().__call__(*args, **kwargs) cls._instances[cls] = instance return cls._instances[cls] + + +class SingletonMetaNoArgs(type): + """ + Singleton metaclass for classes without parameters on constructor, + for compatibility with FastApi Depends() function. + """ + + _instances = {} + + _lock: Lock = Lock() + + def __call__(cls): + with cls._lock: + if cls not in cls._instances: + instance = super().__call__() + cls._instances[cls] = instance + return cls._instances[cls] \ No newline at end of file From 4a0944fec6d3d71cb60d3b5cfa1cba3ac332f829 Mon Sep 17 00:00:00 2001 From: grillazz Date: Fri, 20 Dec 2024 18:06:29 +0100 Subject: [PATCH 2/3] smtp config --- app/config.py | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/app/config.py b/app/config.py index 9ad9a56..5c617d3 100644 --- a/app/config.py +++ b/app/config.py @@ -1,10 +1,17 @@ import os -from pydantic import PostgresDsn, RedisDsn, computed_field +from pydantic import PostgresDsn, RedisDsn, computed_field, BaseModel from pydantic_core import MultiHostUrl from pydantic_settings import BaseSettings, SettingsConfigDict +class SMTPConfig(BaseModel): + server: str = os.getenv("EMAIL_HOST", "smtp_server") + port: int = os.getenv("EMAIL_PORT", 587) + username: str = os.getenv("EMAIL_HOST_USER", "smtp_user") + password: str = os.getenv("EMAIL_HOST_PASSWORD", "smtp_password") + + class Settings(BaseSettings): model_config = SettingsConfigDict( env_file=".env", env_ignore_empty=True, extra="ignore" @@ -12,6 +19,8 @@ class Settings(BaseSettings): jwt_algorithm: str = os.getenv("JWT_ALGORITHM") jwt_expire: int = os.getenv("JWT_EXPIRE") + smtp: SMTPConfig = SMTPConfig() + REDIS_HOST: str REDIS_PORT: int REDIS_DB: str From 189158d31f72c7a81b87153dae3c742db569b519 Mon Sep 17 00:00:00 2001 From: grillazz Date: Fri, 20 Dec 2024 18:06:37 +0100 Subject: [PATCH 3/3] init smtp service --- app/services/smtp.py | 54 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 54 insertions(+) create mode 100644 app/services/smtp.py diff --git a/app/services/smtp.py b/app/services/smtp.py new file mode 100644 index 0000000..d4a2ce0 --- /dev/null +++ b/app/services/smtp.py @@ -0,0 +1,54 @@ +import smtplib +from email.mime.multipart import MIMEMultipart +from email.mime.text import MIMEText + +from app.config import settings as global_settings + +from fastapi.templating import Jinja2Templates + +from pydantic import EmailStr + +from app.utils.logging import AppLogger +from app.utils.singleton import SingletonMetaNoArgs + + +logger = AppLogger().get_logger() + + +class SMTPEmailService(metaclass=SingletonMetaNoArgs): + def __init__(self): + self.server = smtplib.SMTP( + global_settings.smtp.server, global_settings.smtp.port + ) + self.server.starttls() + self.server.login(global_settings.smtp.username, global_settings.smtp.password) + self.templates = Jinja2Templates("templates") + + def send_email( + self, + sender: EmailStr, + recipients: list[EmailStr], + subject: str, + body_text: str = "", + body_html=None, + ): + msg = MIMEMultipart() + msg["From"] = sender + msg["To"] = ",".join(recipients) + msg["Subject"] = subject + msg.attach(MIMEText(body_text, "plain")) + if body_html: + msg.attach(MIMEText(body_html, "html")) + self.server.sendmail(sender, recipients, msg.as_string()) + + def send_template_email( + self, + recipients: list[EmailStr], + subject: str, + template: str = None, + context: dict = None, + sender: EmailStr = global_settings.smtp.from_email, + ): + template_str = self.templates.get_template(template) + body_html = template_str.render(context) + self.send_email(sender, recipients, subject, body_html=body_html)