Coverage for opt/mealie/lib/python3.12/site-packages/mealie/services/email/email_service.py: 81%

40 statements  

« prev     ^ index     » next       coverage.py v7.10.6, created at 2025-11-25 15:32 +0000

1from pathlib import Path 1a

2 

3from jinja2 import Template 1a

4from pydantic import BaseModel 1a

5 

6from mealie.core.root_logger import get_logger 1a

7from mealie.lang import local_provider 1a

8from mealie.lang.providers import Translator 1a

9from mealie.services._base_service import BaseService 1a

10 

11from .email_senders import ABCEmailSender, DefaultEmailSender 1a

12 

13CWD = Path(__file__).parent 1a

14 

15logger = get_logger() 1a

16 

17 

18class EmailTemplate(BaseModel): 1a

19 subject: str 1a

20 header_text: str 1a

21 message_top: str 1a

22 message_bottom: str 1a

23 button_link: str 1a

24 button_text: str 1a

25 

26 def render_html(self, template: Path) -> str: 1a

27 tmpl = Template(template.read_text()) 

28 

29 return tmpl.render(data=self.model_dump()) 

30 

31 

32class EmailService(BaseService): 1a

33 def __init__(self, sender: ABCEmailSender | None = None, locale: str | None = None) -> None: 1a

34 self.templates_dir = CWD / "templates" 

35 self.default_template = self.templates_dir / "default.html" 

36 self.sender: ABCEmailSender = sender or DefaultEmailSender() 

37 self.translator: Translator = local_provider(locale) 

38 

39 super().__init__() 

40 

41 def send_email(self, email_to: str, data: EmailTemplate) -> bool: 1a

42 if not self.settings.SMTP_ENABLE: 42 ↛ 45line 42 didn't jump to line 45 because the condition on line 42 was always true

43 return False 

44 

45 return self.sender.send(email_to, data.subject, data.render_html(self.default_template)) 

46 

47 def send_forgot_password(self, address: str, reset_password_url: str) -> bool: 1a

48 forgot_password = EmailTemplate( 

49 subject=self.translator.t("emails.password.subject"), 

50 header_text=self.translator.t("emails.password.header_text"), 

51 message_top=self.translator.t("emails.password.message_top"), 

52 message_bottom=self.translator.t("emails.password.message_bottom"), 

53 button_link=reset_password_url, 

54 button_text=self.translator.t("emails.password.button_text"), 

55 ) 

56 return self.send_email(address, forgot_password) 

57 

58 def send_invitation(self, address: str, invitation_url: str) -> bool: 1a

59 invitation = EmailTemplate( 

60 subject=self.translator.t("emails.invitation.subject"), 

61 header_text=self.translator.t("emails.invitation.header_text"), 

62 message_top=self.translator.t("emails.invitation.message_top"), 

63 message_bottom=self.translator.t("emails.invitation.message_bottom"), 

64 button_link=invitation_url, 

65 button_text=self.translator.t("emails.invitation.button_text"), 

66 ) 

67 return self.send_email(address, invitation) 

68 

69 def send_test_email(self, address: str) -> bool: 1a

70 test_email = EmailTemplate( 

71 subject=self.translator.t("emails.test.subject"), 

72 header_text=self.translator.t("emails.test.header_text"), 

73 message_top=self.translator.t("emails.test.message_top"), 

74 message_bottom=self.translator.t("emails.test.message_bottom"), 

75 button_link=self.settings.BASE_URL, 

76 button_text=self.translator.t("emails.test.button_text"), 

77 ) 

78 return self.send_email(address, test_email)