Coverage for src/couchers/config.py: 56%

Shortcuts on this page

r m x   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

43 statements  

1""" 

2A simple config system 

3""" 

4import os 

5 

6# Allowed config options, as tuples (name, type, default). 

7# All fields are required 

8CONFIG_OPTIONS = [ 

9 # Whether we're in dev mode 

10 ("DEV", bool), 

11 # Whether we're `api` mode (answering API queries) or `scheduler` (scheduling background jobs), or `worker` 

12 # (servicing background jobs). Can also be set to `all` to do all three simultaneously 

13 ("ROLE", ["api", "scheduler", "worker", "all"], "all"), 

14 # Version string 

15 ("VERSION", str, "unknown"), 

16 # Base URL 

17 ("BASE_URL", str), 

18 # Used to generate a variety of secrets 

19 ("SECRET", bytes), 

20 # Domain that cookies should set as their domain value 

21 ("COOKIE_DOMAIN", str), 

22 # SQLAlchemy database connection string 

23 ("DATABASE_CONNECTION_STRING", str), 

24 # Whether to try adding dummy data 

25 ("ADD_DUMMY_DATA", bool), 

26 # Donations 

27 ("ENABLE_DONATIONS", bool), 

28 ("STRIPE_API_KEY", str), 

29 ("STRIPE_WEBHOOK_SECRET", str), 

30 ("STRIPE_RECURRING_PRODUCT_ID", str), 

31 # SMS 

32 ("ENABLE_SMS", bool), 

33 ("SMS_SENDER_ID", str), 

34 # Email 

35 ("ENABLE_EMAIL", bool), 

36 # Sender name for outgoing notification emails e.g. "Couchers.org" 

37 ("NOTIFICATION_EMAIL_SENDER", str), 

38 # Sender email, e.g. "notify@couchers.org" 

39 ("NOTIFICATION_EMAIL_ADDRESS", str), 

40 # An optional prefix for email subject, e.g. [STAGING] 

41 ("NOTIFICATION_EMAIL_PREFIX", str, ""), 

42 # Address to send emails about reported users 

43 ("REPORTS_EMAIL_RECIPIENT", str), 

44 # Address to send contributor forms when users sign up/fill the form 

45 ("CONTRIBUTOR_FORM_EMAIL_RECIPIENT", str), 

46 # SMTP settings 

47 ("SMTP_HOST", str), 

48 ("SMTP_PORT", int), 

49 ("SMTP_USERNAME", str), 

50 ("SMTP_PASSWORD", str), 

51 # Media server 

52 ("ENABLE_MEDIA", bool), 

53 ("MEDIA_SERVER_SECRET_KEY", bytes), 

54 ("MEDIA_SERVER_BEARER_TOKEN", str), 

55 ("MEDIA_SERVER_BASE_URL", str), 

56 # Bug reporting tool 

57 ("BUG_TOOL_ENABLED", bool), 

58 ("BUG_TOOL_GITHUB_REPO", str), 

59 ("BUG_TOOL_GITHUB_USERNAME", str), 

60 ("BUG_TOOL_GITHUB_TOKEN", str), 

61 # Sentry 

62 ("SENTRY_ENABLED", bool), 

63 ("SENTRY_URL", str), 

64 # Mailchimp (mailing list) 

65 ("MAILCHIMP_ENABLED", bool), 

66 ("MAILCHIMP_API_KEY", str), 

67 ("MAILCHIMP_DC", str), 

68 ("MAILCHIMP_LIST_ID", str), 

69 # Whether we're in test 

70 ("IN_TEST", bool, "0"), 

71] 

72 

73config = {} 

74 

75for config_option in CONFIG_OPTIONS: 

76 if len(config_option) == 2: 

77 name, type_ = config_option 

78 optional = False 

79 elif len(config_option) == 3: 

80 name, type_, default_value = config_option 

81 optional = True 

82 else: 

83 raise ValueError("Invalid CONFIG_OPTIONS") 

84 

85 value = os.getenv(name) 

86 

87 if not value: 

88 if not optional: 

89 # config value not set - will cause a KeyError when trying 

90 # to access it. 

91 continue 

92 else: 

93 value = default_value 

94 

95 if type_ == bool: 

96 # 1 is true, 0 is false, everything else is illegal 

97 if value not in ["0", "1"]: 

98 raise ValueError(f'Invalid bool for {name}, need "0" or "1"') 

99 value = value == "1" 

100 elif type_ == bytes: 

101 # decode from hex 

102 value = bytes.fromhex(value) 

103 elif isinstance(type_, list): 

104 # list of allowed string values 

105 if value not in type_: 

106 raise ValueError(f'Invalid value for {name}, need one of {", ".join(type_)}') 

107 else: 

108 value = type_(value) 

109 

110 config[name] = value 

111 

112 

113## Config checks 

114def check_config(): 

115 for name, *_ in CONFIG_OPTIONS: 

116 if name not in config: 

117 raise ValueError(f"Required config value {name} not set") 

118 

119 if not config["DEV"]: 

120 # checks for prod 

121 if "https" not in config["BASE_URL"]: 

122 raise Exception("Production site must be over HTTPS") 

123 if not config["ENABLE_EMAIL"]: 

124 raise Exception("Production site must have email enabled") 

125 if not config["ENABLE_SMS"]: 

126 raise Exception("Production site must have SMS enabled") 

127 if config["IN_TEST"]: 

128 raise Exception("IN_TEST while not DEV") 

129 

130 if config["ENABLE_DONATIONS"]: 

131 if ( 

132 not config["STRIPE_API_KEY"] 

133 or not config["STRIPE_WEBHOOK_SECRET"] 

134 or not config["STRIPE_RECURRING_PRODUCT_ID"] 

135 ): 

136 raise Exception("No Stripe API key/recurring donation ID but donations enabled")