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
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
1"""
2A simple config system
3"""
4import os
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]
73config = {}
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")
85 value = os.getenv(name)
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
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)
110 config[name] = value
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")
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")
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")