1- import os
2- import yaml
3- import json
41import configparser
5- from typing import Union , Dict , Any , Sequence
2+ import json
3+ import os
64from collections import abc
5+ from typing import Any , Dict , Mapping , Optional
76
7+ import yaml
88
9- __all__ = [' Configuration' , ' ConfigurationError' , ' ConfigurationOverrideError' ]
9+ __all__ = [" Configuration" , " ConfigurationError" , " ConfigurationOverrideError" ]
1010
1111
1212class ConfigurationError (Exception ):
@@ -18,8 +18,8 @@ class ConfigurationOverrideError(ConfigurationError):
1818
1919
2020def apply_key_value (obj , key , value ):
21- key = key .strip ('_:' ) # remove special characters from both ends
22- for token in (':' , '__' ):
21+ key = key .strip ("_:" ) # remove special characters from both ends
22+ for token in (":" , "__" ):
2323 if token in key :
2424 parts = key .split (token )
2525
@@ -31,7 +31,9 @@ def apply_key_value(obj, key, value):
3131 try :
3232 index = int (part )
3333 except ValueError :
34- raise ConfigurationOverrideError (f'{ part } was supposed to be a numeric index in { key } ' )
34+ raise ConfigurationOverrideError (
35+ f"{ part } was supposed to be a numeric index in { key } "
36+ )
3537
3638 sub_property = sub_property [index ]
3739 continue
@@ -42,27 +44,38 @@ def apply_key_value(obj, key, value):
4244 sub_property [part ] = {}
4345 sub_property = sub_property [part ]
4446 else :
45- if not isinstance (sub_property , abc .Mapping ) and not isinstance (sub_property , abc .MutableSequence ):
46- raise ConfigurationOverrideError (f'The key `{ key } ` cannot be used because it overrides another '
47- f'variable with shorter key! ({ part } , { sub_property } )' )
47+ if not isinstance (sub_property , abc .Mapping ) and not isinstance (
48+ sub_property , abc .MutableSequence
49+ ):
50+ raise ConfigurationOverrideError (
51+ f"The key `{ key } ` cannot be used "
52+ f"because it overrides another "
53+ f"variable with shorter key! ({ part } , { sub_property } )"
54+ )
4855
4956 if isinstance (sub_property , abc .MutableSequence ):
5057 try :
5158 index = int (last_part )
5259 except ValueError :
53- raise ConfigurationOverrideError (f'{ last_part } was supposed to be a numeric index in { key } , '
54- f'because the affected property is a mutable sequence.' )
60+ raise ConfigurationOverrideError (
61+ f"{ last_part } was supposed to be a numeric index in { key } , "
62+ f"because the affected property is a mutable sequence."
63+ )
5564
5665 try :
5766 sub_property [index ] = value
5867 except IndexError :
59- raise ConfigurationOverrideError (f'Invalid override for mutable sequence { key } ; '
60- f'assignment index out of range' )
68+ raise ConfigurationOverrideError (
69+ f"Invalid override for mutable sequence { key } ; "
70+ f"assignment index out of range"
71+ )
6172 else :
6273 try :
6374 sub_property [last_part ] = value
6475 except TypeError as te :
65- raise ConfigurationOverrideError (f'Invalid assignment { key } -> { value } ; { str (te )} ' )
76+ raise ConfigurationOverrideError (
77+ f"Invalid assignment { key } -> { value } ; { str (te )} "
78+ )
6679
6780 return obj
6881
@@ -75,7 +88,9 @@ def _develop_configparser_values(parser):
7588 for section_name in parser .sections ():
7689 section_values = {}
7790 for k , v in parser [section_name ].items ():
78- section_values [k ] = _develop_configparser_values (v ) if isinstance (v , abc .Mapping ) else v
91+ section_values [k ] = (
92+ _develop_configparser_values (v ) if isinstance (v , abc .Mapping ) else v
93+ )
7994 values [section_name ] = section_values
8095 return values
8196
@@ -85,10 +100,11 @@ class Configuration:
85100 Provides methods to handle configuration objects.
86101 A read-only façade for navigating configuration objects using attribute notation.
87102
88- Thanks to Fluent Python, book by Luciano Ramalho; this class is inspired by his example of JSON structure explorer.
103+ Thanks to Fluent Python, book by Luciano Ramalho; this class is inspired by his
104+ example of JSON structure explorer.
89105 """
90106
91- __slots__ = (' __data' ,)
107+ __slots__ = (" __data" ,)
92108
93109 def __new__ (cls , arg = None ):
94110 if not arg :
@@ -99,12 +115,14 @@ def __new__(cls, arg=None):
99115 return [cls (item ) for item in arg ]
100116 return arg
101117
102- def __init__ (self , mapping : Union [None , Dict [str , Any ], Sequence [Dict [str , Any ]]] = None ):
103- self .__data = {}
118+ def __init__ (
119+ self , mapping : Optional [Mapping [str , Any ]] = None
120+ ):
121+ self .__data : Dict [str , Any ] = {}
104122 if mapping :
105123 self .add_map (mapping )
106124
107- def __contains__ (self , item ) :
125+ def __contains__ (self , item : str ) -> bool :
108126 return item in self .__data
109127
110128 def __getitem__ (self , name ):
@@ -113,19 +131,19 @@ def __getitem__(self, name):
113131 raise KeyError (name )
114132 return value
115133
116- def __getattr__ (self , name , default = None ):
134+ def __getattr__ (self , name , default = None ) -> Any :
117135 if name in self .__data :
118136 value = self .__data .get (name )
119137 if isinstance (value , abc .Mapping ) or isinstance (value , abc .MutableSequence ):
120- return Configuration (value )
138+ return Configuration (value ) # type: ignore
121139 return value
122140 return default
123141
124- def __repr__ (self ):
142+ def __repr__ (self ) -> str :
125143 return repr (self .values )
126144
127145 @property
128- def values (self ):
146+ def values (self ) -> Dict [ str , Any ] :
129147 """
130148 Returns a copy of the dictionary of current settings.
131149 """
@@ -134,7 +152,7 @@ def values(self):
134152 def to_dict (self ):
135153 return self .values
136154
137- def add_value (self , name , value ):
155+ def add_value (self , name : str , value : Any ):
138156 """
139157 Adds a configuration value by name. The name can contain
140158 paths to nested objects and list indices.
@@ -144,7 +162,7 @@ def add_value(self, name, value):
144162 """
145163 apply_key_value (self .__data , name , value )
146164
147- def add_map (self , value ):
165+ def add_map (self , value : Mapping [ str , Any ] ):
148166 """
149167 Merges a mapping object such as a dictionary,
150168 inside this configuration,
@@ -154,13 +172,18 @@ def add_map(self, value):
154172 for key , value in value .items ():
155173 self .__data [key ] = value
156174
157- def add_environmental_variables (self , prefix = None , strip_prefix = False ):
175+ def add_environmental_variables (
176+ self ,
177+ prefix : Optional [str ] = None ,
178+ strip_prefix : bool = False
179+ ):
158180 """
159181 Reads environmental variables inside this configuration object,
160182 optionally filtered by prefix.
161183
162184 :param prefix: optional prefix, to filter read environmental variables.
163- :param strip_prefix: whether to strip the prefix when overriding keys by matched env variables
185+ :param strip_prefix: whether to strip the prefix when overriding keys
186+ by matched env variables
164187 """
165188 if prefix :
166189 prefix = prefix .lower ()
@@ -169,25 +192,30 @@ def add_environmental_variables(self, prefix=None, strip_prefix=False):
169192 if prefix and not lk .startswith (prefix ):
170193 continue
171194 if prefix and strip_prefix :
172- lk = lk [len (prefix ):]
195+ lk = lk [len (prefix ) :]
173196 apply_key_value (self .__data , lk , v )
174197
175- def add_ini (self , ini_settings ) :
198+ def add_ini (self , ini_settings : str ) -> None :
176199 """
177200 Reads settings from given ini configuration code.
178-
179- :param ini_settings: source ini code
201+
202+ :param ini_settings: source ini code
180203 """
181204 parser = configparser .ConfigParser ()
182205 parser .read_string (ini_settings )
183206 self .add_map (_develop_configparser_values (parser ))
184207
185- def _handle_missing_configuration_file (self , file_path ) :
186- raise FileNotFoundError (f' missing configuration file: { file_path } ' )
208+ def _handle_missing_configuration_file (self , file_path : str ) -> None :
209+ raise FileNotFoundError (f" missing configuration file: { file_path } " )
187210
188- def add_ini_file (self , file_path , optional = False ):
211+ def add_ini_file (
212+ self ,
213+ file_path : str ,
214+ optional : bool = False
215+ ) -> None :
189216 """
190- Reads and parse an ini file, merging its values into an instance of Configuration.
217+ Reads and parse an ini file, merging its values into an instance of
218+ Configuration.
191219
192220 :param file_path: path to an ini file
193221 :param optional: whether the ini file is optional.
@@ -200,9 +228,14 @@ def add_ini_file(self, file_path, optional=False):
200228 parser .read (file_path )
201229 self .add_map (_develop_configparser_values (parser ))
202230
203- def add_json_file (self , file_path , optional = False ):
231+ def add_json_file (
232+ self ,
233+ file_path : str ,
234+ optional : bool = False
235+ ) -> None :
204236 """
205- Reads and parse an json file, merging its values into an instance of Configuration.
237+ Reads and parse an json file, merging its values into an instance of
238+ Configuration.
206239
207240 :param file_path: path to an json file
208241 :param optional: whether the json file is optional.
@@ -212,12 +245,18 @@ def add_json_file(self, file_path, optional=False):
212245 return
213246 self ._handle_missing_configuration_file (file_path )
214247
215- with open (file_path , 'rt' , encoding = ' utf-8' ) as f :
248+ with open (file_path , "rt" , encoding = " utf-8" ) as f :
216249 self .add_map (json .load (f ))
217250
218- def add_yaml_file (self , file_path , optional = False , safe_load = True ):
251+ def add_yaml_file (
252+ self ,
253+ file_path : str ,
254+ optional : bool = False ,
255+ safe_load : bool = True
256+ ) -> None :
219257 """
220- Reads and parse an yaml file, merging its values into an instance of Configuration.
258+ Reads and parse an yaml file, merging its values into an instance of
259+ Configuration.
221260
222261 :param file_path: path to an yaml file
223262 :param optional: whether the yaml file is optional.
@@ -228,7 +267,7 @@ def add_yaml_file(self, file_path, optional=False, safe_load=True):
228267 return
229268 self ._handle_missing_configuration_file (file_path )
230269
231- with open (file_path , 'rt' , encoding = ' utf-8' ) as f :
270+ with open (file_path , "rt" , encoding = " utf-8" ) as f :
232271
233272 if safe_load :
234273 data = yaml .safe_load (f )
0 commit comments