Skip to content

Commit 0d40bcf

Browse files
committed
Change settings precedence so env vars override settings.py
Swap the loading order so environment variables are applied after settings.py, following the 12-factor convention. Extract timezone setup into _apply_timezone() so it runs after all settings are loaded.
1 parent 0cf51dd commit 0d40bcf

File tree

6 files changed

+10
-7
lines changed

6 files changed

+10
-7
lines changed

plain/plain/runtime/README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@ Plain's built-in settings are defined in [`global_settings.py`](./global_setting
6060

6161
## Environment variables
6262

63-
Type-annotated settings can be loaded from environment variables using a `PLAIN_` prefix.
63+
Type-annotated settings can be loaded from environment variables using a `PLAIN_` prefix. Environment variables take the highest precedence — they override values set in `settings.py`, so you can change behavior per deployment without editing code.
6464

6565
For example, if you define a setting with a type annotation:
6666

plain/plain/runtime/user_settings.py

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -74,10 +74,12 @@ def _setup(self) -> None:
7474

7575
# Load default settings from installed packages
7676
self._load_default_settings(mod)
77-
# Load environment settings
78-
self._load_env_settings()
7977
# Load explicit settings from the settings module
8078
self._load_explicit_settings(mod)
79+
# Load environment settings (last, so env vars override settings.py)
80+
self._load_env_settings()
81+
# Apply timezone after all settings are loaded
82+
self._apply_timezone()
8183
# Check for any required settings that are missing
8284
self._check_required_settings()
8385
# Check for any collected errors
@@ -181,6 +183,7 @@ def _load_explicit_settings(self, settings_module: types.ModuleType) -> None:
181183
f"Unknown setting '{setting}'. Custom settings must start with '{_CUSTOM_SETTINGS_PREFIX}'."
182184
)
183185

186+
def _apply_timezone(self) -> None:
184187
if hasattr(time, "tzset") and self.TIME_ZONE:
185188
zoneinfo_root = Path("/usr/share/zoneinfo")
186189
zone_info_file = zoneinfo_root.joinpath(*self.TIME_ZONE.split("/"))

plain/tests/app/settings.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,4 +8,4 @@
88
]
99

1010
EXPLICIT_SETTING = "explicitly changed"
11-
ENV_OVERRIDDEN_SETTING = "explicitly overridden"
11+
EXPLICIT_OVERRIDDEN_SETTING = "explicit value"
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
DEFAULT_SETTING: str = "unchanged default"
22
EXPLICIT_SETTING: str = "unchanged explicit"
33
ENV_SETTING: int = 0
4-
ENV_OVERRIDDEN_SETTING: str = "unchanged env overridden"
4+
EXPLICIT_OVERRIDDEN_SETTING: str = "default value"
55
NULLABLE_SETTING: int | None = None

plain/tests/conftest.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,5 +3,5 @@
33

44
def pytest_configure(config):
55
os.environ["PLAIN_ENV_SETTING"] = "1"
6-
os.environ["PLAIN_ENV_OVERRIDDEN_SETTING"] = "env value"
6+
os.environ["PLAIN_EXPLICIT_OVERRIDDEN_SETTING"] = "env value"
77
os.environ["PLAIN_UNDEFINED_SETTING"] = "not used"

plain/tests/test_runtime.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,4 +6,4 @@ def test_user_settings():
66
assert settings.DEFAULT_SETTING == "unchanged default"
77
assert settings.EXPLICIT_SETTING == "explicitly changed"
88
assert settings.ENV_SETTING == 1
9-
assert settings.ENV_OVERRIDDEN_SETTING == "explicitly overridden"
9+
assert settings.EXPLICIT_OVERRIDDEN_SETTING == "env value"

0 commit comments

Comments
 (0)