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