1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23 """
24 Manage Biskit settings.
25 """
26
27 import Biskit as B
28 import Biskit.tools as T
29 import Biskit.SettingsParser as P
30
31 import user, os
32 import ConfigParser
33
35 pass
36
38 """
39 SettingsManager merges the parameters from a default and a user
40 configuration file into a python module where they are published as
41 normal fields. The general flow is like this::
42
43 default.cfg ---[SettingsParser]---\
44 [SettingsManager]--->[settings]
45 /
46 user.cfg---[SettingsParser]---/
47
48 See L{P.SettingsParser}
49 See L{B.settings}
50
51 The default configurations should be located in:
52
53 * C{biskit/external/defaults/settings.cfg} --> L{B.settings}
54 * C{biskit/external/defaults/settings_Mod.cfg} --> L{B.Mod.settings}
55 * C{biskit/external/defaults/settings_Dock.cfg} --> L{B.Dock.settings}
56
57 The user configurations are expected in files of the same name in
58 C{~/.biskit/}.
59 """
60
61 USER_HEADER = """
62 ## This is a Biskit user configuration file. The parameters in
63 ## this file are overriding the default parameters given in
64 ## %(fdefault)s.
65 ## If missing, Biskit creates a new user configuration file with
66 ## those parameters for which the default value seems
67 ## invalid. The remaining parameters are commented out.
68
69 ## Parameters in this file will be accessible from within python as
70 ## fields of Biskit.settings. For example::
71 ##
72 ## leaprc = some/path/to/leaprc # some comment
73 ##
74 ## will lead to a variable in Biskit.settings::
75 ##
76 ## >>> import Biskit.setting as S
77 ## >>> S.leaprc
78 ## >>> 'some/path/to/leaprc'
79
80 ## ...If, and only if, leaprc also exists in the default settings
81 ## file. Parameters that are not listed in the default settings file
82 ## are ignored.
83
84 ## The default type of parameters is str. A prefix to the name like
85 ## 'int-', 'float-', 'bool-', etc. will be interpreted as
86 ## type-casting. For example::
87 ##
88 ## float-nice_value = 10 # some comment
89 ##
90 ## will lead to a variable in Biskit.settings::
91 ##
92 ## >>> S.nice_value
93 ## >>> 10.0
94
95 """
96
97 - def __init__( self, fdefault, fuser, createmissing=False, verbose=1 ):
98 """
99 @param fdefault: default configuration file
100 @type fdedault: str
101 @param fuser: user configuration file
102 @type fuser: str
103 @param createmissing: create user config file if missing
104 @type createmissing: bool
105 @param verbose: verbosity level (default: 1)
106 @type verbose: 1|0
107 """
108 self.verbose = verbose
109 self.fdefault = fdefault
110 self.fuser = fuser
111 self.createmissing = createmissing
112 self.fusermissing = not os.path.exists( T.absfile(fuser) )
113
114 self.settings = []
115
116 - def __update( self, cfg_default, cfg_user ):
117 """
118 Override default settings by valid (or equally invalid) user settings.
119
120 @param cfg_default: settings read in from default file
121 @type cfg_default: dict {'str':SettingsParser.Setting}
122 @param cfg_user : settings read in from user config file
123 @type cfg_user : dict {'str':SettingsParser.Setting}
124
125 @return: configuration with valid user settings overriding default ones
126 @rtype: dict {'str':SettingsParser.Setting}
127 """
128 r = {}
129 errors = {}
130
131 for name, default in cfg_default.items():
132
133 next = cfg_user.get( name, default )
134
135 if next.error > default.error:
136
137 if self.verbose: B.EHandler.warning(\
138 'User setting %s is reset to default (%r),\n\treason: %s'\
139 % (name, default.value, next.error)\
140 + '\n\tPlease check %s!' % self.fuser )
141
142 next = default
143
144 r[name] = next
145
146 return r
147
148
150 """
151 Parse and combine default and user-defined config files.
152 """
153 try:
154 pdefault = P.SettingsParser( self.fdefault )
155 cdefault = pdefault.parse()
156
157 try:
158 puser = P.SettingsParser( self.fuser )
159 cuser = puser.parse()
160
161 except IOError, e:
162 if self.verbose: B.EHandler.warning(
163 'Could not find file with user-defined settings in %s' \
164 % self.fuser, trace=0, error=0)
165
166 cuser = {}
167
168 self.settings = self.__update( cdefault, cuser )
169
170 except P.SettingsError, e:
171 B.EHandler.fatal( str(e) )
172
173
175 """
176 Create a settings file with all options that are invalid with their
177 default value.
178 """
179 try:
180 T.backup( self.fuser )
181
182 fpath = os.path.dirname(self.fuser)
183 if not os.path.exists( fpath ):
184 if self.verbose:
185 B.EHandler.warning('Creating folder %s for Biskit settings.'\
186 %fpath )
187 os.mkdir( fpath )
188
189 sections = [P.Setting.NORMAL, P.Setting.PATH, P.Setting.BIN]
190 r = {}
191
192 for section in sections:
193
194 r[ section ] = [ s for s in self.settings.values() \
195 if s.section == section]
196 r[ section ].sort()
197
198 f = open( self.fuser, 'w' )
199
200 f.write( SettingsManager.USER_HEADER % self.__dict__ )
201
202 for section in sections:
203
204 f.write( '[%s]\n' % section )
205 f.write('\n')
206
207 for param in r[section]:
208
209 if (not errorsonly) or param.error:
210 f.write( param.formatted() + '\n')
211 else:
212 f.write( '## ' + param.formatted() + '\n')
213
214 f.write('\n')
215
216 f.close()
217
218 except OSError, e:
219 raise WriteCfgError, e
220
222 """
223 Create dictionary from settings.
224 @return: dictionary of parameter names (keys) and values
225 @rtype: dict {str : any}
226 """
227 return dict( [ (s.name, s.value) for s in self.settings.values() ] )
228
229
231 """
232 1. Parse in default configuration and user configuration file
233 2. Merge the two, preferring valid user settings
234 3. Create missing user configuration file if createmissing=True
235 4. Insert parameters into the given namespace
236
237 @param ns: namespace of a module ( obtained with locals() )
238 @type ns: dict {str:any}
239 """
240 self.collectSettings()
241
242 if self.fusermissing and self.createmissing:
243 if self.verbose:
244 B.EHandler.warning('Creating new user configuration file %s.' \
245 % self.fuser, trace=0, error=0)
246 self.writeUserSettings( errorsonly=True )
247
248 d = self.settings2dict()
249
250 ns.update( d )
251
252
253
254
255
256
258 """
259 Test class
260 """
261
262 - def run( self, local=0 ):
263 """
264 run function test
265 @param local: transfer local variables to global and perform
266 other tasks only when run locally
267 @type local: 1|0
268
269 @return: 42
270 @rtype: int
271 """
272 m = SettingsManager( T.projectRoot()+'/external/defaults/settings.cfg',
273 T.tempDir() + '/settings.cfg',
274 createmissing=True,
275 verbose=local )
276
277 ns = locals()
278
279 m.updateNamespace( ns )
280
281 if local:
282 globals().update( locals() )
283
284 T.tryRemove( T.tempDir() + '/settings.cfg' )
285
286 r = m.settings2dict()['testparam']
287
288 return r
289
290
292 """
293 Precalculated result to check for consistent performance.
294
295 @return: 42
296 @rtype: int
297 """
298 return 42
299
300
301
302 if __name__ == '__main__':
303
304 test = Test()
305
306 assert test.run( local=1 ) == test.expected_result()
307