9393import logging
9494import os
9595import warnings
96+ from typing import Any
97+ from typing import Iterable
9698from typing import List
9799from typing import Sequence
100+ from typing import Tuple
101+ from typing import Union
98102
99103from pyapp .conf import base_settings
100- from pyapp .conf .loaders import factory
101104from pyapp .conf .loaders import Loader
102105from pyapp .conf .loaders import ModuleLoader
103106
107+ from . import loaders
108+
104109logger = logging .getLogger (__name__ )
105110
106111DEFAULT_ENV_KEY = "PYAPP_SETTINGS"
@@ -188,15 +193,25 @@ def reset_settings(self):
188193
189194 This is useful for testing CLI entry points
190195 """
191- setting_keys = [
192- key for key in self ._container .keys if key != "SETTINGS_SOURCES"
193- ]
196+ container = self ._container
197+
198+ # Save and remove existing settings
199+ saved_settings = [(key , container .__dict__ .pop (key )) for key in container .keys ]
194200
195- for setting_key in setting_keys :
196- delattr ( self , setting_key )
201+ # Initialise base settings
202+ container . _populate_base_settings () # pylint: disable=protected-access
197203
198- # Clear settings sources
199- setattr (self , "SETTINGS_SOURCES" , [])
204+ def restore_settings ():
205+ # Remove new settings
206+ for key in container .keys :
207+ del container .__dict__ [key ]
208+
209+ # Restore saved settings
210+ container .__dict__ .update (saved_settings )
211+
212+ # Add restore action
213+ action = restore_settings , ()
214+ self ._roll_back .append (action )
200215
201216
202217class Settings :
@@ -205,25 +220,30 @@ class Settings:
205220 """
206221
207222 def __init__ (self , base_settings_ = None ):
208- base_settings_ = base_settings_ or base_settings
209-
210- # Copy values from base settings file.
211- self .__dict__ .update (
212- (k , getattr (base_settings_ , k )) for k in dir (base_settings_ ) if k .upper ()
213- )
214-
215- self .__dict__ ["SETTINGS_SOURCES" ] = [] # pylint: disable=invalid-name
223+ self ._populate_base_settings (base_settings_ )
216224
217225 def __getattr__ (self , item ):
218226 raise AttributeError ("Setting not defined {!r}" .format (item ))
219227
220228 def __setattr__ (self , key , value ):
221229 raise AttributeError ("Readonly object" )
222230
231+ def __getitem__ (self , item ):
232+ return self .__dict__ [item ]
233+
223234 def __repr__ (self ) -> str :
224235 sources = self .SETTINGS_SOURCES or "UN-CONFIGURED"
225236 return f"{ self .__class__ .__name__ } ({ sources } )"
226237
238+ def _populate_base_settings (self , base_settings_ = None ):
239+ base_settings_ = base_settings_ or base_settings
240+
241+ # Copy values from base settings file.
242+ self .__dict__ .update (
243+ (k , getattr (base_settings_ , k )) for k in dir (base_settings_ ) if k .upper ()
244+ )
245+ self .__dict__ ["SETTINGS_SOURCES" ] = [] # pylint: disable=invalid-name
246+
227247 @property
228248 def is_configured (self ) -> bool :
229249 """
@@ -238,6 +258,14 @@ def keys(self) -> Sequence[str]:
238258 """
239259 return [key for key in self .__dict__ if key .isupper ()]
240260
261+ def items (self ) -> Iterable [Tuple [str , Any ]]:
262+ """
263+ Return a sorted iterable of all key/value pairs of settings
264+ """
265+ data = self .__dict__
266+ for key in sorted (self .keys ):
267+ yield key , data [key ]
268+
241269 def load (self , loader : Loader , apply_method = None ):
242270 """
243271 Load settings from a loader instance. A loader is an iterator that yields key/value pairs.
@@ -270,7 +298,7 @@ def load(self, loader: Loader, apply_method=None):
270298 include_settings = self .__dict__ .pop ("INCLUDE_SETTINGS" , None )
271299 if include_settings :
272300 for source_url in include_settings :
273- self .load (factory (source_url ), apply_method )
301+ self .load (loaders . factory (source_url ), apply_method )
274302
275303 def load_from_loaders (self , loader_list : Sequence [Loader ], override : bool = True ):
276304 """
@@ -288,7 +316,7 @@ def load_from_loaders(self, loader_list: Sequence[Loader], override: bool = True
288316
289317 def configure (
290318 self ,
291- default_settings : Sequence [str ],
319+ default_settings : Union [ str , Sequence [str ] ],
292320 runtime_settings : str = None ,
293321 additional_loaders : Sequence [Loader ] = None ,
294322 env_settings_key : str = DEFAULT_ENV_KEY ,
@@ -304,13 +332,18 @@ def configure(
304332 """
305333 logger .debug ("Configuring settings..." )
306334
335+ # Allow a simple string to be supplied
336+ if isinstance (default_settings , str ):
337+ default_settings = [default_settings ]
338+
339+ # Build list of loaders
307340 loader_list : List [Loader ] = [ModuleLoader (s ) for s in default_settings ]
308341
309342 # Add run time settings (which can be overridden or specified by an
310343 # environment variable).
311344 runtime_settings = runtime_settings or os .environ .get (env_settings_key )
312345 if runtime_settings :
313- loader_list .append (ModuleLoader (runtime_settings ))
346+ loader_list .append (loaders . factory (runtime_settings ))
314347
315348 # Append the additional loaders if defined
316349 if additional_loaders :
0 commit comments