@@ -11,18 +11,20 @@ def json_parser(file_buff):
1111 try :
1212 return json .loads (file_buff )
1313 except json .JSONDecodeError as e :
14- raise ConfigFileDecodeError (e )
14+ raise ConfigFileDecodeError (f'Unable to decode config file using json' , e )
1515
1616
1717def yaml_parser (file_buff ):
1818 try :
19- return yaml .load (file_buff , yaml . FullLoader )
19+ return yaml .safe_load (file_buff )
2020 except yaml .YAMLError as e :
21- raise ConfigFileDecodeError (e )
21+ raise ConfigFileDecodeError (f'Unable to decode config file using yaml' , e )
2222
2323
24+ LINUX_KEY_VARIABLE_PATTERN = r'\$([a-zA-Z][\w]+|\{[a-zA-Z][\w]+\})$'
2425DEFAULT_CONFIG_FILES = ('config.json' , 'config.yaml' , 'config.yml' )
25- ENTITY_NAME_PATTERN = '^[\w\d_]+$'
26+ ENTITY_NAME_PATTERN = r'^[a-zA-Z][\w]+$'
27+
2628SUPPORTED_EXTENSIONS = {
2729 'json' : json_parser ,
2830 'yaml' : yaml_parser ,
@@ -41,15 +43,30 @@ def __iter__(self):
4143
4244class Config :
4345 __instance = None
46+ __hold_an_instance = True
47+
48+ @classmethod
49+ def hold_an_instance (cls ):
50+ return cls .__hold_an_instance
51+
52+ @classmethod
53+ def set_hold_an_instance (cls , value ):
54+ if type (value ) is not bool :
55+ raise ValueError ('value must be a bool' )
56+ cls .__hold_an_instance = value
4457
4558 def __new__ (cls , * args , ** kwargs ):
4659 raise RuntimeError ('A instance of config is not allowed, use Config.get_config() instead' )
4760
4861 @classmethod
4962 def get_config (cls , schema : dict = None , config_dir : str = 'config' , file_name : Any = DEFAULT_CONFIG_FILES ):
5063
51- if cls .__instance is None or schema is not None :
52- cls .__create_new_instance (schema , config_dir , file_name )
64+ if cls .__instance is None :
65+ instance = cls .__create_new_instance (schema , config_dir , file_name )
66+ if cls .__hold_an_instance :
67+ cls .__instance = instance
68+ else :
69+ return instance
5370 return cls .__instance
5471
5572 @classmethod
@@ -61,17 +78,17 @@ def __create_new_instance(cls, schema, config_dir, file_name):
6178
6279 try :
6380 config = Schema (schema ).validate (parser (file_buff ))
64- cls . __instance = cls .__dict_2_obj (config )
81+ return cls .__dict_2_obj (config )
6582 except SchemaError as e :
66- raise ConfigFileModelError ( str ( e ) )
83+ raise ConfigError ( 'Schema validation error' , e )
6784
6885 @classmethod
6986 def __get_file_parser (cls , file_path ):
7087 try :
7188 extension = file_path .split ('.' )[- 1 ]
7289 return SUPPORTED_EXTENSIONS [extension ]
7390 except KeyError :
74- raise ConfigFileExtensionNotSupportedError (f'Supported extensions: { list (SUPPORTED_EXTENSIONS .keys ())} ' )
91+ raise ConfigError (f'Supported extensions: { list (SUPPORTED_EXTENSIONS .keys ())} ' )
7592
7693 @classmethod
7794 def __get_file_path (cls , config_dir , file_name ):
@@ -94,11 +111,8 @@ def __check_schema(cls, schema):
94111
95112 @classmethod
96113 def __get_file_buff (cls , path_file : str ):
97- try :
98- with open (path_file , 'r' ) as f :
99- return f .read ()
100- except Exception as e :
101- raise ConfigFileOpenReadError (str (e ))
114+ with open (path_file , 'r' ) as f :
115+ return f .read ()
102116
103117 @classmethod
104118 def __dict_2_obj (cls , data : Any ):
@@ -108,43 +122,39 @@ def __dict_2_obj(cls, data: Any):
108122 obj = ConfigValue ()
109123 for key , value in data .items ():
110124 if re .search (ENTITY_NAME_PATTERN , key ) is None :
111- raise ConfigEntitiesWithWrongNameError (
125+ raise ConfigError (
112126 f'The key { key } is invalid. The entity keys only may have words, number and underscores.' )
113127 setattr (obj , key , cls .__dict_2_obj (value ))
114128 return obj
115129 if _type in (list , set , tuple ):
116130 return list (map (lambda v : cls .__dict_2_obj (v ), data ))
117131 else :
132+ if type (data ) is str and re .search (LINUX_KEY_VARIABLE_PATTERN , data ) is not None :
133+ return cls .interpol_variable (data )
118134 return data
119135
136+ @classmethod
137+ def interpol_variable (cls , data ):
138+ try :
139+ return os .environ [cls .extract_env_variable_key (data )]
140+ except KeyError :
141+ raise ConfigError (f'Environment variable { data } was not found' )
120142
121- class ConfigError (Exception ):
122- pass
143+ @classmethod
144+ def extract_env_variable_key (cls , variable ):
145+ variable = variable [1 :]
146+ if variable [0 ] == '{' :
147+ return variable [1 :- 1 ]
148+ return variable
123149
124150
125- class ConfigFileModelError ( ConfigError ):
151+ class ConfigError ( Exception ):
126152 pass
127153
128154
129155class ConfigFileDecodeError (ConfigError ):
130156 pass
131157
132158
133- class ConfigSchemaModelError (ConfigError ):
134- pass
135-
136-
137- class ConfigFileOpenReadError (ConfigError ):
138- pass
139-
140-
141159class ConfigFileNotFoundError (ConfigError ):
142160 pass
143-
144-
145- class ConfigFileExtensionNotSupportedError (ConfigError ):
146- pass
147-
148-
149- class ConfigEntitiesWithWrongNameError (ConfigError ):
150- pass
0 commit comments