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