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