Coverage for lazy_settings/conf/__init__.py: 66%
128 statements
« prev ^ index » next coverage.py v7.8.0, created at 2025-05-11 03:57 +0330
« prev ^ index » next coverage.py v7.8.0, created at 2025-05-11 03:57 +0330
1"""
2Settings and configuration for Django.
4Read values from the module specified by the DJANGO_SETTINGS_MODULE environment
5variable, and then from django.conf.global_settings; see the global_settings.py
6for a list of all possible variables.
7"""
9import importlib
10import os
11from types import ModuleType
12from typing import Any, Self
14from lazy_settings.exceptions import ImproperlyConfigured
15from lazy_settings.utils.functional import LazyObject, empty
17ENVIRONMENT_VARIABLE = "SETTINGS_MODULE"
20class SettingsReference(str):
21 """
22 String subclass which references a current settings value. It's treated as
23 the value in memory but serializes to a settings.NAME attribute reference.
24 """
26 def __new__(self, value: Any, setting_name: str) -> Self:
27 return str.__new__(self, value)
29 def __init__(self, value: str, setting_name: str) -> None:
30 self.setting_name = setting_name
33class LazySettings(LazyObject["Settings | UserSettingsHolder"]):
34 """
35 A lazy proxy for either global Django settings or a custom settings object.
36 The user can manually configure settings prior to using them. Otherwise,
37 Django uses the settings module pointed to by DJANGO_SETTINGS_MODULE.
38 """
40 def _setup(self, name: str | None = None) -> None:
41 """
42 Load the settings module pointed to by the environment variable. This
43 is used the first time settings are needed, if the user hasn't
44 configured settings manually.
45 """
46 settings_module: str | None = os.environ.get(ENVIRONMENT_VARIABLE)
47 if not settings_module:
48 desc = ("setting %s" % name) if name else "settings"
49 raise ImproperlyConfigured(
50 "Requested %s, but settings are not configured. "
51 "You must either define the environment variable %s "
52 "or call settings.configure() before accessing settings."
53 % (desc, ENVIRONMENT_VARIABLE)
54 )
56 self._wrapped = Settings(settings_module)
58 def __repr__(self) -> str:
59 # Hardcode the class name as otherwise it yields 'Settings'.
60 if self._wrapped is empty:
61 return "<LazySettings [Unevaluated]>"
62 return '<LazySettings "%(settings_module)s">' % {
63 "settings_module": self._wrapped.SETTINGS_MODULE, # type: ignore[attr-defined]
64 }
66 def __getattr__(self, name: str) -> Any:
67 """Return the value of a setting and cache it in self.__dict__."""
68 if (_wrapped := self._wrapped) is empty:
69 self._setup(name)
70 _wrapped = self._wrapped
71 val = getattr(_wrapped, name)
73 self.__dict__[name] = val
74 return val
76 def __setattr__(self, name: str, value: Any) -> None:
77 """
78 Set the value of setting. Clear all cached values if _wrapped changes
79 (@override_settings does this) or clear single values when set.
80 """
81 if name == "_wrapped":
82 self.__dict__.clear()
83 else:
84 self.__dict__.pop(name, None)
85 super().__setattr__(name, value)
87 def __delattr__(self, name: str) -> None:
88 """Delete a setting and clear it from cache if needed."""
89 super().__delattr__(name)
90 self.__dict__.pop(name, None)
92 def __iter__(self):
93 if self._wrapped is empty:
94 raise ImproperlyConfigured(
95 "settings are not configured. "
96 "You must either define the environment variable %s "
97 "or call settings.configure() before accessing settings."
98 % (ENVIRONMENT_VARIABLE)
99 )
100 for setting in self._wrapped:
101 yield setting
103 def configure(self, **options) -> None:
104 """
105 Called to manually configure the settings.
106 """
107 if not self.configured:
108 raise RuntimeError("Settings already configured.")
109 holder = UserSettingsHolder()
110 for name, value in options.items():
111 if not name.isupper():
112 raise TypeError("Setting %r must be uppercase." % name)
113 setattr(holder, name, value)
114 self._wrapped = holder
116 @property
117 def configured(self) -> bool:
118 """Return True if the settings have already been configured."""
119 return self._wrapped is not empty
121 def register(self, settings_module: ModuleType) -> None:
122 if self._wrapped is empty:
123 self._setup()
124 self._wrapped.register(settings_module) # type: ignore[attr-defined]
126 def clear(self) -> None:
127 if self._wrapped is empty:
128 return
129 self._wrapped.clear() # type: ignore[attr-defined]
132class Settings:
133 def __init__(self, settings_module: str):
134 # store the settings module in case someone later cares
135 self.SETTINGS_MODULE = settings_module
137 mod = importlib.import_module(self.SETTINGS_MODULE)
139 self._explicit_settings = set()
140 for setting in dir(mod):
141 if setting.isupper():
142 setting_value = getattr(mod, setting)
144 setattr(self, setting, setting_value)
145 self._explicit_settings.add(setting)
147 def __repr__(self) -> str:
148 return '<%(cls)s "%(settings_module)s">' % {
149 "cls": self.__class__.__name__,
150 "settings_module": self.SETTINGS_MODULE,
151 }
153 def __iter__(self):
154 for setting in dir(self):
155 if setting.isupper():
156 yield setting
158 def is_overridden(self, setting: str) -> bool:
159 return setting in self._explicit_settings
161 def register(self, settings_module: ModuleType) -> None:
162 for setting in dir(settings_module):
163 if setting.isupper() and setting not in self:
164 setattr(self, setting, getattr(settings_module, setting))
166 def clear(self) -> None:
167 for attr in self:
168 if attr.isupper():
169 delattr(self, attr)
172class UserSettingsHolder:
173 """Holder for user configured settings."""
175 # SETTINGS_MODULE doesn't make much sense in the manually configured
176 # (standalone) case.
177 SETTINGS_MODULE: None = None
179 def __init__(self, settings=None):
180 self.__dict__["_deleted"] = set()
182 # if settings is None, it won't have any upper case attribute
183 for setting in dir(settings):
184 if setting not in self and setting.isupper():
185 setattr(self, setting, getattr(settings, setting))
187 def __getattr__(self, name: str) -> Any:
188 if not name.isupper() or name in self._deleted:
189 raise AttributeError
191 return getattr(self, name)
193 def __setattr__(self, name: str, value: Any) -> None:
194 self._deleted.discard(name)
195 super().__setattr__(name, value)
197 def __delattr__(self, name: str) -> None:
198 self._deleted.add(name)
199 if hasattr(self, name):
200 super().__delattr__(name)
202 def __dir__(self) -> list:
203 return sorted(
204 s
205 for s in [
206 *self.__dict__,
207 ]
208 if s not in self._deleted
209 )
211 def __repr__(self):
212 return "<%(cls)s>" % {
213 "cls": self.__class__.__name__,
214 }
216 def __iter__(self):
217 for setting in dir(self):
218 if setting.isupper():
219 yield setting
221 def register(self, settings_module: ModuleType | Settings) -> None:
222 for setting in dir(settings_module):
223 if setting.isupper() and setting not in dir(self):
224 setattr(self, setting, getattr(settings_module, setting))
226 def clear(self):
227 for attr in dir(self):
228 if attr.isupper():
229 delattr(self, attr)
231 def is_overridden(self, setting: str) -> bool:
232 deleted = setting in self._deleted
233 set_locally = setting in self.__dict__
234 return deleted or set_locally
237settings = LazySettings()