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