##// END OF EJS Templates
Equivalent but simpler/faster code in loader.py.
Fernando Perez -
Show More
@@ -1,353 +1,352 b''
1 1 # coding: utf-8
2 2 """A simple configuration system.
3 3
4 4 Authors
5 5 -------
6 6 * Brian Granger
7 7 * Fernando Perez
8 8 """
9 9
10 10 #-----------------------------------------------------------------------------
11 11 # Copyright (C) 2008-2009 The IPython Development Team
12 12 #
13 13 # Distributed under the terms of the BSD License. The full license is in
14 14 # the file COPYING, distributed as part of this software.
15 15 #-----------------------------------------------------------------------------
16 16
17 17 #-----------------------------------------------------------------------------
18 18 # Imports
19 19 #-----------------------------------------------------------------------------
20 20
21 21 import __builtin__
22 22 import os
23 23 import sys
24 24
25 25 from IPython.external import argparse
26 26 from IPython.utils.genutils import filefind
27 27
28 28 #-----------------------------------------------------------------------------
29 29 # Exceptions
30 30 #-----------------------------------------------------------------------------
31 31
32 32
33 33 class ConfigError(Exception):
34 34 pass
35 35
36 36
37 37 class ConfigLoaderError(ConfigError):
38 38 pass
39 39
40 40 #-----------------------------------------------------------------------------
41 41 # Argparse fix
42 42 #-----------------------------------------------------------------------------
43 43 # Unfortunately argparse by default prints help messages to stderr instead of
44 44 # stdout. This makes it annoying to capture long help screens at the command
45 45 # line, since one must know how to pipe stderr, which many users don't know how
46 46 # to do. So we override the print_help method with one that defaults to
47 47 # stdout and use our class instead.
48 48
49 49 class ArgumentParser(argparse.ArgumentParser):
50 50 """Simple argparse subclass that prints help to stdout by default."""
51 51
52 52 def print_help(self, file=None):
53 53 if file is None:
54 54 file = sys.stdout
55 55 return super(ArgumentParser, self).print_help(file)
56 56
57 57 print_help.__doc__ = argparse.ArgumentParser.print_help.__doc__
58 58
59 59 #-----------------------------------------------------------------------------
60 60 # Config class for holding config information
61 61 #-----------------------------------------------------------------------------
62 62
63 63
64 64 class Config(dict):
65 65 """An attribute based dict that can do smart merges."""
66 66
67 67 def __init__(self, *args, **kwds):
68 68 dict.__init__(self, *args, **kwds)
69 69 # This sets self.__dict__ = self, but it has to be done this way
70 70 # because we are also overriding __setattr__.
71 71 dict.__setattr__(self, '__dict__', self)
72 72
73 73 def _merge(self, other):
74 74 to_update = {}
75 75 for k, v in other.items():
76 76 if not self.has_key(k):
77 77 to_update[k] = v
78 78 else: # I have this key
79 79 if isinstance(v, Config):
80 80 # Recursively merge common sub Configs
81 81 self[k]._merge(v)
82 82 else:
83 83 # Plain updates for non-Configs
84 84 to_update[k] = v
85 85
86 86 self.update(to_update)
87 87
88 88 def _is_section_key(self, key):
89 89 if key[0].upper()==key[0] and not key.startswith('_'):
90 90 return True
91 91 else:
92 92 return False
93 93
94 94 def has_key(self, key):
95 95 if self._is_section_key(key):
96 96 return True
97 97 else:
98 98 return dict.has_key(self, key)
99 99
100 100 def _has_section(self, key):
101 101 if self._is_section_key(key):
102 102 if dict.has_key(self, key):
103 103 return True
104 104 return False
105 105
106 106 def copy(self):
107 107 return type(self)(dict.copy(self))
108 108
109 109 def __copy__(self):
110 110 return self.copy()
111 111
112 112 def __deepcopy__(self, memo):
113 113 import copy
114 114 return type(self)(copy.deepcopy(self.items()))
115 115
116 116 def __getitem__(self, key):
117 117 # Because we use this for an exec namespace, we need to delegate
118 118 # the lookup of names in __builtin__ to itself. This means
119 119 # that you can't have section or attribute names that are
120 120 # builtins.
121 121 try:
122 122 return getattr(__builtin__, key)
123 123 except AttributeError:
124 124 pass
125 125 if self._is_section_key(key):
126 126 try:
127 127 return dict.__getitem__(self, key)
128 128 except KeyError:
129 129 c = Config()
130 130 dict.__setitem__(self, key, c)
131 131 return c
132 132 else:
133 133 return dict.__getitem__(self, key)
134 134
135 135 def __setitem__(self, key, value):
136 136 # Don't allow names in __builtin__ to be modified.
137 137 if hasattr(__builtin__, key):
138 138 raise ConfigError('Config variable names cannot have the same name '
139 139 'as a Python builtin: %s' % key)
140 140 if self._is_section_key(key):
141 141 if not isinstance(value, Config):
142 142 raise ValueError('values whose keys begin with an uppercase '
143 143 'char must be Config instances: %r, %r' % (key, value))
144 144 else:
145 145 dict.__setitem__(self, key, value)
146 146
147 147 def __getattr__(self, key):
148 148 try:
149 149 return self.__getitem__(key)
150 150 except KeyError, e:
151 151 raise AttributeError(e)
152 152
153 153 def __setattr__(self, key, value):
154 154 try:
155 155 self.__setitem__(key, value)
156 156 except KeyError, e:
157 157 raise AttributeError(e)
158 158
159 159 def __delattr__(self, key):
160 160 try:
161 161 dict.__delitem__(self, key)
162 162 except KeyError, e:
163 163 raise AttributeError(e)
164 164
165 165
166 166 #-----------------------------------------------------------------------------
167 167 # Config loading classes
168 168 #-----------------------------------------------------------------------------
169 169
170 170
171 171 class ConfigLoader(object):
172 172 """A object for loading configurations from just about anywhere.
173 173
174 174 The resulting configuration is packaged as a :class:`Struct`.
175 175
176 176 Notes
177 177 -----
178 178 A :class:`ConfigLoader` does one thing: load a config from a source
179 179 (file, command line arguments) and returns the data as a :class:`Struct`.
180 180 There are lots of things that :class:`ConfigLoader` does not do. It does
181 181 not implement complex logic for finding config files. It does not handle
182 182 default values or merge multiple configs. These things need to be
183 183 handled elsewhere.
184 184 """
185 185
186 186 def __init__(self):
187 187 """A base class for config loaders.
188 188
189 189 Examples
190 190 --------
191 191
192 192 >>> cl = ConfigLoader()
193 193 >>> config = cl.load_config()
194 194 >>> config
195 195 {}
196 196 """
197 197 self.clear()
198 198
199 199 def clear(self):
200 200 self.config = Config()
201 201
202 202 def load_config(self):
203 203 """Load a config from somewhere, return a Struct.
204 204
205 205 Usually, this will cause self.config to be set and then returned.
206 206 """
207 207 return self.config
208 208
209 209
210 210 class FileConfigLoader(ConfigLoader):
211 211 """A base class for file based configurations.
212 212
213 213 As we add more file based config loaders, the common logic should go
214 214 here.
215 215 """
216 216 pass
217 217
218 218
219 219 class PyFileConfigLoader(FileConfigLoader):
220 220 """A config loader for pure python files.
221 221
222 222 This calls execfile on a plain python file and looks for attributes
223 223 that are all caps. These attribute are added to the config Struct.
224 224 """
225 225
226 226 def __init__(self, filename, path=None):
227 227 """Build a config loader for a filename and path.
228 228
229 229 Parameters
230 230 ----------
231 231 filename : str
232 232 The file name of the config file.
233 233 path : str, list, tuple
234 234 The path to search for the config file on, or a sequence of
235 235 paths to try in order.
236 236 """
237 237 super(PyFileConfigLoader, self).__init__()
238 238 self.filename = filename
239 239 self.path = path
240 240 self.full_filename = ''
241 241 self.data = None
242 242
243 243 def load_config(self):
244 244 """Load the config from a file and return it as a Struct."""
245 245 self._find_file()
246 246 self._read_file_as_dict()
247 247 self._convert_to_config()
248 248 return self.config
249 249
250 250 def _find_file(self):
251 251 """Try to find the file by searching the paths."""
252 252 self.full_filename = filefind(self.filename, self.path)
253 253
254 254 def _read_file_as_dict(self):
255 255 """Load the config file into self.config, with recursive loading."""
256 256 # This closure is made available in the namespace that is used
257 257 # to exec the config file. This allows users to call
258 258 # load_subconfig('myconfig.py') to load config files recursively.
259 259 # It needs to be a closure because it has references to self.path
260 260 # and self.config. The sub-config is loaded with the same path
261 261 # as the parent, but it uses an empty config which is then merged
262 262 # with the parents.
263 263 def load_subconfig(fname):
264 264 loader = PyFileConfigLoader(fname, self.path)
265 265 try:
266 266 sub_config = loader.load_config()
267 267 except IOError:
268 268 # Pass silently if the sub config is not there. This happens
269 269 # when a user us using a profile, but not the default config.
270 270 pass
271 271 else:
272 272 self.config._merge(sub_config)
273 273
274 274 # Again, this needs to be a closure and should be used in config
275 275 # files to get the config being loaded.
276 276 def get_config():
277 277 return self.config
278 278
279 279 namespace = dict(load_subconfig=load_subconfig, get_config=get_config)
280 280 execfile(self.full_filename, namespace)
281 281
282 282 def _convert_to_config(self):
283 283 if self.data is None:
284 284 ConfigLoaderError('self.data does not exist')
285 285
286 286
287 287 class CommandLineConfigLoader(ConfigLoader):
288 288 """A config loader for command line arguments.
289 289
290 290 As we add more command line based loaders, the common logic should go
291 291 here.
292 292 """
293 293
294 294
295 295 class NoConfigDefault(object): pass
296 296 NoConfigDefault = NoConfigDefault()
297 297
298 298
299 299 class ArgParseConfigLoader(CommandLineConfigLoader):
300 300
301 301 # arguments = [(('-f','--file'),dict(type=str,dest='file'))]
302 302 arguments = ()
303 303
304 304 def __init__(self, *args, **kw):
305 305 """Create a config loader for use with argparse.
306 306
307 307 The args and kwargs arguments here are passed onto the constructor
308 308 of :class:`argparse.ArgumentParser`.
309 309 """
310 310 super(CommandLineConfigLoader, self).__init__()
311 311 self.args = args
312 312 self.kw = kw
313 313
314 314 def load_config(self, args=None):
315 315 """Parse command line arguments and return as a Struct."""
316 316 self._create_parser()
317 317 self._parse_args(args)
318 318 self._convert_to_config()
319 319 return self.config
320 320
321 321 def get_extra_args(self):
322 322 if hasattr(self, 'extra_args'):
323 323 return self.extra_args
324 324 else:
325 325 return []
326 326
327 327 def _create_parser(self):
328 328 self.parser = ArgumentParser(*self.args, **self.kw)
329 329 self._add_arguments()
330 330 self._add_other_arguments()
331 331
332 332 def _add_other_arguments(self):
333 333 pass
334 334
335 335 def _add_arguments(self):
336 336 for argument in self.arguments:
337 if not argument[1].has_key('default'):
338 argument[1]['default'] = NoConfigDefault
337 argument[1].setdefault('default', NoConfigDefault)
339 338 self.parser.add_argument(*argument[0],**argument[1])
340 339
341 340 def _parse_args(self, args=None):
342 341 """self.parser->self.parsed_data"""
343 342 if args is None:
344 343 self.parsed_data, self.extra_args = self.parser.parse_known_args()
345 344 else:
346 345 self.parsed_data, self.extra_args = self.parser.parse_known_args(args)
347 346
348 347 def _convert_to_config(self):
349 348 """self.parsed_data->self.config"""
350 349 for k, v in vars(self.parsed_data).items():
351 350 if v is not NoConfigDefault:
352 351 exec_str = 'self.config.' + k + '= v'
353 352 exec exec_str in locals(), globals()
General Comments 0
You need to be logged in to leave comments. Login now