11import keyword
22import logging
33from pathlib import Path
4- from typing import Any , Dict , Optional , Union
4+ from typing import Any , Dict , List , Optional , Union
55
66import yaml
7- from camel_converter import to_snake
8- from munch import Munch , munchify
7+ from camel_converter import to_snake as _to_snake
8+ from munch import Munch as _Munch
9+ from munch import munchify as _munchify
910
1011
1112logging .basicConfig (format = '%(asctime)-15s \t %(levelname)-8s \t %(name)-8s \t %(message)s' )
1516ConfigType = Dict [str , Any ]
1617
1718
19+ class PyyaConfig (_Munch ): ...
20+
21+
1822class PyyaError (RuntimeError ): ...
1923
2024
@@ -23,29 +27,37 @@ def init_config(
2327 default_config : Union [str , Path ] = 'default.config.yaml' ,
2428 * ,
2529 merge_configs : bool = True ,
30+ sections_ignored_on_merge : Optional [List [str ]] = None ,
2631 convert_keys_to_snake_case : bool = False ,
2732 add_underscore_prefix_to_keywords : bool = False ,
2833 raise_error_non_identifiers : bool = False ,
29- ) -> Munch :
30- """
31- Initialize attribute-stylish configuration from YAML file.
34+ ) -> PyyaConfig :
35+ """Initialize attribute-stylish configuration from YAML file.
3236
3337 Args:
3438 config: path to config file
3539 default_config: path to default config file
3640 merge_configs: merge default config with config (setting to `False` disables other flags)
37- convert_keys_to_snake_case: convert config keys to snake case
41+ sections_ignored_on_merge: list of sections to ignore when merging configs
42+ convert_keys_to_snake_case: convert config section names to snake case
3843 add_underscore_prefix_to_keywords: add underscore prefix to Python keywords
39- raise_error_non_identifiers: raise error if config key is not a valid identifier
44+ raise_error_non_identifiers: raise error if config section name is not a valid identifier
4045 """
4146
4247 def _merge_configs (_raw_data : ConfigType , _default_raw_data : ConfigType ) -> None :
4348 for section , entry in _default_raw_data .items ():
49+ if sections_ignored_on_merge :
50+ if section in sections_ignored_on_merge :
51+ logger .debug (f'section `{ section } ` ignored on merge' )
52+ continue
53+ elif isinstance (entry , dict ):
54+ # is it fine to proccess already poped dicts on recursion?
55+ entry = _pop_ignored_keys (entry )
4456 if section not in _raw_data or _raw_data [section ] is None :
4557 f_section = _sanitize_section (section )
4658 if f_section not in _raw_data :
4759 _raw_data [f_section ] = entry
48- logger .warning (f'section `{ f_section } ` with value `{ entry } ` taken from { default_config } ' )
60+ logger .info (f'section `{ f_section } ` with value `{ entry } ` taken from { default_config } ' )
4961 else :
5062 logger .debug (f'section `{ f_section } ` already exists in { config } , skipping' )
5163 elif isinstance (entry , dict ):
@@ -61,16 +73,25 @@ def _merge_configs(_raw_data: ConfigType, _default_raw_data: ConfigType) -> None
6173 def _sanitize_section (section : str ) -> str :
6274 if convert_keys_to_snake_case :
6375 logger .debug (f'converting section `{ section } ` to snake case' )
64- section = to_snake (section )
76+ section = _to_snake (section )
6577 if raise_error_non_identifiers and not section .isidentifier ():
6678 err_msg = f'section `{ section } ` is not a valid identifier, aborting'
6779 logger .error (err_msg )
6880 raise PyyaError (err_msg )
6981 if add_underscore_prefix_to_keywords and keyword .iskeyword (section ):
70- logger .warning (f'section `{ section } ` is a keyword, renaming to `_{ section } `' )
82+ logger .info (f'section `{ section } ` is a keyword, renaming to `_{ section } `' )
7183 section = f'_{ section } '
7284 return section
7385
86+ def _pop_ignored_keys (data : ConfigType ) -> ConfigType :
87+ for key , entry in data .copy ().items ():
88+ if key in sections_ignored_on_merge :
89+ data .pop (key )
90+ logger .debug (f'section `{ key } ` ignored on merge' )
91+ elif isinstance (entry , dict ):
92+ _pop_ignored_keys (entry )
93+ return data
94+
7495 try :
7596 with open (Path (config )) as fstream :
7697 _raw_data : ConfigType = yaml .safe_load (fstream ) or {}
@@ -79,7 +100,7 @@ def _sanitize_section(section: str) -> str:
79100 logger .error (err_msg )
80101 raise PyyaError (err_msg ) from None
81102 except FileNotFoundError :
82- logger .warning (f'{ config } file not found, using { default_config } ' )
103+ logger .info (f'{ config } file not found, using { default_config } ' )
83104 _raw_data = {}
84105
85106 if merge_configs :
@@ -98,7 +119,7 @@ def _sanitize_section(section: str) -> str:
98119 raise PyyaError (f'{ default_config } file is missing or empty' ) from None
99120 _merge_configs (_raw_data , _default_raw_data )
100121 try :
101- return munchify (_raw_data )
122+ return _munchify (_raw_data )
102123 except Exception as e :
103124 err_msg = f'Failed parsing config file: { e } '
104125 logger .error (err_msg )
0 commit comments