1- from parsers import ParseError , json_parser , yaml_parser
21from schema import Schema , SchemaError
32from typing import Any
43from os import path
4+ import json
5+ import yaml
56import os
67import re
78
8- VARIABLE_PATTERN = r'\$([a-zA-Z][\w]+|\{[a-zA-Z][\w]+\})$'
9- DEFAULT_CONFIG_FILES = ('config.json' , 'config.yaml' , 'config.yml' )
10- ENTITY_NAME_PATTERN = r'^[a-zA-Z][\w]+$'
11- SUPPORTED_EXTENSIONS = {
12- 'json' : json_parser ,
13- 'yaml' : yaml_parser ,
14- 'yml' : yaml_parser
15- }
9+ __all__ = [
10+ "ConfigError" ,
11+ "ConfigFileNotFoundError" ,
12+ "Config" ,
13+ "ConfigParser" ,
14+ "configparser"
15+ ]
1616
1717
1818class ConfigError (Exception ):
1919 pass
2020
2121
22- class ConfigFileNotFoundError (ConfigError ):
22+ class ConfigFileNotFoundError (Exception ):
2323 pass
2424
2525
26+ def _json_parser (file_buff ):
27+ try :
28+ return json .loads (file_buff )
29+ except json .JSONDecodeError as e :
30+ raise ConfigError ('Unable to decode config file using json' , e )
31+
32+
33+ def _yaml_parser (file_buff ):
34+ try :
35+ return yaml .safe_load (file_buff )
36+ except yaml .YAMLError as e :
37+ raise ConfigError ('Unable to decode config file using yaml' , e )
38+
39+
40+ def _validate_schema (schema , config_obj ):
41+ if schema is None :
42+ return config_obj
43+ elif type (schema ) not in (dict , list ):
44+ raise ConfigError ('The first config\' s schema element should be a Map or a List' )
45+
46+ return Schema (schema ).validate (config_obj )
47+
48+
49+ def _get_file_buff (path_file : str ):
50+ with open (path_file , 'r' ) as f :
51+ return f .read ()
52+
53+
54+ def _get_file_parser (file_path ):
55+ try :
56+ extension = file_path .split ('.' )[- 1 ]
57+ return _SUPPORTED_EXTENSIONS [extension ]
58+ except KeyError :
59+ raise ConfigError (f'Supported extensions: { list (_SUPPORTED_EXTENSIONS .keys ())} ' )
60+
61+
62+ def _get_file_path (config_dir , file_name ):
63+ file_path = f'{ os .getcwd ()} /{ config_dir } /'
64+ if type (file_name ) is str :
65+ file_name = [file_name ]
66+
67+ for f_name in file_name :
68+ if path .isfile (file_path + f_name ):
69+ return file_path + f_name
70+
71+ raise ConfigFileNotFoundError (f'Config file { file_path } { file_name } was not found' )
72+
73+
74+ def _is_a_valid_object_key (key ):
75+ if re .search (_ENTITY_NAME_PATTERN , key ) is None :
76+ raise ConfigError (f'The key { key } is invalid. The entity keys only may have words, number and underscores.' )
77+
78+
79+ def _is_variable (data ):
80+ return type (data ) is str and re .search (_VARIABLE_PATTERN , data ) is not None
81+
82+
83+ def _interpol_variable (data , ignore_unset_env_vars ):
84+ try :
85+ return os .environ [_extract_env_variable_key (data )]
86+ except KeyError :
87+ if ignore_unset_env_vars :
88+ return None
89+ raise ConfigError (f'Environment variable { data } was not found' )
90+
91+
92+ def _extract_env_variable_key (variable ):
93+ variable = variable [1 :]
94+ if variable [0 ] == '{' :
95+ return variable [1 :- 1 ]
96+ return variable
97+
98+
99+ _VARIABLE_PATTERN = r'\$([a-zA-Z][\w]+|\{[a-zA-Z][\w]+\})$'
100+ _DEFAULT_CONFIG_FILES = ('config.json' , 'config.yaml' , 'config.yml' )
101+ _ENTITY_NAME_PATTERN = r'^[a-zA-Z][\w]+$'
102+ _SUPPORTED_EXTENSIONS = {
103+ 'json' : _json_parser ,
104+ 'yaml' : _yaml_parser ,
105+ 'yml' : _yaml_parser
106+ }
107+
108+
26109class Config :
27110
28111 def __getitem__ (self , item ):
@@ -45,7 +128,7 @@ class ConfigParser:
45128 def __init__ (self ):
46129 self .__instance = None
47130 self .__hold_an_instance = True
48- self .__ignore_unsetted_env_vars = False
131+ self .__ignore_unset_env_vars = False
49132
50133 @property
51134 def hold_an_instance (self ):
@@ -59,100 +142,49 @@ def hold_an_instance(self, value):
59142
60143 @property
61144 def ignore_unset_env_vars (self ):
62- return self .__ignore_unsetted_env_vars
145+ return self .__ignore_unset_env_vars
63146
64147 @ignore_unset_env_vars .setter
65148 def ignore_unset_env_vars (self , value ):
66149 if type (value ) is not bool :
67150 raise ValueError ('value must be a bool' )
68- self .__ignore_unsetted_env_vars = value
151+ self .__ignore_unset_env_vars = value
69152
70- def get_config (self , schema : dict = None , config_dir : str = 'config' , file_name : Any = DEFAULT_CONFIG_FILES ):
153+ def get_config (self , schema : dict = None , config_dir : str = 'config' , file_name : Any = _DEFAULT_CONFIG_FILES ):
71154 if self .__hold_an_instance :
72155 if self .__instance is None :
73156 self .__instance = self .__create_new_instance (schema , config_dir , file_name )
74157 return self .__instance
75158 return self .__create_new_instance (schema , config_dir , file_name )
76159
77160 def __create_new_instance (self , schema , config_dir , file_name ):
78- file_path = self . __get_file_path (config_dir , file_name )
79- parser = self . __get_file_parser (file_path )
80- file_buff = self . __get_file_buff (file_path )
161+ file_path = _get_file_path (config_dir , file_name )
162+ parser = _get_file_parser (file_path )
163+ file_buff = _get_file_buff (file_path )
81164
82165 try :
83- config = self . __validate_schema (schema , parser (file_buff ))
166+ config = _validate_schema (schema , parser (file_buff ))
84167 return self .__dict_2_obj (config )
85- except ParseError as e :
86- raise ConfigError (e )
87168 except SchemaError as e :
88169 raise ConfigError ('Schema validation error' , e )
89170
90- def __get_file_parser (self , file_path ):
91- try :
92- extension = file_path .split ('.' )[- 1 ]
93- return SUPPORTED_EXTENSIONS [extension ]
94- except KeyError :
95- raise ConfigError (f'Supported extensions: { list (SUPPORTED_EXTENSIONS .keys ())} ' )
96-
97- def __get_file_path (self , config_dir , file_name ):
98- file_path = f'{ os .getcwd ()} /{ config_dir } /'
99- if type (file_name ) is str :
100- file_name = [file_name ]
101-
102- for f_name in file_name :
103- if path .isfile (file_path + f_name ):
104- return file_path + f_name
105-
106- raise ConfigFileNotFoundError (f'Config file { file_path } { file_name } was not found' )
107-
108- def __validate_schema (self , schema , config_obj ):
109- if schema is None :
110- return config_obj
111- elif type (schema ) not in (dict , list ):
112- raise ConfigError ('The first config\' s schema element should be a Map or a List' )
113-
114- return Schema (schema ).validate (config_obj )
115-
116- def __get_file_buff (self , path_file : str ):
117- with open (path_file , 'r' ) as f :
118- return f .read ()
119-
120171 def __dict_2_obj (self , data : Any ):
121172 _type = type (data )
122173
123174 if _type is dict :
124175 obj = Config ()
125176 for key , value in data .items ():
126- self . __is_a_valid_object_key (key )
177+ _is_a_valid_object_key (key )
127178 setattr (obj , key , self .__dict_2_obj (value ))
128179 return obj
180+
129181 if _type in (list , set , tuple ):
130182 return list (map (lambda v : self .__dict_2_obj (v ), data ))
183+
131184 else :
132- if self . __is_variable (data ):
133- return self . __interpol_variable (data )
185+ if _is_variable (data ):
186+ return _interpol_variable (data , self . __ignore_unset_env_vars )
134187 return data
135188
136- def __interpol_variable (self , data ):
137- try :
138- return os .environ [self .__extract_env_variable_key (data )]
139- except KeyError :
140- if self .__ignore_unsetted_env_vars :
141- return None
142- raise ConfigError (f'Environment variable { data } was not found' )
143-
144- def __is_a_valid_object_key (self , key ):
145- if re .search (ENTITY_NAME_PATTERN , key ) is None :
146- raise ConfigError (f'The key { key } is invalid. The entity keys only may have words, number and underscores.' )
147-
148- def __is_variable (self , data ):
149- return type (data ) is str and re .search (VARIABLE_PATTERN , data ) is not None
150-
151- def __extract_env_variable_key (self , variable ):
152- variable = variable [1 :]
153- if variable [0 ] == '{' :
154- return variable [1 :- 1 ]
155- return variable
156-
157189
158190configparser = ConfigParser ()
0 commit comments