Source code for duck.settings.settings
"""
Provides access to application settings.
"""
import os
import sys
from functools import lru_cache
from duck.exceptions.all import SettingsError
from duck.utils.importer import import_module_once
# Set default settings if not provided
os.environ.setdefault("DUCK_SETTINGS_MODULE", "web.settings")
SETTINGS_MODULE = os.environ.get("DUCK_SETTINGS_MODULE")
[docs]
class Settings(dict):
"""
A class for managing **Duck** settings.
This class extends the built-in `dict` to store settings in a dictionary-like format.
It also provides a custom representation of the settings for debugging and logging.
**Notes:**
- These settings can be altered at runtime once imported but the `SETTINGS` should be imported and
edited at top level before importing anything related to **Duck**.
**Example:**
```py
from duck.settings import SETTINGS
# Edit settings inplace .e.g.,
SETTINGS['ENABLE_HTTPS'] = True
from duck.app import App
app = App()
if __name__ == '__main__':
app.run()
```
"""
source = None
"""
The source of settings e.g., `web.settings`.
"""
[docs]
def reload(self):
"""
Re-execute the settings module and update this dict in-place.
Notes:
The settings module will be the latest one set in `DUCK_SETTINGS_MODULE`.
"""
import importlib
mod_str = os.environ.get("DUCK_SETTINGS_MODULE")
mod = sys.modules.get(mod_str, None)
mods = []
if mod:
importlib.reload_module(mod)
mods.append(mod)
else:
mod = import_module_once(mod_str)
mods.append(import_module_once("duck.etc.settings"))
mods.append(mod)
# Apply settings inplace
self.source = mod
for mod in mods:
for var in dir(mod):
if var.isupper():
# is a valid setting variable
self[var] = getattr(mod, var)
[docs]
def __repr__(self):
# Provide a more detailed and readable string representation of the Settings object
return (
"<" + f"{self.__class__.__name__} "
f"source={repr(self.source)}".replace('<', "[").replace('>', "]") + ">"
)
[docs]
@lru_cache
def settings_to_dict(settings_module: str) -> Settings:
"""
Converts a settings module to a dictionary.
Args:
settings_module (str): The path to the settings module.
Returns:
Settings: Settings object derived from a dictionary containing the settings.
Raises:
ImportError: If the settings module cannot be imported.
"""
settings_mod = import_module_once(settings_module)
settings = Settings({})
settings.source = settings_mod
for var in dir(settings_mod):
if var.isupper():
# is a valid setting variable
settings[var] = getattr(settings_mod, var)
return settings
[docs]
def get_combined_settings() -> Settings:
"""
Combines default and user settings into a single dictionary.
Reads the default settings from `duck.etc.settings` and attempts to
read user settings from a `settings` module in the current directory.
Returns:
Settings: Settings object derived from a dictionary containing the settings.
Raises:
SettingsError: If there's an error loading user settings.
"""
default_settings = settings_to_dict("duck.etc.settings")
try:
user_settings = settings_to_dict(SETTINGS_MODULE)
except Exception as e:
raise SettingsError(
f"Error loading Duck settings module, ensure environment variable DUCK_SETTINGS_MODULE is set correctly: {e}."
) from e
# Update the default settings with custom ones.
default_settings.update(user_settings)
settings = Settings(default_settings)
settings.source = user_settings.source
return settings
# Set and load important settings, objects, etc.
SETTINGS: Settings = get_combined_settings()
# Set Django specific configurations
if not SETTINGS_MODULE.startswith("web") and SETTINGS['DJANGO_SETTINGS_MODULE'].startswith('web.backend.django.duckapp.duckapp.settings'):
# Duck settings module is external yet the Django settings module is default.
try:
# Try to resolve the settings
import_module_once(SETTINGS['DJANGO_SETTINGS_MODULE'])
except ImportError:
# We need to fix the Django settings module
SETTINGS['DJANGO_SETTINGS_MODULE'] = SETTINGS_MODULE.rsplit('.', 1)[0] + ".backend.django.duckapp.duckapp.settings"
# Set django settings module
os.environ.setdefault('DJANGO_SETTINGS_MODULE', SETTINGS['DJANGO_SETTINGS_MODULE'])
if (os.getenv("DUCK_USE_DJANGO", None) == "true"
or "-dj" in sys.argv or "--use-django" in sys.argv):
SETTINGS["USE_DJANGO"] = True