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