88import logging
99import os
1010import stat
11+ import warnings
1112from collections .abc import Iterable
1213from operator import methodcaller
1314from pathlib import Path
@@ -46,9 +47,11 @@ class ConfigSlice(NamedTuple):
4647class ConfigOption :
4748 """ConfigOption represents a flag/setting.
4849
49- The class knows how to read the value out of all sources and implements
50+ This class knows how to read the value out of all different sources and implements
5051 order of precedence between them.
5152
53+ It also provides value parsing and verification.
54+
5255 Attributes:
5356 name: Name of this ConfigOption.
5457 parse_str: A function that can turn str to the desired type, useful
@@ -57,14 +60,14 @@ class ConfigOption:
5760 this option.
5861 env_name: Environmental variable value should be read from, if not
5962 supplied, we'll construct this. False disables reading from
60- environmental variable , None uses the auto generated variable name
63+ environmental variables , None uses the auto generated variable name
6164 and explicitly provided string overwrites the default one.
62- _root_manager: Reference to the root parser . Used to efficiently
65+ _root_manager: Reference to the root manager . Used to efficiently
6366 refer to cached config file. Is supplied by the parent
6467 ConfigManager.
6568 _nest_path: The names of the ConfigManagers that this option is
66- nested in. Used be able to efficiently resolve where to grab
67- value out of configuration file and construct environment
69+ nested in. Used to be able to efficiently resolve where to retrieve
70+ value out of the configuration file and construct environment
6871 variable name. This is supplied by the parent ConfigManager.
6972 """
7073
@@ -78,14 +81,17 @@ def __init__(
7881 _root_manager : ConfigManager | None = None ,
7982 _nest_path : list [str ] | None ,
8083 ) -> None :
81- """Create a config option that can read values from different locations .
84+ """Create a config option that can read values from different sources .
8285
8386 Args:
8487 name: Name to assign to this ConfigOption.
85- parse_str: String parser function for this ConfigOption .
86- choices: List of possible values for this ConfigOption .
88+ parse_str: String parser function for this instance .
89+ choices: List of possible values for this instance .
8790 env_name: Environmental variable name value should be read from.
88- _root_manager: Reference to the root parser. Should be supplied by
91+ Providing a string will use that environment variable, False disables
92+ reading value from environmental variables and the default None generates
93+ an environmental variable name for it using the _nest_path and name.
94+ _root_manager: Reference to the root manager. Should be supplied by
8995 the parent ConfigManager.
9096 _nest_path: The names of the ConfigManagers that this option is
9197 nested in. This is supplied by the parent ConfigManager.
@@ -155,7 +161,11 @@ def _get_env(self) -> tuple[bool, str | _T | None]:
155161 return True , loaded_var
156162
157163 def _get_config (self ) -> Any :
158- """Get value from the cached config file."""
164+ """Get value from the cached config file if possible.
165+
166+ Since this is the last resource for retrieving the value it raises
167+ a MissingConfigOptionError if it's unable to find this option.
168+ """
159169 if (
160170 self ._root_manager .conf_file_cache is None
161171 and self ._root_manager .file_path is not None
@@ -164,7 +174,7 @@ def _get_config(self) -> Any:
164174 e = self ._root_manager .conf_file_cache
165175 if e is None :
166176 raise ConfigManagerError (
167- f"Root parser '{ self ._root_manager .name } ' is missing file_path" ,
177+ f"Root manager '{ self ._root_manager .name } ' is missing file_path" ,
168178 )
169179 for k in self ._nest_path [1 :]:
170180 try :
@@ -183,13 +193,21 @@ def _get_config(self) -> Any:
183193
184194
185195class ConfigManager :
186- """Read TOML configuration file with managed multi-source precedence.
196+ """Read a TOML configuration file with managed multi-source precedence.
197+
198+ Note that multi-source precedence is actually implemented by ConfigOption.
199+ This is done to make sure that special handling can be done for special options.
200+ As an example, think of not allowing to provide passwords by command line arguments.
201+
202+ This class is updatable at run-time, allowing other libraries to add their
203+ own configuration options and sub-managers before resolution.
187204
188- This class is updatable at run-time, allowing other libraries to add their
189- own configuration options and sub-parsers. Sub-parsers allow
190- options groups to exist, e.g. the group "snowflake.cli.output" could have
191- 2 options in it: debug (boolean flag) and format (a string like "json", or
192- "csv").
205+ This class can simply be thought of as nestable containers for ConfigOptions.
206+ It holds extra information necessary for efficient nesting purposes.
207+
208+ Sub-managers allow option groups to exist, e.g. the group "snowflake.cli.output"
209+ could have 2 options in it: debug (boolean flag) and format (a string like "json",
210+ or "csv").
193211
194212 When a ConfigManager tries to retrieve ConfigOptions' value the _root_manager
195213 will read and cache the TOML file from the file it's pointing at, afterwards
@@ -200,14 +218,20 @@ class ConfigManager:
200218 useful error messages.
201219 file_path: Path to the file where this and all child ConfigManagers
202220 should read their values out of. Can be omitted for all child
203- parsers.
221+ managers. Root manager could also miss this value, but this will
222+ result in an exception when a value is read that isn't available from
223+ a preceding config source.
204224 conf_file_cache: Cache to store what we read from the TOML file.
205- _sub_parsers: List of ConfigManagers that are nested under us.
206- _options: List of ConfigOptions that are our children.
207- _root_manager: Reference to root parser. Used to efficiently propagate to
225+ _sub_managers: List of ConfigManagers that are nested under the current manager.
226+ _sub_parsers: Alias for the old name of _sub_managers in the first release, please use
227+ the new name now, as this might get deprecated in the future.
228+ _options: List of ConfigOptions that are under the current manager.
229+ _root_manager: Reference to the root manager. Used to efficiently propagate to
208230 child options.
209- _nest_path: The names of the ConfigManagers that this parser is nested
231+ _nest_path: The names of the ConfigManagers that this manager is nested
210232 under. Used to efficiently propagate to child options.
233+ _slices: List of config slices, where optional sections could be read from.
234+ Note that this feature might become deprecated soon.
211235 """
212236
213237 def __init__ (
@@ -217,37 +241,54 @@ def __init__(
217241 file_path : Path | None = None ,
218242 _slices : list [ConfigSlice ] | None = None ,
219243 ):
220- """Create a new ConfigManager.
244+ """Creates a new ConfigManager.
221245
222246 Args:
223247 name: Name of this ConfigManager.
224- file_path: File this parser should read values from. Can be omitted
225- for all child parsers.
248+ file_path: File this manager should read values from. Can be omitted
249+ for all child managers.
250+ _slices: List of ConfigSlices to consider. A configuration file's slice is a
251+ section that can optionally reside in a different file. Note that this
252+ feature might get deprecated soon.
226253 """
227254 if _slices is None :
228255 _slices = list ()
229256 self .name = name
230257 self .file_path = file_path
231258 self ._slices = _slices
232- # Objects holding subparsers and options
259+ # Objects holding sub-managers and options
233260 self ._options : dict [str , ConfigOption ] = dict ()
234- self ._sub_parsers : dict [str , ConfigManager ] = dict ()
261+ self ._sub_managers : dict [str , ConfigManager ] = dict ()
235262 # Dictionary to cache read in config file
236263 self .conf_file_cache : tomlkit .TOMLDocument | None = None
237264 # Information necessary to be able to nest elements
238265 # and add options in O(1)
239266 self ._root_manager : ConfigManager = self
240267 self ._nest_path = [name ]
241268
269+ @property
270+ def _sub_parsers (self ) -> dict [str , ConfigManager ]:
271+ """
272+ Alias for the old name of ``_sub_managers``.
273+
274+ This used to be the original name in the first release, please use the
275+ new name, as this might get deprecated in the future.
276+ """
277+ warnings .warn (
278+ "_sub_parsers has been deprecated, use _sub_managers instead" ,
279+ DeprecationWarning ,
280+ stacklevel = 2 ,
281+ )
282+ return self ._sub_managers
283+
242284 def read_config (
243285 self ,
244286 ) -> None :
245- """Read and cache config file.
287+ """Read and cache config file contents .
246288
247- This function should be called if the ConfigManager's cache is outdated.
248- Maybe in the case when we want to replace the file_path assigned to a
249- ConfigManager, or if one's doing development and are interactively
250- adding new options to their configuration files.
289+ This function should be explicitly called if the ConfigManager's cache is
290+ outdated. Most likely when someone's doing development and are interactively
291+ adding new options to their configuration file.
251292 """
252293 if self .file_path is None :
253294 raise ConfigManagerError (
@@ -297,10 +338,10 @@ def add_option(
297338 option_cls : type [ConfigOption ] = ConfigOption ,
298339 ** kwargs ,
299340 ) -> None :
300- """Add an ConfigOption to this ConfigManager.
341+ """Add a ConfigOption to this ConfigManager.
301342
302343 Args:
303- option_cls: The class that should be instantiated. This is class
344+ option_cls: The class that should be instantiated. This class
304345 should be a child class of ConfigOption. Mainly useful for cases
305346 where the default ConfigOption needs to be extended, for example
306347 if a new configuration option source needs to be supported.
@@ -314,17 +355,17 @@ def add_option(
314355 self ._options [new_option .name ] = new_option
315356
316357 def _check_child_conflict (self , name : str ) -> None :
317- """Check if a sub-parser , or ConfigOption conflicts with given name.
358+ """Check if a sub-manager , or ConfigOption conflicts with given name.
318359
319360 Args:
320361 name: Name to check against children.
321362 """
322- if name in (self ._options .keys () | self ._sub_parsers .keys ()):
363+ if name in (self ._options .keys () | self ._sub_managers .keys ()):
323364 raise ConfigManagerError (
324- f"'{ name } ' subparser , or option conflicts with a child element of '{ self .name } '"
365+ f"'{ name } ' sub-manager , or option conflicts with a child element of '{ self .name } '"
325366 )
326367
327- def add_subparser (self , new_child : ConfigManager ) -> None :
368+ def add_submanager (self , new_child : ConfigManager ) -> None :
328369 """Nest another ConfigManager under this one.
329370
330371 This function recursively updates _nest_path and _root_manager of all
@@ -334,44 +375,52 @@ def add_subparser(self, new_child: ConfigManager) -> None:
334375 new_child: The ConfigManager to be nested under the current one.
335376 Notes:
336377 We currently don't support re-nesting a ConfigManager. Only nest a
337- parser under another one once.
378+ manager under another one once.
338379 """
339380 self ._check_child_conflict (new_child .name )
340- self ._sub_parsers [new_child .name ] = new_child
381+ self ._sub_managers [new_child .name ] = new_child
341382
342383 def _root_setter_helper (node : ConfigManager ):
343384 # Deal with ConfigManagers
344385 node ._root_manager = self ._root_manager
345386 node ._nest_path = self ._nest_path + node ._nest_path
346- for sub_parser in node ._sub_parsers .values ():
347- _root_setter_helper (sub_parser )
387+ for sub_manager in node ._sub_managers .values ():
388+ _root_setter_helper (sub_manager )
348389 # Deal with ConfigOptions
349390 for option in node ._options .values ():
350391 option ._root_manager = self ._root_manager
351392 option ._nest_path = self ._nest_path + option ._nest_path
352393
353394 _root_setter_helper (new_child )
354395
396+ def add_subparser (self , * args , ** kwargs ) -> None :
397+ warnings .warn (
398+ "add_subparser has been deprecated, use add_submanager instead" ,
399+ DeprecationWarning ,
400+ stacklevel = 2 ,
401+ )
402+ return self .add_submanager (* args , ** kwargs )
403+
355404 def __getitem__ (self , name : str ) -> ConfigOption | ConfigManager :
356- """Get either sub-parser , or option in this parser with name.
405+ """Get either sub-manager , or option in this manager with name.
357406
358- If option is retrieved, we call get() on it to return its value instead.
407+ If an option is retrieved, we call get() on it to return its value instead.
359408
360409 Args:
361410 name: Name to retrieve.
362411 """
363412 if name in self ._options :
364413 return self ._options [name ].value ()
365- if name not in self ._sub_parsers :
414+ if name not in self ._sub_managers :
366415 raise ConfigSourceError (
367416 "No ConfigManager, or ConfigOption can be found"
368417 f" with the name '{ name } '"
369418 )
370- return self ._sub_parsers [name ]
419+ return self ._sub_managers [name ]
371420
372421
373- CONFIG_PARSER = ConfigManager (
374- name = "CONFIG_PARSER " ,
422+ CONFIG_MANAGER = ConfigManager (
423+ name = "CONFIG_MANAGER " ,
375424 file_path = CONFIG_FILE ,
376425 _slices = [
377426 ConfigSlice ( # Optional connections file to read in connections from
@@ -383,13 +432,26 @@ def __getitem__(self, name: str) -> ConfigOption | ConfigManager:
383432 ),
384433 ],
385434)
386- CONFIG_PARSER .add_option (
435+ CONFIG_MANAGER .add_option (
387436 name = "connections" ,
388437 parse_str = tomlkit .parse ,
389438)
390439
391- __all__ = [
440+
441+ def __getattr__ (name ):
442+ if name == "CONFIG_PARSER" :
443+ warnings .warn (
444+ "CONFIG_PARSER has been deprecated, use CONFIG_MANAGER instead" ,
445+ DeprecationWarning ,
446+ stacklevel = 2 ,
447+ )
448+ return CONFIG_MANAGER
449+ raise AttributeError (f"module { __name__ !r} has no attribute { name !r} " )
450+
451+
452+ __all__ = [ # noqa: F822
392453 "ConfigOption" ,
393454 "ConfigManager" ,
455+ "CONFIG_MANAGER" ,
394456 "CONFIG_PARSER" ,
395457]
0 commit comments