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