Coverage for app / backend / src / couchers / email / dump_emails.py: 0%

48 statements  

« prev     ^ index     » next       coverage.py v7.13.4, created at 2026-02-24 17:34 +0000

1""" 

2Dumps emails subjects and html/plaintext bodies with dummy data. 

3""" 

4 

5import inspect 

6import re 

7import sys 

8from argparse import ArgumentParser 

9from dataclasses import dataclass 

10from pathlib import Path 

11from zoneinfo import ZoneInfo 

12 

13import couchers.email.emails 

14from couchers.email.emails import EmailBase 

15from couchers.email.rendering import ( 

16 EmailFooter, 

17 UnsubscribeInfo, 

18 UnsubscribeLink, 

19 render_html_body, 

20 render_plaintext_body, 

21) 

22from couchers.i18n import LocalizationContext 

23from couchers.templating import template_folder 

24 

25 

26@dataclass 

27class CommandLineArgs: 

28 filter: str 

29 outdir: Path 

30 locale: str 

31 

32 @staticmethod 

33 def parse(args: list[str]) -> CommandLineArgs: 

34 parser = ArgumentParser(description=__doc__) 

35 parser.add_argument("--filter", type=str, default="*", help="A filter for email classes to dump.") 

36 parser.add_argument( 

37 "--outdir", type=Path, default=template_folder, help="The directory to write email bodies to." 

38 ) 

39 parser.add_argument("--locale", type=str, default="en", help="The locale to use.") 

40 parsed_args = parser.parse_args(args) 

41 return CommandLineArgs(**parsed_args.__dict__) 

42 

43 

44def main() -> None: 

45 args = CommandLineArgs.parse(sys.argv[1:]) 

46 loc_context = LocalizationContext(locale=args.locale, timezone=ZoneInfo("Etc/UTC")) 

47 

48 footer = EmailFooter( 

49 unsubscribe_info=UnsubscribeInfo( 

50 manage_notifications_url="https://example.com/manage-notifications", 

51 do_not_email_url="https://example.com/do-not-email", 

52 topic_action_link=UnsubscribeLink(text="topic-action", url="https://example.com/unsubscribe"), 

53 ) 

54 ) 

55 

56 filter_regex = re.compile(re.escape(args.filter).replace(r"\*", ".*?")) 

57 

58 for _, klass in inspect.getmembers(couchers.email.emails, lambda o: inspect.isclass(o) and o.__base__ == EmailBase): 

59 email_class: type[EmailBase] = klass 

60 if filter_regex.fullmatch(email_class.__name__): 

61 dump_email(email_class.dummy_data(), footer, loc_context, args.outdir) 

62 

63 

64def dump_email(email: EmailBase, footer: EmailFooter, loc_context: LocalizationContext, outdir: Path) -> None: 

65 """Dumps an email's subject and plaintext+html body to a file.""" 

66 subject_line = email.get_subject_line(loc_context) 

67 preview_line = email.get_preview_line(loc_context) 

68 blocks = email.get_body_blocks(loc_context) 

69 

70 outdir.mkdir(exist_ok=True) 

71 

72 print(f"Dumping email class {email.__class__.__name__}") 

73 print(f" Subject: {subject_line}") 

74 

75 html_path = outdir / f"{email.__class__.__name__}.html" 

76 print(f" Rendering html to {html_path}...") 

77 html = render_html_body( 

78 subject=subject_line, preview=preview_line, blocks=blocks, footer=footer, loc_context=loc_context 

79 ) 

80 html_path.write_text(html) 

81 

82 plaintext_path = outdir / f"{email.__class__.__name__}.txt" 

83 print(f" Rendering plaintext to {plaintext_path}...") 

84 plaintext = render_plaintext_body(blocks=blocks, footer=footer, loc_context=loc_context) 

85 plaintext_path.write_text(plaintext) 

86 

87 

88if __name__ == "__main__": 

89 main()