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

48 statements  

« prev     ^ index     » next       coverage.py v7.14.1, created at 2026-05-29 04:01 +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 datetime import UTC 

11from pathlib import Path 

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=UTC) 

47 

48 footer = EmailFooter( 

49 timezone_name="UTC", 

50 unsubscribe_info=UnsubscribeInfo( 

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

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

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

54 ), 

55 ) 

56 

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

58 

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

60 email_class: type[EmailBase] = klass 

61 if filter_regex.fullmatch(email_class.__name__): 

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

63 

64 

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

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

67 subject_line = email.get_subject_line(loc_context) 

68 preview_line = email.get_preview_line(loc_context) 

69 blocks = email.get_body_blocks(loc_context) 

70 

71 outdir.mkdir(exist_ok=True) 

72 

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

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

75 

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

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

78 html = render_html_body( 

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

80 ) 

81 html_path.write_text(html) 

82 

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

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

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

86 plaintext_path.write_text(plaintext) 

87 

88 

89if __name__ == "__main__": 

90 main()