##// END OF EJS Templates
Remove unused imports
Spencer Nelson -
Show More
@@ -1,846 +1,846 b''
1 """A simple configuration system.
1 """A simple configuration system.
2
2
3 Inheritance diagram:
3 Inheritance diagram:
4
4
5 .. inheritance-diagram:: IPython.config.loader
5 .. inheritance-diagram:: IPython.config.loader
6 :parts: 3
6 :parts: 3
7
7
8 Authors
8 Authors
9 -------
9 -------
10 * Brian Granger
10 * Brian Granger
11 * Fernando Perez
11 * Fernando Perez
12 * Min RK
12 * Min RK
13 """
13 """
14
14
15 #-----------------------------------------------------------------------------
15 #-----------------------------------------------------------------------------
16 # Copyright (C) 2008-2011 The IPython Development Team
16 # Copyright (C) 2008-2011 The IPython Development Team
17 #
17 #
18 # Distributed under the terms of the BSD License. The full license is in
18 # Distributed under the terms of the BSD License. The full license is in
19 # the file COPYING, distributed as part of this software.
19 # the file COPYING, distributed as part of this software.
20 #-----------------------------------------------------------------------------
20 #-----------------------------------------------------------------------------
21
21
22 #-----------------------------------------------------------------------------
22 #-----------------------------------------------------------------------------
23 # Imports
23 # Imports
24 #-----------------------------------------------------------------------------
24 #-----------------------------------------------------------------------------
25
25
26 import argparse
26 import argparse
27 import copy
27 import copy
28 import logging
28 import logging
29 import os
29 import os
30 import re
30 import re
31 import sys
31 import sys
32 import json
32 import json
33
33
34 from IPython.utils.path import filefind, get_ipython_dir
34 from IPython.utils.path import filefind, get_ipython_dir
35 from IPython.utils import py3compat
35 from IPython.utils import py3compat
36 from IPython.utils.encoding import DEFAULT_ENCODING
36 from IPython.utils.encoding import DEFAULT_ENCODING
37 from IPython.utils.py3compat import unicode_type, iteritems
37 from IPython.utils.py3compat import unicode_type, iteritems
38 from IPython.utils.traitlets import HasTraits, List, Any, TraitError
38 from IPython.utils.traitlets import HasTraits, List, Any
39
39
40 #-----------------------------------------------------------------------------
40 #-----------------------------------------------------------------------------
41 # Exceptions
41 # Exceptions
42 #-----------------------------------------------------------------------------
42 #-----------------------------------------------------------------------------
43
43
44
44
45 class ConfigError(Exception):
45 class ConfigError(Exception):
46 pass
46 pass
47
47
48 class ConfigLoaderError(ConfigError):
48 class ConfigLoaderError(ConfigError):
49 pass
49 pass
50
50
51 class ConfigFileNotFound(ConfigError):
51 class ConfigFileNotFound(ConfigError):
52 pass
52 pass
53
53
54 class ArgumentError(ConfigLoaderError):
54 class ArgumentError(ConfigLoaderError):
55 pass
55 pass
56
56
57 #-----------------------------------------------------------------------------
57 #-----------------------------------------------------------------------------
58 # Argparse fix
58 # Argparse fix
59 #-----------------------------------------------------------------------------
59 #-----------------------------------------------------------------------------
60
60
61 # Unfortunately argparse by default prints help messages to stderr instead of
61 # Unfortunately argparse by default prints help messages to stderr instead of
62 # stdout. This makes it annoying to capture long help screens at the command
62 # stdout. This makes it annoying to capture long help screens at the command
63 # line, since one must know how to pipe stderr, which many users don't know how
63 # line, since one must know how to pipe stderr, which many users don't know how
64 # to do. So we override the print_help method with one that defaults to
64 # to do. So we override the print_help method with one that defaults to
65 # stdout and use our class instead.
65 # stdout and use our class instead.
66
66
67 class ArgumentParser(argparse.ArgumentParser):
67 class ArgumentParser(argparse.ArgumentParser):
68 """Simple argparse subclass that prints help to stdout by default."""
68 """Simple argparse subclass that prints help to stdout by default."""
69
69
70 def print_help(self, file=None):
70 def print_help(self, file=None):
71 if file is None:
71 if file is None:
72 file = sys.stdout
72 file = sys.stdout
73 return super(ArgumentParser, self).print_help(file)
73 return super(ArgumentParser, self).print_help(file)
74
74
75 print_help.__doc__ = argparse.ArgumentParser.print_help.__doc__
75 print_help.__doc__ = argparse.ArgumentParser.print_help.__doc__
76
76
77 #-----------------------------------------------------------------------------
77 #-----------------------------------------------------------------------------
78 # Config class for holding config information
78 # Config class for holding config information
79 #-----------------------------------------------------------------------------
79 #-----------------------------------------------------------------------------
80
80
81 class LazyConfigValue(HasTraits):
81 class LazyConfigValue(HasTraits):
82 """Proxy object for exposing methods on configurable containers
82 """Proxy object for exposing methods on configurable containers
83
83
84 Exposes:
84 Exposes:
85
85
86 - append, extend, insert on lists
86 - append, extend, insert on lists
87 - update on dicts
87 - update on dicts
88 - update, add on sets
88 - update, add on sets
89 """
89 """
90
90
91 _value = None
91 _value = None
92
92
93 # list methods
93 # list methods
94 _extend = List()
94 _extend = List()
95 _prepend = List()
95 _prepend = List()
96
96
97 def append(self, obj):
97 def append(self, obj):
98 self._extend.append(obj)
98 self._extend.append(obj)
99
99
100 def extend(self, other):
100 def extend(self, other):
101 self._extend.extend(other)
101 self._extend.extend(other)
102
102
103 def prepend(self, other):
103 def prepend(self, other):
104 """like list.extend, but for the front"""
104 """like list.extend, but for the front"""
105 self._prepend[:0] = other
105 self._prepend[:0] = other
106
106
107 _inserts = List()
107 _inserts = List()
108 def insert(self, index, other):
108 def insert(self, index, other):
109 if not isinstance(index, int):
109 if not isinstance(index, int):
110 raise TypeError("An integer is required")
110 raise TypeError("An integer is required")
111 self._inserts.append((index, other))
111 self._inserts.append((index, other))
112
112
113 # dict methods
113 # dict methods
114 # update is used for both dict and set
114 # update is used for both dict and set
115 _update = Any()
115 _update = Any()
116 def update(self, other):
116 def update(self, other):
117 if self._update is None:
117 if self._update is None:
118 if isinstance(other, dict):
118 if isinstance(other, dict):
119 self._update = {}
119 self._update = {}
120 else:
120 else:
121 self._update = set()
121 self._update = set()
122 self._update.update(other)
122 self._update.update(other)
123
123
124 # set methods
124 # set methods
125 def add(self, obj):
125 def add(self, obj):
126 self.update({obj})
126 self.update({obj})
127
127
128 def get_value(self, initial):
128 def get_value(self, initial):
129 """construct the value from the initial one
129 """construct the value from the initial one
130
130
131 after applying any insert / extend / update changes
131 after applying any insert / extend / update changes
132 """
132 """
133 if self._value is not None:
133 if self._value is not None:
134 return self._value
134 return self._value
135 value = copy.deepcopy(initial)
135 value = copy.deepcopy(initial)
136 if isinstance(value, list):
136 if isinstance(value, list):
137 for idx, obj in self._inserts:
137 for idx, obj in self._inserts:
138 value.insert(idx, obj)
138 value.insert(idx, obj)
139 value[:0] = self._prepend
139 value[:0] = self._prepend
140 value.extend(self._extend)
140 value.extend(self._extend)
141
141
142 elif isinstance(value, dict):
142 elif isinstance(value, dict):
143 if self._update:
143 if self._update:
144 value.update(self._update)
144 value.update(self._update)
145 elif isinstance(value, set):
145 elif isinstance(value, set):
146 if self._update:
146 if self._update:
147 value.update(self._update)
147 value.update(self._update)
148 self._value = value
148 self._value = value
149 return value
149 return value
150
150
151 def to_dict(self):
151 def to_dict(self):
152 """return JSONable dict form of my data
152 """return JSONable dict form of my data
153
153
154 Currently update as dict or set, extend, prepend as lists, and inserts as list of tuples.
154 Currently update as dict or set, extend, prepend as lists, and inserts as list of tuples.
155 """
155 """
156 d = {}
156 d = {}
157 if self._update:
157 if self._update:
158 d['update'] = self._update
158 d['update'] = self._update
159 if self._extend:
159 if self._extend:
160 d['extend'] = self._extend
160 d['extend'] = self._extend
161 if self._prepend:
161 if self._prepend:
162 d['prepend'] = self._prepend
162 d['prepend'] = self._prepend
163 elif self._inserts:
163 elif self._inserts:
164 d['inserts'] = self._inserts
164 d['inserts'] = self._inserts
165 return d
165 return d
166
166
167
167
168 def _is_section_key(key):
168 def _is_section_key(key):
169 """Is a Config key a section name (does it start with a capital)?"""
169 """Is a Config key a section name (does it start with a capital)?"""
170 if key and key[0].upper()==key[0] and not key.startswith('_'):
170 if key and key[0].upper()==key[0] and not key.startswith('_'):
171 return True
171 return True
172 else:
172 else:
173 return False
173 return False
174
174
175
175
176 class Config(dict):
176 class Config(dict):
177 """An attribute based dict that can do smart merges."""
177 """An attribute based dict that can do smart merges."""
178
178
179 def __init__(self, *args, **kwds):
179 def __init__(self, *args, **kwds):
180 dict.__init__(self, *args, **kwds)
180 dict.__init__(self, *args, **kwds)
181 self._ensure_subconfig()
181 self._ensure_subconfig()
182
182
183 def _ensure_subconfig(self):
183 def _ensure_subconfig(self):
184 """ensure that sub-dicts that should be Config objects are
184 """ensure that sub-dicts that should be Config objects are
185
185
186 casts dicts that are under section keys to Config objects,
186 casts dicts that are under section keys to Config objects,
187 which is necessary for constructing Config objects from dict literals.
187 which is necessary for constructing Config objects from dict literals.
188 """
188 """
189 for key in self:
189 for key in self:
190 obj = self[key]
190 obj = self[key]
191 if _is_section_key(key) \
191 if _is_section_key(key) \
192 and isinstance(obj, dict) \
192 and isinstance(obj, dict) \
193 and not isinstance(obj, Config):
193 and not isinstance(obj, Config):
194 setattr(self, key, Config(obj))
194 setattr(self, key, Config(obj))
195
195
196 def _merge(self, other):
196 def _merge(self, other):
197 """deprecated alias, use Config.merge()"""
197 """deprecated alias, use Config.merge()"""
198 self.merge(other)
198 self.merge(other)
199
199
200 def merge(self, other):
200 def merge(self, other):
201 """merge another config object into this one"""
201 """merge another config object into this one"""
202 to_update = {}
202 to_update = {}
203 for k, v in iteritems(other):
203 for k, v in iteritems(other):
204 if k not in self:
204 if k not in self:
205 to_update[k] = copy.deepcopy(v)
205 to_update[k] = copy.deepcopy(v)
206 else: # I have this key
206 else: # I have this key
207 if isinstance(v, Config) and isinstance(self[k], Config):
207 if isinstance(v, Config) and isinstance(self[k], Config):
208 # Recursively merge common sub Configs
208 # Recursively merge common sub Configs
209 self[k].merge(v)
209 self[k].merge(v)
210 else:
210 else:
211 # Plain updates for non-Configs
211 # Plain updates for non-Configs
212 to_update[k] = copy.deepcopy(v)
212 to_update[k] = copy.deepcopy(v)
213
213
214 self.update(to_update)
214 self.update(to_update)
215
215
216 def __contains__(self, key):
216 def __contains__(self, key):
217 # allow nested contains of the form `"Section.key" in config`
217 # allow nested contains of the form `"Section.key" in config`
218 if '.' in key:
218 if '.' in key:
219 first, remainder = key.split('.', 1)
219 first, remainder = key.split('.', 1)
220 if first not in self:
220 if first not in self:
221 return False
221 return False
222 return remainder in self[first]
222 return remainder in self[first]
223
223
224 return super(Config, self).__contains__(key)
224 return super(Config, self).__contains__(key)
225
225
226 # .has_key is deprecated for dictionaries.
226 # .has_key is deprecated for dictionaries.
227 has_key = __contains__
227 has_key = __contains__
228
228
229 def _has_section(self, key):
229 def _has_section(self, key):
230 return _is_section_key(key) and key in self
230 return _is_section_key(key) and key in self
231
231
232 def copy(self):
232 def copy(self):
233 return type(self)(dict.copy(self))
233 return type(self)(dict.copy(self))
234
234
235 def __copy__(self):
235 def __copy__(self):
236 return self.copy()
236 return self.copy()
237
237
238 def __deepcopy__(self, memo):
238 def __deepcopy__(self, memo):
239 import copy
239 import copy
240 return type(self)(copy.deepcopy(list(self.items())))
240 return type(self)(copy.deepcopy(list(self.items())))
241
241
242 def __getitem__(self, key):
242 def __getitem__(self, key):
243 try:
243 try:
244 return dict.__getitem__(self, key)
244 return dict.__getitem__(self, key)
245 except KeyError:
245 except KeyError:
246 if _is_section_key(key):
246 if _is_section_key(key):
247 c = Config()
247 c = Config()
248 dict.__setitem__(self, key, c)
248 dict.__setitem__(self, key, c)
249 return c
249 return c
250 elif not key.startswith('_'):
250 elif not key.startswith('_'):
251 # undefined, create lazy value, used for container methods
251 # undefined, create lazy value, used for container methods
252 v = LazyConfigValue()
252 v = LazyConfigValue()
253 dict.__setitem__(self, key, v)
253 dict.__setitem__(self, key, v)
254 return v
254 return v
255 else:
255 else:
256 raise KeyError
256 raise KeyError
257
257
258 def __setitem__(self, key, value):
258 def __setitem__(self, key, value):
259 if _is_section_key(key):
259 if _is_section_key(key):
260 if not isinstance(value, Config):
260 if not isinstance(value, Config):
261 raise ValueError('values whose keys begin with an uppercase '
261 raise ValueError('values whose keys begin with an uppercase '
262 'char must be Config instances: %r, %r' % (key, value))
262 'char must be Config instances: %r, %r' % (key, value))
263 dict.__setitem__(self, key, value)
263 dict.__setitem__(self, key, value)
264
264
265 def __getattr__(self, key):
265 def __getattr__(self, key):
266 if key.startswith('__'):
266 if key.startswith('__'):
267 return dict.__getattr__(self, key)
267 return dict.__getattr__(self, key)
268 try:
268 try:
269 return self.__getitem__(key)
269 return self.__getitem__(key)
270 except KeyError as e:
270 except KeyError as e:
271 raise AttributeError(e)
271 raise AttributeError(e)
272
272
273 def __setattr__(self, key, value):
273 def __setattr__(self, key, value):
274 if key.startswith('__'):
274 if key.startswith('__'):
275 return dict.__setattr__(self, key, value)
275 return dict.__setattr__(self, key, value)
276 try:
276 try:
277 self.__setitem__(key, value)
277 self.__setitem__(key, value)
278 except KeyError as e:
278 except KeyError as e:
279 raise AttributeError(e)
279 raise AttributeError(e)
280
280
281 def __delattr__(self, key):
281 def __delattr__(self, key):
282 if key.startswith('__'):
282 if key.startswith('__'):
283 return dict.__delattr__(self, key)
283 return dict.__delattr__(self, key)
284 try:
284 try:
285 dict.__delitem__(self, key)
285 dict.__delitem__(self, key)
286 except KeyError as e:
286 except KeyError as e:
287 raise AttributeError(e)
287 raise AttributeError(e)
288
288
289
289
290 #-----------------------------------------------------------------------------
290 #-----------------------------------------------------------------------------
291 # Config loading classes
291 # Config loading classes
292 #-----------------------------------------------------------------------------
292 #-----------------------------------------------------------------------------
293
293
294
294
295 class ConfigLoader(object):
295 class ConfigLoader(object):
296 """A object for loading configurations from just about anywhere.
296 """A object for loading configurations from just about anywhere.
297
297
298 The resulting configuration is packaged as a :class:`Config`.
298 The resulting configuration is packaged as a :class:`Config`.
299
299
300 Notes
300 Notes
301 -----
301 -----
302 A :class:`ConfigLoader` does one thing: load a config from a source
302 A :class:`ConfigLoader` does one thing: load a config from a source
303 (file, command line arguments) and returns the data as a :class:`Config` object.
303 (file, command line arguments) and returns the data as a :class:`Config` object.
304 There are lots of things that :class:`ConfigLoader` does not do. It does
304 There are lots of things that :class:`ConfigLoader` does not do. It does
305 not implement complex logic for finding config files. It does not handle
305 not implement complex logic for finding config files. It does not handle
306 default values or merge multiple configs. These things need to be
306 default values or merge multiple configs. These things need to be
307 handled elsewhere.
307 handled elsewhere.
308 """
308 """
309
309
310 def _log_default(self):
310 def _log_default(self):
311 from IPython.config.application import Application
311 from IPython.config.application import Application
312 if Application.initialized():
312 if Application.initialized():
313 return Application.instance().log
313 return Application.instance().log
314 else:
314 else:
315 return logging.getLogger()
315 return logging.getLogger()
316
316
317 def __init__(self, log=None):
317 def __init__(self, log=None):
318 """A base class for config loaders.
318 """A base class for config loaders.
319
319
320 log : instance of :class:`logging.Logger` to use.
320 log : instance of :class:`logging.Logger` to use.
321 By default loger of :meth:`IPython.config.application.Application.instance()`
321 By default loger of :meth:`IPython.config.application.Application.instance()`
322 will be used
322 will be used
323
323
324 Examples
324 Examples
325 --------
325 --------
326
326
327 >>> cl = ConfigLoader()
327 >>> cl = ConfigLoader()
328 >>> config = cl.load_config()
328 >>> config = cl.load_config()
329 >>> config
329 >>> config
330 {}
330 {}
331 """
331 """
332 self.clear()
332 self.clear()
333 if log is None:
333 if log is None:
334 self.log = self._log_default()
334 self.log = self._log_default()
335 self.log.debug('Using default logger')
335 self.log.debug('Using default logger')
336 else:
336 else:
337 self.log = log
337 self.log = log
338
338
339 def clear(self):
339 def clear(self):
340 self.config = Config()
340 self.config = Config()
341
341
342 def load_config(self):
342 def load_config(self):
343 """Load a config from somewhere, return a :class:`Config` instance.
343 """Load a config from somewhere, return a :class:`Config` instance.
344
344
345 Usually, this will cause self.config to be set and then returned.
345 Usually, this will cause self.config to be set and then returned.
346 However, in most cases, :meth:`ConfigLoader.clear` should be called
346 However, in most cases, :meth:`ConfigLoader.clear` should be called
347 to erase any previous state.
347 to erase any previous state.
348 """
348 """
349 self.clear()
349 self.clear()
350 return self.config
350 return self.config
351
351
352
352
353 class FileConfigLoader(ConfigLoader):
353 class FileConfigLoader(ConfigLoader):
354 """A base class for file based configurations.
354 """A base class for file based configurations.
355
355
356 As we add more file based config loaders, the common logic should go
356 As we add more file based config loaders, the common logic should go
357 here.
357 here.
358 """
358 """
359
359
360 def __init__(self, filename, path=None, **kw):
360 def __init__(self, filename, path=None, **kw):
361 """Build a config loader for a filename and path.
361 """Build a config loader for a filename and path.
362
362
363 Parameters
363 Parameters
364 ----------
364 ----------
365 filename : str
365 filename : str
366 The file name of the config file.
366 The file name of the config file.
367 path : str, list, tuple
367 path : str, list, tuple
368 The path to search for the config file on, or a sequence of
368 The path to search for the config file on, or a sequence of
369 paths to try in order.
369 paths to try in order.
370 """
370 """
371 super(FileConfigLoader, self).__init__(**kw)
371 super(FileConfigLoader, self).__init__(**kw)
372 self.filename = filename
372 self.filename = filename
373 self.path = path
373 self.path = path
374 self.full_filename = ''
374 self.full_filename = ''
375
375
376 def _find_file(self):
376 def _find_file(self):
377 """Try to find the file by searching the paths."""
377 """Try to find the file by searching the paths."""
378 self.full_filename = filefind(self.filename, self.path)
378 self.full_filename = filefind(self.filename, self.path)
379
379
380 class JSONFileConfigLoader(FileConfigLoader):
380 class JSONFileConfigLoader(FileConfigLoader):
381 """A Json file loader for config"""
381 """A Json file loader for config"""
382
382
383 def load_config(self):
383 def load_config(self):
384 """Load the config from a file and return it as a Config object."""
384 """Load the config from a file and return it as a Config object."""
385 self.clear()
385 self.clear()
386 try:
386 try:
387 self._find_file()
387 self._find_file()
388 except IOError as e:
388 except IOError as e:
389 raise ConfigFileNotFound(str(e))
389 raise ConfigFileNotFound(str(e))
390 dct = self._read_file_as_dict()
390 dct = self._read_file_as_dict()
391 self.config = self._convert_to_config(dct)
391 self.config = self._convert_to_config(dct)
392 return self.config
392 return self.config
393
393
394 def _read_file_as_dict(self):
394 def _read_file_as_dict(self):
395 with open(self.full_filename) as f:
395 with open(self.full_filename) as f:
396 return json.load(f)
396 return json.load(f)
397
397
398 def _convert_to_config(self, dictionary):
398 def _convert_to_config(self, dictionary):
399 if 'version' in dictionary:
399 if 'version' in dictionary:
400 version = dictionary.pop('version')
400 version = dictionary.pop('version')
401 else:
401 else:
402 version = 1
402 version = 1
403 self.log.warn("Unrecognized JSON config file version, assuming version {}".format(version))
403 self.log.warn("Unrecognized JSON config file version, assuming version {}".format(version))
404
404
405 if version == 1:
405 if version == 1:
406 return Config(dictionary)
406 return Config(dictionary)
407 else:
407 else:
408 raise ValueError('Unknown version of JSON config file: {version}'.format(version=version))
408 raise ValueError('Unknown version of JSON config file: {version}'.format(version=version))
409
409
410
410
411 class PyFileConfigLoader(FileConfigLoader):
411 class PyFileConfigLoader(FileConfigLoader):
412 """A config loader for pure python files.
412 """A config loader for pure python files.
413
413
414 This is responsible for locating a Python config file by filename and
414 This is responsible for locating a Python config file by filename and
415 path, then executing it to construct a Config object.
415 path, then executing it to construct a Config object.
416 """
416 """
417
417
418 def load_config(self):
418 def load_config(self):
419 """Load the config from a file and return it as a Config object."""
419 """Load the config from a file and return it as a Config object."""
420 self.clear()
420 self.clear()
421 try:
421 try:
422 self._find_file()
422 self._find_file()
423 except IOError as e:
423 except IOError as e:
424 raise ConfigFileNotFound(str(e))
424 raise ConfigFileNotFound(str(e))
425 self._read_file_as_dict()
425 self._read_file_as_dict()
426 return self.config
426 return self.config
427
427
428
428
429 def _read_file_as_dict(self):
429 def _read_file_as_dict(self):
430 """Load the config file into self.config, with recursive loading."""
430 """Load the config file into self.config, with recursive loading."""
431 # This closure is made available in the namespace that is used
431 # This closure is made available in the namespace that is used
432 # to exec the config file. It allows users to call
432 # to exec the config file. It allows users to call
433 # load_subconfig('myconfig.py') to load config files recursively.
433 # load_subconfig('myconfig.py') to load config files recursively.
434 # It needs to be a closure because it has references to self.path
434 # It needs to be a closure because it has references to self.path
435 # and self.config. The sub-config is loaded with the same path
435 # and self.config. The sub-config is loaded with the same path
436 # as the parent, but it uses an empty config which is then merged
436 # as the parent, but it uses an empty config which is then merged
437 # with the parents.
437 # with the parents.
438
438
439 # If a profile is specified, the config file will be loaded
439 # If a profile is specified, the config file will be loaded
440 # from that profile
440 # from that profile
441
441
442 def load_subconfig(fname, profile=None):
442 def load_subconfig(fname, profile=None):
443 # import here to prevent circular imports
443 # import here to prevent circular imports
444 from IPython.core.profiledir import ProfileDir, ProfileDirError
444 from IPython.core.profiledir import ProfileDir, ProfileDirError
445 if profile is not None:
445 if profile is not None:
446 try:
446 try:
447 profile_dir = ProfileDir.find_profile_dir_by_name(
447 profile_dir = ProfileDir.find_profile_dir_by_name(
448 get_ipython_dir(),
448 get_ipython_dir(),
449 profile,
449 profile,
450 )
450 )
451 except ProfileDirError:
451 except ProfileDirError:
452 return
452 return
453 path = profile_dir.location
453 path = profile_dir.location
454 else:
454 else:
455 path = self.path
455 path = self.path
456 loader = PyFileConfigLoader(fname, path)
456 loader = PyFileConfigLoader(fname, path)
457 try:
457 try:
458 sub_config = loader.load_config()
458 sub_config = loader.load_config()
459 except ConfigFileNotFound:
459 except ConfigFileNotFound:
460 # Pass silently if the sub config is not there. This happens
460 # Pass silently if the sub config is not there. This happens
461 # when a user s using a profile, but not the default config.
461 # when a user s using a profile, but not the default config.
462 pass
462 pass
463 else:
463 else:
464 self.config.merge(sub_config)
464 self.config.merge(sub_config)
465
465
466 # Again, this needs to be a closure and should be used in config
466 # Again, this needs to be a closure and should be used in config
467 # files to get the config being loaded.
467 # files to get the config being loaded.
468 def get_config():
468 def get_config():
469 return self.config
469 return self.config
470
470
471 namespace = dict(
471 namespace = dict(
472 load_subconfig=load_subconfig,
472 load_subconfig=load_subconfig,
473 get_config=get_config,
473 get_config=get_config,
474 __file__=self.full_filename,
474 __file__=self.full_filename,
475 )
475 )
476 fs_encoding = sys.getfilesystemencoding() or 'ascii'
476 fs_encoding = sys.getfilesystemencoding() or 'ascii'
477 conf_filename = self.full_filename.encode(fs_encoding)
477 conf_filename = self.full_filename.encode(fs_encoding)
478 py3compat.execfile(conf_filename, namespace)
478 py3compat.execfile(conf_filename, namespace)
479
479
480
480
481 class CommandLineConfigLoader(ConfigLoader):
481 class CommandLineConfigLoader(ConfigLoader):
482 """A config loader for command line arguments.
482 """A config loader for command line arguments.
483
483
484 As we add more command line based loaders, the common logic should go
484 As we add more command line based loaders, the common logic should go
485 here.
485 here.
486 """
486 """
487
487
488 def _exec_config_str(self, lhs, rhs):
488 def _exec_config_str(self, lhs, rhs):
489 """execute self.config.<lhs> = <rhs>
489 """execute self.config.<lhs> = <rhs>
490
490
491 * expands ~ with expanduser
491 * expands ~ with expanduser
492 * tries to assign with raw eval, otherwise assigns with just the string,
492 * tries to assign with raw eval, otherwise assigns with just the string,
493 allowing `--C.a=foobar` and `--C.a="foobar"` to be equivalent. *Not*
493 allowing `--C.a=foobar` and `--C.a="foobar"` to be equivalent. *Not*
494 equivalent are `--C.a=4` and `--C.a='4'`.
494 equivalent are `--C.a=4` and `--C.a='4'`.
495 """
495 """
496 rhs = os.path.expanduser(rhs)
496 rhs = os.path.expanduser(rhs)
497 try:
497 try:
498 # Try to see if regular Python syntax will work. This
498 # Try to see if regular Python syntax will work. This
499 # won't handle strings as the quote marks are removed
499 # won't handle strings as the quote marks are removed
500 # by the system shell.
500 # by the system shell.
501 value = eval(rhs)
501 value = eval(rhs)
502 except (NameError, SyntaxError):
502 except (NameError, SyntaxError):
503 # This case happens if the rhs is a string.
503 # This case happens if the rhs is a string.
504 value = rhs
504 value = rhs
505
505
506 exec(u'self.config.%s = value' % lhs)
506 exec(u'self.config.%s = value' % lhs)
507
507
508 def _load_flag(self, cfg):
508 def _load_flag(self, cfg):
509 """update self.config from a flag, which can be a dict or Config"""
509 """update self.config from a flag, which can be a dict or Config"""
510 if isinstance(cfg, (dict, Config)):
510 if isinstance(cfg, (dict, Config)):
511 # don't clobber whole config sections, update
511 # don't clobber whole config sections, update
512 # each section from config:
512 # each section from config:
513 for sec,c in iteritems(cfg):
513 for sec,c in iteritems(cfg):
514 self.config[sec].update(c)
514 self.config[sec].update(c)
515 else:
515 else:
516 raise TypeError("Invalid flag: %r" % cfg)
516 raise TypeError("Invalid flag: %r" % cfg)
517
517
518 # raw --identifier=value pattern
518 # raw --identifier=value pattern
519 # but *also* accept '-' as wordsep, for aliases
519 # but *also* accept '-' as wordsep, for aliases
520 # accepts: --foo=a
520 # accepts: --foo=a
521 # --Class.trait=value
521 # --Class.trait=value
522 # --alias-name=value
522 # --alias-name=value
523 # rejects: -foo=value
523 # rejects: -foo=value
524 # --foo
524 # --foo
525 # --Class.trait
525 # --Class.trait
526 kv_pattern = re.compile(r'\-\-[A-Za-z][\w\-]*(\.[\w\-]+)*\=.*')
526 kv_pattern = re.compile(r'\-\-[A-Za-z][\w\-]*(\.[\w\-]+)*\=.*')
527
527
528 # just flags, no assignments, with two *or one* leading '-'
528 # just flags, no assignments, with two *or one* leading '-'
529 # accepts: --foo
529 # accepts: --foo
530 # -foo-bar-again
530 # -foo-bar-again
531 # rejects: --anything=anything
531 # rejects: --anything=anything
532 # --two.word
532 # --two.word
533
533
534 flag_pattern = re.compile(r'\-\-?\w+[\-\w]*$')
534 flag_pattern = re.compile(r'\-\-?\w+[\-\w]*$')
535
535
536 class KeyValueConfigLoader(CommandLineConfigLoader):
536 class KeyValueConfigLoader(CommandLineConfigLoader):
537 """A config loader that loads key value pairs from the command line.
537 """A config loader that loads key value pairs from the command line.
538
538
539 This allows command line options to be gives in the following form::
539 This allows command line options to be gives in the following form::
540
540
541 ipython --profile="foo" --InteractiveShell.autocall=False
541 ipython --profile="foo" --InteractiveShell.autocall=False
542 """
542 """
543
543
544 def __init__(self, argv=None, aliases=None, flags=None, **kw):
544 def __init__(self, argv=None, aliases=None, flags=None, **kw):
545 """Create a key value pair config loader.
545 """Create a key value pair config loader.
546
546
547 Parameters
547 Parameters
548 ----------
548 ----------
549 argv : list
549 argv : list
550 A list that has the form of sys.argv[1:] which has unicode
550 A list that has the form of sys.argv[1:] which has unicode
551 elements of the form u"key=value". If this is None (default),
551 elements of the form u"key=value". If this is None (default),
552 then sys.argv[1:] will be used.
552 then sys.argv[1:] will be used.
553 aliases : dict
553 aliases : dict
554 A dict of aliases for configurable traits.
554 A dict of aliases for configurable traits.
555 Keys are the short aliases, Values are the resolved trait.
555 Keys are the short aliases, Values are the resolved trait.
556 Of the form: `{'alias' : 'Configurable.trait'}`
556 Of the form: `{'alias' : 'Configurable.trait'}`
557 flags : dict
557 flags : dict
558 A dict of flags, keyed by str name. Vaues can be Config objects,
558 A dict of flags, keyed by str name. Vaues can be Config objects,
559 dicts, or "key=value" strings. If Config or dict, when the flag
559 dicts, or "key=value" strings. If Config or dict, when the flag
560 is triggered, The flag is loaded as `self.config.update(m)`.
560 is triggered, The flag is loaded as `self.config.update(m)`.
561
561
562 Returns
562 Returns
563 -------
563 -------
564 config : Config
564 config : Config
565 The resulting Config object.
565 The resulting Config object.
566
566
567 Examples
567 Examples
568 --------
568 --------
569
569
570 >>> from IPython.config.loader import KeyValueConfigLoader
570 >>> from IPython.config.loader import KeyValueConfigLoader
571 >>> cl = KeyValueConfigLoader()
571 >>> cl = KeyValueConfigLoader()
572 >>> d = cl.load_config(["--A.name='brian'","--B.number=0"])
572 >>> d = cl.load_config(["--A.name='brian'","--B.number=0"])
573 >>> sorted(d.items())
573 >>> sorted(d.items())
574 [('A', {'name': 'brian'}), ('B', {'number': 0})]
574 [('A', {'name': 'brian'}), ('B', {'number': 0})]
575 """
575 """
576 super(KeyValueConfigLoader, self).__init__(**kw)
576 super(KeyValueConfigLoader, self).__init__(**kw)
577 if argv is None:
577 if argv is None:
578 argv = sys.argv[1:]
578 argv = sys.argv[1:]
579 self.argv = argv
579 self.argv = argv
580 self.aliases = aliases or {}
580 self.aliases = aliases or {}
581 self.flags = flags or {}
581 self.flags = flags or {}
582
582
583
583
584 def clear(self):
584 def clear(self):
585 super(KeyValueConfigLoader, self).clear()
585 super(KeyValueConfigLoader, self).clear()
586 self.extra_args = []
586 self.extra_args = []
587
587
588
588
589 def _decode_argv(self, argv, enc=None):
589 def _decode_argv(self, argv, enc=None):
590 """decode argv if bytes, using stin.encoding, falling back on default enc"""
590 """decode argv if bytes, using stin.encoding, falling back on default enc"""
591 uargv = []
591 uargv = []
592 if enc is None:
592 if enc is None:
593 enc = DEFAULT_ENCODING
593 enc = DEFAULT_ENCODING
594 for arg in argv:
594 for arg in argv:
595 if not isinstance(arg, unicode_type):
595 if not isinstance(arg, unicode_type):
596 # only decode if not already decoded
596 # only decode if not already decoded
597 arg = arg.decode(enc)
597 arg = arg.decode(enc)
598 uargv.append(arg)
598 uargv.append(arg)
599 return uargv
599 return uargv
600
600
601
601
602 def load_config(self, argv=None, aliases=None, flags=None):
602 def load_config(self, argv=None, aliases=None, flags=None):
603 """Parse the configuration and generate the Config object.
603 """Parse the configuration and generate the Config object.
604
604
605 After loading, any arguments that are not key-value or
605 After loading, any arguments that are not key-value or
606 flags will be stored in self.extra_args - a list of
606 flags will be stored in self.extra_args - a list of
607 unparsed command-line arguments. This is used for
607 unparsed command-line arguments. This is used for
608 arguments such as input files or subcommands.
608 arguments such as input files or subcommands.
609
609
610 Parameters
610 Parameters
611 ----------
611 ----------
612 argv : list, optional
612 argv : list, optional
613 A list that has the form of sys.argv[1:] which has unicode
613 A list that has the form of sys.argv[1:] which has unicode
614 elements of the form u"key=value". If this is None (default),
614 elements of the form u"key=value". If this is None (default),
615 then self.argv will be used.
615 then self.argv will be used.
616 aliases : dict
616 aliases : dict
617 A dict of aliases for configurable traits.
617 A dict of aliases for configurable traits.
618 Keys are the short aliases, Values are the resolved trait.
618 Keys are the short aliases, Values are the resolved trait.
619 Of the form: `{'alias' : 'Configurable.trait'}`
619 Of the form: `{'alias' : 'Configurable.trait'}`
620 flags : dict
620 flags : dict
621 A dict of flags, keyed by str name. Values can be Config objects
621 A dict of flags, keyed by str name. Values can be Config objects
622 or dicts. When the flag is triggered, The config is loaded as
622 or dicts. When the flag is triggered, The config is loaded as
623 `self.config.update(cfg)`.
623 `self.config.update(cfg)`.
624 """
624 """
625 self.clear()
625 self.clear()
626 if argv is None:
626 if argv is None:
627 argv = self.argv
627 argv = self.argv
628 if aliases is None:
628 if aliases is None:
629 aliases = self.aliases
629 aliases = self.aliases
630 if flags is None:
630 if flags is None:
631 flags = self.flags
631 flags = self.flags
632
632
633 # ensure argv is a list of unicode strings:
633 # ensure argv is a list of unicode strings:
634 uargv = self._decode_argv(argv)
634 uargv = self._decode_argv(argv)
635 for idx,raw in enumerate(uargv):
635 for idx,raw in enumerate(uargv):
636 # strip leading '-'
636 # strip leading '-'
637 item = raw.lstrip('-')
637 item = raw.lstrip('-')
638
638
639 if raw == '--':
639 if raw == '--':
640 # don't parse arguments after '--'
640 # don't parse arguments after '--'
641 # this is useful for relaying arguments to scripts, e.g.
641 # this is useful for relaying arguments to scripts, e.g.
642 # ipython -i foo.py --matplotlib=qt -- args after '--' go-to-foo.py
642 # ipython -i foo.py --matplotlib=qt -- args after '--' go-to-foo.py
643 self.extra_args.extend(uargv[idx+1:])
643 self.extra_args.extend(uargv[idx+1:])
644 break
644 break
645
645
646 if kv_pattern.match(raw):
646 if kv_pattern.match(raw):
647 lhs,rhs = item.split('=',1)
647 lhs,rhs = item.split('=',1)
648 # Substitute longnames for aliases.
648 # Substitute longnames for aliases.
649 if lhs in aliases:
649 if lhs in aliases:
650 lhs = aliases[lhs]
650 lhs = aliases[lhs]
651 if '.' not in lhs:
651 if '.' not in lhs:
652 # probably a mistyped alias, but not technically illegal
652 # probably a mistyped alias, but not technically illegal
653 self.log.warn("Unrecognized alias: '%s', it will probably have no effect.", raw)
653 self.log.warn("Unrecognized alias: '%s', it will probably have no effect.", raw)
654 try:
654 try:
655 self._exec_config_str(lhs, rhs)
655 self._exec_config_str(lhs, rhs)
656 except Exception:
656 except Exception:
657 raise ArgumentError("Invalid argument: '%s'" % raw)
657 raise ArgumentError("Invalid argument: '%s'" % raw)
658
658
659 elif flag_pattern.match(raw):
659 elif flag_pattern.match(raw):
660 if item in flags:
660 if item in flags:
661 cfg,help = flags[item]
661 cfg,help = flags[item]
662 self._load_flag(cfg)
662 self._load_flag(cfg)
663 else:
663 else:
664 raise ArgumentError("Unrecognized flag: '%s'"%raw)
664 raise ArgumentError("Unrecognized flag: '%s'"%raw)
665 elif raw.startswith('-'):
665 elif raw.startswith('-'):
666 kv = '--'+item
666 kv = '--'+item
667 if kv_pattern.match(kv):
667 if kv_pattern.match(kv):
668 raise ArgumentError("Invalid argument: '%s', did you mean '%s'?"%(raw, kv))
668 raise ArgumentError("Invalid argument: '%s', did you mean '%s'?"%(raw, kv))
669 else:
669 else:
670 raise ArgumentError("Invalid argument: '%s'"%raw)
670 raise ArgumentError("Invalid argument: '%s'"%raw)
671 else:
671 else:
672 # keep all args that aren't valid in a list,
672 # keep all args that aren't valid in a list,
673 # in case our parent knows what to do with them.
673 # in case our parent knows what to do with them.
674 self.extra_args.append(item)
674 self.extra_args.append(item)
675 return self.config
675 return self.config
676
676
677 class ArgParseConfigLoader(CommandLineConfigLoader):
677 class ArgParseConfigLoader(CommandLineConfigLoader):
678 """A loader that uses the argparse module to load from the command line."""
678 """A loader that uses the argparse module to load from the command line."""
679
679
680 def __init__(self, argv=None, aliases=None, flags=None, log=None, *parser_args, **parser_kw):
680 def __init__(self, argv=None, aliases=None, flags=None, log=None, *parser_args, **parser_kw):
681 """Create a config loader for use with argparse.
681 """Create a config loader for use with argparse.
682
682
683 Parameters
683 Parameters
684 ----------
684 ----------
685
685
686 argv : optional, list
686 argv : optional, list
687 If given, used to read command-line arguments from, otherwise
687 If given, used to read command-line arguments from, otherwise
688 sys.argv[1:] is used.
688 sys.argv[1:] is used.
689
689
690 parser_args : tuple
690 parser_args : tuple
691 A tuple of positional arguments that will be passed to the
691 A tuple of positional arguments that will be passed to the
692 constructor of :class:`argparse.ArgumentParser`.
692 constructor of :class:`argparse.ArgumentParser`.
693
693
694 parser_kw : dict
694 parser_kw : dict
695 A tuple of keyword arguments that will be passed to the
695 A tuple of keyword arguments that will be passed to the
696 constructor of :class:`argparse.ArgumentParser`.
696 constructor of :class:`argparse.ArgumentParser`.
697
697
698 Returns
698 Returns
699 -------
699 -------
700 config : Config
700 config : Config
701 The resulting Config object.
701 The resulting Config object.
702 """
702 """
703 super(CommandLineConfigLoader, self).__init__(log=log)
703 super(CommandLineConfigLoader, self).__init__(log=log)
704 self.clear()
704 self.clear()
705 if argv is None:
705 if argv is None:
706 argv = sys.argv[1:]
706 argv = sys.argv[1:]
707 self.argv = argv
707 self.argv = argv
708 self.aliases = aliases or {}
708 self.aliases = aliases or {}
709 self.flags = flags or {}
709 self.flags = flags or {}
710
710
711 self.parser_args = parser_args
711 self.parser_args = parser_args
712 self.version = parser_kw.pop("version", None)
712 self.version = parser_kw.pop("version", None)
713 kwargs = dict(argument_default=argparse.SUPPRESS)
713 kwargs = dict(argument_default=argparse.SUPPRESS)
714 kwargs.update(parser_kw)
714 kwargs.update(parser_kw)
715 self.parser_kw = kwargs
715 self.parser_kw = kwargs
716
716
717 def load_config(self, argv=None, aliases=None, flags=None):
717 def load_config(self, argv=None, aliases=None, flags=None):
718 """Parse command line arguments and return as a Config object.
718 """Parse command line arguments and return as a Config object.
719
719
720 Parameters
720 Parameters
721 ----------
721 ----------
722
722
723 args : optional, list
723 args : optional, list
724 If given, a list with the structure of sys.argv[1:] to parse
724 If given, a list with the structure of sys.argv[1:] to parse
725 arguments from. If not given, the instance's self.argv attribute
725 arguments from. If not given, the instance's self.argv attribute
726 (given at construction time) is used."""
726 (given at construction time) is used."""
727 self.clear()
727 self.clear()
728 if argv is None:
728 if argv is None:
729 argv = self.argv
729 argv = self.argv
730 if aliases is None:
730 if aliases is None:
731 aliases = self.aliases
731 aliases = self.aliases
732 if flags is None:
732 if flags is None:
733 flags = self.flags
733 flags = self.flags
734 self._create_parser(aliases, flags)
734 self._create_parser(aliases, flags)
735 self._parse_args(argv)
735 self._parse_args(argv)
736 self._convert_to_config()
736 self._convert_to_config()
737 return self.config
737 return self.config
738
738
739 def get_extra_args(self):
739 def get_extra_args(self):
740 if hasattr(self, 'extra_args'):
740 if hasattr(self, 'extra_args'):
741 return self.extra_args
741 return self.extra_args
742 else:
742 else:
743 return []
743 return []
744
744
745 def _create_parser(self, aliases=None, flags=None):
745 def _create_parser(self, aliases=None, flags=None):
746 self.parser = ArgumentParser(*self.parser_args, **self.parser_kw)
746 self.parser = ArgumentParser(*self.parser_args, **self.parser_kw)
747 self._add_arguments(aliases, flags)
747 self._add_arguments(aliases, flags)
748
748
749 def _add_arguments(self, aliases=None, flags=None):
749 def _add_arguments(self, aliases=None, flags=None):
750 raise NotImplementedError("subclasses must implement _add_arguments")
750 raise NotImplementedError("subclasses must implement _add_arguments")
751
751
752 def _parse_args(self, args):
752 def _parse_args(self, args):
753 """self.parser->self.parsed_data"""
753 """self.parser->self.parsed_data"""
754 # decode sys.argv to support unicode command-line options
754 # decode sys.argv to support unicode command-line options
755 enc = DEFAULT_ENCODING
755 enc = DEFAULT_ENCODING
756 uargs = [py3compat.cast_unicode(a, enc) for a in args]
756 uargs = [py3compat.cast_unicode(a, enc) for a in args]
757 self.parsed_data, self.extra_args = self.parser.parse_known_args(uargs)
757 self.parsed_data, self.extra_args = self.parser.parse_known_args(uargs)
758
758
759 def _convert_to_config(self):
759 def _convert_to_config(self):
760 """self.parsed_data->self.config"""
760 """self.parsed_data->self.config"""
761 for k, v in iteritems(vars(self.parsed_data)):
761 for k, v in iteritems(vars(self.parsed_data)):
762 exec("self.config.%s = v"%k, locals(), globals())
762 exec("self.config.%s = v"%k, locals(), globals())
763
763
764 class KVArgParseConfigLoader(ArgParseConfigLoader):
764 class KVArgParseConfigLoader(ArgParseConfigLoader):
765 """A config loader that loads aliases and flags with argparse,
765 """A config loader that loads aliases and flags with argparse,
766 but will use KVLoader for the rest. This allows better parsing
766 but will use KVLoader for the rest. This allows better parsing
767 of common args, such as `ipython -c 'print 5'`, but still gets
767 of common args, such as `ipython -c 'print 5'`, but still gets
768 arbitrary config with `ipython --InteractiveShell.use_readline=False`"""
768 arbitrary config with `ipython --InteractiveShell.use_readline=False`"""
769
769
770 def _add_arguments(self, aliases=None, flags=None):
770 def _add_arguments(self, aliases=None, flags=None):
771 self.alias_flags = {}
771 self.alias_flags = {}
772 # print aliases, flags
772 # print aliases, flags
773 if aliases is None:
773 if aliases is None:
774 aliases = self.aliases
774 aliases = self.aliases
775 if flags is None:
775 if flags is None:
776 flags = self.flags
776 flags = self.flags
777 paa = self.parser.add_argument
777 paa = self.parser.add_argument
778 for key,value in iteritems(aliases):
778 for key,value in iteritems(aliases):
779 if key in flags:
779 if key in flags:
780 # flags
780 # flags
781 nargs = '?'
781 nargs = '?'
782 else:
782 else:
783 nargs = None
783 nargs = None
784 if len(key) is 1:
784 if len(key) is 1:
785 paa('-'+key, '--'+key, type=unicode_type, dest=value, nargs=nargs)
785 paa('-'+key, '--'+key, type=unicode_type, dest=value, nargs=nargs)
786 else:
786 else:
787 paa('--'+key, type=unicode_type, dest=value, nargs=nargs)
787 paa('--'+key, type=unicode_type, dest=value, nargs=nargs)
788 for key, (value, help) in iteritems(flags):
788 for key, (value, help) in iteritems(flags):
789 if key in self.aliases:
789 if key in self.aliases:
790 #
790 #
791 self.alias_flags[self.aliases[key]] = value
791 self.alias_flags[self.aliases[key]] = value
792 continue
792 continue
793 if len(key) is 1:
793 if len(key) is 1:
794 paa('-'+key, '--'+key, action='append_const', dest='_flags', const=value)
794 paa('-'+key, '--'+key, action='append_const', dest='_flags', const=value)
795 else:
795 else:
796 paa('--'+key, action='append_const', dest='_flags', const=value)
796 paa('--'+key, action='append_const', dest='_flags', const=value)
797
797
798 def _convert_to_config(self):
798 def _convert_to_config(self):
799 """self.parsed_data->self.config, parse unrecognized extra args via KVLoader."""
799 """self.parsed_data->self.config, parse unrecognized extra args via KVLoader."""
800 # remove subconfigs list from namespace before transforming the Namespace
800 # remove subconfigs list from namespace before transforming the Namespace
801 if '_flags' in self.parsed_data:
801 if '_flags' in self.parsed_data:
802 subcs = self.parsed_data._flags
802 subcs = self.parsed_data._flags
803 del self.parsed_data._flags
803 del self.parsed_data._flags
804 else:
804 else:
805 subcs = []
805 subcs = []
806
806
807 for k, v in iteritems(vars(self.parsed_data)):
807 for k, v in iteritems(vars(self.parsed_data)):
808 if v is None:
808 if v is None:
809 # it was a flag that shares the name of an alias
809 # it was a flag that shares the name of an alias
810 subcs.append(self.alias_flags[k])
810 subcs.append(self.alias_flags[k])
811 else:
811 else:
812 # eval the KV assignment
812 # eval the KV assignment
813 self._exec_config_str(k, v)
813 self._exec_config_str(k, v)
814
814
815 for subc in subcs:
815 for subc in subcs:
816 self._load_flag(subc)
816 self._load_flag(subc)
817
817
818 if self.extra_args:
818 if self.extra_args:
819 sub_parser = KeyValueConfigLoader(log=self.log)
819 sub_parser = KeyValueConfigLoader(log=self.log)
820 sub_parser.load_config(self.extra_args)
820 sub_parser.load_config(self.extra_args)
821 self.config.merge(sub_parser.config)
821 self.config.merge(sub_parser.config)
822 self.extra_args = sub_parser.extra_args
822 self.extra_args = sub_parser.extra_args
823
823
824
824
825 def load_pyconfig_files(config_files, path):
825 def load_pyconfig_files(config_files, path):
826 """Load multiple Python config files, merging each of them in turn.
826 """Load multiple Python config files, merging each of them in turn.
827
827
828 Parameters
828 Parameters
829 ==========
829 ==========
830 config_files : list of str
830 config_files : list of str
831 List of config files names to load and merge into the config.
831 List of config files names to load and merge into the config.
832 path : unicode
832 path : unicode
833 The full path to the location of the config files.
833 The full path to the location of the config files.
834 """
834 """
835 config = Config()
835 config = Config()
836 for cf in config_files:
836 for cf in config_files:
837 loader = PyFileConfigLoader(cf, path=path)
837 loader = PyFileConfigLoader(cf, path=path)
838 try:
838 try:
839 next_config = loader.load_config()
839 next_config = loader.load_config()
840 except ConfigFileNotFound:
840 except ConfigFileNotFound:
841 pass
841 pass
842 except:
842 except:
843 raise
843 raise
844 else:
844 else:
845 config.merge(next_config)
845 config.merge(next_config)
846 return config
846 return config
@@ -1,390 +1,389 b''
1 # encoding: utf-8
1 # encoding: utf-8
2 """
2 """
3 An application for IPython.
3 An application for IPython.
4
4
5 All top-level applications should use the classes in this module for
5 All top-level applications should use the classes in this module for
6 handling configuration and creating configurables.
6 handling configuration and creating configurables.
7
7
8 The job of an :class:`Application` is to create the master configuration
8 The job of an :class:`Application` is to create the master configuration
9 object and then create the configurable objects, passing the config to them.
9 object and then create the configurable objects, passing the config to them.
10
10
11 Authors:
11 Authors:
12
12
13 * Brian Granger
13 * Brian Granger
14 * Fernando Perez
14 * Fernando Perez
15 * Min RK
15 * Min RK
16
16
17 """
17 """
18
18
19 #-----------------------------------------------------------------------------
19 #-----------------------------------------------------------------------------
20 # Copyright (C) 2008 The IPython Development Team
20 # Copyright (C) 2008 The IPython Development Team
21 #
21 #
22 # Distributed under the terms of the BSD License. The full license is in
22 # Distributed under the terms of the BSD License. The full license is in
23 # the file COPYING, distributed as part of this software.
23 # the file COPYING, distributed as part of this software.
24 #-----------------------------------------------------------------------------
24 #-----------------------------------------------------------------------------
25
25
26 #-----------------------------------------------------------------------------
26 #-----------------------------------------------------------------------------
27 # Imports
27 # Imports
28 #-----------------------------------------------------------------------------
28 #-----------------------------------------------------------------------------
29
29
30 import atexit
30 import atexit
31 import errno
32 import glob
31 import glob
33 import logging
32 import logging
34 import os
33 import os
35 import shutil
34 import shutil
36 import sys
35 import sys
37
36
38 from IPython.config.application import Application, catch_config_error
37 from IPython.config.application import Application, catch_config_error
39 from IPython.config.loader import ConfigFileNotFound
38 from IPython.config.loader import ConfigFileNotFound
40 from IPython.core import release, crashhandler
39 from IPython.core import release, crashhandler
41 from IPython.core.profiledir import ProfileDir, ProfileDirError
40 from IPython.core.profiledir import ProfileDir, ProfileDirError
42 from IPython.utils.path import get_ipython_dir, get_ipython_package_dir, ensure_dir_exists
41 from IPython.utils.path import get_ipython_dir, get_ipython_package_dir, ensure_dir_exists
43 from IPython.utils import py3compat
42 from IPython.utils import py3compat
44 from IPython.utils.traitlets import List, Unicode, Type, Bool, Dict, Set, Instance
43 from IPython.utils.traitlets import List, Unicode, Type, Bool, Dict, Set, Instance
45
44
46 #-----------------------------------------------------------------------------
45 #-----------------------------------------------------------------------------
47 # Classes and functions
46 # Classes and functions
48 #-----------------------------------------------------------------------------
47 #-----------------------------------------------------------------------------
49
48
50
49
51 #-----------------------------------------------------------------------------
50 #-----------------------------------------------------------------------------
52 # Base Application Class
51 # Base Application Class
53 #-----------------------------------------------------------------------------
52 #-----------------------------------------------------------------------------
54
53
55 # aliases and flags
54 # aliases and flags
56
55
57 base_aliases = {
56 base_aliases = {
58 'profile-dir' : 'ProfileDir.location',
57 'profile-dir' : 'ProfileDir.location',
59 'profile' : 'BaseIPythonApplication.profile',
58 'profile' : 'BaseIPythonApplication.profile',
60 'ipython-dir' : 'BaseIPythonApplication.ipython_dir',
59 'ipython-dir' : 'BaseIPythonApplication.ipython_dir',
61 'log-level' : 'Application.log_level',
60 'log-level' : 'Application.log_level',
62 'config' : 'BaseIPythonApplication.extra_config_file',
61 'config' : 'BaseIPythonApplication.extra_config_file',
63 }
62 }
64
63
65 base_flags = dict(
64 base_flags = dict(
66 debug = ({'Application' : {'log_level' : logging.DEBUG}},
65 debug = ({'Application' : {'log_level' : logging.DEBUG}},
67 "set log level to logging.DEBUG (maximize logging output)"),
66 "set log level to logging.DEBUG (maximize logging output)"),
68 quiet = ({'Application' : {'log_level' : logging.CRITICAL}},
67 quiet = ({'Application' : {'log_level' : logging.CRITICAL}},
69 "set log level to logging.CRITICAL (minimize logging output)"),
68 "set log level to logging.CRITICAL (minimize logging output)"),
70 init = ({'BaseIPythonApplication' : {
69 init = ({'BaseIPythonApplication' : {
71 'copy_config_files' : True,
70 'copy_config_files' : True,
72 'auto_create' : True}
71 'auto_create' : True}
73 }, """Initialize profile with default config files. This is equivalent
72 }, """Initialize profile with default config files. This is equivalent
74 to running `ipython profile create <profile>` prior to startup.
73 to running `ipython profile create <profile>` prior to startup.
75 """)
74 """)
76 )
75 )
77
76
78
77
79 class BaseIPythonApplication(Application):
78 class BaseIPythonApplication(Application):
80
79
81 name = Unicode(u'ipython')
80 name = Unicode(u'ipython')
82 description = Unicode(u'IPython: an enhanced interactive Python shell.')
81 description = Unicode(u'IPython: an enhanced interactive Python shell.')
83 version = Unicode(release.version)
82 version = Unicode(release.version)
84
83
85 aliases = Dict(base_aliases)
84 aliases = Dict(base_aliases)
86 flags = Dict(base_flags)
85 flags = Dict(base_flags)
87 classes = List([ProfileDir])
86 classes = List([ProfileDir])
88
87
89 # Track whether the config_file has changed,
88 # Track whether the config_file has changed,
90 # because some logic happens only if we aren't using the default.
89 # because some logic happens only if we aren't using the default.
91 config_file_specified = Set()
90 config_file_specified = Set()
92
91
93 config_file_name = Unicode()
92 config_file_name = Unicode()
94 def _config_file_name_default(self):
93 def _config_file_name_default(self):
95 return self.name.replace('-','_') + u'_config.py'
94 return self.name.replace('-','_') + u'_config.py'
96 def _config_file_name_changed(self, name, old, new):
95 def _config_file_name_changed(self, name, old, new):
97 if new != old:
96 if new != old:
98 self.config_file_specified.add(new)
97 self.config_file_specified.add(new)
99
98
100 # The directory that contains IPython's builtin profiles.
99 # The directory that contains IPython's builtin profiles.
101 builtin_profile_dir = Unicode(
100 builtin_profile_dir = Unicode(
102 os.path.join(get_ipython_package_dir(), u'config', u'profile', u'default')
101 os.path.join(get_ipython_package_dir(), u'config', u'profile', u'default')
103 )
102 )
104
103
105 config_file_paths = List(Unicode)
104 config_file_paths = List(Unicode)
106 def _config_file_paths_default(self):
105 def _config_file_paths_default(self):
107 return [py3compat.getcwd()]
106 return [py3compat.getcwd()]
108
107
109 extra_config_file = Unicode(config=True,
108 extra_config_file = Unicode(config=True,
110 help="""Path to an extra config file to load.
109 help="""Path to an extra config file to load.
111
110
112 If specified, load this config file in addition to any other IPython config.
111 If specified, load this config file in addition to any other IPython config.
113 """)
112 """)
114 def _extra_config_file_changed(self, name, old, new):
113 def _extra_config_file_changed(self, name, old, new):
115 try:
114 try:
116 self.config_files.remove(old)
115 self.config_files.remove(old)
117 except ValueError:
116 except ValueError:
118 pass
117 pass
119 self.config_file_specified.add(new)
118 self.config_file_specified.add(new)
120 self.config_files.append(new)
119 self.config_files.append(new)
121
120
122 profile = Unicode(u'default', config=True,
121 profile = Unicode(u'default', config=True,
123 help="""The IPython profile to use."""
122 help="""The IPython profile to use."""
124 )
123 )
125
124
126 def _profile_changed(self, name, old, new):
125 def _profile_changed(self, name, old, new):
127 self.builtin_profile_dir = os.path.join(
126 self.builtin_profile_dir = os.path.join(
128 get_ipython_package_dir(), u'config', u'profile', new
127 get_ipython_package_dir(), u'config', u'profile', new
129 )
128 )
130
129
131 ipython_dir = Unicode(config=True,
130 ipython_dir = Unicode(config=True,
132 help="""
131 help="""
133 The name of the IPython directory. This directory is used for logging
132 The name of the IPython directory. This directory is used for logging
134 configuration (through profiles), history storage, etc. The default
133 configuration (through profiles), history storage, etc. The default
135 is usually $HOME/.ipython. This options can also be specified through
134 is usually $HOME/.ipython. This options can also be specified through
136 the environment variable IPYTHONDIR.
135 the environment variable IPYTHONDIR.
137 """
136 """
138 )
137 )
139 def _ipython_dir_default(self):
138 def _ipython_dir_default(self):
140 d = get_ipython_dir()
139 d = get_ipython_dir()
141 self._ipython_dir_changed('ipython_dir', d, d)
140 self._ipython_dir_changed('ipython_dir', d, d)
142 return d
141 return d
143
142
144 _in_init_profile_dir = False
143 _in_init_profile_dir = False
145 profile_dir = Instance(ProfileDir)
144 profile_dir = Instance(ProfileDir)
146 def _profile_dir_default(self):
145 def _profile_dir_default(self):
147 # avoid recursion
146 # avoid recursion
148 if self._in_init_profile_dir:
147 if self._in_init_profile_dir:
149 return
148 return
150 # profile_dir requested early, force initialization
149 # profile_dir requested early, force initialization
151 self.init_profile_dir()
150 self.init_profile_dir()
152 return self.profile_dir
151 return self.profile_dir
153
152
154 overwrite = Bool(False, config=True,
153 overwrite = Bool(False, config=True,
155 help="""Whether to overwrite existing config files when copying""")
154 help="""Whether to overwrite existing config files when copying""")
156 auto_create = Bool(False, config=True,
155 auto_create = Bool(False, config=True,
157 help="""Whether to create profile dir if it doesn't exist""")
156 help="""Whether to create profile dir if it doesn't exist""")
158
157
159 config_files = List(Unicode)
158 config_files = List(Unicode)
160 def _config_files_default(self):
159 def _config_files_default(self):
161 return [self.config_file_name]
160 return [self.config_file_name]
162
161
163 copy_config_files = Bool(False, config=True,
162 copy_config_files = Bool(False, config=True,
164 help="""Whether to install the default config files into the profile dir.
163 help="""Whether to install the default config files into the profile dir.
165 If a new profile is being created, and IPython contains config files for that
164 If a new profile is being created, and IPython contains config files for that
166 profile, then they will be staged into the new directory. Otherwise,
165 profile, then they will be staged into the new directory. Otherwise,
167 default config files will be automatically generated.
166 default config files will be automatically generated.
168 """)
167 """)
169
168
170 verbose_crash = Bool(False, config=True,
169 verbose_crash = Bool(False, config=True,
171 help="""Create a massive crash report when IPython encounters what may be an
170 help="""Create a massive crash report when IPython encounters what may be an
172 internal error. The default is to append a short message to the
171 internal error. The default is to append a short message to the
173 usual traceback""")
172 usual traceback""")
174
173
175 # The class to use as the crash handler.
174 # The class to use as the crash handler.
176 crash_handler_class = Type(crashhandler.CrashHandler)
175 crash_handler_class = Type(crashhandler.CrashHandler)
177
176
178 @catch_config_error
177 @catch_config_error
179 def __init__(self, **kwargs):
178 def __init__(self, **kwargs):
180 super(BaseIPythonApplication, self).__init__(**kwargs)
179 super(BaseIPythonApplication, self).__init__(**kwargs)
181 # ensure current working directory exists
180 # ensure current working directory exists
182 try:
181 try:
183 directory = py3compat.getcwd()
182 directory = py3compat.getcwd()
184 except:
183 except:
185 # raise exception
184 # raise exception
186 self.log.error("Current working directory doesn't exist.")
185 self.log.error("Current working directory doesn't exist.")
187 raise
186 raise
188
187
189 #-------------------------------------------------------------------------
188 #-------------------------------------------------------------------------
190 # Various stages of Application creation
189 # Various stages of Application creation
191 #-------------------------------------------------------------------------
190 #-------------------------------------------------------------------------
192
191
193 def init_crash_handler(self):
192 def init_crash_handler(self):
194 """Create a crash handler, typically setting sys.excepthook to it."""
193 """Create a crash handler, typically setting sys.excepthook to it."""
195 self.crash_handler = self.crash_handler_class(self)
194 self.crash_handler = self.crash_handler_class(self)
196 sys.excepthook = self.excepthook
195 sys.excepthook = self.excepthook
197 def unset_crashhandler():
196 def unset_crashhandler():
198 sys.excepthook = sys.__excepthook__
197 sys.excepthook = sys.__excepthook__
199 atexit.register(unset_crashhandler)
198 atexit.register(unset_crashhandler)
200
199
201 def excepthook(self, etype, evalue, tb):
200 def excepthook(self, etype, evalue, tb):
202 """this is sys.excepthook after init_crashhandler
201 """this is sys.excepthook after init_crashhandler
203
202
204 set self.verbose_crash=True to use our full crashhandler, instead of
203 set self.verbose_crash=True to use our full crashhandler, instead of
205 a regular traceback with a short message (crash_handler_lite)
204 a regular traceback with a short message (crash_handler_lite)
206 """
205 """
207
206
208 if self.verbose_crash:
207 if self.verbose_crash:
209 return self.crash_handler(etype, evalue, tb)
208 return self.crash_handler(etype, evalue, tb)
210 else:
209 else:
211 return crashhandler.crash_handler_lite(etype, evalue, tb)
210 return crashhandler.crash_handler_lite(etype, evalue, tb)
212
211
213 def _ipython_dir_changed(self, name, old, new):
212 def _ipython_dir_changed(self, name, old, new):
214 str_old = py3compat.cast_bytes_py2(os.path.abspath(old),
213 str_old = py3compat.cast_bytes_py2(os.path.abspath(old),
215 sys.getfilesystemencoding()
214 sys.getfilesystemencoding()
216 )
215 )
217 if str_old in sys.path:
216 if str_old in sys.path:
218 sys.path.remove(str_old)
217 sys.path.remove(str_old)
219 str_path = py3compat.cast_bytes_py2(os.path.abspath(new),
218 str_path = py3compat.cast_bytes_py2(os.path.abspath(new),
220 sys.getfilesystemencoding()
219 sys.getfilesystemencoding()
221 )
220 )
222 sys.path.append(str_path)
221 sys.path.append(str_path)
223 ensure_dir_exists(new)
222 ensure_dir_exists(new)
224 readme = os.path.join(new, 'README')
223 readme = os.path.join(new, 'README')
225 readme_src = os.path.join(get_ipython_package_dir(), u'config', u'profile', 'README')
224 readme_src = os.path.join(get_ipython_package_dir(), u'config', u'profile', 'README')
226 if not os.path.exists(readme) and os.path.exists(readme_src):
225 if not os.path.exists(readme) and os.path.exists(readme_src):
227 shutil.copy(readme_src, readme)
226 shutil.copy(readme_src, readme)
228 for d in ('extensions', 'nbextensions'):
227 for d in ('extensions', 'nbextensions'):
229 path = os.path.join(new, d)
228 path = os.path.join(new, d)
230 try:
229 try:
231 ensure_dir_exists(path)
230 ensure_dir_exists(path)
232 except OSError:
231 except OSError:
233 # this will not be EEXIST
232 # this will not be EEXIST
234 self.log.error("couldn't create path %s: %s", path, e)
233 self.log.error("couldn't create path %s: %s", path, e)
235 self.log.debug("IPYTHONDIR set to: %s" % new)
234 self.log.debug("IPYTHONDIR set to: %s" % new)
236
235
237 def load_config_file(self, suppress_errors=True):
236 def load_config_file(self, suppress_errors=True):
238 """Load the config file.
237 """Load the config file.
239
238
240 By default, errors in loading config are handled, and a warning
239 By default, errors in loading config are handled, and a warning
241 printed on screen. For testing, the suppress_errors option is set
240 printed on screen. For testing, the suppress_errors option is set
242 to False, so errors will make tests fail.
241 to False, so errors will make tests fail.
243 """
242 """
244 self.log.debug("Searching path %s for config files", self.config_file_paths)
243 self.log.debug("Searching path %s for config files", self.config_file_paths)
245 base_config = 'ipython_config.py'
244 base_config = 'ipython_config.py'
246 self.log.debug("Attempting to load config file: %s" %
245 self.log.debug("Attempting to load config file: %s" %
247 base_config)
246 base_config)
248 try:
247 try:
249 Application.load_config_file(
248 Application.load_config_file(
250 self,
249 self,
251 base_config,
250 base_config,
252 path=self.config_file_paths
251 path=self.config_file_paths
253 )
252 )
254 except ConfigFileNotFound:
253 except ConfigFileNotFound:
255 # ignore errors loading parent
254 # ignore errors loading parent
256 self.log.debug("Config file %s not found", base_config)
255 self.log.debug("Config file %s not found", base_config)
257 pass
256 pass
258
257
259 for config_file_name in self.config_files:
258 for config_file_name in self.config_files:
260 if not config_file_name or config_file_name == base_config:
259 if not config_file_name or config_file_name == base_config:
261 continue
260 continue
262 self.log.debug("Attempting to load config file: %s" %
261 self.log.debug("Attempting to load config file: %s" %
263 self.config_file_name)
262 self.config_file_name)
264 try:
263 try:
265 Application.load_config_file(
264 Application.load_config_file(
266 self,
265 self,
267 config_file_name,
266 config_file_name,
268 path=self.config_file_paths
267 path=self.config_file_paths
269 )
268 )
270 except ConfigFileNotFound:
269 except ConfigFileNotFound:
271 # Only warn if the default config file was NOT being used.
270 # Only warn if the default config file was NOT being used.
272 if config_file_name in self.config_file_specified:
271 if config_file_name in self.config_file_specified:
273 msg = self.log.warn
272 msg = self.log.warn
274 else:
273 else:
275 msg = self.log.debug
274 msg = self.log.debug
276 msg("Config file not found, skipping: %s", config_file_name)
275 msg("Config file not found, skipping: %s", config_file_name)
277 except:
276 except:
278 # For testing purposes.
277 # For testing purposes.
279 if not suppress_errors:
278 if not suppress_errors:
280 raise
279 raise
281 self.log.warn("Error loading config file: %s" %
280 self.log.warn("Error loading config file: %s" %
282 self.config_file_name, exc_info=True)
281 self.config_file_name, exc_info=True)
283
282
284 def init_profile_dir(self):
283 def init_profile_dir(self):
285 """initialize the profile dir"""
284 """initialize the profile dir"""
286 self._in_init_profile_dir = True
285 self._in_init_profile_dir = True
287 if self.profile_dir is not None:
286 if self.profile_dir is not None:
288 # already ran
287 # already ran
289 return
288 return
290 if 'ProfileDir.location' not in self.config:
289 if 'ProfileDir.location' not in self.config:
291 # location not specified, find by profile name
290 # location not specified, find by profile name
292 try:
291 try:
293 p = ProfileDir.find_profile_dir_by_name(self.ipython_dir, self.profile, self.config)
292 p = ProfileDir.find_profile_dir_by_name(self.ipython_dir, self.profile, self.config)
294 except ProfileDirError:
293 except ProfileDirError:
295 # not found, maybe create it (always create default profile)
294 # not found, maybe create it (always create default profile)
296 if self.auto_create or self.profile == 'default':
295 if self.auto_create or self.profile == 'default':
297 try:
296 try:
298 p = ProfileDir.create_profile_dir_by_name(self.ipython_dir, self.profile, self.config)
297 p = ProfileDir.create_profile_dir_by_name(self.ipython_dir, self.profile, self.config)
299 except ProfileDirError:
298 except ProfileDirError:
300 self.log.fatal("Could not create profile: %r"%self.profile)
299 self.log.fatal("Could not create profile: %r"%self.profile)
301 self.exit(1)
300 self.exit(1)
302 else:
301 else:
303 self.log.info("Created profile dir: %r"%p.location)
302 self.log.info("Created profile dir: %r"%p.location)
304 else:
303 else:
305 self.log.fatal("Profile %r not found."%self.profile)
304 self.log.fatal("Profile %r not found."%self.profile)
306 self.exit(1)
305 self.exit(1)
307 else:
306 else:
308 self.log.info("Using existing profile dir: %r"%p.location)
307 self.log.info("Using existing profile dir: %r"%p.location)
309 else:
308 else:
310 location = self.config.ProfileDir.location
309 location = self.config.ProfileDir.location
311 # location is fully specified
310 # location is fully specified
312 try:
311 try:
313 p = ProfileDir.find_profile_dir(location, self.config)
312 p = ProfileDir.find_profile_dir(location, self.config)
314 except ProfileDirError:
313 except ProfileDirError:
315 # not found, maybe create it
314 # not found, maybe create it
316 if self.auto_create:
315 if self.auto_create:
317 try:
316 try:
318 p = ProfileDir.create_profile_dir(location, self.config)
317 p = ProfileDir.create_profile_dir(location, self.config)
319 except ProfileDirError:
318 except ProfileDirError:
320 self.log.fatal("Could not create profile directory: %r"%location)
319 self.log.fatal("Could not create profile directory: %r"%location)
321 self.exit(1)
320 self.exit(1)
322 else:
321 else:
323 self.log.info("Creating new profile dir: %r"%location)
322 self.log.info("Creating new profile dir: %r"%location)
324 else:
323 else:
325 self.log.fatal("Profile directory %r not found."%location)
324 self.log.fatal("Profile directory %r not found."%location)
326 self.exit(1)
325 self.exit(1)
327 else:
326 else:
328 self.log.info("Using existing profile dir: %r"%location)
327 self.log.info("Using existing profile dir: %r"%location)
329 # if profile_dir is specified explicitly, set profile name
328 # if profile_dir is specified explicitly, set profile name
330 dir_name = os.path.basename(p.location)
329 dir_name = os.path.basename(p.location)
331 if dir_name.startswith('profile_'):
330 if dir_name.startswith('profile_'):
332 self.profile = dir_name[8:]
331 self.profile = dir_name[8:]
333
332
334 self.profile_dir = p
333 self.profile_dir = p
335 self.config_file_paths.append(p.location)
334 self.config_file_paths.append(p.location)
336 self._in_init_profile_dir = False
335 self._in_init_profile_dir = False
337
336
338 def init_config_files(self):
337 def init_config_files(self):
339 """[optionally] copy default config files into profile dir."""
338 """[optionally] copy default config files into profile dir."""
340 # copy config files
339 # copy config files
341 path = self.builtin_profile_dir
340 path = self.builtin_profile_dir
342 if self.copy_config_files:
341 if self.copy_config_files:
343 src = self.profile
342 src = self.profile
344
343
345 cfg = self.config_file_name
344 cfg = self.config_file_name
346 if path and os.path.exists(os.path.join(path, cfg)):
345 if path and os.path.exists(os.path.join(path, cfg)):
347 self.log.warn("Staging %r from %s into %r [overwrite=%s]"%(
346 self.log.warn("Staging %r from %s into %r [overwrite=%s]"%(
348 cfg, src, self.profile_dir.location, self.overwrite)
347 cfg, src, self.profile_dir.location, self.overwrite)
349 )
348 )
350 self.profile_dir.copy_config_file(cfg, path=path, overwrite=self.overwrite)
349 self.profile_dir.copy_config_file(cfg, path=path, overwrite=self.overwrite)
351 else:
350 else:
352 self.stage_default_config_file()
351 self.stage_default_config_file()
353 else:
352 else:
354 # Still stage *bundled* config files, but not generated ones
353 # Still stage *bundled* config files, but not generated ones
355 # This is necessary for `ipython profile=sympy` to load the profile
354 # This is necessary for `ipython profile=sympy` to load the profile
356 # on the first go
355 # on the first go
357 files = glob.glob(os.path.join(path, '*.py'))
356 files = glob.glob(os.path.join(path, '*.py'))
358 for fullpath in files:
357 for fullpath in files:
359 cfg = os.path.basename(fullpath)
358 cfg = os.path.basename(fullpath)
360 if self.profile_dir.copy_config_file(cfg, path=path, overwrite=False):
359 if self.profile_dir.copy_config_file(cfg, path=path, overwrite=False):
361 # file was copied
360 # file was copied
362 self.log.warn("Staging bundled %s from %s into %r"%(
361 self.log.warn("Staging bundled %s from %s into %r"%(
363 cfg, self.profile, self.profile_dir.location)
362 cfg, self.profile, self.profile_dir.location)
364 )
363 )
365
364
366
365
367 def stage_default_config_file(self):
366 def stage_default_config_file(self):
368 """auto generate default config file, and stage it into the profile."""
367 """auto generate default config file, and stage it into the profile."""
369 s = self.generate_config_file()
368 s = self.generate_config_file()
370 fname = os.path.join(self.profile_dir.location, self.config_file_name)
369 fname = os.path.join(self.profile_dir.location, self.config_file_name)
371 if self.overwrite or not os.path.exists(fname):
370 if self.overwrite or not os.path.exists(fname):
372 self.log.warn("Generating default config file: %r"%(fname))
371 self.log.warn("Generating default config file: %r"%(fname))
373 with open(fname, 'w') as f:
372 with open(fname, 'w') as f:
374 f.write(s)
373 f.write(s)
375
374
376 @catch_config_error
375 @catch_config_error
377 def initialize(self, argv=None):
376 def initialize(self, argv=None):
378 # don't hook up crash handler before parsing command-line
377 # don't hook up crash handler before parsing command-line
379 self.parse_command_line(argv)
378 self.parse_command_line(argv)
380 self.init_crash_handler()
379 self.init_crash_handler()
381 if self.subapp is not None:
380 if self.subapp is not None:
382 # stop here if subapp is taking over
381 # stop here if subapp is taking over
383 return
382 return
384 cl_config = self.config
383 cl_config = self.config
385 self.init_profile_dir()
384 self.init_profile_dir()
386 self.init_config_files()
385 self.init_config_files()
387 self.load_config_file()
386 self.load_config_file()
388 # enforce cl-opts override configfile opts:
387 # enforce cl-opts override configfile opts:
389 self.update_config(cl_config)
388 self.update_config(cl_config)
390
389
@@ -1,997 +1,996 b''
1 """Word completion for IPython.
1 """Word completion for IPython.
2
2
3 This module is a fork of the rlcompleter module in the Python standard
3 This module is a fork of the rlcompleter module in the Python standard
4 library. The original enhancements made to rlcompleter have been sent
4 library. The original enhancements made to rlcompleter have been sent
5 upstream and were accepted as of Python 2.3, but we need a lot more
5 upstream and were accepted as of Python 2.3, but we need a lot more
6 functionality specific to IPython, so this module will continue to live as an
6 functionality specific to IPython, so this module will continue to live as an
7 IPython-specific utility.
7 IPython-specific utility.
8
8
9 Original rlcompleter documentation:
9 Original rlcompleter documentation:
10
10
11 This requires the latest extension to the readline module (the
11 This requires the latest extension to the readline module (the
12 completes keywords, built-ins and globals in __main__; when completing
12 completes keywords, built-ins and globals in __main__; when completing
13 NAME.NAME..., it evaluates (!) the expression up to the last dot and
13 NAME.NAME..., it evaluates (!) the expression up to the last dot and
14 completes its attributes.
14 completes its attributes.
15
15
16 It's very cool to do "import string" type "string.", hit the
16 It's very cool to do "import string" type "string.", hit the
17 completion key (twice), and see the list of names defined by the
17 completion key (twice), and see the list of names defined by the
18 string module!
18 string module!
19
19
20 Tip: to use the tab key as the completion key, call
20 Tip: to use the tab key as the completion key, call
21
21
22 readline.parse_and_bind("tab: complete")
22 readline.parse_and_bind("tab: complete")
23
23
24 Notes:
24 Notes:
25
25
26 - Exceptions raised by the completer function are *ignored* (and
26 - Exceptions raised by the completer function are *ignored* (and
27 generally cause the completion to fail). This is a feature -- since
27 generally cause the completion to fail). This is a feature -- since
28 readline sets the tty device in raw (or cbreak) mode, printing a
28 readline sets the tty device in raw (or cbreak) mode, printing a
29 traceback wouldn't work well without some complicated hoopla to save,
29 traceback wouldn't work well without some complicated hoopla to save,
30 reset and restore the tty state.
30 reset and restore the tty state.
31
31
32 - The evaluation of the NAME.NAME... form may cause arbitrary
32 - The evaluation of the NAME.NAME... form may cause arbitrary
33 application defined code to be executed if an object with a
33 application defined code to be executed if an object with a
34 ``__getattr__`` hook is found. Since it is the responsibility of the
34 ``__getattr__`` hook is found. Since it is the responsibility of the
35 application (or the user) to enable this feature, I consider this an
35 application (or the user) to enable this feature, I consider this an
36 acceptable risk. More complicated expressions (e.g. function calls or
36 acceptable risk. More complicated expressions (e.g. function calls or
37 indexing operations) are *not* evaluated.
37 indexing operations) are *not* evaluated.
38
38
39 - GNU readline is also used by the built-in functions input() and
39 - GNU readline is also used by the built-in functions input() and
40 raw_input(), and thus these also benefit/suffer from the completer
40 raw_input(), and thus these also benefit/suffer from the completer
41 features. Clearly an interactive application can benefit by
41 features. Clearly an interactive application can benefit by
42 specifying its own completer function and using raw_input() for all
42 specifying its own completer function and using raw_input() for all
43 its input.
43 its input.
44
44
45 - When the original stdin is not a tty device, GNU readline is never
45 - When the original stdin is not a tty device, GNU readline is never
46 used, and this module (and the readline module) are silently inactive.
46 used, and this module (and the readline module) are silently inactive.
47 """
47 """
48
48
49 #*****************************************************************************
49 #*****************************************************************************
50 #
50 #
51 # Since this file is essentially a minimally modified copy of the rlcompleter
51 # Since this file is essentially a minimally modified copy of the rlcompleter
52 # module which is part of the standard Python distribution, I assume that the
52 # module which is part of the standard Python distribution, I assume that the
53 # proper procedure is to maintain its copyright as belonging to the Python
53 # proper procedure is to maintain its copyright as belonging to the Python
54 # Software Foundation (in addition to my own, for all new code).
54 # Software Foundation (in addition to my own, for all new code).
55 #
55 #
56 # Copyright (C) 2008 IPython Development Team
56 # Copyright (C) 2008 IPython Development Team
57 # Copyright (C) 2001 Fernando Perez. <fperez@colorado.edu>
57 # Copyright (C) 2001 Fernando Perez. <fperez@colorado.edu>
58 # Copyright (C) 2001 Python Software Foundation, www.python.org
58 # Copyright (C) 2001 Python Software Foundation, www.python.org
59 #
59 #
60 # Distributed under the terms of the BSD License. The full license is in
60 # Distributed under the terms of the BSD License. The full license is in
61 # the file COPYING, distributed as part of this software.
61 # the file COPYING, distributed as part of this software.
62 #
62 #
63 #*****************************************************************************
63 #*****************************************************************************
64
64
65 #-----------------------------------------------------------------------------
65 #-----------------------------------------------------------------------------
66 # Imports
66 # Imports
67 #-----------------------------------------------------------------------------
67 #-----------------------------------------------------------------------------
68
68
69 import __main__
69 import __main__
70 import glob
70 import glob
71 import inspect
71 import inspect
72 import itertools
72 import itertools
73 import keyword
73 import keyword
74 import os
74 import os
75 import re
75 import re
76 import sys
76 import sys
77
77
78 from IPython.config.configurable import Configurable
78 from IPython.config.configurable import Configurable
79 from IPython.core.error import TryNext
79 from IPython.core.error import TryNext
80 from IPython.core.inputsplitter import ESC_MAGIC
80 from IPython.core.inputsplitter import ESC_MAGIC
81 from IPython.utils import generics
81 from IPython.utils import generics
82 from IPython.utils import io
83 from IPython.utils.dir2 import dir2
82 from IPython.utils.dir2 import dir2
84 from IPython.utils.process import arg_split
83 from IPython.utils.process import arg_split
85 from IPython.utils.py3compat import builtin_mod, string_types
84 from IPython.utils.py3compat import builtin_mod, string_types
86 from IPython.utils.traitlets import CBool, Enum
85 from IPython.utils.traitlets import CBool, Enum
87
86
88 #-----------------------------------------------------------------------------
87 #-----------------------------------------------------------------------------
89 # Globals
88 # Globals
90 #-----------------------------------------------------------------------------
89 #-----------------------------------------------------------------------------
91
90
92 # Public API
91 # Public API
93 __all__ = ['Completer','IPCompleter']
92 __all__ = ['Completer','IPCompleter']
94
93
95 if sys.platform == 'win32':
94 if sys.platform == 'win32':
96 PROTECTABLES = ' '
95 PROTECTABLES = ' '
97 else:
96 else:
98 PROTECTABLES = ' ()[]{}?=\\|;:\'#*"^&'
97 PROTECTABLES = ' ()[]{}?=\\|;:\'#*"^&'
99
98
100 #-----------------------------------------------------------------------------
99 #-----------------------------------------------------------------------------
101 # Main functions and classes
100 # Main functions and classes
102 #-----------------------------------------------------------------------------
101 #-----------------------------------------------------------------------------
103
102
104 def has_open_quotes(s):
103 def has_open_quotes(s):
105 """Return whether a string has open quotes.
104 """Return whether a string has open quotes.
106
105
107 This simply counts whether the number of quote characters of either type in
106 This simply counts whether the number of quote characters of either type in
108 the string is odd.
107 the string is odd.
109
108
110 Returns
109 Returns
111 -------
110 -------
112 If there is an open quote, the quote character is returned. Else, return
111 If there is an open quote, the quote character is returned. Else, return
113 False.
112 False.
114 """
113 """
115 # We check " first, then ', so complex cases with nested quotes will get
114 # We check " first, then ', so complex cases with nested quotes will get
116 # the " to take precedence.
115 # the " to take precedence.
117 if s.count('"') % 2:
116 if s.count('"') % 2:
118 return '"'
117 return '"'
119 elif s.count("'") % 2:
118 elif s.count("'") % 2:
120 return "'"
119 return "'"
121 else:
120 else:
122 return False
121 return False
123
122
124
123
125 def protect_filename(s):
124 def protect_filename(s):
126 """Escape a string to protect certain characters."""
125 """Escape a string to protect certain characters."""
127
126
128 return "".join([(ch in PROTECTABLES and '\\' + ch or ch)
127 return "".join([(ch in PROTECTABLES and '\\' + ch or ch)
129 for ch in s])
128 for ch in s])
130
129
131 def expand_user(path):
130 def expand_user(path):
132 """Expand '~'-style usernames in strings.
131 """Expand '~'-style usernames in strings.
133
132
134 This is similar to :func:`os.path.expanduser`, but it computes and returns
133 This is similar to :func:`os.path.expanduser`, but it computes and returns
135 extra information that will be useful if the input was being used in
134 extra information that will be useful if the input was being used in
136 computing completions, and you wish to return the completions with the
135 computing completions, and you wish to return the completions with the
137 original '~' instead of its expanded value.
136 original '~' instead of its expanded value.
138
137
139 Parameters
138 Parameters
140 ----------
139 ----------
141 path : str
140 path : str
142 String to be expanded. If no ~ is present, the output is the same as the
141 String to be expanded. If no ~ is present, the output is the same as the
143 input.
142 input.
144
143
145 Returns
144 Returns
146 -------
145 -------
147 newpath : str
146 newpath : str
148 Result of ~ expansion in the input path.
147 Result of ~ expansion in the input path.
149 tilde_expand : bool
148 tilde_expand : bool
150 Whether any expansion was performed or not.
149 Whether any expansion was performed or not.
151 tilde_val : str
150 tilde_val : str
152 The value that ~ was replaced with.
151 The value that ~ was replaced with.
153 """
152 """
154 # Default values
153 # Default values
155 tilde_expand = False
154 tilde_expand = False
156 tilde_val = ''
155 tilde_val = ''
157 newpath = path
156 newpath = path
158
157
159 if path.startswith('~'):
158 if path.startswith('~'):
160 tilde_expand = True
159 tilde_expand = True
161 rest = len(path)-1
160 rest = len(path)-1
162 newpath = os.path.expanduser(path)
161 newpath = os.path.expanduser(path)
163 if rest:
162 if rest:
164 tilde_val = newpath[:-rest]
163 tilde_val = newpath[:-rest]
165 else:
164 else:
166 tilde_val = newpath
165 tilde_val = newpath
167
166
168 return newpath, tilde_expand, tilde_val
167 return newpath, tilde_expand, tilde_val
169
168
170
169
171 def compress_user(path, tilde_expand, tilde_val):
170 def compress_user(path, tilde_expand, tilde_val):
172 """Does the opposite of expand_user, with its outputs.
171 """Does the opposite of expand_user, with its outputs.
173 """
172 """
174 if tilde_expand:
173 if tilde_expand:
175 return path.replace(tilde_val, '~')
174 return path.replace(tilde_val, '~')
176 else:
175 else:
177 return path
176 return path
178
177
179
178
180
179
181 def penalize_magics_key(word):
180 def penalize_magics_key(word):
182 """key for sorting that penalizes magic commands in the ordering
181 """key for sorting that penalizes magic commands in the ordering
183
182
184 Normal words are left alone.
183 Normal words are left alone.
185
184
186 Magic commands have the initial % moved to the end, e.g.
185 Magic commands have the initial % moved to the end, e.g.
187 %matplotlib is transformed as follows:
186 %matplotlib is transformed as follows:
188
187
189 %matplotlib -> matplotlib%
188 %matplotlib -> matplotlib%
190
189
191 [The choice of the final % is arbitrary.]
190 [The choice of the final % is arbitrary.]
192
191
193 Since "matplotlib" < "matplotlib%" as strings,
192 Since "matplotlib" < "matplotlib%" as strings,
194 "timeit" will appear before the magic "%timeit" in the ordering
193 "timeit" will appear before the magic "%timeit" in the ordering
195
194
196 For consistency, move "%%" to the end, so cell magics appear *after*
195 For consistency, move "%%" to the end, so cell magics appear *after*
197 line magics with the same name.
196 line magics with the same name.
198
197
199 A check is performed that there are no other "%" in the string;
198 A check is performed that there are no other "%" in the string;
200 if there are, then the string is not a magic command and is left unchanged.
199 if there are, then the string is not a magic command and is left unchanged.
201
200
202 """
201 """
203
202
204 # Move any % signs from start to end of the key
203 # Move any % signs from start to end of the key
205 # provided there are no others elsewhere in the string
204 # provided there are no others elsewhere in the string
206
205
207 if word[:2] == "%%":
206 if word[:2] == "%%":
208 if not "%" in word[2:]:
207 if not "%" in word[2:]:
209 return word[2:] + "%%"
208 return word[2:] + "%%"
210
209
211 if word[:1] == "%":
210 if word[:1] == "%":
212 if not "%" in word[1:]:
211 if not "%" in word[1:]:
213 return word[1:] + "%"
212 return word[1:] + "%"
214
213
215 return word
214 return word
216
215
217
216
218
217
219 class Bunch(object): pass
218 class Bunch(object): pass
220
219
221
220
222 DELIMS = ' \t\n`!@#$^&*()=+[{]}\\|;:\'",<>?'
221 DELIMS = ' \t\n`!@#$^&*()=+[{]}\\|;:\'",<>?'
223 GREEDY_DELIMS = ' =\r\n'
222 GREEDY_DELIMS = ' =\r\n'
224
223
225
224
226 class CompletionSplitter(object):
225 class CompletionSplitter(object):
227 """An object to split an input line in a manner similar to readline.
226 """An object to split an input line in a manner similar to readline.
228
227
229 By having our own implementation, we can expose readline-like completion in
228 By having our own implementation, we can expose readline-like completion in
230 a uniform manner to all frontends. This object only needs to be given the
229 a uniform manner to all frontends. This object only needs to be given the
231 line of text to be split and the cursor position on said line, and it
230 line of text to be split and the cursor position on said line, and it
232 returns the 'word' to be completed on at the cursor after splitting the
231 returns the 'word' to be completed on at the cursor after splitting the
233 entire line.
232 entire line.
234
233
235 What characters are used as splitting delimiters can be controlled by
234 What characters are used as splitting delimiters can be controlled by
236 setting the `delims` attribute (this is a property that internally
235 setting the `delims` attribute (this is a property that internally
237 automatically builds the necessary regular expression)"""
236 automatically builds the necessary regular expression)"""
238
237
239 # Private interface
238 # Private interface
240
239
241 # A string of delimiter characters. The default value makes sense for
240 # A string of delimiter characters. The default value makes sense for
242 # IPython's most typical usage patterns.
241 # IPython's most typical usage patterns.
243 _delims = DELIMS
242 _delims = DELIMS
244
243
245 # The expression (a normal string) to be compiled into a regular expression
244 # The expression (a normal string) to be compiled into a regular expression
246 # for actual splitting. We store it as an attribute mostly for ease of
245 # for actual splitting. We store it as an attribute mostly for ease of
247 # debugging, since this type of code can be so tricky to debug.
246 # debugging, since this type of code can be so tricky to debug.
248 _delim_expr = None
247 _delim_expr = None
249
248
250 # The regular expression that does the actual splitting
249 # The regular expression that does the actual splitting
251 _delim_re = None
250 _delim_re = None
252
251
253 def __init__(self, delims=None):
252 def __init__(self, delims=None):
254 delims = CompletionSplitter._delims if delims is None else delims
253 delims = CompletionSplitter._delims if delims is None else delims
255 self.delims = delims
254 self.delims = delims
256
255
257 @property
256 @property
258 def delims(self):
257 def delims(self):
259 """Return the string of delimiter characters."""
258 """Return the string of delimiter characters."""
260 return self._delims
259 return self._delims
261
260
262 @delims.setter
261 @delims.setter
263 def delims(self, delims):
262 def delims(self, delims):
264 """Set the delimiters for line splitting."""
263 """Set the delimiters for line splitting."""
265 expr = '[' + ''.join('\\'+ c for c in delims) + ']'
264 expr = '[' + ''.join('\\'+ c for c in delims) + ']'
266 self._delim_re = re.compile(expr)
265 self._delim_re = re.compile(expr)
267 self._delims = delims
266 self._delims = delims
268 self._delim_expr = expr
267 self._delim_expr = expr
269
268
270 def split_line(self, line, cursor_pos=None):
269 def split_line(self, line, cursor_pos=None):
271 """Split a line of text with a cursor at the given position.
270 """Split a line of text with a cursor at the given position.
272 """
271 """
273 l = line if cursor_pos is None else line[:cursor_pos]
272 l = line if cursor_pos is None else line[:cursor_pos]
274 return self._delim_re.split(l)[-1]
273 return self._delim_re.split(l)[-1]
275
274
276
275
277 class Completer(Configurable):
276 class Completer(Configurable):
278
277
279 greedy = CBool(False, config=True,
278 greedy = CBool(False, config=True,
280 help="""Activate greedy completion
279 help="""Activate greedy completion
281
280
282 This will enable completion on elements of lists, results of function calls, etc.,
281 This will enable completion on elements of lists, results of function calls, etc.,
283 but can be unsafe because the code is actually evaluated on TAB.
282 but can be unsafe because the code is actually evaluated on TAB.
284 """
283 """
285 )
284 )
286
285
287
286
288 def __init__(self, namespace=None, global_namespace=None, **kwargs):
287 def __init__(self, namespace=None, global_namespace=None, **kwargs):
289 """Create a new completer for the command line.
288 """Create a new completer for the command line.
290
289
291 Completer(namespace=ns,global_namespace=ns2) -> completer instance.
290 Completer(namespace=ns,global_namespace=ns2) -> completer instance.
292
291
293 If unspecified, the default namespace where completions are performed
292 If unspecified, the default namespace where completions are performed
294 is __main__ (technically, __main__.__dict__). Namespaces should be
293 is __main__ (technically, __main__.__dict__). Namespaces should be
295 given as dictionaries.
294 given as dictionaries.
296
295
297 An optional second namespace can be given. This allows the completer
296 An optional second namespace can be given. This allows the completer
298 to handle cases where both the local and global scopes need to be
297 to handle cases where both the local and global scopes need to be
299 distinguished.
298 distinguished.
300
299
301 Completer instances should be used as the completion mechanism of
300 Completer instances should be used as the completion mechanism of
302 readline via the set_completer() call:
301 readline via the set_completer() call:
303
302
304 readline.set_completer(Completer(my_namespace).complete)
303 readline.set_completer(Completer(my_namespace).complete)
305 """
304 """
306
305
307 # Don't bind to namespace quite yet, but flag whether the user wants a
306 # Don't bind to namespace quite yet, but flag whether the user wants a
308 # specific namespace or to use __main__.__dict__. This will allow us
307 # specific namespace or to use __main__.__dict__. This will allow us
309 # to bind to __main__.__dict__ at completion time, not now.
308 # to bind to __main__.__dict__ at completion time, not now.
310 if namespace is None:
309 if namespace is None:
311 self.use_main_ns = 1
310 self.use_main_ns = 1
312 else:
311 else:
313 self.use_main_ns = 0
312 self.use_main_ns = 0
314 self.namespace = namespace
313 self.namespace = namespace
315
314
316 # The global namespace, if given, can be bound directly
315 # The global namespace, if given, can be bound directly
317 if global_namespace is None:
316 if global_namespace is None:
318 self.global_namespace = {}
317 self.global_namespace = {}
319 else:
318 else:
320 self.global_namespace = global_namespace
319 self.global_namespace = global_namespace
321
320
322 super(Completer, self).__init__(**kwargs)
321 super(Completer, self).__init__(**kwargs)
323
322
324 def complete(self, text, state):
323 def complete(self, text, state):
325 """Return the next possible completion for 'text'.
324 """Return the next possible completion for 'text'.
326
325
327 This is called successively with state == 0, 1, 2, ... until it
326 This is called successively with state == 0, 1, 2, ... until it
328 returns None. The completion should begin with 'text'.
327 returns None. The completion should begin with 'text'.
329
328
330 """
329 """
331 if self.use_main_ns:
330 if self.use_main_ns:
332 self.namespace = __main__.__dict__
331 self.namespace = __main__.__dict__
333
332
334 if state == 0:
333 if state == 0:
335 if "." in text:
334 if "." in text:
336 self.matches = self.attr_matches(text)
335 self.matches = self.attr_matches(text)
337 else:
336 else:
338 self.matches = self.global_matches(text)
337 self.matches = self.global_matches(text)
339 try:
338 try:
340 return self.matches[state]
339 return self.matches[state]
341 except IndexError:
340 except IndexError:
342 return None
341 return None
343
342
344 def global_matches(self, text):
343 def global_matches(self, text):
345 """Compute matches when text is a simple name.
344 """Compute matches when text is a simple name.
346
345
347 Return a list of all keywords, built-in functions and names currently
346 Return a list of all keywords, built-in functions and names currently
348 defined in self.namespace or self.global_namespace that match.
347 defined in self.namespace or self.global_namespace that match.
349
348
350 """
349 """
351 #print 'Completer->global_matches, txt=%r' % text # dbg
350 #print 'Completer->global_matches, txt=%r' % text # dbg
352 matches = []
351 matches = []
353 match_append = matches.append
352 match_append = matches.append
354 n = len(text)
353 n = len(text)
355 for lst in [keyword.kwlist,
354 for lst in [keyword.kwlist,
356 builtin_mod.__dict__.keys(),
355 builtin_mod.__dict__.keys(),
357 self.namespace.keys(),
356 self.namespace.keys(),
358 self.global_namespace.keys()]:
357 self.global_namespace.keys()]:
359 for word in lst:
358 for word in lst:
360 if word[:n] == text and word != "__builtins__":
359 if word[:n] == text and word != "__builtins__":
361 match_append(word)
360 match_append(word)
362 return matches
361 return matches
363
362
364 def attr_matches(self, text):
363 def attr_matches(self, text):
365 """Compute matches when text contains a dot.
364 """Compute matches when text contains a dot.
366
365
367 Assuming the text is of the form NAME.NAME....[NAME], and is
366 Assuming the text is of the form NAME.NAME....[NAME], and is
368 evaluatable in self.namespace or self.global_namespace, it will be
367 evaluatable in self.namespace or self.global_namespace, it will be
369 evaluated and its attributes (as revealed by dir()) are used as
368 evaluated and its attributes (as revealed by dir()) are used as
370 possible completions. (For class instances, class members are are
369 possible completions. (For class instances, class members are are
371 also considered.)
370 also considered.)
372
371
373 WARNING: this can still invoke arbitrary C code, if an object
372 WARNING: this can still invoke arbitrary C code, if an object
374 with a __getattr__ hook is evaluated.
373 with a __getattr__ hook is evaluated.
375
374
376 """
375 """
377
376
378 #io.rprint('Completer->attr_matches, txt=%r' % text) # dbg
377 #io.rprint('Completer->attr_matches, txt=%r' % text) # dbg
379 # Another option, seems to work great. Catches things like ''.<tab>
378 # Another option, seems to work great. Catches things like ''.<tab>
380 m = re.match(r"(\S+(\.\w+)*)\.(\w*)$", text)
379 m = re.match(r"(\S+(\.\w+)*)\.(\w*)$", text)
381
380
382 if m:
381 if m:
383 expr, attr = m.group(1, 3)
382 expr, attr = m.group(1, 3)
384 elif self.greedy:
383 elif self.greedy:
385 m2 = re.match(r"(.+)\.(\w*)$", self.line_buffer)
384 m2 = re.match(r"(.+)\.(\w*)$", self.line_buffer)
386 if not m2:
385 if not m2:
387 return []
386 return []
388 expr, attr = m2.group(1,2)
387 expr, attr = m2.group(1,2)
389 else:
388 else:
390 return []
389 return []
391
390
392 try:
391 try:
393 obj = eval(expr, self.namespace)
392 obj = eval(expr, self.namespace)
394 except:
393 except:
395 try:
394 try:
396 obj = eval(expr, self.global_namespace)
395 obj = eval(expr, self.global_namespace)
397 except:
396 except:
398 return []
397 return []
399
398
400 if self.limit_to__all__ and hasattr(obj, '__all__'):
399 if self.limit_to__all__ and hasattr(obj, '__all__'):
401 words = get__all__entries(obj)
400 words = get__all__entries(obj)
402 else:
401 else:
403 words = dir2(obj)
402 words = dir2(obj)
404
403
405 try:
404 try:
406 words = generics.complete_object(obj, words)
405 words = generics.complete_object(obj, words)
407 except TryNext:
406 except TryNext:
408 pass
407 pass
409 except Exception:
408 except Exception:
410 # Silence errors from completion function
409 # Silence errors from completion function
411 #raise # dbg
410 #raise # dbg
412 pass
411 pass
413 # Build match list to return
412 # Build match list to return
414 n = len(attr)
413 n = len(attr)
415 res = ["%s.%s" % (expr, w) for w in words if w[:n] == attr ]
414 res = ["%s.%s" % (expr, w) for w in words if w[:n] == attr ]
416 return res
415 return res
417
416
418
417
419 def get__all__entries(obj):
418 def get__all__entries(obj):
420 """returns the strings in the __all__ attribute"""
419 """returns the strings in the __all__ attribute"""
421 try:
420 try:
422 words = getattr(obj, '__all__')
421 words = getattr(obj, '__all__')
423 except:
422 except:
424 return []
423 return []
425
424
426 return [w for w in words if isinstance(w, string_types)]
425 return [w for w in words if isinstance(w, string_types)]
427
426
428
427
429 class IPCompleter(Completer):
428 class IPCompleter(Completer):
430 """Extension of the completer class with IPython-specific features"""
429 """Extension of the completer class with IPython-specific features"""
431
430
432 def _greedy_changed(self, name, old, new):
431 def _greedy_changed(self, name, old, new):
433 """update the splitter and readline delims when greedy is changed"""
432 """update the splitter and readline delims when greedy is changed"""
434 if new:
433 if new:
435 self.splitter.delims = GREEDY_DELIMS
434 self.splitter.delims = GREEDY_DELIMS
436 else:
435 else:
437 self.splitter.delims = DELIMS
436 self.splitter.delims = DELIMS
438
437
439 if self.readline:
438 if self.readline:
440 self.readline.set_completer_delims(self.splitter.delims)
439 self.readline.set_completer_delims(self.splitter.delims)
441
440
442 merge_completions = CBool(True, config=True,
441 merge_completions = CBool(True, config=True,
443 help="""Whether to merge completion results into a single list
442 help="""Whether to merge completion results into a single list
444
443
445 If False, only the completion results from the first non-empty
444 If False, only the completion results from the first non-empty
446 completer will be returned.
445 completer will be returned.
447 """
446 """
448 )
447 )
449 omit__names = Enum((0,1,2), default_value=2, config=True,
448 omit__names = Enum((0,1,2), default_value=2, config=True,
450 help="""Instruct the completer to omit private method names
449 help="""Instruct the completer to omit private method names
451
450
452 Specifically, when completing on ``object.<tab>``.
451 Specifically, when completing on ``object.<tab>``.
453
452
454 When 2 [default]: all names that start with '_' will be excluded.
453 When 2 [default]: all names that start with '_' will be excluded.
455
454
456 When 1: all 'magic' names (``__foo__``) will be excluded.
455 When 1: all 'magic' names (``__foo__``) will be excluded.
457
456
458 When 0: nothing will be excluded.
457 When 0: nothing will be excluded.
459 """
458 """
460 )
459 )
461 limit_to__all__ = CBool(default_value=False, config=True,
460 limit_to__all__ = CBool(default_value=False, config=True,
462 help="""Instruct the completer to use __all__ for the completion
461 help="""Instruct the completer to use __all__ for the completion
463
462
464 Specifically, when completing on ``object.<tab>``.
463 Specifically, when completing on ``object.<tab>``.
465
464
466 When True: only those names in obj.__all__ will be included.
465 When True: only those names in obj.__all__ will be included.
467
466
468 When False [default]: the __all__ attribute is ignored
467 When False [default]: the __all__ attribute is ignored
469 """
468 """
470 )
469 )
471
470
472 def __init__(self, shell=None, namespace=None, global_namespace=None,
471 def __init__(self, shell=None, namespace=None, global_namespace=None,
473 use_readline=True, config=None, **kwargs):
472 use_readline=True, config=None, **kwargs):
474 """IPCompleter() -> completer
473 """IPCompleter() -> completer
475
474
476 Return a completer object suitable for use by the readline library
475 Return a completer object suitable for use by the readline library
477 via readline.set_completer().
476 via readline.set_completer().
478
477
479 Inputs:
478 Inputs:
480
479
481 - shell: a pointer to the ipython shell itself. This is needed
480 - shell: a pointer to the ipython shell itself. This is needed
482 because this completer knows about magic functions, and those can
481 because this completer knows about magic functions, and those can
483 only be accessed via the ipython instance.
482 only be accessed via the ipython instance.
484
483
485 - namespace: an optional dict where completions are performed.
484 - namespace: an optional dict where completions are performed.
486
485
487 - global_namespace: secondary optional dict for completions, to
486 - global_namespace: secondary optional dict for completions, to
488 handle cases (such as IPython embedded inside functions) where
487 handle cases (such as IPython embedded inside functions) where
489 both Python scopes are visible.
488 both Python scopes are visible.
490
489
491 use_readline : bool, optional
490 use_readline : bool, optional
492 If true, use the readline library. This completer can still function
491 If true, use the readline library. This completer can still function
493 without readline, though in that case callers must provide some extra
492 without readline, though in that case callers must provide some extra
494 information on each call about the current line."""
493 information on each call about the current line."""
495
494
496 self.magic_escape = ESC_MAGIC
495 self.magic_escape = ESC_MAGIC
497 self.splitter = CompletionSplitter()
496 self.splitter = CompletionSplitter()
498
497
499 # Readline configuration, only used by the rlcompleter method.
498 # Readline configuration, only used by the rlcompleter method.
500 if use_readline:
499 if use_readline:
501 # We store the right version of readline so that later code
500 # We store the right version of readline so that later code
502 import IPython.utils.rlineimpl as readline
501 import IPython.utils.rlineimpl as readline
503 self.readline = readline
502 self.readline = readline
504 else:
503 else:
505 self.readline = None
504 self.readline = None
506
505
507 # _greedy_changed() depends on splitter and readline being defined:
506 # _greedy_changed() depends on splitter and readline being defined:
508 Completer.__init__(self, namespace=namespace, global_namespace=global_namespace,
507 Completer.__init__(self, namespace=namespace, global_namespace=global_namespace,
509 config=config, **kwargs)
508 config=config, **kwargs)
510
509
511 # List where completion matches will be stored
510 # List where completion matches will be stored
512 self.matches = []
511 self.matches = []
513 self.shell = shell
512 self.shell = shell
514 # Regexp to split filenames with spaces in them
513 # Regexp to split filenames with spaces in them
515 self.space_name_re = re.compile(r'([^\\] )')
514 self.space_name_re = re.compile(r'([^\\] )')
516 # Hold a local ref. to glob.glob for speed
515 # Hold a local ref. to glob.glob for speed
517 self.glob = glob.glob
516 self.glob = glob.glob
518
517
519 # Determine if we are running on 'dumb' terminals, like (X)Emacs
518 # Determine if we are running on 'dumb' terminals, like (X)Emacs
520 # buffers, to avoid completion problems.
519 # buffers, to avoid completion problems.
521 term = os.environ.get('TERM','xterm')
520 term = os.environ.get('TERM','xterm')
522 self.dumb_terminal = term in ['dumb','emacs']
521 self.dumb_terminal = term in ['dumb','emacs']
523
522
524 # Special handling of backslashes needed in win32 platforms
523 # Special handling of backslashes needed in win32 platforms
525 if sys.platform == "win32":
524 if sys.platform == "win32":
526 self.clean_glob = self._clean_glob_win32
525 self.clean_glob = self._clean_glob_win32
527 else:
526 else:
528 self.clean_glob = self._clean_glob
527 self.clean_glob = self._clean_glob
529
528
530 #regexp to parse docstring for function signature
529 #regexp to parse docstring for function signature
531 self.docstring_sig_re = re.compile(r'^[\w|\s.]+\(([^)]*)\).*')
530 self.docstring_sig_re = re.compile(r'^[\w|\s.]+\(([^)]*)\).*')
532 self.docstring_kwd_re = re.compile(r'[\s|\[]*(\w+)(?:\s*=\s*.*)')
531 self.docstring_kwd_re = re.compile(r'[\s|\[]*(\w+)(?:\s*=\s*.*)')
533 #use this if positional argument name is also needed
532 #use this if positional argument name is also needed
534 #= re.compile(r'[\s|\[]*(\w+)(?:\s*=?\s*.*)')
533 #= re.compile(r'[\s|\[]*(\w+)(?:\s*=?\s*.*)')
535
534
536 # All active matcher routines for completion
535 # All active matcher routines for completion
537 self.matchers = [self.python_matches,
536 self.matchers = [self.python_matches,
538 self.file_matches,
537 self.file_matches,
539 self.magic_matches,
538 self.magic_matches,
540 self.python_func_kw_matches,
539 self.python_func_kw_matches,
541 ]
540 ]
542
541
543 def all_completions(self, text):
542 def all_completions(self, text):
544 """
543 """
545 Wrapper around the complete method for the benefit of emacs
544 Wrapper around the complete method for the benefit of emacs
546 and pydb.
545 and pydb.
547 """
546 """
548 return self.complete(text)[1]
547 return self.complete(text)[1]
549
548
550 def _clean_glob(self,text):
549 def _clean_glob(self,text):
551 return self.glob("%s*" % text)
550 return self.glob("%s*" % text)
552
551
553 def _clean_glob_win32(self,text):
552 def _clean_glob_win32(self,text):
554 return [f.replace("\\","/")
553 return [f.replace("\\","/")
555 for f in self.glob("%s*" % text)]
554 for f in self.glob("%s*" % text)]
556
555
557 def file_matches(self, text):
556 def file_matches(self, text):
558 """Match filenames, expanding ~USER type strings.
557 """Match filenames, expanding ~USER type strings.
559
558
560 Most of the seemingly convoluted logic in this completer is an
559 Most of the seemingly convoluted logic in this completer is an
561 attempt to handle filenames with spaces in them. And yet it's not
560 attempt to handle filenames with spaces in them. And yet it's not
562 quite perfect, because Python's readline doesn't expose all of the
561 quite perfect, because Python's readline doesn't expose all of the
563 GNU readline details needed for this to be done correctly.
562 GNU readline details needed for this to be done correctly.
564
563
565 For a filename with a space in it, the printed completions will be
564 For a filename with a space in it, the printed completions will be
566 only the parts after what's already been typed (instead of the
565 only the parts after what's already been typed (instead of the
567 full completions, as is normally done). I don't think with the
566 full completions, as is normally done). I don't think with the
568 current (as of Python 2.3) Python readline it's possible to do
567 current (as of Python 2.3) Python readline it's possible to do
569 better."""
568 better."""
570
569
571 #io.rprint('Completer->file_matches: <%r>' % text) # dbg
570 #io.rprint('Completer->file_matches: <%r>' % text) # dbg
572
571
573 # chars that require escaping with backslash - i.e. chars
572 # chars that require escaping with backslash - i.e. chars
574 # that readline treats incorrectly as delimiters, but we
573 # that readline treats incorrectly as delimiters, but we
575 # don't want to treat as delimiters in filename matching
574 # don't want to treat as delimiters in filename matching
576 # when escaped with backslash
575 # when escaped with backslash
577 if text.startswith('!'):
576 if text.startswith('!'):
578 text = text[1:]
577 text = text[1:]
579 text_prefix = '!'
578 text_prefix = '!'
580 else:
579 else:
581 text_prefix = ''
580 text_prefix = ''
582
581
583 text_until_cursor = self.text_until_cursor
582 text_until_cursor = self.text_until_cursor
584 # track strings with open quotes
583 # track strings with open quotes
585 open_quotes = has_open_quotes(text_until_cursor)
584 open_quotes = has_open_quotes(text_until_cursor)
586
585
587 if '(' in text_until_cursor or '[' in text_until_cursor:
586 if '(' in text_until_cursor or '[' in text_until_cursor:
588 lsplit = text
587 lsplit = text
589 else:
588 else:
590 try:
589 try:
591 # arg_split ~ shlex.split, but with unicode bugs fixed by us
590 # arg_split ~ shlex.split, but with unicode bugs fixed by us
592 lsplit = arg_split(text_until_cursor)[-1]
591 lsplit = arg_split(text_until_cursor)[-1]
593 except ValueError:
592 except ValueError:
594 # typically an unmatched ", or backslash without escaped char.
593 # typically an unmatched ", or backslash without escaped char.
595 if open_quotes:
594 if open_quotes:
596 lsplit = text_until_cursor.split(open_quotes)[-1]
595 lsplit = text_until_cursor.split(open_quotes)[-1]
597 else:
596 else:
598 return []
597 return []
599 except IndexError:
598 except IndexError:
600 # tab pressed on empty line
599 # tab pressed on empty line
601 lsplit = ""
600 lsplit = ""
602
601
603 if not open_quotes and lsplit != protect_filename(lsplit):
602 if not open_quotes and lsplit != protect_filename(lsplit):
604 # if protectables are found, do matching on the whole escaped name
603 # if protectables are found, do matching on the whole escaped name
605 has_protectables = True
604 has_protectables = True
606 text0,text = text,lsplit
605 text0,text = text,lsplit
607 else:
606 else:
608 has_protectables = False
607 has_protectables = False
609 text = os.path.expanduser(text)
608 text = os.path.expanduser(text)
610
609
611 if text == "":
610 if text == "":
612 return [text_prefix + protect_filename(f) for f in self.glob("*")]
611 return [text_prefix + protect_filename(f) for f in self.glob("*")]
613
612
614 # Compute the matches from the filesystem
613 # Compute the matches from the filesystem
615 m0 = self.clean_glob(text.replace('\\',''))
614 m0 = self.clean_glob(text.replace('\\',''))
616
615
617 if has_protectables:
616 if has_protectables:
618 # If we had protectables, we need to revert our changes to the
617 # If we had protectables, we need to revert our changes to the
619 # beginning of filename so that we don't double-write the part
618 # beginning of filename so that we don't double-write the part
620 # of the filename we have so far
619 # of the filename we have so far
621 len_lsplit = len(lsplit)
620 len_lsplit = len(lsplit)
622 matches = [text_prefix + text0 +
621 matches = [text_prefix + text0 +
623 protect_filename(f[len_lsplit:]) for f in m0]
622 protect_filename(f[len_lsplit:]) for f in m0]
624 else:
623 else:
625 if open_quotes:
624 if open_quotes:
626 # if we have a string with an open quote, we don't need to
625 # if we have a string with an open quote, we don't need to
627 # protect the names at all (and we _shouldn't_, as it
626 # protect the names at all (and we _shouldn't_, as it
628 # would cause bugs when the filesystem call is made).
627 # would cause bugs when the filesystem call is made).
629 matches = m0
628 matches = m0
630 else:
629 else:
631 matches = [text_prefix +
630 matches = [text_prefix +
632 protect_filename(f) for f in m0]
631 protect_filename(f) for f in m0]
633
632
634 #io.rprint('mm', matches) # dbg
633 #io.rprint('mm', matches) # dbg
635
634
636 # Mark directories in input list by appending '/' to their names.
635 # Mark directories in input list by appending '/' to their names.
637 matches = [x+'/' if os.path.isdir(x) else x for x in matches]
636 matches = [x+'/' if os.path.isdir(x) else x for x in matches]
638 return matches
637 return matches
639
638
640 def magic_matches(self, text):
639 def magic_matches(self, text):
641 """Match magics"""
640 """Match magics"""
642 #print 'Completer->magic_matches:',text,'lb',self.text_until_cursor # dbg
641 #print 'Completer->magic_matches:',text,'lb',self.text_until_cursor # dbg
643 # Get all shell magics now rather than statically, so magics loaded at
642 # Get all shell magics now rather than statically, so magics loaded at
644 # runtime show up too.
643 # runtime show up too.
645 lsm = self.shell.magics_manager.lsmagic()
644 lsm = self.shell.magics_manager.lsmagic()
646 line_magics = lsm['line']
645 line_magics = lsm['line']
647 cell_magics = lsm['cell']
646 cell_magics = lsm['cell']
648 pre = self.magic_escape
647 pre = self.magic_escape
649 pre2 = pre+pre
648 pre2 = pre+pre
650
649
651 # Completion logic:
650 # Completion logic:
652 # - user gives %%: only do cell magics
651 # - user gives %%: only do cell magics
653 # - user gives %: do both line and cell magics
652 # - user gives %: do both line and cell magics
654 # - no prefix: do both
653 # - no prefix: do both
655 # In other words, line magics are skipped if the user gives %% explicitly
654 # In other words, line magics are skipped if the user gives %% explicitly
656 bare_text = text.lstrip(pre)
655 bare_text = text.lstrip(pre)
657 comp = [ pre2+m for m in cell_magics if m.startswith(bare_text)]
656 comp = [ pre2+m for m in cell_magics if m.startswith(bare_text)]
658 if not text.startswith(pre2):
657 if not text.startswith(pre2):
659 comp += [ pre+m for m in line_magics if m.startswith(bare_text)]
658 comp += [ pre+m for m in line_magics if m.startswith(bare_text)]
660 return comp
659 return comp
661
660
662 def python_matches(self,text):
661 def python_matches(self,text):
663 """Match attributes or global python names"""
662 """Match attributes or global python names"""
664
663
665 #io.rprint('Completer->python_matches, txt=%r' % text) # dbg
664 #io.rprint('Completer->python_matches, txt=%r' % text) # dbg
666 if "." in text:
665 if "." in text:
667 try:
666 try:
668 matches = self.attr_matches(text)
667 matches = self.attr_matches(text)
669 if text.endswith('.') and self.omit__names:
668 if text.endswith('.') and self.omit__names:
670 if self.omit__names == 1:
669 if self.omit__names == 1:
671 # true if txt is _not_ a __ name, false otherwise:
670 # true if txt is _not_ a __ name, false otherwise:
672 no__name = (lambda txt:
671 no__name = (lambda txt:
673 re.match(r'.*\.__.*?__',txt) is None)
672 re.match(r'.*\.__.*?__',txt) is None)
674 else:
673 else:
675 # true if txt is _not_ a _ name, false otherwise:
674 # true if txt is _not_ a _ name, false otherwise:
676 no__name = (lambda txt:
675 no__name = (lambda txt:
677 re.match(r'.*\._.*?',txt) is None)
676 re.match(r'.*\._.*?',txt) is None)
678 matches = filter(no__name, matches)
677 matches = filter(no__name, matches)
679 except NameError:
678 except NameError:
680 # catches <undefined attributes>.<tab>
679 # catches <undefined attributes>.<tab>
681 matches = []
680 matches = []
682 else:
681 else:
683 matches = self.global_matches(text)
682 matches = self.global_matches(text)
684
683
685 return matches
684 return matches
686
685
687 def _default_arguments_from_docstring(self, doc):
686 def _default_arguments_from_docstring(self, doc):
688 """Parse the first line of docstring for call signature.
687 """Parse the first line of docstring for call signature.
689
688
690 Docstring should be of the form 'min(iterable[, key=func])\n'.
689 Docstring should be of the form 'min(iterable[, key=func])\n'.
691 It can also parse cython docstring of the form
690 It can also parse cython docstring of the form
692 'Minuit.migrad(self, int ncall=10000, resume=True, int nsplit=1)'.
691 'Minuit.migrad(self, int ncall=10000, resume=True, int nsplit=1)'.
693 """
692 """
694 if doc is None:
693 if doc is None:
695 return []
694 return []
696
695
697 #care only the firstline
696 #care only the firstline
698 line = doc.lstrip().splitlines()[0]
697 line = doc.lstrip().splitlines()[0]
699
698
700 #p = re.compile(r'^[\w|\s.]+\(([^)]*)\).*')
699 #p = re.compile(r'^[\w|\s.]+\(([^)]*)\).*')
701 #'min(iterable[, key=func])\n' -> 'iterable[, key=func]'
700 #'min(iterable[, key=func])\n' -> 'iterable[, key=func]'
702 sig = self.docstring_sig_re.search(line)
701 sig = self.docstring_sig_re.search(line)
703 if sig is None:
702 if sig is None:
704 return []
703 return []
705 # iterable[, key=func]' -> ['iterable[' ,' key=func]']
704 # iterable[, key=func]' -> ['iterable[' ,' key=func]']
706 sig = sig.groups()[0].split(',')
705 sig = sig.groups()[0].split(',')
707 ret = []
706 ret = []
708 for s in sig:
707 for s in sig:
709 #re.compile(r'[\s|\[]*(\w+)(?:\s*=\s*.*)')
708 #re.compile(r'[\s|\[]*(\w+)(?:\s*=\s*.*)')
710 ret += self.docstring_kwd_re.findall(s)
709 ret += self.docstring_kwd_re.findall(s)
711 return ret
710 return ret
712
711
713 def _default_arguments(self, obj):
712 def _default_arguments(self, obj):
714 """Return the list of default arguments of obj if it is callable,
713 """Return the list of default arguments of obj if it is callable,
715 or empty list otherwise."""
714 or empty list otherwise."""
716 call_obj = obj
715 call_obj = obj
717 ret = []
716 ret = []
718 if inspect.isbuiltin(obj):
717 if inspect.isbuiltin(obj):
719 pass
718 pass
720 elif not (inspect.isfunction(obj) or inspect.ismethod(obj)):
719 elif not (inspect.isfunction(obj) or inspect.ismethod(obj)):
721 if inspect.isclass(obj):
720 if inspect.isclass(obj):
722 #for cython embededsignature=True the constructor docstring
721 #for cython embededsignature=True the constructor docstring
723 #belongs to the object itself not __init__
722 #belongs to the object itself not __init__
724 ret += self._default_arguments_from_docstring(
723 ret += self._default_arguments_from_docstring(
725 getattr(obj, '__doc__', ''))
724 getattr(obj, '__doc__', ''))
726 # for classes, check for __init__,__new__
725 # for classes, check for __init__,__new__
727 call_obj = (getattr(obj, '__init__', None) or
726 call_obj = (getattr(obj, '__init__', None) or
728 getattr(obj, '__new__', None))
727 getattr(obj, '__new__', None))
729 # for all others, check if they are __call__able
728 # for all others, check if they are __call__able
730 elif hasattr(obj, '__call__'):
729 elif hasattr(obj, '__call__'):
731 call_obj = obj.__call__
730 call_obj = obj.__call__
732
731
733 ret += self._default_arguments_from_docstring(
732 ret += self._default_arguments_from_docstring(
734 getattr(call_obj, '__doc__', ''))
733 getattr(call_obj, '__doc__', ''))
735
734
736 try:
735 try:
737 args,_,_1,defaults = inspect.getargspec(call_obj)
736 args,_,_1,defaults = inspect.getargspec(call_obj)
738 if defaults:
737 if defaults:
739 ret+=args[-len(defaults):]
738 ret+=args[-len(defaults):]
740 except TypeError:
739 except TypeError:
741 pass
740 pass
742
741
743 return list(set(ret))
742 return list(set(ret))
744
743
745 def python_func_kw_matches(self,text):
744 def python_func_kw_matches(self,text):
746 """Match named parameters (kwargs) of the last open function"""
745 """Match named parameters (kwargs) of the last open function"""
747
746
748 if "." in text: # a parameter cannot be dotted
747 if "." in text: # a parameter cannot be dotted
749 return []
748 return []
750 try: regexp = self.__funcParamsRegex
749 try: regexp = self.__funcParamsRegex
751 except AttributeError:
750 except AttributeError:
752 regexp = self.__funcParamsRegex = re.compile(r'''
751 regexp = self.__funcParamsRegex = re.compile(r'''
753 '.*?(?<!\\)' | # single quoted strings or
752 '.*?(?<!\\)' | # single quoted strings or
754 ".*?(?<!\\)" | # double quoted strings or
753 ".*?(?<!\\)" | # double quoted strings or
755 \w+ | # identifier
754 \w+ | # identifier
756 \S # other characters
755 \S # other characters
757 ''', re.VERBOSE | re.DOTALL)
756 ''', re.VERBOSE | re.DOTALL)
758 # 1. find the nearest identifier that comes before an unclosed
757 # 1. find the nearest identifier that comes before an unclosed
759 # parenthesis before the cursor
758 # parenthesis before the cursor
760 # e.g. for "foo (1+bar(x), pa<cursor>,a=1)", the candidate is "foo"
759 # e.g. for "foo (1+bar(x), pa<cursor>,a=1)", the candidate is "foo"
761 tokens = regexp.findall(self.text_until_cursor)
760 tokens = regexp.findall(self.text_until_cursor)
762 tokens.reverse()
761 tokens.reverse()
763 iterTokens = iter(tokens); openPar = 0
762 iterTokens = iter(tokens); openPar = 0
764
763
765 for token in iterTokens:
764 for token in iterTokens:
766 if token == ')':
765 if token == ')':
767 openPar -= 1
766 openPar -= 1
768 elif token == '(':
767 elif token == '(':
769 openPar += 1
768 openPar += 1
770 if openPar > 0:
769 if openPar > 0:
771 # found the last unclosed parenthesis
770 # found the last unclosed parenthesis
772 break
771 break
773 else:
772 else:
774 return []
773 return []
775 # 2. Concatenate dotted names ("foo.bar" for "foo.bar(x, pa" )
774 # 2. Concatenate dotted names ("foo.bar" for "foo.bar(x, pa" )
776 ids = []
775 ids = []
777 isId = re.compile(r'\w+$').match
776 isId = re.compile(r'\w+$').match
778
777
779 while True:
778 while True:
780 try:
779 try:
781 ids.append(next(iterTokens))
780 ids.append(next(iterTokens))
782 if not isId(ids[-1]):
781 if not isId(ids[-1]):
783 ids.pop(); break
782 ids.pop(); break
784 if not next(iterTokens) == '.':
783 if not next(iterTokens) == '.':
785 break
784 break
786 except StopIteration:
785 except StopIteration:
787 break
786 break
788 # lookup the candidate callable matches either using global_matches
787 # lookup the candidate callable matches either using global_matches
789 # or attr_matches for dotted names
788 # or attr_matches for dotted names
790 if len(ids) == 1:
789 if len(ids) == 1:
791 callableMatches = self.global_matches(ids[0])
790 callableMatches = self.global_matches(ids[0])
792 else:
791 else:
793 callableMatches = self.attr_matches('.'.join(ids[::-1]))
792 callableMatches = self.attr_matches('.'.join(ids[::-1]))
794 argMatches = []
793 argMatches = []
795 for callableMatch in callableMatches:
794 for callableMatch in callableMatches:
796 try:
795 try:
797 namedArgs = self._default_arguments(eval(callableMatch,
796 namedArgs = self._default_arguments(eval(callableMatch,
798 self.namespace))
797 self.namespace))
799 except:
798 except:
800 continue
799 continue
801
800
802 for namedArg in namedArgs:
801 for namedArg in namedArgs:
803 if namedArg.startswith(text):
802 if namedArg.startswith(text):
804 argMatches.append("%s=" %namedArg)
803 argMatches.append("%s=" %namedArg)
805 return argMatches
804 return argMatches
806
805
807 def dispatch_custom_completer(self, text):
806 def dispatch_custom_completer(self, text):
808 #io.rprint("Custom! '%s' %s" % (text, self.custom_completers)) # dbg
807 #io.rprint("Custom! '%s' %s" % (text, self.custom_completers)) # dbg
809 line = self.line_buffer
808 line = self.line_buffer
810 if not line.strip():
809 if not line.strip():
811 return None
810 return None
812
811
813 # Create a little structure to pass all the relevant information about
812 # Create a little structure to pass all the relevant information about
814 # the current completion to any custom completer.
813 # the current completion to any custom completer.
815 event = Bunch()
814 event = Bunch()
816 event.line = line
815 event.line = line
817 event.symbol = text
816 event.symbol = text
818 cmd = line.split(None,1)[0]
817 cmd = line.split(None,1)[0]
819 event.command = cmd
818 event.command = cmd
820 event.text_until_cursor = self.text_until_cursor
819 event.text_until_cursor = self.text_until_cursor
821
820
822 #print "\ncustom:{%s]\n" % event # dbg
821 #print "\ncustom:{%s]\n" % event # dbg
823
822
824 # for foo etc, try also to find completer for %foo
823 # for foo etc, try also to find completer for %foo
825 if not cmd.startswith(self.magic_escape):
824 if not cmd.startswith(self.magic_escape):
826 try_magic = self.custom_completers.s_matches(
825 try_magic = self.custom_completers.s_matches(
827 self.magic_escape + cmd)
826 self.magic_escape + cmd)
828 else:
827 else:
829 try_magic = []
828 try_magic = []
830
829
831 for c in itertools.chain(self.custom_completers.s_matches(cmd),
830 for c in itertools.chain(self.custom_completers.s_matches(cmd),
832 try_magic,
831 try_magic,
833 self.custom_completers.flat_matches(self.text_until_cursor)):
832 self.custom_completers.flat_matches(self.text_until_cursor)):
834 #print "try",c # dbg
833 #print "try",c # dbg
835 try:
834 try:
836 res = c(event)
835 res = c(event)
837 if res:
836 if res:
838 # first, try case sensitive match
837 # first, try case sensitive match
839 withcase = [r for r in res if r.startswith(text)]
838 withcase = [r for r in res if r.startswith(text)]
840 if withcase:
839 if withcase:
841 return withcase
840 return withcase
842 # if none, then case insensitive ones are ok too
841 # if none, then case insensitive ones are ok too
843 text_low = text.lower()
842 text_low = text.lower()
844 return [r for r in res if r.lower().startswith(text_low)]
843 return [r for r in res if r.lower().startswith(text_low)]
845 except TryNext:
844 except TryNext:
846 pass
845 pass
847
846
848 return None
847 return None
849
848
850 def complete(self, text=None, line_buffer=None, cursor_pos=None):
849 def complete(self, text=None, line_buffer=None, cursor_pos=None):
851 """Find completions for the given text and line context.
850 """Find completions for the given text and line context.
852
851
853 This is called successively with state == 0, 1, 2, ... until it
852 This is called successively with state == 0, 1, 2, ... until it
854 returns None. The completion should begin with 'text'.
853 returns None. The completion should begin with 'text'.
855
854
856 Note that both the text and the line_buffer are optional, but at least
855 Note that both the text and the line_buffer are optional, but at least
857 one of them must be given.
856 one of them must be given.
858
857
859 Parameters
858 Parameters
860 ----------
859 ----------
861 text : string, optional
860 text : string, optional
862 Text to perform the completion on. If not given, the line buffer
861 Text to perform the completion on. If not given, the line buffer
863 is split using the instance's CompletionSplitter object.
862 is split using the instance's CompletionSplitter object.
864
863
865 line_buffer : string, optional
864 line_buffer : string, optional
866 If not given, the completer attempts to obtain the current line
865 If not given, the completer attempts to obtain the current line
867 buffer via readline. This keyword allows clients which are
866 buffer via readline. This keyword allows clients which are
868 requesting for text completions in non-readline contexts to inform
867 requesting for text completions in non-readline contexts to inform
869 the completer of the entire text.
868 the completer of the entire text.
870
869
871 cursor_pos : int, optional
870 cursor_pos : int, optional
872 Index of the cursor in the full line buffer. Should be provided by
871 Index of the cursor in the full line buffer. Should be provided by
873 remote frontends where kernel has no access to frontend state.
872 remote frontends where kernel has no access to frontend state.
874
873
875 Returns
874 Returns
876 -------
875 -------
877 text : str
876 text : str
878 Text that was actually used in the completion.
877 Text that was actually used in the completion.
879
878
880 matches : list
879 matches : list
881 A list of completion matches.
880 A list of completion matches.
882 """
881 """
883 #io.rprint('\nCOMP1 %r %r %r' % (text, line_buffer, cursor_pos)) # dbg
882 #io.rprint('\nCOMP1 %r %r %r' % (text, line_buffer, cursor_pos)) # dbg
884
883
885 # if the cursor position isn't given, the only sane assumption we can
884 # if the cursor position isn't given, the only sane assumption we can
886 # make is that it's at the end of the line (the common case)
885 # make is that it's at the end of the line (the common case)
887 if cursor_pos is None:
886 if cursor_pos is None:
888 cursor_pos = len(line_buffer) if text is None else len(text)
887 cursor_pos = len(line_buffer) if text is None else len(text)
889
888
890 # if text is either None or an empty string, rely on the line buffer
889 # if text is either None or an empty string, rely on the line buffer
891 if not text:
890 if not text:
892 text = self.splitter.split_line(line_buffer, cursor_pos)
891 text = self.splitter.split_line(line_buffer, cursor_pos)
893
892
894 # If no line buffer is given, assume the input text is all there was
893 # If no line buffer is given, assume the input text is all there was
895 if line_buffer is None:
894 if line_buffer is None:
896 line_buffer = text
895 line_buffer = text
897
896
898 self.line_buffer = line_buffer
897 self.line_buffer = line_buffer
899 self.text_until_cursor = self.line_buffer[:cursor_pos]
898 self.text_until_cursor = self.line_buffer[:cursor_pos]
900 #io.rprint('COMP2 %r %r %r' % (text, line_buffer, cursor_pos)) # dbg
899 #io.rprint('COMP2 %r %r %r' % (text, line_buffer, cursor_pos)) # dbg
901
900
902 # Start with a clean slate of completions
901 # Start with a clean slate of completions
903 self.matches[:] = []
902 self.matches[:] = []
904 custom_res = self.dispatch_custom_completer(text)
903 custom_res = self.dispatch_custom_completer(text)
905 if custom_res is not None:
904 if custom_res is not None:
906 # did custom completers produce something?
905 # did custom completers produce something?
907 self.matches = custom_res
906 self.matches = custom_res
908 else:
907 else:
909 # Extend the list of completions with the results of each
908 # Extend the list of completions with the results of each
910 # matcher, so we return results to the user from all
909 # matcher, so we return results to the user from all
911 # namespaces.
910 # namespaces.
912 if self.merge_completions:
911 if self.merge_completions:
913 self.matches = []
912 self.matches = []
914 for matcher in self.matchers:
913 for matcher in self.matchers:
915 try:
914 try:
916 self.matches.extend(matcher(text))
915 self.matches.extend(matcher(text))
917 except:
916 except:
918 # Show the ugly traceback if the matcher causes an
917 # Show the ugly traceback if the matcher causes an
919 # exception, but do NOT crash the kernel!
918 # exception, but do NOT crash the kernel!
920 sys.excepthook(*sys.exc_info())
919 sys.excepthook(*sys.exc_info())
921 else:
920 else:
922 for matcher in self.matchers:
921 for matcher in self.matchers:
923 self.matches = matcher(text)
922 self.matches = matcher(text)
924 if self.matches:
923 if self.matches:
925 break
924 break
926 # FIXME: we should extend our api to return a dict with completions for
925 # FIXME: we should extend our api to return a dict with completions for
927 # different types of objects. The rlcomplete() method could then
926 # different types of objects. The rlcomplete() method could then
928 # simply collapse the dict into a list for readline, but we'd have
927 # simply collapse the dict into a list for readline, but we'd have
929 # richer completion semantics in other evironments.
928 # richer completion semantics in other evironments.
930
929
931 # use penalize_magics_key to put magics after variables with same name
930 # use penalize_magics_key to put magics after variables with same name
932 self.matches = sorted(set(self.matches), key=penalize_magics_key)
931 self.matches = sorted(set(self.matches), key=penalize_magics_key)
933
932
934 #io.rprint('COMP TEXT, MATCHES: %r, %r' % (text, self.matches)) # dbg
933 #io.rprint('COMP TEXT, MATCHES: %r, %r' % (text, self.matches)) # dbg
935 return text, self.matches
934 return text, self.matches
936
935
937 def rlcomplete(self, text, state):
936 def rlcomplete(self, text, state):
938 """Return the state-th possible completion for 'text'.
937 """Return the state-th possible completion for 'text'.
939
938
940 This is called successively with state == 0, 1, 2, ... until it
939 This is called successively with state == 0, 1, 2, ... until it
941 returns None. The completion should begin with 'text'.
940 returns None. The completion should begin with 'text'.
942
941
943 Parameters
942 Parameters
944 ----------
943 ----------
945 text : string
944 text : string
946 Text to perform the completion on.
945 Text to perform the completion on.
947
946
948 state : int
947 state : int
949 Counter used by readline.
948 Counter used by readline.
950 """
949 """
951 if state==0:
950 if state==0:
952
951
953 self.line_buffer = line_buffer = self.readline.get_line_buffer()
952 self.line_buffer = line_buffer = self.readline.get_line_buffer()
954 cursor_pos = self.readline.get_endidx()
953 cursor_pos = self.readline.get_endidx()
955
954
956 #io.rprint("\nRLCOMPLETE: %r %r %r" %
955 #io.rprint("\nRLCOMPLETE: %r %r %r" %
957 # (text, line_buffer, cursor_pos) ) # dbg
956 # (text, line_buffer, cursor_pos) ) # dbg
958
957
959 # if there is only a tab on a line with only whitespace, instead of
958 # if there is only a tab on a line with only whitespace, instead of
960 # the mostly useless 'do you want to see all million completions'
959 # the mostly useless 'do you want to see all million completions'
961 # message, just do the right thing and give the user his tab!
960 # message, just do the right thing and give the user his tab!
962 # Incidentally, this enables pasting of tabbed text from an editor
961 # Incidentally, this enables pasting of tabbed text from an editor
963 # (as long as autoindent is off).
962 # (as long as autoindent is off).
964
963
965 # It should be noted that at least pyreadline still shows file
964 # It should be noted that at least pyreadline still shows file
966 # completions - is there a way around it?
965 # completions - is there a way around it?
967
966
968 # don't apply this on 'dumb' terminals, such as emacs buffers, so
967 # don't apply this on 'dumb' terminals, such as emacs buffers, so
969 # we don't interfere with their own tab-completion mechanism.
968 # we don't interfere with their own tab-completion mechanism.
970 if not (self.dumb_terminal or line_buffer.strip()):
969 if not (self.dumb_terminal or line_buffer.strip()):
971 self.readline.insert_text('\t')
970 self.readline.insert_text('\t')
972 sys.stdout.flush()
971 sys.stdout.flush()
973 return None
972 return None
974
973
975 # Note: debugging exceptions that may occur in completion is very
974 # Note: debugging exceptions that may occur in completion is very
976 # tricky, because readline unconditionally silences them. So if
975 # tricky, because readline unconditionally silences them. So if
977 # during development you suspect a bug in the completion code, turn
976 # during development you suspect a bug in the completion code, turn
978 # this flag on temporarily by uncommenting the second form (don't
977 # this flag on temporarily by uncommenting the second form (don't
979 # flip the value in the first line, as the '# dbg' marker can be
978 # flip the value in the first line, as the '# dbg' marker can be
980 # automatically detected and is used elsewhere).
979 # automatically detected and is used elsewhere).
981 DEBUG = False
980 DEBUG = False
982 #DEBUG = True # dbg
981 #DEBUG = True # dbg
983 if DEBUG:
982 if DEBUG:
984 try:
983 try:
985 self.complete(text, line_buffer, cursor_pos)
984 self.complete(text, line_buffer, cursor_pos)
986 except:
985 except:
987 import traceback; traceback.print_exc()
986 import traceback; traceback.print_exc()
988 else:
987 else:
989 # The normal production version is here
988 # The normal production version is here
990
989
991 # This method computes the self.matches array
990 # This method computes the self.matches array
992 self.complete(text, line_buffer, cursor_pos)
991 self.complete(text, line_buffer, cursor_pos)
993
992
994 try:
993 try:
995 return self.matches[state]
994 return self.matches[state]
996 except IndexError:
995 except IndexError:
997 return None
996 return None
@@ -1,902 +1,900 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 """Display formatters.
2 """Display formatters.
3
3
4 Inheritance diagram:
4 Inheritance diagram:
5
5
6 .. inheritance-diagram:: IPython.core.formatters
6 .. inheritance-diagram:: IPython.core.formatters
7 :parts: 3
7 :parts: 3
8
8
9 Authors:
9 Authors:
10
10
11 * Robert Kern
11 * Robert Kern
12 * Brian Granger
12 * Brian Granger
13 """
13 """
14 #-----------------------------------------------------------------------------
14 #-----------------------------------------------------------------------------
15 # Copyright (C) 2010-2011, IPython Development Team.
15 # Copyright (C) 2010-2011, IPython Development Team.
16 #
16 #
17 # Distributed under the terms of the Modified BSD License.
17 # Distributed under the terms of the Modified BSD License.
18 #
18 #
19 # The full license is in the file COPYING.txt, distributed with this software.
19 # The full license is in the file COPYING.txt, distributed with this software.
20 #-----------------------------------------------------------------------------
20 #-----------------------------------------------------------------------------
21
21
22 #-----------------------------------------------------------------------------
22 #-----------------------------------------------------------------------------
23 # Imports
23 # Imports
24 #-----------------------------------------------------------------------------
24 #-----------------------------------------------------------------------------
25
25
26 # Stdlib imports
26 # Stdlib imports
27 import abc
27 import abc
28 import inspect
28 import inspect
29 import sys
29 import sys
30 import types
30 import types
31 import warnings
31 import warnings
32
32
33 from IPython.external.decorator import decorator
33 from IPython.external.decorator import decorator
34
34
35 # Our own imports
35 # Our own imports
36 from IPython.config.configurable import Configurable
36 from IPython.config.configurable import Configurable
37 from IPython.lib import pretty
37 from IPython.lib import pretty
38 from IPython.utils import io
39 from IPython.utils.traitlets import (
38 from IPython.utils.traitlets import (
40 Bool, Dict, Integer, Unicode, CUnicode, ObjectName, List,
39 Bool, Dict, Integer, Unicode, CUnicode, ObjectName, List,
41 )
40 )
42 from IPython.utils.warn import warn
43 from IPython.utils.py3compat import (
41 from IPython.utils.py3compat import (
44 unicode_to_str, with_metaclass, PY3, string_types, unicode_type,
42 unicode_to_str, with_metaclass, PY3, string_types, unicode_type,
45 )
43 )
46
44
47 if PY3:
45 if PY3:
48 from io import StringIO
46 from io import StringIO
49 else:
47 else:
50 from StringIO import StringIO
48 from StringIO import StringIO
51
49
52
50
53 #-----------------------------------------------------------------------------
51 #-----------------------------------------------------------------------------
54 # The main DisplayFormatter class
52 # The main DisplayFormatter class
55 #-----------------------------------------------------------------------------
53 #-----------------------------------------------------------------------------
56
54
57
55
58 def _valid_formatter(f):
56 def _valid_formatter(f):
59 """Return whether an object is a valid formatter
57 """Return whether an object is a valid formatter
60
58
61 Cases checked:
59 Cases checked:
62
60
63 - bound methods OK
61 - bound methods OK
64 - unbound methods NO
62 - unbound methods NO
65 - callable with zero args OK
63 - callable with zero args OK
66 """
64 """
67 if f is None:
65 if f is None:
68 return False
66 return False
69 elif isinstance(f, type(str.find)):
67 elif isinstance(f, type(str.find)):
70 # unbound methods on compiled classes have type method_descriptor
68 # unbound methods on compiled classes have type method_descriptor
71 return False
69 return False
72 elif isinstance(f, types.BuiltinFunctionType):
70 elif isinstance(f, types.BuiltinFunctionType):
73 # bound methods on compiled classes have type builtin_function
71 # bound methods on compiled classes have type builtin_function
74 return True
72 return True
75 elif callable(f):
73 elif callable(f):
76 # anything that works with zero args should be okay
74 # anything that works with zero args should be okay
77 try:
75 try:
78 inspect.getcallargs(f)
76 inspect.getcallargs(f)
79 except TypeError:
77 except TypeError:
80 return False
78 return False
81 else:
79 else:
82 return True
80 return True
83 return False
81 return False
84
82
85 def _safe_get_formatter_method(obj, name):
83 def _safe_get_formatter_method(obj, name):
86 """Safely get a formatter method"""
84 """Safely get a formatter method"""
87 method = pretty._safe_getattr(obj, name, None)
85 method = pretty._safe_getattr(obj, name, None)
88 # formatter methods must be bound
86 # formatter methods must be bound
89 if _valid_formatter(method):
87 if _valid_formatter(method):
90 return method
88 return method
91
89
92
90
93 class DisplayFormatter(Configurable):
91 class DisplayFormatter(Configurable):
94
92
95 # When set to true only the default plain text formatter will be used.
93 # When set to true only the default plain text formatter will be used.
96 plain_text_only = Bool(False, config=True)
94 plain_text_only = Bool(False, config=True)
97 def _plain_text_only_changed(self, name, old, new):
95 def _plain_text_only_changed(self, name, old, new):
98 warnings.warn("""DisplayFormatter.plain_text_only is deprecated.
96 warnings.warn("""DisplayFormatter.plain_text_only is deprecated.
99
97
100 Use DisplayFormatter.active_types = ['text/plain']
98 Use DisplayFormatter.active_types = ['text/plain']
101 for the same effect.
99 for the same effect.
102 """, DeprecationWarning)
100 """, DeprecationWarning)
103 if new:
101 if new:
104 self.active_types = ['text/plain']
102 self.active_types = ['text/plain']
105 else:
103 else:
106 self.active_types = self.format_types
104 self.active_types = self.format_types
107
105
108 active_types = List(Unicode, config=True,
106 active_types = List(Unicode, config=True,
109 help="""List of currently active mime-types to display.
107 help="""List of currently active mime-types to display.
110 You can use this to set a white-list for formats to display.
108 You can use this to set a white-list for formats to display.
111
109
112 Most users will not need to change this value.
110 Most users will not need to change this value.
113 """)
111 """)
114 def _active_types_default(self):
112 def _active_types_default(self):
115 return self.format_types
113 return self.format_types
116
114
117 def _active_types_changed(self, name, old, new):
115 def _active_types_changed(self, name, old, new):
118 for key, formatter in self.formatters.items():
116 for key, formatter in self.formatters.items():
119 if key in new:
117 if key in new:
120 formatter.enabled = True
118 formatter.enabled = True
121 else:
119 else:
122 formatter.enabled = False
120 formatter.enabled = False
123
121
124 # A dict of formatter whose keys are format types (MIME types) and whose
122 # A dict of formatter whose keys are format types (MIME types) and whose
125 # values are subclasses of BaseFormatter.
123 # values are subclasses of BaseFormatter.
126 formatters = Dict()
124 formatters = Dict()
127 def _formatters_default(self):
125 def _formatters_default(self):
128 """Activate the default formatters."""
126 """Activate the default formatters."""
129 formatter_classes = [
127 formatter_classes = [
130 PlainTextFormatter,
128 PlainTextFormatter,
131 HTMLFormatter,
129 HTMLFormatter,
132 MarkdownFormatter,
130 MarkdownFormatter,
133 SVGFormatter,
131 SVGFormatter,
134 PNGFormatter,
132 PNGFormatter,
135 PDFFormatter,
133 PDFFormatter,
136 JPEGFormatter,
134 JPEGFormatter,
137 LatexFormatter,
135 LatexFormatter,
138 JSONFormatter,
136 JSONFormatter,
139 JavascriptFormatter
137 JavascriptFormatter
140 ]
138 ]
141 d = {}
139 d = {}
142 for cls in formatter_classes:
140 for cls in formatter_classes:
143 f = cls(parent=self)
141 f = cls(parent=self)
144 d[f.format_type] = f
142 d[f.format_type] = f
145 return d
143 return d
146
144
147 def format(self, obj, include=None, exclude=None):
145 def format(self, obj, include=None, exclude=None):
148 """Return a format data dict for an object.
146 """Return a format data dict for an object.
149
147
150 By default all format types will be computed.
148 By default all format types will be computed.
151
149
152 The following MIME types are currently implemented:
150 The following MIME types are currently implemented:
153
151
154 * text/plain
152 * text/plain
155 * text/html
153 * text/html
156 * text/markdown
154 * text/markdown
157 * text/latex
155 * text/latex
158 * application/json
156 * application/json
159 * application/javascript
157 * application/javascript
160 * application/pdf
158 * application/pdf
161 * image/png
159 * image/png
162 * image/jpeg
160 * image/jpeg
163 * image/svg+xml
161 * image/svg+xml
164
162
165 Parameters
163 Parameters
166 ----------
164 ----------
167 obj : object
165 obj : object
168 The Python object whose format data will be computed.
166 The Python object whose format data will be computed.
169 include : list or tuple, optional
167 include : list or tuple, optional
170 A list of format type strings (MIME types) to include in the
168 A list of format type strings (MIME types) to include in the
171 format data dict. If this is set *only* the format types included
169 format data dict. If this is set *only* the format types included
172 in this list will be computed.
170 in this list will be computed.
173 exclude : list or tuple, optional
171 exclude : list or tuple, optional
174 A list of format type string (MIME types) to exclude in the format
172 A list of format type string (MIME types) to exclude in the format
175 data dict. If this is set all format types will be computed,
173 data dict. If this is set all format types will be computed,
176 except for those included in this argument.
174 except for those included in this argument.
177
175
178 Returns
176 Returns
179 -------
177 -------
180 (format_dict, metadata_dict) : tuple of two dicts
178 (format_dict, metadata_dict) : tuple of two dicts
181
179
182 format_dict is a dictionary of key/value pairs, one of each format that was
180 format_dict is a dictionary of key/value pairs, one of each format that was
183 generated for the object. The keys are the format types, which
181 generated for the object. The keys are the format types, which
184 will usually be MIME type strings and the values and JSON'able
182 will usually be MIME type strings and the values and JSON'able
185 data structure containing the raw data for the representation in
183 data structure containing the raw data for the representation in
186 that format.
184 that format.
187
185
188 metadata_dict is a dictionary of metadata about each mime-type output.
186 metadata_dict is a dictionary of metadata about each mime-type output.
189 Its keys will be a strict subset of the keys in format_dict.
187 Its keys will be a strict subset of the keys in format_dict.
190 """
188 """
191 format_dict = {}
189 format_dict = {}
192 md_dict = {}
190 md_dict = {}
193
191
194 for format_type, formatter in self.formatters.items():
192 for format_type, formatter in self.formatters.items():
195 if include and format_type not in include:
193 if include and format_type not in include:
196 continue
194 continue
197 if exclude and format_type in exclude:
195 if exclude and format_type in exclude:
198 continue
196 continue
199
197
200 md = None
198 md = None
201 try:
199 try:
202 data = formatter(obj)
200 data = formatter(obj)
203 except:
201 except:
204 # FIXME: log the exception
202 # FIXME: log the exception
205 raise
203 raise
206
204
207 # formatters can return raw data or (data, metadata)
205 # formatters can return raw data or (data, metadata)
208 if isinstance(data, tuple) and len(data) == 2:
206 if isinstance(data, tuple) and len(data) == 2:
209 data, md = data
207 data, md = data
210
208
211 if data is not None:
209 if data is not None:
212 format_dict[format_type] = data
210 format_dict[format_type] = data
213 if md is not None:
211 if md is not None:
214 md_dict[format_type] = md
212 md_dict[format_type] = md
215
213
216 return format_dict, md_dict
214 return format_dict, md_dict
217
215
218 @property
216 @property
219 def format_types(self):
217 def format_types(self):
220 """Return the format types (MIME types) of the active formatters."""
218 """Return the format types (MIME types) of the active formatters."""
221 return list(self.formatters.keys())
219 return list(self.formatters.keys())
222
220
223
221
224 #-----------------------------------------------------------------------------
222 #-----------------------------------------------------------------------------
225 # Formatters for specific format types (text, html, svg, etc.)
223 # Formatters for specific format types (text, html, svg, etc.)
226 #-----------------------------------------------------------------------------
224 #-----------------------------------------------------------------------------
227
225
228 class FormatterWarning(UserWarning):
226 class FormatterWarning(UserWarning):
229 """Warning class for errors in formatters"""
227 """Warning class for errors in formatters"""
230
228
231 @decorator
229 @decorator
232 def warn_format_error(method, self, *args, **kwargs):
230 def warn_format_error(method, self, *args, **kwargs):
233 """decorator for warning on failed format call"""
231 """decorator for warning on failed format call"""
234 try:
232 try:
235 r = method(self, *args, **kwargs)
233 r = method(self, *args, **kwargs)
236 except NotImplementedError as e:
234 except NotImplementedError as e:
237 # don't warn on NotImplementedErrors
235 # don't warn on NotImplementedErrors
238 return None
236 return None
239 except Exception as e:
237 except Exception as e:
240 warnings.warn("Exception in %s formatter: %s" % (self.format_type, e),
238 warnings.warn("Exception in %s formatter: %s" % (self.format_type, e),
241 FormatterWarning,
239 FormatterWarning,
242 )
240 )
243 return None
241 return None
244 if r is None or isinstance(r, self._return_type) or \
242 if r is None or isinstance(r, self._return_type) or \
245 (isinstance(r, tuple) and r and isinstance(r[0], self._return_type)):
243 (isinstance(r, tuple) and r and isinstance(r[0], self._return_type)):
246 return r
244 return r
247 else:
245 else:
248 warnings.warn(
246 warnings.warn(
249 "%s formatter returned invalid type %s (expected %s) for object: %s" % \
247 "%s formatter returned invalid type %s (expected %s) for object: %s" % \
250 (self.format_type, type(r), self._return_type, pretty._safe_repr(args[0])),
248 (self.format_type, type(r), self._return_type, pretty._safe_repr(args[0])),
251 FormatterWarning
249 FormatterWarning
252 )
250 )
253
251
254
252
255 class FormatterABC(with_metaclass(abc.ABCMeta, object)):
253 class FormatterABC(with_metaclass(abc.ABCMeta, object)):
256 """ Abstract base class for Formatters.
254 """ Abstract base class for Formatters.
257
255
258 A formatter is a callable class that is responsible for computing the
256 A formatter is a callable class that is responsible for computing the
259 raw format data for a particular format type (MIME type). For example,
257 raw format data for a particular format type (MIME type). For example,
260 an HTML formatter would have a format type of `text/html` and would return
258 an HTML formatter would have a format type of `text/html` and would return
261 the HTML representation of the object when called.
259 the HTML representation of the object when called.
262 """
260 """
263
261
264 # The format type of the data returned, usually a MIME type.
262 # The format type of the data returned, usually a MIME type.
265 format_type = 'text/plain'
263 format_type = 'text/plain'
266
264
267 # Is the formatter enabled...
265 # Is the formatter enabled...
268 enabled = True
266 enabled = True
269
267
270 @abc.abstractmethod
268 @abc.abstractmethod
271 @warn_format_error
269 @warn_format_error
272 def __call__(self, obj):
270 def __call__(self, obj):
273 """Return a JSON'able representation of the object.
271 """Return a JSON'able representation of the object.
274
272
275 If the object cannot be formatted by this formatter,
273 If the object cannot be formatted by this formatter,
276 warn and return None.
274 warn and return None.
277 """
275 """
278 return repr(obj)
276 return repr(obj)
279
277
280
278
281 def _mod_name_key(typ):
279 def _mod_name_key(typ):
282 """Return a (__module__, __name__) tuple for a type.
280 """Return a (__module__, __name__) tuple for a type.
283
281
284 Used as key in Formatter.deferred_printers.
282 Used as key in Formatter.deferred_printers.
285 """
283 """
286 module = getattr(typ, '__module__', None)
284 module = getattr(typ, '__module__', None)
287 name = getattr(typ, '__name__', None)
285 name = getattr(typ, '__name__', None)
288 return (module, name)
286 return (module, name)
289
287
290
288
291 def _get_type(obj):
289 def _get_type(obj):
292 """Return the type of an instance (old and new-style)"""
290 """Return the type of an instance (old and new-style)"""
293 return getattr(obj, '__class__', None) or type(obj)
291 return getattr(obj, '__class__', None) or type(obj)
294
292
295 _raise_key_error = object()
293 _raise_key_error = object()
296
294
297
295
298 class BaseFormatter(Configurable):
296 class BaseFormatter(Configurable):
299 """A base formatter class that is configurable.
297 """A base formatter class that is configurable.
300
298
301 This formatter should usually be used as the base class of all formatters.
299 This formatter should usually be used as the base class of all formatters.
302 It is a traited :class:`Configurable` class and includes an extensible
300 It is a traited :class:`Configurable` class and includes an extensible
303 API for users to determine how their objects are formatted. The following
301 API for users to determine how their objects are formatted. The following
304 logic is used to find a function to format an given object.
302 logic is used to find a function to format an given object.
305
303
306 1. The object is introspected to see if it has a method with the name
304 1. The object is introspected to see if it has a method with the name
307 :attr:`print_method`. If is does, that object is passed to that method
305 :attr:`print_method`. If is does, that object is passed to that method
308 for formatting.
306 for formatting.
309 2. If no print method is found, three internal dictionaries are consulted
307 2. If no print method is found, three internal dictionaries are consulted
310 to find print method: :attr:`singleton_printers`, :attr:`type_printers`
308 to find print method: :attr:`singleton_printers`, :attr:`type_printers`
311 and :attr:`deferred_printers`.
309 and :attr:`deferred_printers`.
312
310
313 Users should use these dictionaries to register functions that will be
311 Users should use these dictionaries to register functions that will be
314 used to compute the format data for their objects (if those objects don't
312 used to compute the format data for their objects (if those objects don't
315 have the special print methods). The easiest way of using these
313 have the special print methods). The easiest way of using these
316 dictionaries is through the :meth:`for_type` and :meth:`for_type_by_name`
314 dictionaries is through the :meth:`for_type` and :meth:`for_type_by_name`
317 methods.
315 methods.
318
316
319 If no function/callable is found to compute the format data, ``None`` is
317 If no function/callable is found to compute the format data, ``None`` is
320 returned and this format type is not used.
318 returned and this format type is not used.
321 """
319 """
322
320
323 format_type = Unicode('text/plain')
321 format_type = Unicode('text/plain')
324 _return_type = string_types
322 _return_type = string_types
325
323
326 enabled = Bool(True, config=True)
324 enabled = Bool(True, config=True)
327
325
328 print_method = ObjectName('__repr__')
326 print_method = ObjectName('__repr__')
329
327
330 # The singleton printers.
328 # The singleton printers.
331 # Maps the IDs of the builtin singleton objects to the format functions.
329 # Maps the IDs of the builtin singleton objects to the format functions.
332 singleton_printers = Dict(config=True)
330 singleton_printers = Dict(config=True)
333
331
334 # The type-specific printers.
332 # The type-specific printers.
335 # Map type objects to the format functions.
333 # Map type objects to the format functions.
336 type_printers = Dict(config=True)
334 type_printers = Dict(config=True)
337
335
338 # The deferred-import type-specific printers.
336 # The deferred-import type-specific printers.
339 # Map (modulename, classname) pairs to the format functions.
337 # Map (modulename, classname) pairs to the format functions.
340 deferred_printers = Dict(config=True)
338 deferred_printers = Dict(config=True)
341
339
342 @warn_format_error
340 @warn_format_error
343 def __call__(self, obj):
341 def __call__(self, obj):
344 """Compute the format for an object."""
342 """Compute the format for an object."""
345 if self.enabled:
343 if self.enabled:
346 # lookup registered printer
344 # lookup registered printer
347 try:
345 try:
348 printer = self.lookup(obj)
346 printer = self.lookup(obj)
349 except KeyError:
347 except KeyError:
350 pass
348 pass
351 else:
349 else:
352 return printer(obj)
350 return printer(obj)
353 # Finally look for special method names
351 # Finally look for special method names
354 method = _safe_get_formatter_method(obj, self.print_method)
352 method = _safe_get_formatter_method(obj, self.print_method)
355 if method is not None:
353 if method is not None:
356 return method()
354 return method()
357 return None
355 return None
358 else:
356 else:
359 return None
357 return None
360
358
361 def __contains__(self, typ):
359 def __contains__(self, typ):
362 """map in to lookup_by_type"""
360 """map in to lookup_by_type"""
363 try:
361 try:
364 self.lookup_by_type(typ)
362 self.lookup_by_type(typ)
365 except KeyError:
363 except KeyError:
366 return False
364 return False
367 else:
365 else:
368 return True
366 return True
369
367
370 def lookup(self, obj):
368 def lookup(self, obj):
371 """Look up the formatter for a given instance.
369 """Look up the formatter for a given instance.
372
370
373 Parameters
371 Parameters
374 ----------
372 ----------
375 obj : object instance
373 obj : object instance
376
374
377 Returns
375 Returns
378 -------
376 -------
379 f : callable
377 f : callable
380 The registered formatting callable for the type.
378 The registered formatting callable for the type.
381
379
382 Raises
380 Raises
383 ------
381 ------
384 KeyError if the type has not been registered.
382 KeyError if the type has not been registered.
385 """
383 """
386 # look for singleton first
384 # look for singleton first
387 obj_id = id(obj)
385 obj_id = id(obj)
388 if obj_id in self.singleton_printers:
386 if obj_id in self.singleton_printers:
389 return self.singleton_printers[obj_id]
387 return self.singleton_printers[obj_id]
390 # then lookup by type
388 # then lookup by type
391 return self.lookup_by_type(_get_type(obj))
389 return self.lookup_by_type(_get_type(obj))
392
390
393 def lookup_by_type(self, typ):
391 def lookup_by_type(self, typ):
394 """Look up the registered formatter for a type.
392 """Look up the registered formatter for a type.
395
393
396 Parameters
394 Parameters
397 ----------
395 ----------
398 typ : type or '__module__.__name__' string for a type
396 typ : type or '__module__.__name__' string for a type
399
397
400 Returns
398 Returns
401 -------
399 -------
402 f : callable
400 f : callable
403 The registered formatting callable for the type.
401 The registered formatting callable for the type.
404
402
405 Raises
403 Raises
406 ------
404 ------
407 KeyError if the type has not been registered.
405 KeyError if the type has not been registered.
408 """
406 """
409 if isinstance(typ, string_types):
407 if isinstance(typ, string_types):
410 typ_key = tuple(typ.rsplit('.',1))
408 typ_key = tuple(typ.rsplit('.',1))
411 if typ_key not in self.deferred_printers:
409 if typ_key not in self.deferred_printers:
412 # We may have it cached in the type map. We will have to
410 # We may have it cached in the type map. We will have to
413 # iterate over all of the types to check.
411 # iterate over all of the types to check.
414 for cls in self.type_printers:
412 for cls in self.type_printers:
415 if _mod_name_key(cls) == typ_key:
413 if _mod_name_key(cls) == typ_key:
416 return self.type_printers[cls]
414 return self.type_printers[cls]
417 else:
415 else:
418 return self.deferred_printers[typ_key]
416 return self.deferred_printers[typ_key]
419 else:
417 else:
420 for cls in pretty._get_mro(typ):
418 for cls in pretty._get_mro(typ):
421 if cls in self.type_printers or self._in_deferred_types(cls):
419 if cls in self.type_printers or self._in_deferred_types(cls):
422 return self.type_printers[cls]
420 return self.type_printers[cls]
423
421
424 # If we have reached here, the lookup failed.
422 # If we have reached here, the lookup failed.
425 raise KeyError("No registered printer for {0!r}".format(typ))
423 raise KeyError("No registered printer for {0!r}".format(typ))
426
424
427 def for_type(self, typ, func=None):
425 def for_type(self, typ, func=None):
428 """Add a format function for a given type.
426 """Add a format function for a given type.
429
427
430 Parameters
428 Parameters
431 -----------
429 -----------
432 typ : type or '__module__.__name__' string for a type
430 typ : type or '__module__.__name__' string for a type
433 The class of the object that will be formatted using `func`.
431 The class of the object that will be formatted using `func`.
434 func : callable
432 func : callable
435 A callable for computing the format data.
433 A callable for computing the format data.
436 `func` will be called with the object to be formatted,
434 `func` will be called with the object to be formatted,
437 and will return the raw data in this formatter's format.
435 and will return the raw data in this formatter's format.
438 Subclasses may use a different call signature for the
436 Subclasses may use a different call signature for the
439 `func` argument.
437 `func` argument.
440
438
441 If `func` is None or not specified, there will be no change,
439 If `func` is None or not specified, there will be no change,
442 only returning the current value.
440 only returning the current value.
443
441
444 Returns
442 Returns
445 -------
443 -------
446 oldfunc : callable
444 oldfunc : callable
447 The currently registered callable.
445 The currently registered callable.
448 If you are registering a new formatter,
446 If you are registering a new formatter,
449 this will be the previous value (to enable restoring later).
447 this will be the previous value (to enable restoring later).
450 """
448 """
451 # if string given, interpret as 'pkg.module.class_name'
449 # if string given, interpret as 'pkg.module.class_name'
452 if isinstance(typ, string_types):
450 if isinstance(typ, string_types):
453 type_module, type_name = typ.rsplit('.', 1)
451 type_module, type_name = typ.rsplit('.', 1)
454 return self.for_type_by_name(type_module, type_name, func)
452 return self.for_type_by_name(type_module, type_name, func)
455
453
456 try:
454 try:
457 oldfunc = self.lookup_by_type(typ)
455 oldfunc = self.lookup_by_type(typ)
458 except KeyError:
456 except KeyError:
459 oldfunc = None
457 oldfunc = None
460
458
461 if func is not None:
459 if func is not None:
462 self.type_printers[typ] = func
460 self.type_printers[typ] = func
463
461
464 return oldfunc
462 return oldfunc
465
463
466 def for_type_by_name(self, type_module, type_name, func=None):
464 def for_type_by_name(self, type_module, type_name, func=None):
467 """Add a format function for a type specified by the full dotted
465 """Add a format function for a type specified by the full dotted
468 module and name of the type, rather than the type of the object.
466 module and name of the type, rather than the type of the object.
469
467
470 Parameters
468 Parameters
471 ----------
469 ----------
472 type_module : str
470 type_module : str
473 The full dotted name of the module the type is defined in, like
471 The full dotted name of the module the type is defined in, like
474 ``numpy``.
472 ``numpy``.
475 type_name : str
473 type_name : str
476 The name of the type (the class name), like ``dtype``
474 The name of the type (the class name), like ``dtype``
477 func : callable
475 func : callable
478 A callable for computing the format data.
476 A callable for computing the format data.
479 `func` will be called with the object to be formatted,
477 `func` will be called with the object to be formatted,
480 and will return the raw data in this formatter's format.
478 and will return the raw data in this formatter's format.
481 Subclasses may use a different call signature for the
479 Subclasses may use a different call signature for the
482 `func` argument.
480 `func` argument.
483
481
484 If `func` is None or unspecified, there will be no change,
482 If `func` is None or unspecified, there will be no change,
485 only returning the current value.
483 only returning the current value.
486
484
487 Returns
485 Returns
488 -------
486 -------
489 oldfunc : callable
487 oldfunc : callable
490 The currently registered callable.
488 The currently registered callable.
491 If you are registering a new formatter,
489 If you are registering a new formatter,
492 this will be the previous value (to enable restoring later).
490 this will be the previous value (to enable restoring later).
493 """
491 """
494 key = (type_module, type_name)
492 key = (type_module, type_name)
495
493
496 try:
494 try:
497 oldfunc = self.lookup_by_type("%s.%s" % key)
495 oldfunc = self.lookup_by_type("%s.%s" % key)
498 except KeyError:
496 except KeyError:
499 oldfunc = None
497 oldfunc = None
500
498
501 if func is not None:
499 if func is not None:
502 self.deferred_printers[key] = func
500 self.deferred_printers[key] = func
503 return oldfunc
501 return oldfunc
504
502
505 def pop(self, typ, default=_raise_key_error):
503 def pop(self, typ, default=_raise_key_error):
506 """Pop a formatter for the given type.
504 """Pop a formatter for the given type.
507
505
508 Parameters
506 Parameters
509 ----------
507 ----------
510 typ : type or '__module__.__name__' string for a type
508 typ : type or '__module__.__name__' string for a type
511 default : object
509 default : object
512 value to be returned if no formatter is registered for typ.
510 value to be returned if no formatter is registered for typ.
513
511
514 Returns
512 Returns
515 -------
513 -------
516 obj : object
514 obj : object
517 The last registered object for the type.
515 The last registered object for the type.
518
516
519 Raises
517 Raises
520 ------
518 ------
521 KeyError if the type is not registered and default is not specified.
519 KeyError if the type is not registered and default is not specified.
522 """
520 """
523
521
524 if isinstance(typ, string_types):
522 if isinstance(typ, string_types):
525 typ_key = tuple(typ.rsplit('.',1))
523 typ_key = tuple(typ.rsplit('.',1))
526 if typ_key not in self.deferred_printers:
524 if typ_key not in self.deferred_printers:
527 # We may have it cached in the type map. We will have to
525 # We may have it cached in the type map. We will have to
528 # iterate over all of the types to check.
526 # iterate over all of the types to check.
529 for cls in self.type_printers:
527 for cls in self.type_printers:
530 if _mod_name_key(cls) == typ_key:
528 if _mod_name_key(cls) == typ_key:
531 old = self.type_printers.pop(cls)
529 old = self.type_printers.pop(cls)
532 break
530 break
533 else:
531 else:
534 old = default
532 old = default
535 else:
533 else:
536 old = self.deferred_printers.pop(typ_key)
534 old = self.deferred_printers.pop(typ_key)
537 else:
535 else:
538 if typ in self.type_printers:
536 if typ in self.type_printers:
539 old = self.type_printers.pop(typ)
537 old = self.type_printers.pop(typ)
540 else:
538 else:
541 old = self.deferred_printers.pop(_mod_name_key(typ), default)
539 old = self.deferred_printers.pop(_mod_name_key(typ), default)
542 if old is _raise_key_error:
540 if old is _raise_key_error:
543 raise KeyError("No registered value for {0!r}".format(typ))
541 raise KeyError("No registered value for {0!r}".format(typ))
544 return old
542 return old
545
543
546 def _in_deferred_types(self, cls):
544 def _in_deferred_types(self, cls):
547 """
545 """
548 Check if the given class is specified in the deferred type registry.
546 Check if the given class is specified in the deferred type registry.
549
547
550 Successful matches will be moved to the regular type registry for future use.
548 Successful matches will be moved to the regular type registry for future use.
551 """
549 """
552 mod = getattr(cls, '__module__', None)
550 mod = getattr(cls, '__module__', None)
553 name = getattr(cls, '__name__', None)
551 name = getattr(cls, '__name__', None)
554 key = (mod, name)
552 key = (mod, name)
555 if key in self.deferred_printers:
553 if key in self.deferred_printers:
556 # Move the printer over to the regular registry.
554 # Move the printer over to the regular registry.
557 printer = self.deferred_printers.pop(key)
555 printer = self.deferred_printers.pop(key)
558 self.type_printers[cls] = printer
556 self.type_printers[cls] = printer
559 return True
557 return True
560 return False
558 return False
561
559
562
560
563 class PlainTextFormatter(BaseFormatter):
561 class PlainTextFormatter(BaseFormatter):
564 """The default pretty-printer.
562 """The default pretty-printer.
565
563
566 This uses :mod:`IPython.lib.pretty` to compute the format data of
564 This uses :mod:`IPython.lib.pretty` to compute the format data of
567 the object. If the object cannot be pretty printed, :func:`repr` is used.
565 the object. If the object cannot be pretty printed, :func:`repr` is used.
568 See the documentation of :mod:`IPython.lib.pretty` for details on
566 See the documentation of :mod:`IPython.lib.pretty` for details on
569 how to write pretty printers. Here is a simple example::
567 how to write pretty printers. Here is a simple example::
570
568
571 def dtype_pprinter(obj, p, cycle):
569 def dtype_pprinter(obj, p, cycle):
572 if cycle:
570 if cycle:
573 return p.text('dtype(...)')
571 return p.text('dtype(...)')
574 if hasattr(obj, 'fields'):
572 if hasattr(obj, 'fields'):
575 if obj.fields is None:
573 if obj.fields is None:
576 p.text(repr(obj))
574 p.text(repr(obj))
577 else:
575 else:
578 p.begin_group(7, 'dtype([')
576 p.begin_group(7, 'dtype([')
579 for i, field in enumerate(obj.descr):
577 for i, field in enumerate(obj.descr):
580 if i > 0:
578 if i > 0:
581 p.text(',')
579 p.text(',')
582 p.breakable()
580 p.breakable()
583 p.pretty(field)
581 p.pretty(field)
584 p.end_group(7, '])')
582 p.end_group(7, '])')
585 """
583 """
586
584
587 # The format type of data returned.
585 # The format type of data returned.
588 format_type = Unicode('text/plain')
586 format_type = Unicode('text/plain')
589
587
590 # This subclass ignores this attribute as it always need to return
588 # This subclass ignores this attribute as it always need to return
591 # something.
589 # something.
592 enabled = Bool(True, config=False)
590 enabled = Bool(True, config=False)
593
591
594 # Look for a _repr_pretty_ methods to use for pretty printing.
592 # Look for a _repr_pretty_ methods to use for pretty printing.
595 print_method = ObjectName('_repr_pretty_')
593 print_method = ObjectName('_repr_pretty_')
596
594
597 # Whether to pretty-print or not.
595 # Whether to pretty-print or not.
598 pprint = Bool(True, config=True)
596 pprint = Bool(True, config=True)
599
597
600 # Whether to be verbose or not.
598 # Whether to be verbose or not.
601 verbose = Bool(False, config=True)
599 verbose = Bool(False, config=True)
602
600
603 # The maximum width.
601 # The maximum width.
604 max_width = Integer(79, config=True)
602 max_width = Integer(79, config=True)
605
603
606 # The newline character.
604 # The newline character.
607 newline = Unicode('\n', config=True)
605 newline = Unicode('\n', config=True)
608
606
609 # format-string for pprinting floats
607 # format-string for pprinting floats
610 float_format = Unicode('%r')
608 float_format = Unicode('%r')
611 # setter for float precision, either int or direct format-string
609 # setter for float precision, either int or direct format-string
612 float_precision = CUnicode('', config=True)
610 float_precision = CUnicode('', config=True)
613
611
614 def _float_precision_changed(self, name, old, new):
612 def _float_precision_changed(self, name, old, new):
615 """float_precision changed, set float_format accordingly.
613 """float_precision changed, set float_format accordingly.
616
614
617 float_precision can be set by int or str.
615 float_precision can be set by int or str.
618 This will set float_format, after interpreting input.
616 This will set float_format, after interpreting input.
619 If numpy has been imported, numpy print precision will also be set.
617 If numpy has been imported, numpy print precision will also be set.
620
618
621 integer `n` sets format to '%.nf', otherwise, format set directly.
619 integer `n` sets format to '%.nf', otherwise, format set directly.
622
620
623 An empty string returns to defaults (repr for float, 8 for numpy).
621 An empty string returns to defaults (repr for float, 8 for numpy).
624
622
625 This parameter can be set via the '%precision' magic.
623 This parameter can be set via the '%precision' magic.
626 """
624 """
627
625
628 if '%' in new:
626 if '%' in new:
629 # got explicit format string
627 # got explicit format string
630 fmt = new
628 fmt = new
631 try:
629 try:
632 fmt%3.14159
630 fmt%3.14159
633 except Exception:
631 except Exception:
634 raise ValueError("Precision must be int or format string, not %r"%new)
632 raise ValueError("Precision must be int or format string, not %r"%new)
635 elif new:
633 elif new:
636 # otherwise, should be an int
634 # otherwise, should be an int
637 try:
635 try:
638 i = int(new)
636 i = int(new)
639 assert i >= 0
637 assert i >= 0
640 except ValueError:
638 except ValueError:
641 raise ValueError("Precision must be int or format string, not %r"%new)
639 raise ValueError("Precision must be int or format string, not %r"%new)
642 except AssertionError:
640 except AssertionError:
643 raise ValueError("int precision must be non-negative, not %r"%i)
641 raise ValueError("int precision must be non-negative, not %r"%i)
644
642
645 fmt = '%%.%if'%i
643 fmt = '%%.%if'%i
646 if 'numpy' in sys.modules:
644 if 'numpy' in sys.modules:
647 # set numpy precision if it has been imported
645 # set numpy precision if it has been imported
648 import numpy
646 import numpy
649 numpy.set_printoptions(precision=i)
647 numpy.set_printoptions(precision=i)
650 else:
648 else:
651 # default back to repr
649 # default back to repr
652 fmt = '%r'
650 fmt = '%r'
653 if 'numpy' in sys.modules:
651 if 'numpy' in sys.modules:
654 import numpy
652 import numpy
655 # numpy default is 8
653 # numpy default is 8
656 numpy.set_printoptions(precision=8)
654 numpy.set_printoptions(precision=8)
657 self.float_format = fmt
655 self.float_format = fmt
658
656
659 # Use the default pretty printers from IPython.lib.pretty.
657 # Use the default pretty printers from IPython.lib.pretty.
660 def _singleton_printers_default(self):
658 def _singleton_printers_default(self):
661 return pretty._singleton_pprinters.copy()
659 return pretty._singleton_pprinters.copy()
662
660
663 def _type_printers_default(self):
661 def _type_printers_default(self):
664 d = pretty._type_pprinters.copy()
662 d = pretty._type_pprinters.copy()
665 d[float] = lambda obj,p,cycle: p.text(self.float_format%obj)
663 d[float] = lambda obj,p,cycle: p.text(self.float_format%obj)
666 return d
664 return d
667
665
668 def _deferred_printers_default(self):
666 def _deferred_printers_default(self):
669 return pretty._deferred_type_pprinters.copy()
667 return pretty._deferred_type_pprinters.copy()
670
668
671 #### FormatterABC interface ####
669 #### FormatterABC interface ####
672
670
673 @warn_format_error
671 @warn_format_error
674 def __call__(self, obj):
672 def __call__(self, obj):
675 """Compute the pretty representation of the object."""
673 """Compute the pretty representation of the object."""
676 if not self.pprint:
674 if not self.pprint:
677 return pretty._safe_repr(obj)
675 return pretty._safe_repr(obj)
678 else:
676 else:
679 # This uses use StringIO, as cStringIO doesn't handle unicode.
677 # This uses use StringIO, as cStringIO doesn't handle unicode.
680 stream = StringIO()
678 stream = StringIO()
681 # self.newline.encode() is a quick fix for issue gh-597. We need to
679 # self.newline.encode() is a quick fix for issue gh-597. We need to
682 # ensure that stream does not get a mix of unicode and bytestrings,
680 # ensure that stream does not get a mix of unicode and bytestrings,
683 # or it will cause trouble.
681 # or it will cause trouble.
684 printer = pretty.RepresentationPrinter(stream, self.verbose,
682 printer = pretty.RepresentationPrinter(stream, self.verbose,
685 self.max_width, unicode_to_str(self.newline),
683 self.max_width, unicode_to_str(self.newline),
686 singleton_pprinters=self.singleton_printers,
684 singleton_pprinters=self.singleton_printers,
687 type_pprinters=self.type_printers,
685 type_pprinters=self.type_printers,
688 deferred_pprinters=self.deferred_printers)
686 deferred_pprinters=self.deferred_printers)
689 printer.pretty(obj)
687 printer.pretty(obj)
690 printer.flush()
688 printer.flush()
691 return stream.getvalue()
689 return stream.getvalue()
692
690
693
691
694 class HTMLFormatter(BaseFormatter):
692 class HTMLFormatter(BaseFormatter):
695 """An HTML formatter.
693 """An HTML formatter.
696
694
697 To define the callables that compute the HTML representation of your
695 To define the callables that compute the HTML representation of your
698 objects, define a :meth:`_repr_html_` method or use the :meth:`for_type`
696 objects, define a :meth:`_repr_html_` method or use the :meth:`for_type`
699 or :meth:`for_type_by_name` methods to register functions that handle
697 or :meth:`for_type_by_name` methods to register functions that handle
700 this.
698 this.
701
699
702 The return value of this formatter should be a valid HTML snippet that
700 The return value of this formatter should be a valid HTML snippet that
703 could be injected into an existing DOM. It should *not* include the
701 could be injected into an existing DOM. It should *not* include the
704 ```<html>`` or ```<body>`` tags.
702 ```<html>`` or ```<body>`` tags.
705 """
703 """
706 format_type = Unicode('text/html')
704 format_type = Unicode('text/html')
707
705
708 print_method = ObjectName('_repr_html_')
706 print_method = ObjectName('_repr_html_')
709
707
710
708
711 class MarkdownFormatter(BaseFormatter):
709 class MarkdownFormatter(BaseFormatter):
712 """A Markdown formatter.
710 """A Markdown formatter.
713
711
714 To define the callables that compute the Markdown representation of your
712 To define the callables that compute the Markdown representation of your
715 objects, define a :meth:`_repr_markdown_` method or use the :meth:`for_type`
713 objects, define a :meth:`_repr_markdown_` method or use the :meth:`for_type`
716 or :meth:`for_type_by_name` methods to register functions that handle
714 or :meth:`for_type_by_name` methods to register functions that handle
717 this.
715 this.
718
716
719 The return value of this formatter should be a valid Markdown.
717 The return value of this formatter should be a valid Markdown.
720 """
718 """
721 format_type = Unicode('text/markdown')
719 format_type = Unicode('text/markdown')
722
720
723 print_method = ObjectName('_repr_markdown_')
721 print_method = ObjectName('_repr_markdown_')
724
722
725 class SVGFormatter(BaseFormatter):
723 class SVGFormatter(BaseFormatter):
726 """An SVG formatter.
724 """An SVG formatter.
727
725
728 To define the callables that compute the SVG representation of your
726 To define the callables that compute the SVG representation of your
729 objects, define a :meth:`_repr_svg_` method or use the :meth:`for_type`
727 objects, define a :meth:`_repr_svg_` method or use the :meth:`for_type`
730 or :meth:`for_type_by_name` methods to register functions that handle
728 or :meth:`for_type_by_name` methods to register functions that handle
731 this.
729 this.
732
730
733 The return value of this formatter should be valid SVG enclosed in
731 The return value of this formatter should be valid SVG enclosed in
734 ```<svg>``` tags, that could be injected into an existing DOM. It should
732 ```<svg>``` tags, that could be injected into an existing DOM. It should
735 *not* include the ```<html>`` or ```<body>`` tags.
733 *not* include the ```<html>`` or ```<body>`` tags.
736 """
734 """
737 format_type = Unicode('image/svg+xml')
735 format_type = Unicode('image/svg+xml')
738
736
739 print_method = ObjectName('_repr_svg_')
737 print_method = ObjectName('_repr_svg_')
740
738
741
739
742 class PNGFormatter(BaseFormatter):
740 class PNGFormatter(BaseFormatter):
743 """A PNG formatter.
741 """A PNG formatter.
744
742
745 To define the callables that compute the PNG representation of your
743 To define the callables that compute the PNG representation of your
746 objects, define a :meth:`_repr_png_` method or use the :meth:`for_type`
744 objects, define a :meth:`_repr_png_` method or use the :meth:`for_type`
747 or :meth:`for_type_by_name` methods to register functions that handle
745 or :meth:`for_type_by_name` methods to register functions that handle
748 this.
746 this.
749
747
750 The return value of this formatter should be raw PNG data, *not*
748 The return value of this formatter should be raw PNG data, *not*
751 base64 encoded.
749 base64 encoded.
752 """
750 """
753 format_type = Unicode('image/png')
751 format_type = Unicode('image/png')
754
752
755 print_method = ObjectName('_repr_png_')
753 print_method = ObjectName('_repr_png_')
756
754
757 _return_type = (bytes, unicode_type)
755 _return_type = (bytes, unicode_type)
758
756
759
757
760 class JPEGFormatter(BaseFormatter):
758 class JPEGFormatter(BaseFormatter):
761 """A JPEG formatter.
759 """A JPEG formatter.
762
760
763 To define the callables that compute the JPEG representation of your
761 To define the callables that compute the JPEG representation of your
764 objects, define a :meth:`_repr_jpeg_` method or use the :meth:`for_type`
762 objects, define a :meth:`_repr_jpeg_` method or use the :meth:`for_type`
765 or :meth:`for_type_by_name` methods to register functions that handle
763 or :meth:`for_type_by_name` methods to register functions that handle
766 this.
764 this.
767
765
768 The return value of this formatter should be raw JPEG data, *not*
766 The return value of this formatter should be raw JPEG data, *not*
769 base64 encoded.
767 base64 encoded.
770 """
768 """
771 format_type = Unicode('image/jpeg')
769 format_type = Unicode('image/jpeg')
772
770
773 print_method = ObjectName('_repr_jpeg_')
771 print_method = ObjectName('_repr_jpeg_')
774
772
775 _return_type = (bytes, unicode_type)
773 _return_type = (bytes, unicode_type)
776
774
777
775
778 class LatexFormatter(BaseFormatter):
776 class LatexFormatter(BaseFormatter):
779 """A LaTeX formatter.
777 """A LaTeX formatter.
780
778
781 To define the callables that compute the LaTeX representation of your
779 To define the callables that compute the LaTeX representation of your
782 objects, define a :meth:`_repr_latex_` method or use the :meth:`for_type`
780 objects, define a :meth:`_repr_latex_` method or use the :meth:`for_type`
783 or :meth:`for_type_by_name` methods to register functions that handle
781 or :meth:`for_type_by_name` methods to register functions that handle
784 this.
782 this.
785
783
786 The return value of this formatter should be a valid LaTeX equation,
784 The return value of this formatter should be a valid LaTeX equation,
787 enclosed in either ```$```, ```$$``` or another LaTeX equation
785 enclosed in either ```$```, ```$$``` or another LaTeX equation
788 environment.
786 environment.
789 """
787 """
790 format_type = Unicode('text/latex')
788 format_type = Unicode('text/latex')
791
789
792 print_method = ObjectName('_repr_latex_')
790 print_method = ObjectName('_repr_latex_')
793
791
794
792
795 class JSONFormatter(BaseFormatter):
793 class JSONFormatter(BaseFormatter):
796 """A JSON string formatter.
794 """A JSON string formatter.
797
795
798 To define the callables that compute the JSON string representation of
796 To define the callables that compute the JSON string representation of
799 your objects, define a :meth:`_repr_json_` method or use the :meth:`for_type`
797 your objects, define a :meth:`_repr_json_` method or use the :meth:`for_type`
800 or :meth:`for_type_by_name` methods to register functions that handle
798 or :meth:`for_type_by_name` methods to register functions that handle
801 this.
799 this.
802
800
803 The return value of this formatter should be a valid JSON string.
801 The return value of this formatter should be a valid JSON string.
804 """
802 """
805 format_type = Unicode('application/json')
803 format_type = Unicode('application/json')
806
804
807 print_method = ObjectName('_repr_json_')
805 print_method = ObjectName('_repr_json_')
808
806
809
807
810 class JavascriptFormatter(BaseFormatter):
808 class JavascriptFormatter(BaseFormatter):
811 """A Javascript formatter.
809 """A Javascript formatter.
812
810
813 To define the callables that compute the Javascript representation of
811 To define the callables that compute the Javascript representation of
814 your objects, define a :meth:`_repr_javascript_` method or use the
812 your objects, define a :meth:`_repr_javascript_` method or use the
815 :meth:`for_type` or :meth:`for_type_by_name` methods to register functions
813 :meth:`for_type` or :meth:`for_type_by_name` methods to register functions
816 that handle this.
814 that handle this.
817
815
818 The return value of this formatter should be valid Javascript code and
816 The return value of this formatter should be valid Javascript code and
819 should *not* be enclosed in ```<script>``` tags.
817 should *not* be enclosed in ```<script>``` tags.
820 """
818 """
821 format_type = Unicode('application/javascript')
819 format_type = Unicode('application/javascript')
822
820
823 print_method = ObjectName('_repr_javascript_')
821 print_method = ObjectName('_repr_javascript_')
824
822
825
823
826 class PDFFormatter(BaseFormatter):
824 class PDFFormatter(BaseFormatter):
827 """A PDF formatter.
825 """A PDF formatter.
828
826
829 To define the callables that compute the PDF representation of your
827 To define the callables that compute the PDF representation of your
830 objects, define a :meth:`_repr_pdf_` method or use the :meth:`for_type`
828 objects, define a :meth:`_repr_pdf_` method or use the :meth:`for_type`
831 or :meth:`for_type_by_name` methods to register functions that handle
829 or :meth:`for_type_by_name` methods to register functions that handle
832 this.
830 this.
833
831
834 The return value of this formatter should be raw PDF data, *not*
832 The return value of this formatter should be raw PDF data, *not*
835 base64 encoded.
833 base64 encoded.
836 """
834 """
837 format_type = Unicode('application/pdf')
835 format_type = Unicode('application/pdf')
838
836
839 print_method = ObjectName('_repr_pdf_')
837 print_method = ObjectName('_repr_pdf_')
840
838
841
839
842 FormatterABC.register(BaseFormatter)
840 FormatterABC.register(BaseFormatter)
843 FormatterABC.register(PlainTextFormatter)
841 FormatterABC.register(PlainTextFormatter)
844 FormatterABC.register(HTMLFormatter)
842 FormatterABC.register(HTMLFormatter)
845 FormatterABC.register(MarkdownFormatter)
843 FormatterABC.register(MarkdownFormatter)
846 FormatterABC.register(SVGFormatter)
844 FormatterABC.register(SVGFormatter)
847 FormatterABC.register(PNGFormatter)
845 FormatterABC.register(PNGFormatter)
848 FormatterABC.register(PDFFormatter)
846 FormatterABC.register(PDFFormatter)
849 FormatterABC.register(JPEGFormatter)
847 FormatterABC.register(JPEGFormatter)
850 FormatterABC.register(LatexFormatter)
848 FormatterABC.register(LatexFormatter)
851 FormatterABC.register(JSONFormatter)
849 FormatterABC.register(JSONFormatter)
852 FormatterABC.register(JavascriptFormatter)
850 FormatterABC.register(JavascriptFormatter)
853
851
854
852
855 def format_display_data(obj, include=None, exclude=None):
853 def format_display_data(obj, include=None, exclude=None):
856 """Return a format data dict for an object.
854 """Return a format data dict for an object.
857
855
858 By default all format types will be computed.
856 By default all format types will be computed.
859
857
860 The following MIME types are currently implemented:
858 The following MIME types are currently implemented:
861
859
862 * text/plain
860 * text/plain
863 * text/html
861 * text/html
864 * text/markdown
862 * text/markdown
865 * text/latex
863 * text/latex
866 * application/json
864 * application/json
867 * application/javascript
865 * application/javascript
868 * application/pdf
866 * application/pdf
869 * image/png
867 * image/png
870 * image/jpeg
868 * image/jpeg
871 * image/svg+xml
869 * image/svg+xml
872
870
873 Parameters
871 Parameters
874 ----------
872 ----------
875 obj : object
873 obj : object
876 The Python object whose format data will be computed.
874 The Python object whose format data will be computed.
877
875
878 Returns
876 Returns
879 -------
877 -------
880 format_dict : dict
878 format_dict : dict
881 A dictionary of key/value pairs, one or each format that was
879 A dictionary of key/value pairs, one or each format that was
882 generated for the object. The keys are the format types, which
880 generated for the object. The keys are the format types, which
883 will usually be MIME type strings and the values and JSON'able
881 will usually be MIME type strings and the values and JSON'able
884 data structure containing the raw data for the representation in
882 data structure containing the raw data for the representation in
885 that format.
883 that format.
886 include : list or tuple, optional
884 include : list or tuple, optional
887 A list of format type strings (MIME types) to include in the
885 A list of format type strings (MIME types) to include in the
888 format data dict. If this is set *only* the format types included
886 format data dict. If this is set *only* the format types included
889 in this list will be computed.
887 in this list will be computed.
890 exclude : list or tuple, optional
888 exclude : list or tuple, optional
891 A list of format type string (MIME types) to exclue in the format
889 A list of format type string (MIME types) to exclue in the format
892 data dict. If this is set all format types will be computed,
890 data dict. If this is set all format types will be computed,
893 except for those included in this argument.
891 except for those included in this argument.
894 """
892 """
895 from IPython.core.interactiveshell import InteractiveShell
893 from IPython.core.interactiveshell import InteractiveShell
896
894
897 InteractiveShell.instance().display_formatter.format(
895 InteractiveShell.instance().display_formatter.format(
898 obj,
896 obj,
899 include,
897 include,
900 exclude
898 exclude
901 )
899 )
902
900
@@ -1,250 +1,249 b''
1 # encoding: utf-8
1 # encoding: utf-8
2 """An object for managing IPython profile directories."""
2 """An object for managing IPython profile directories."""
3
3
4 # Copyright (c) IPython Development Team.
4 # Copyright (c) IPython Development Team.
5 # Distributed under the terms of the Modified BSD License.
5 # Distributed under the terms of the Modified BSD License.
6
6
7 import os
7 import os
8 import shutil
8 import shutil
9 import errno
9 import errno
10 import time
11
10
12 from IPython.config.configurable import LoggingConfigurable
11 from IPython.config.configurable import LoggingConfigurable
13 from IPython.utils.path import get_ipython_package_dir, expand_path, ensure_dir_exists
12 from IPython.utils.path import get_ipython_package_dir, expand_path, ensure_dir_exists
14 from IPython.utils import py3compat
13 from IPython.utils import py3compat
15 from IPython.utils.traitlets import Unicode, Bool
14 from IPython.utils.traitlets import Unicode, Bool
16
15
17 #-----------------------------------------------------------------------------
16 #-----------------------------------------------------------------------------
18 # Module errors
17 # Module errors
19 #-----------------------------------------------------------------------------
18 #-----------------------------------------------------------------------------
20
19
21 class ProfileDirError(Exception):
20 class ProfileDirError(Exception):
22 pass
21 pass
23
22
24
23
25 #-----------------------------------------------------------------------------
24 #-----------------------------------------------------------------------------
26 # Class for managing profile directories
25 # Class for managing profile directories
27 #-----------------------------------------------------------------------------
26 #-----------------------------------------------------------------------------
28
27
29 class ProfileDir(LoggingConfigurable):
28 class ProfileDir(LoggingConfigurable):
30 """An object to manage the profile directory and its resources.
29 """An object to manage the profile directory and its resources.
31
30
32 The profile directory is used by all IPython applications, to manage
31 The profile directory is used by all IPython applications, to manage
33 configuration, logging and security.
32 configuration, logging and security.
34
33
35 This object knows how to find, create and manage these directories. This
34 This object knows how to find, create and manage these directories. This
36 should be used by any code that wants to handle profiles.
35 should be used by any code that wants to handle profiles.
37 """
36 """
38
37
39 security_dir_name = Unicode('security')
38 security_dir_name = Unicode('security')
40 log_dir_name = Unicode('log')
39 log_dir_name = Unicode('log')
41 startup_dir_name = Unicode('startup')
40 startup_dir_name = Unicode('startup')
42 pid_dir_name = Unicode('pid')
41 pid_dir_name = Unicode('pid')
43 static_dir_name = Unicode('static')
42 static_dir_name = Unicode('static')
44 security_dir = Unicode(u'')
43 security_dir = Unicode(u'')
45 log_dir = Unicode(u'')
44 log_dir = Unicode(u'')
46 startup_dir = Unicode(u'')
45 startup_dir = Unicode(u'')
47 pid_dir = Unicode(u'')
46 pid_dir = Unicode(u'')
48 static_dir = Unicode(u'')
47 static_dir = Unicode(u'')
49
48
50 location = Unicode(u'', config=True,
49 location = Unicode(u'', config=True,
51 help="""Set the profile location directly. This overrides the logic used by the
50 help="""Set the profile location directly. This overrides the logic used by the
52 `profile` option.""",
51 `profile` option.""",
53 )
52 )
54
53
55 _location_isset = Bool(False) # flag for detecting multiply set location
54 _location_isset = Bool(False) # flag for detecting multiply set location
56
55
57 def _location_changed(self, name, old, new):
56 def _location_changed(self, name, old, new):
58 if self._location_isset:
57 if self._location_isset:
59 raise RuntimeError("Cannot set profile location more than once.")
58 raise RuntimeError("Cannot set profile location more than once.")
60 self._location_isset = True
59 self._location_isset = True
61 ensure_dir_exists(new)
60 ensure_dir_exists(new)
62
61
63 # ensure config files exist:
62 # ensure config files exist:
64 self.security_dir = os.path.join(new, self.security_dir_name)
63 self.security_dir = os.path.join(new, self.security_dir_name)
65 self.log_dir = os.path.join(new, self.log_dir_name)
64 self.log_dir = os.path.join(new, self.log_dir_name)
66 self.startup_dir = os.path.join(new, self.startup_dir_name)
65 self.startup_dir = os.path.join(new, self.startup_dir_name)
67 self.pid_dir = os.path.join(new, self.pid_dir_name)
66 self.pid_dir = os.path.join(new, self.pid_dir_name)
68 self.static_dir = os.path.join(new, self.static_dir_name)
67 self.static_dir = os.path.join(new, self.static_dir_name)
69 self.check_dirs()
68 self.check_dirs()
70
69
71 def _log_dir_changed(self, name, old, new):
70 def _log_dir_changed(self, name, old, new):
72 self.check_log_dir()
71 self.check_log_dir()
73
72
74 def _mkdir(self, path, mode=None):
73 def _mkdir(self, path, mode=None):
75 """ensure a directory exists at a given path
74 """ensure a directory exists at a given path
76
75
77 This is a version of os.mkdir, with the following differences:
76 This is a version of os.mkdir, with the following differences:
78
77
79 - returns True if it created the directory, False otherwise
78 - returns True if it created the directory, False otherwise
80 - ignores EEXIST, protecting against race conditions where
79 - ignores EEXIST, protecting against race conditions where
81 the dir may have been created in between the check and
80 the dir may have been created in between the check and
82 the creation
81 the creation
83 - sets permissions if requested and the dir already exists
82 - sets permissions if requested and the dir already exists
84 """
83 """
85 if os.path.exists(path):
84 if os.path.exists(path):
86 if mode and os.stat(path).st_mode != mode:
85 if mode and os.stat(path).st_mode != mode:
87 try:
86 try:
88 os.chmod(path, mode)
87 os.chmod(path, mode)
89 except OSError:
88 except OSError:
90 self.log.warn(
89 self.log.warn(
91 "Could not set permissions on %s",
90 "Could not set permissions on %s",
92 path
91 path
93 )
92 )
94 return False
93 return False
95 try:
94 try:
96 if mode:
95 if mode:
97 os.mkdir(path, mode)
96 os.mkdir(path, mode)
98 else:
97 else:
99 os.mkdir(path)
98 os.mkdir(path)
100 except OSError as e:
99 except OSError as e:
101 if e.errno == errno.EEXIST:
100 if e.errno == errno.EEXIST:
102 return False
101 return False
103 else:
102 else:
104 raise
103 raise
105
104
106 return True
105 return True
107
106
108 def check_log_dir(self):
107 def check_log_dir(self):
109 self._mkdir(self.log_dir)
108 self._mkdir(self.log_dir)
110
109
111 def _startup_dir_changed(self, name, old, new):
110 def _startup_dir_changed(self, name, old, new):
112 self.check_startup_dir()
111 self.check_startup_dir()
113
112
114 def check_startup_dir(self):
113 def check_startup_dir(self):
115 self._mkdir(self.startup_dir)
114 self._mkdir(self.startup_dir)
116
115
117 readme = os.path.join(self.startup_dir, 'README')
116 readme = os.path.join(self.startup_dir, 'README')
118 src = os.path.join(get_ipython_package_dir(), u'config', u'profile', u'README_STARTUP')
117 src = os.path.join(get_ipython_package_dir(), u'config', u'profile', u'README_STARTUP')
119
118
120 if not os.path.exists(src):
119 if not os.path.exists(src):
121 self.log.warn("Could not copy README_STARTUP to startup dir. Source file %s does not exist.", src)
120 self.log.warn("Could not copy README_STARTUP to startup dir. Source file %s does not exist.", src)
122
121
123 if os.path.exists(src) and not os.path.exists(readme):
122 if os.path.exists(src) and not os.path.exists(readme):
124 shutil.copy(src, readme)
123 shutil.copy(src, readme)
125
124
126 def _security_dir_changed(self, name, old, new):
125 def _security_dir_changed(self, name, old, new):
127 self.check_security_dir()
126 self.check_security_dir()
128
127
129 def check_security_dir(self):
128 def check_security_dir(self):
130 self._mkdir(self.security_dir, 0o40700)
129 self._mkdir(self.security_dir, 0o40700)
131
130
132 def _pid_dir_changed(self, name, old, new):
131 def _pid_dir_changed(self, name, old, new):
133 self.check_pid_dir()
132 self.check_pid_dir()
134
133
135 def check_pid_dir(self):
134 def check_pid_dir(self):
136 self._mkdir(self.pid_dir, 0o40700)
135 self._mkdir(self.pid_dir, 0o40700)
137
136
138 def _static_dir_changed(self, name, old, new):
137 def _static_dir_changed(self, name, old, new):
139 self.check_startup_dir()
138 self.check_startup_dir()
140
139
141 def check_static_dir(self):
140 def check_static_dir(self):
142 self._mkdir(self.static_dir)
141 self._mkdir(self.static_dir)
143 custom = os.path.join(self.static_dir, 'custom')
142 custom = os.path.join(self.static_dir, 'custom')
144 self._mkdir(custom)
143 self._mkdir(custom)
145 from IPython.html import DEFAULT_STATIC_FILES_PATH
144 from IPython.html import DEFAULT_STATIC_FILES_PATH
146 for fname in ('custom.js', 'custom.css'):
145 for fname in ('custom.js', 'custom.css'):
147 src = os.path.join(DEFAULT_STATIC_FILES_PATH, 'custom', fname)
146 src = os.path.join(DEFAULT_STATIC_FILES_PATH, 'custom', fname)
148 dest = os.path.join(custom, fname)
147 dest = os.path.join(custom, fname)
149 if not os.path.exists(src):
148 if not os.path.exists(src):
150 self.log.warn("Could not copy default file to static dir. Source file %s does not exist.", src)
149 self.log.warn("Could not copy default file to static dir. Source file %s does not exist.", src)
151 continue
150 continue
152 if not os.path.exists(dest):
151 if not os.path.exists(dest):
153 shutil.copy(src, dest)
152 shutil.copy(src, dest)
154
153
155 def check_dirs(self):
154 def check_dirs(self):
156 self.check_security_dir()
155 self.check_security_dir()
157 self.check_log_dir()
156 self.check_log_dir()
158 self.check_pid_dir()
157 self.check_pid_dir()
159 self.check_startup_dir()
158 self.check_startup_dir()
160 self.check_static_dir()
159 self.check_static_dir()
161
160
162 def copy_config_file(self, config_file, path=None, overwrite=False):
161 def copy_config_file(self, config_file, path=None, overwrite=False):
163 """Copy a default config file into the active profile directory.
162 """Copy a default config file into the active profile directory.
164
163
165 Default configuration files are kept in :mod:`IPython.config.default`.
164 Default configuration files are kept in :mod:`IPython.config.default`.
166 This function moves these from that location to the working profile
165 This function moves these from that location to the working profile
167 directory.
166 directory.
168 """
167 """
169 dst = os.path.join(self.location, config_file)
168 dst = os.path.join(self.location, config_file)
170 if os.path.isfile(dst) and not overwrite:
169 if os.path.isfile(dst) and not overwrite:
171 return False
170 return False
172 if path is None:
171 if path is None:
173 path = os.path.join(get_ipython_package_dir(), u'config', u'profile', u'default')
172 path = os.path.join(get_ipython_package_dir(), u'config', u'profile', u'default')
174 src = os.path.join(path, config_file)
173 src = os.path.join(path, config_file)
175 shutil.copy(src, dst)
174 shutil.copy(src, dst)
176 return True
175 return True
177
176
178 @classmethod
177 @classmethod
179 def create_profile_dir(cls, profile_dir, config=None):
178 def create_profile_dir(cls, profile_dir, config=None):
180 """Create a new profile directory given a full path.
179 """Create a new profile directory given a full path.
181
180
182 Parameters
181 Parameters
183 ----------
182 ----------
184 profile_dir : str
183 profile_dir : str
185 The full path to the profile directory. If it does exist, it will
184 The full path to the profile directory. If it does exist, it will
186 be used. If not, it will be created.
185 be used. If not, it will be created.
187 """
186 """
188 return cls(location=profile_dir, config=config)
187 return cls(location=profile_dir, config=config)
189
188
190 @classmethod
189 @classmethod
191 def create_profile_dir_by_name(cls, path, name=u'default', config=None):
190 def create_profile_dir_by_name(cls, path, name=u'default', config=None):
192 """Create a profile dir by profile name and path.
191 """Create a profile dir by profile name and path.
193
192
194 Parameters
193 Parameters
195 ----------
194 ----------
196 path : unicode
195 path : unicode
197 The path (directory) to put the profile directory in.
196 The path (directory) to put the profile directory in.
198 name : unicode
197 name : unicode
199 The name of the profile. The name of the profile directory will
198 The name of the profile. The name of the profile directory will
200 be "profile_<profile>".
199 be "profile_<profile>".
201 """
200 """
202 if not os.path.isdir(path):
201 if not os.path.isdir(path):
203 raise ProfileDirError('Directory not found: %s' % path)
202 raise ProfileDirError('Directory not found: %s' % path)
204 profile_dir = os.path.join(path, u'profile_' + name)
203 profile_dir = os.path.join(path, u'profile_' + name)
205 return cls(location=profile_dir, config=config)
204 return cls(location=profile_dir, config=config)
206
205
207 @classmethod
206 @classmethod
208 def find_profile_dir_by_name(cls, ipython_dir, name=u'default', config=None):
207 def find_profile_dir_by_name(cls, ipython_dir, name=u'default', config=None):
209 """Find an existing profile dir by profile name, return its ProfileDir.
208 """Find an existing profile dir by profile name, return its ProfileDir.
210
209
211 This searches through a sequence of paths for a profile dir. If it
210 This searches through a sequence of paths for a profile dir. If it
212 is not found, a :class:`ProfileDirError` exception will be raised.
211 is not found, a :class:`ProfileDirError` exception will be raised.
213
212
214 The search path algorithm is:
213 The search path algorithm is:
215 1. ``py3compat.getcwd()``
214 1. ``py3compat.getcwd()``
216 2. ``ipython_dir``
215 2. ``ipython_dir``
217
216
218 Parameters
217 Parameters
219 ----------
218 ----------
220 ipython_dir : unicode or str
219 ipython_dir : unicode or str
221 The IPython directory to use.
220 The IPython directory to use.
222 name : unicode or str
221 name : unicode or str
223 The name of the profile. The name of the profile directory
222 The name of the profile. The name of the profile directory
224 will be "profile_<profile>".
223 will be "profile_<profile>".
225 """
224 """
226 dirname = u'profile_' + name
225 dirname = u'profile_' + name
227 paths = [py3compat.getcwd(), ipython_dir]
226 paths = [py3compat.getcwd(), ipython_dir]
228 for p in paths:
227 for p in paths:
229 profile_dir = os.path.join(p, dirname)
228 profile_dir = os.path.join(p, dirname)
230 if os.path.isdir(profile_dir):
229 if os.path.isdir(profile_dir):
231 return cls(location=profile_dir, config=config)
230 return cls(location=profile_dir, config=config)
232 else:
231 else:
233 raise ProfileDirError('Profile directory not found in paths: %s' % dirname)
232 raise ProfileDirError('Profile directory not found in paths: %s' % dirname)
234
233
235 @classmethod
234 @classmethod
236 def find_profile_dir(cls, profile_dir, config=None):
235 def find_profile_dir(cls, profile_dir, config=None):
237 """Find/create a profile dir and return its ProfileDir.
236 """Find/create a profile dir and return its ProfileDir.
238
237
239 This will create the profile directory if it doesn't exist.
238 This will create the profile directory if it doesn't exist.
240
239
241 Parameters
240 Parameters
242 ----------
241 ----------
243 profile_dir : unicode or str
242 profile_dir : unicode or str
244 The path of the profile directory. This is expanded using
243 The path of the profile directory. This is expanded using
245 :func:`IPython.utils.genutils.expand_path`.
244 :func:`IPython.utils.genutils.expand_path`.
246 """
245 """
247 profile_dir = expand_path(profile_dir)
246 profile_dir = expand_path(profile_dir)
248 if not os.path.isdir(profile_dir):
247 if not os.path.isdir(profile_dir):
249 raise ProfileDirError('Profile directory not found: %s' % profile_dir)
248 raise ProfileDirError('Profile directory not found: %s' % profile_dir)
250 return cls(location=profile_dir, config=config)
249 return cls(location=profile_dir, config=config)
@@ -1,382 +1,381 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 """Pylab (matplotlib) support utilities.
2 """Pylab (matplotlib) support utilities.
3
3
4 Authors
4 Authors
5 -------
5 -------
6
6
7 * Fernando Perez.
7 * Fernando Perez.
8 * Brian Granger
8 * Brian Granger
9 """
9 """
10 from __future__ import print_function
10 from __future__ import print_function
11
11
12 #-----------------------------------------------------------------------------
12 #-----------------------------------------------------------------------------
13 # Copyright (C) 2009 The IPython Development Team
13 # Copyright (C) 2009 The IPython Development Team
14 #
14 #
15 # Distributed under the terms of the BSD License. The full license is in
15 # Distributed under the terms of the BSD License. The full license is in
16 # the file COPYING, distributed as part of this software.
16 # the file COPYING, distributed as part of this software.
17 #-----------------------------------------------------------------------------
17 #-----------------------------------------------------------------------------
18
18
19 #-----------------------------------------------------------------------------
19 #-----------------------------------------------------------------------------
20 # Imports
20 # Imports
21 #-----------------------------------------------------------------------------
21 #-----------------------------------------------------------------------------
22
22
23 import sys
24 from io import BytesIO
23 from io import BytesIO
25
24
26 from IPython.core.display import _pngxy
25 from IPython.core.display import _pngxy
27 from IPython.utils.decorators import flag_calls
26 from IPython.utils.decorators import flag_calls
28 from IPython.utils import py3compat
27 from IPython.utils import py3compat
29
28
30 # If user specifies a GUI, that dictates the backend, otherwise we read the
29 # If user specifies a GUI, that dictates the backend, otherwise we read the
31 # user's mpl default from the mpl rc structure
30 # user's mpl default from the mpl rc structure
32 backends = {'tk': 'TkAgg',
31 backends = {'tk': 'TkAgg',
33 'gtk': 'GTKAgg',
32 'gtk': 'GTKAgg',
34 'gtk3': 'GTK3Agg',
33 'gtk3': 'GTK3Agg',
35 'wx': 'WXAgg',
34 'wx': 'WXAgg',
36 'qt': 'Qt4Agg', # qt3 not supported
35 'qt': 'Qt4Agg', # qt3 not supported
37 'qt4': 'Qt4Agg',
36 'qt4': 'Qt4Agg',
38 'osx': 'MacOSX',
37 'osx': 'MacOSX',
39 'inline' : 'module://IPython.kernel.zmq.pylab.backend_inline'}
38 'inline' : 'module://IPython.kernel.zmq.pylab.backend_inline'}
40
39
41 # We also need a reverse backends2guis mapping that will properly choose which
40 # We also need a reverse backends2guis mapping that will properly choose which
42 # GUI support to activate based on the desired matplotlib backend. For the
41 # GUI support to activate based on the desired matplotlib backend. For the
43 # most part it's just a reverse of the above dict, but we also need to add a
42 # most part it's just a reverse of the above dict, but we also need to add a
44 # few others that map to the same GUI manually:
43 # few others that map to the same GUI manually:
45 backend2gui = dict(zip(backends.values(), backends.keys()))
44 backend2gui = dict(zip(backends.values(), backends.keys()))
46 # Our tests expect backend2gui to just return 'qt'
45 # Our tests expect backend2gui to just return 'qt'
47 backend2gui['Qt4Agg'] = 'qt'
46 backend2gui['Qt4Agg'] = 'qt'
48 # In the reverse mapping, there are a few extra valid matplotlib backends that
47 # In the reverse mapping, there are a few extra valid matplotlib backends that
49 # map to the same GUI support
48 # map to the same GUI support
50 backend2gui['GTK'] = backend2gui['GTKCairo'] = 'gtk'
49 backend2gui['GTK'] = backend2gui['GTKCairo'] = 'gtk'
51 backend2gui['GTK3Cairo'] = 'gtk3'
50 backend2gui['GTK3Cairo'] = 'gtk3'
52 backend2gui['WX'] = 'wx'
51 backend2gui['WX'] = 'wx'
53 backend2gui['CocoaAgg'] = 'osx'
52 backend2gui['CocoaAgg'] = 'osx'
54
53
55 #-----------------------------------------------------------------------------
54 #-----------------------------------------------------------------------------
56 # Matplotlib utilities
55 # Matplotlib utilities
57 #-----------------------------------------------------------------------------
56 #-----------------------------------------------------------------------------
58
57
59
58
60 def getfigs(*fig_nums):
59 def getfigs(*fig_nums):
61 """Get a list of matplotlib figures by figure numbers.
60 """Get a list of matplotlib figures by figure numbers.
62
61
63 If no arguments are given, all available figures are returned. If the
62 If no arguments are given, all available figures are returned. If the
64 argument list contains references to invalid figures, a warning is printed
63 argument list contains references to invalid figures, a warning is printed
65 but the function continues pasting further figures.
64 but the function continues pasting further figures.
66
65
67 Parameters
66 Parameters
68 ----------
67 ----------
69 figs : tuple
68 figs : tuple
70 A tuple of ints giving the figure numbers of the figures to return.
69 A tuple of ints giving the figure numbers of the figures to return.
71 """
70 """
72 from matplotlib._pylab_helpers import Gcf
71 from matplotlib._pylab_helpers import Gcf
73 if not fig_nums:
72 if not fig_nums:
74 fig_managers = Gcf.get_all_fig_managers()
73 fig_managers = Gcf.get_all_fig_managers()
75 return [fm.canvas.figure for fm in fig_managers]
74 return [fm.canvas.figure for fm in fig_managers]
76 else:
75 else:
77 figs = []
76 figs = []
78 for num in fig_nums:
77 for num in fig_nums:
79 f = Gcf.figs.get(num)
78 f = Gcf.figs.get(num)
80 if f is None:
79 if f is None:
81 print('Warning: figure %s not available.' % num)
80 print('Warning: figure %s not available.' % num)
82 else:
81 else:
83 figs.append(f.canvas.figure)
82 figs.append(f.canvas.figure)
84 return figs
83 return figs
85
84
86
85
87 def figsize(sizex, sizey):
86 def figsize(sizex, sizey):
88 """Set the default figure size to be [sizex, sizey].
87 """Set the default figure size to be [sizex, sizey].
89
88
90 This is just an easy to remember, convenience wrapper that sets::
89 This is just an easy to remember, convenience wrapper that sets::
91
90
92 matplotlib.rcParams['figure.figsize'] = [sizex, sizey]
91 matplotlib.rcParams['figure.figsize'] = [sizex, sizey]
93 """
92 """
94 import matplotlib
93 import matplotlib
95 matplotlib.rcParams['figure.figsize'] = [sizex, sizey]
94 matplotlib.rcParams['figure.figsize'] = [sizex, sizey]
96
95
97
96
98 def print_figure(fig, fmt='png', bbox_inches='tight', **kwargs):
97 def print_figure(fig, fmt='png', bbox_inches='tight', **kwargs):
99 """Print a figure to an image, and return the resulting file data
98 """Print a figure to an image, and return the resulting file data
100
99
101 Returned data will be bytes unless ``fmt='svg'``,
100 Returned data will be bytes unless ``fmt='svg'``,
102 in which case it will be unicode.
101 in which case it will be unicode.
103
102
104 Any keyword args are passed to fig.canvas.print_figure,
103 Any keyword args are passed to fig.canvas.print_figure,
105 such as ``quality`` or ``bbox_inches``.
104 such as ``quality`` or ``bbox_inches``.
106 """
105 """
107 from matplotlib import rcParams
106 from matplotlib import rcParams
108 # When there's an empty figure, we shouldn't return anything, otherwise we
107 # When there's an empty figure, we shouldn't return anything, otherwise we
109 # get big blank areas in the qt console.
108 # get big blank areas in the qt console.
110 if not fig.axes and not fig.lines:
109 if not fig.axes and not fig.lines:
111 return
110 return
112
111
113 dpi = rcParams['savefig.dpi']
112 dpi = rcParams['savefig.dpi']
114 if fmt == 'retina':
113 if fmt == 'retina':
115 dpi = dpi * 2
114 dpi = dpi * 2
116 fmt = 'png'
115 fmt = 'png'
117
116
118 # build keyword args
117 # build keyword args
119 kw = dict(
118 kw = dict(
120 format=fmt,
119 format=fmt,
121 facecolor=fig.get_facecolor(),
120 facecolor=fig.get_facecolor(),
122 edgecolor=fig.get_edgecolor(),
121 edgecolor=fig.get_edgecolor(),
123 dpi=dpi,
122 dpi=dpi,
124 bbox_inches=bbox_inches,
123 bbox_inches=bbox_inches,
125 )
124 )
126 # **kwargs get higher priority
125 # **kwargs get higher priority
127 kw.update(kwargs)
126 kw.update(kwargs)
128
127
129 bytes_io = BytesIO()
128 bytes_io = BytesIO()
130 fig.canvas.print_figure(bytes_io, **kw)
129 fig.canvas.print_figure(bytes_io, **kw)
131 data = bytes_io.getvalue()
130 data = bytes_io.getvalue()
132 if fmt == 'svg':
131 if fmt == 'svg':
133 data = data.decode('utf-8')
132 data = data.decode('utf-8')
134 return data
133 return data
135
134
136 def retina_figure(fig, **kwargs):
135 def retina_figure(fig, **kwargs):
137 """format a figure as a pixel-doubled (retina) PNG"""
136 """format a figure as a pixel-doubled (retina) PNG"""
138 pngdata = print_figure(fig, fmt='retina', **kwargs)
137 pngdata = print_figure(fig, fmt='retina', **kwargs)
139 w, h = _pngxy(pngdata)
138 w, h = _pngxy(pngdata)
140 metadata = dict(width=w//2, height=h//2)
139 metadata = dict(width=w//2, height=h//2)
141 return pngdata, metadata
140 return pngdata, metadata
142
141
143 # We need a little factory function here to create the closure where
142 # We need a little factory function here to create the closure where
144 # safe_execfile can live.
143 # safe_execfile can live.
145 def mpl_runner(safe_execfile):
144 def mpl_runner(safe_execfile):
146 """Factory to return a matplotlib-enabled runner for %run.
145 """Factory to return a matplotlib-enabled runner for %run.
147
146
148 Parameters
147 Parameters
149 ----------
148 ----------
150 safe_execfile : function
149 safe_execfile : function
151 This must be a function with the same interface as the
150 This must be a function with the same interface as the
152 :meth:`safe_execfile` method of IPython.
151 :meth:`safe_execfile` method of IPython.
153
152
154 Returns
153 Returns
155 -------
154 -------
156 A function suitable for use as the ``runner`` argument of the %run magic
155 A function suitable for use as the ``runner`` argument of the %run magic
157 function.
156 function.
158 """
157 """
159
158
160 def mpl_execfile(fname,*where,**kw):
159 def mpl_execfile(fname,*where,**kw):
161 """matplotlib-aware wrapper around safe_execfile.
160 """matplotlib-aware wrapper around safe_execfile.
162
161
163 Its interface is identical to that of the :func:`execfile` builtin.
162 Its interface is identical to that of the :func:`execfile` builtin.
164
163
165 This is ultimately a call to execfile(), but wrapped in safeties to
164 This is ultimately a call to execfile(), but wrapped in safeties to
166 properly handle interactive rendering."""
165 properly handle interactive rendering."""
167
166
168 import matplotlib
167 import matplotlib
169 import matplotlib.pylab as pylab
168 import matplotlib.pylab as pylab
170
169
171 #print '*** Matplotlib runner ***' # dbg
170 #print '*** Matplotlib runner ***' # dbg
172 # turn off rendering until end of script
171 # turn off rendering until end of script
173 is_interactive = matplotlib.rcParams['interactive']
172 is_interactive = matplotlib.rcParams['interactive']
174 matplotlib.interactive(False)
173 matplotlib.interactive(False)
175 safe_execfile(fname,*where,**kw)
174 safe_execfile(fname,*where,**kw)
176 matplotlib.interactive(is_interactive)
175 matplotlib.interactive(is_interactive)
177 # make rendering call now, if the user tried to do it
176 # make rendering call now, if the user tried to do it
178 if pylab.draw_if_interactive.called:
177 if pylab.draw_if_interactive.called:
179 pylab.draw()
178 pylab.draw()
180 pylab.draw_if_interactive.called = False
179 pylab.draw_if_interactive.called = False
181
180
182 return mpl_execfile
181 return mpl_execfile
183
182
184
183
185 def select_figure_formats(shell, formats, **kwargs):
184 def select_figure_formats(shell, formats, **kwargs):
186 """Select figure formats for the inline backend.
185 """Select figure formats for the inline backend.
187
186
188 Parameters
187 Parameters
189 ==========
188 ==========
190 shell : InteractiveShell
189 shell : InteractiveShell
191 The main IPython instance.
190 The main IPython instance.
192 formats : str or set
191 formats : str or set
193 One or a set of figure formats to enable: 'png', 'retina', 'jpeg', 'svg', 'pdf'.
192 One or a set of figure formats to enable: 'png', 'retina', 'jpeg', 'svg', 'pdf'.
194 **kwargs : any
193 **kwargs : any
195 Extra keyword arguments to be passed to fig.canvas.print_figure.
194 Extra keyword arguments to be passed to fig.canvas.print_figure.
196 """
195 """
197 from matplotlib.figure import Figure
196 from matplotlib.figure import Figure
198 from IPython.kernel.zmq.pylab import backend_inline
197 from IPython.kernel.zmq.pylab import backend_inline
199
198
200 svg_formatter = shell.display_formatter.formatters['image/svg+xml']
199 svg_formatter = shell.display_formatter.formatters['image/svg+xml']
201 png_formatter = shell.display_formatter.formatters['image/png']
200 png_formatter = shell.display_formatter.formatters['image/png']
202 jpg_formatter = shell.display_formatter.formatters['image/jpeg']
201 jpg_formatter = shell.display_formatter.formatters['image/jpeg']
203 pdf_formatter = shell.display_formatter.formatters['application/pdf']
202 pdf_formatter = shell.display_formatter.formatters['application/pdf']
204
203
205 if isinstance(formats, py3compat.string_types):
204 if isinstance(formats, py3compat.string_types):
206 formats = {formats}
205 formats = {formats}
207 # cast in case of list / tuple
206 # cast in case of list / tuple
208 formats = set(formats)
207 formats = set(formats)
209
208
210 [ f.pop(Figure, None) for f in shell.display_formatter.formatters.values() ]
209 [ f.pop(Figure, None) for f in shell.display_formatter.formatters.values() ]
211
210
212 supported = {'png', 'png2x', 'retina', 'jpg', 'jpeg', 'svg', 'pdf'}
211 supported = {'png', 'png2x', 'retina', 'jpg', 'jpeg', 'svg', 'pdf'}
213 bad = formats.difference(supported)
212 bad = formats.difference(supported)
214 if bad:
213 if bad:
215 bs = "%s" % ','.join([repr(f) for f in bad])
214 bs = "%s" % ','.join([repr(f) for f in bad])
216 gs = "%s" % ','.join([repr(f) for f in supported])
215 gs = "%s" % ','.join([repr(f) for f in supported])
217 raise ValueError("supported formats are: %s not %s" % (gs, bs))
216 raise ValueError("supported formats are: %s not %s" % (gs, bs))
218
217
219 if 'png' in formats:
218 if 'png' in formats:
220 png_formatter.for_type(Figure, lambda fig: print_figure(fig, 'png', **kwargs))
219 png_formatter.for_type(Figure, lambda fig: print_figure(fig, 'png', **kwargs))
221 if 'retina' in formats or 'png2x' in formats:
220 if 'retina' in formats or 'png2x' in formats:
222 png_formatter.for_type(Figure, lambda fig: retina_figure(fig, **kwargs))
221 png_formatter.for_type(Figure, lambda fig: retina_figure(fig, **kwargs))
223 if 'jpg' in formats or 'jpeg' in formats:
222 if 'jpg' in formats or 'jpeg' in formats:
224 jpg_formatter.for_type(Figure, lambda fig: print_figure(fig, 'jpg', **kwargs))
223 jpg_formatter.for_type(Figure, lambda fig: print_figure(fig, 'jpg', **kwargs))
225 if 'svg' in formats:
224 if 'svg' in formats:
226 svg_formatter.for_type(Figure, lambda fig: print_figure(fig, 'svg', **kwargs))
225 svg_formatter.for_type(Figure, lambda fig: print_figure(fig, 'svg', **kwargs))
227 if 'pdf' in formats:
226 if 'pdf' in formats:
228 pdf_formatter.for_type(Figure, lambda fig: print_figure(fig, 'pdf', **kwargs))
227 pdf_formatter.for_type(Figure, lambda fig: print_figure(fig, 'pdf', **kwargs))
229
228
230 #-----------------------------------------------------------------------------
229 #-----------------------------------------------------------------------------
231 # Code for initializing matplotlib and importing pylab
230 # Code for initializing matplotlib and importing pylab
232 #-----------------------------------------------------------------------------
231 #-----------------------------------------------------------------------------
233
232
234
233
235 def find_gui_and_backend(gui=None, gui_select=None):
234 def find_gui_and_backend(gui=None, gui_select=None):
236 """Given a gui string return the gui and mpl backend.
235 """Given a gui string return the gui and mpl backend.
237
236
238 Parameters
237 Parameters
239 ----------
238 ----------
240 gui : str
239 gui : str
241 Can be one of ('tk','gtk','wx','qt','qt4','inline').
240 Can be one of ('tk','gtk','wx','qt','qt4','inline').
242 gui_select : str
241 gui_select : str
243 Can be one of ('tk','gtk','wx','qt','qt4','inline').
242 Can be one of ('tk','gtk','wx','qt','qt4','inline').
244 This is any gui already selected by the shell.
243 This is any gui already selected by the shell.
245
244
246 Returns
245 Returns
247 -------
246 -------
248 A tuple of (gui, backend) where backend is one of ('TkAgg','GTKAgg',
247 A tuple of (gui, backend) where backend is one of ('TkAgg','GTKAgg',
249 'WXAgg','Qt4Agg','module://IPython.kernel.zmq.pylab.backend_inline').
248 'WXAgg','Qt4Agg','module://IPython.kernel.zmq.pylab.backend_inline').
250 """
249 """
251
250
252 import matplotlib
251 import matplotlib
253
252
254 if gui and gui != 'auto':
253 if gui and gui != 'auto':
255 # select backend based on requested gui
254 # select backend based on requested gui
256 backend = backends[gui]
255 backend = backends[gui]
257 else:
256 else:
258 # We need to read the backend from the original data structure, *not*
257 # We need to read the backend from the original data structure, *not*
259 # from mpl.rcParams, since a prior invocation of %matplotlib may have
258 # from mpl.rcParams, since a prior invocation of %matplotlib may have
260 # overwritten that.
259 # overwritten that.
261 # WARNING: this assumes matplotlib 1.1 or newer!!
260 # WARNING: this assumes matplotlib 1.1 or newer!!
262 backend = matplotlib.rcParamsOrig['backend']
261 backend = matplotlib.rcParamsOrig['backend']
263 # In this case, we need to find what the appropriate gui selection call
262 # In this case, we need to find what the appropriate gui selection call
264 # should be for IPython, so we can activate inputhook accordingly
263 # should be for IPython, so we can activate inputhook accordingly
265 gui = backend2gui.get(backend, None)
264 gui = backend2gui.get(backend, None)
266
265
267 # If we have already had a gui active, we need it and inline are the
266 # If we have already had a gui active, we need it and inline are the
268 # ones allowed.
267 # ones allowed.
269 if gui_select and gui != gui_select:
268 if gui_select and gui != gui_select:
270 gui = gui_select
269 gui = gui_select
271 backend = backends[gui]
270 backend = backends[gui]
272
271
273 return gui, backend
272 return gui, backend
274
273
275
274
276 def activate_matplotlib(backend):
275 def activate_matplotlib(backend):
277 """Activate the given backend and set interactive to True."""
276 """Activate the given backend and set interactive to True."""
278
277
279 import matplotlib
278 import matplotlib
280 matplotlib.interactive(True)
279 matplotlib.interactive(True)
281
280
282 # Matplotlib had a bug where even switch_backend could not force
281 # Matplotlib had a bug where even switch_backend could not force
283 # the rcParam to update. This needs to be set *before* the module
282 # the rcParam to update. This needs to be set *before* the module
284 # magic of switch_backend().
283 # magic of switch_backend().
285 matplotlib.rcParams['backend'] = backend
284 matplotlib.rcParams['backend'] = backend
286
285
287 import matplotlib.pyplot
286 import matplotlib.pyplot
288 matplotlib.pyplot.switch_backend(backend)
287 matplotlib.pyplot.switch_backend(backend)
289
288
290 # This must be imported last in the matplotlib series, after
289 # This must be imported last in the matplotlib series, after
291 # backend/interactivity choices have been made
290 # backend/interactivity choices have been made
292 import matplotlib.pylab as pylab
291 import matplotlib.pylab as pylab
293
292
294 pylab.show._needmain = False
293 pylab.show._needmain = False
295 # We need to detect at runtime whether show() is called by the user.
294 # We need to detect at runtime whether show() is called by the user.
296 # For this, we wrap it into a decorator which adds a 'called' flag.
295 # For this, we wrap it into a decorator which adds a 'called' flag.
297 pylab.draw_if_interactive = flag_calls(pylab.draw_if_interactive)
296 pylab.draw_if_interactive = flag_calls(pylab.draw_if_interactive)
298
297
299
298
300 def import_pylab(user_ns, import_all=True):
299 def import_pylab(user_ns, import_all=True):
301 """Populate the namespace with pylab-related values.
300 """Populate the namespace with pylab-related values.
302
301
303 Imports matplotlib, pylab, numpy, and everything from pylab and numpy.
302 Imports matplotlib, pylab, numpy, and everything from pylab and numpy.
304
303
305 Also imports a few names from IPython (figsize, display, getfigs)
304 Also imports a few names from IPython (figsize, display, getfigs)
306
305
307 """
306 """
308
307
309 # Import numpy as np/pyplot as plt are conventions we're trying to
308 # Import numpy as np/pyplot as plt are conventions we're trying to
310 # somewhat standardize on. Making them available to users by default
309 # somewhat standardize on. Making them available to users by default
311 # will greatly help this.
310 # will greatly help this.
312 s = ("import numpy\n"
311 s = ("import numpy\n"
313 "import matplotlib\n"
312 "import matplotlib\n"
314 "from matplotlib import pylab, mlab, pyplot\n"
313 "from matplotlib import pylab, mlab, pyplot\n"
315 "np = numpy\n"
314 "np = numpy\n"
316 "plt = pyplot\n"
315 "plt = pyplot\n"
317 )
316 )
318 exec(s, user_ns)
317 exec(s, user_ns)
319
318
320 if import_all:
319 if import_all:
321 s = ("from matplotlib.pylab import *\n"
320 s = ("from matplotlib.pylab import *\n"
322 "from numpy import *\n")
321 "from numpy import *\n")
323 exec(s, user_ns)
322 exec(s, user_ns)
324
323
325 # IPython symbols to add
324 # IPython symbols to add
326 user_ns['figsize'] = figsize
325 user_ns['figsize'] = figsize
327 from IPython.core.display import display
326 from IPython.core.display import display
328 # Add display and getfigs to the user's namespace
327 # Add display and getfigs to the user's namespace
329 user_ns['display'] = display
328 user_ns['display'] = display
330 user_ns['getfigs'] = getfigs
329 user_ns['getfigs'] = getfigs
331
330
332
331
333 def configure_inline_support(shell, backend):
332 def configure_inline_support(shell, backend):
334 """Configure an IPython shell object for matplotlib use.
333 """Configure an IPython shell object for matplotlib use.
335
334
336 Parameters
335 Parameters
337 ----------
336 ----------
338 shell : InteractiveShell instance
337 shell : InteractiveShell instance
339
338
340 backend : matplotlib backend
339 backend : matplotlib backend
341 """
340 """
342 # If using our svg payload backend, register the post-execution
341 # If using our svg payload backend, register the post-execution
343 # function that will pick up the results for display. This can only be
342 # function that will pick up the results for display. This can only be
344 # done with access to the real shell object.
343 # done with access to the real shell object.
345
344
346 # Note: if we can't load the inline backend, then there's no point
345 # Note: if we can't load the inline backend, then there's no point
347 # continuing (such as in terminal-only shells in environments without
346 # continuing (such as in terminal-only shells in environments without
348 # zeromq available).
347 # zeromq available).
349 try:
348 try:
350 from IPython.kernel.zmq.pylab.backend_inline import InlineBackend
349 from IPython.kernel.zmq.pylab.backend_inline import InlineBackend
351 except ImportError:
350 except ImportError:
352 return
351 return
353 from matplotlib import pyplot
352 from matplotlib import pyplot
354
353
355 cfg = InlineBackend.instance(parent=shell)
354 cfg = InlineBackend.instance(parent=shell)
356 cfg.shell = shell
355 cfg.shell = shell
357 if cfg not in shell.configurables:
356 if cfg not in shell.configurables:
358 shell.configurables.append(cfg)
357 shell.configurables.append(cfg)
359
358
360 if backend == backends['inline']:
359 if backend == backends['inline']:
361 from IPython.kernel.zmq.pylab.backend_inline import flush_figures
360 from IPython.kernel.zmq.pylab.backend_inline import flush_figures
362 shell.events.register('post_execute', flush_figures)
361 shell.events.register('post_execute', flush_figures)
363
362
364 # Save rcParams that will be overwrittern
363 # Save rcParams that will be overwrittern
365 shell._saved_rcParams = dict()
364 shell._saved_rcParams = dict()
366 for k in cfg.rc:
365 for k in cfg.rc:
367 shell._saved_rcParams[k] = pyplot.rcParams[k]
366 shell._saved_rcParams[k] = pyplot.rcParams[k]
368 # load inline_rc
367 # load inline_rc
369 pyplot.rcParams.update(cfg.rc)
368 pyplot.rcParams.update(cfg.rc)
370 else:
369 else:
371 from IPython.kernel.zmq.pylab.backend_inline import flush_figures
370 from IPython.kernel.zmq.pylab.backend_inline import flush_figures
372 try:
371 try:
373 shell.events.unregister('post_execute', flush_figures)
372 shell.events.unregister('post_execute', flush_figures)
374 except ValueError:
373 except ValueError:
375 pass
374 pass
376 if hasattr(shell, '_saved_rcParams'):
375 if hasattr(shell, '_saved_rcParams'):
377 pyplot.rcParams.update(shell._saved_rcParams)
376 pyplot.rcParams.update(shell._saved_rcParams)
378 del shell._saved_rcParams
377 del shell._saved_rcParams
379
378
380 # Setup the default figure format
379 # Setup the default figure format
381 select_figure_formats(shell, cfg.figure_formats, **cfg.print_figure_kwargs)
380 select_figure_formats(shell, cfg.figure_formats, **cfg.print_figure_kwargs)
382
381
@@ -1,438 +1,438 b''
1 # encoding: utf-8
1 # encoding: utf-8
2 """
2 """
3 A mixin for :class:`~IPython.core.application.Application` classes that
3 A mixin for :class:`~IPython.core.application.Application` classes that
4 launch InteractiveShell instances, load extensions, etc.
4 launch InteractiveShell instances, load extensions, etc.
5
5
6 Authors
6 Authors
7 -------
7 -------
8
8
9 * Min Ragan-Kelley
9 * Min Ragan-Kelley
10 """
10 """
11
11
12 #-----------------------------------------------------------------------------
12 #-----------------------------------------------------------------------------
13 # Copyright (C) 2008-2011 The IPython Development Team
13 # Copyright (C) 2008-2011 The IPython Development Team
14 #
14 #
15 # Distributed under the terms of the BSD License. The full license is in
15 # Distributed under the terms of the BSD License. The full license is in
16 # the file COPYING, distributed as part of this software.
16 # the file COPYING, distributed as part of this software.
17 #-----------------------------------------------------------------------------
17 #-----------------------------------------------------------------------------
18
18
19 #-----------------------------------------------------------------------------
19 #-----------------------------------------------------------------------------
20 # Imports
20 # Imports
21 #-----------------------------------------------------------------------------
21 #-----------------------------------------------------------------------------
22
22
23 from __future__ import absolute_import
23 from __future__ import absolute_import
24 from __future__ import print_function
24 from __future__ import print_function
25
25
26 import glob
26 import glob
27 import os
27 import os
28 import sys
28 import sys
29
29
30 from IPython.config.application import boolean_flag
30 from IPython.config.application import boolean_flag
31 from IPython.config.configurable import Configurable
31 from IPython.config.configurable import Configurable
32 from IPython.config.loader import Config
32 from IPython.config.loader import Config
33 from IPython.core import pylabtools
33 from IPython.core import pylabtools
34 from IPython.utils import py3compat
34 from IPython.utils import py3compat
35 from IPython.utils.contexts import preserve_keys
35 from IPython.utils.contexts import preserve_keys
36 from IPython.utils.path import filefind
36 from IPython.utils.path import filefind
37 from IPython.utils.traitlets import (
37 from IPython.utils.traitlets import (
38 Unicode, Instance, List, Bool, CaselessStrEnum, Dict
38 Unicode, Instance, List, Bool, CaselessStrEnum
39 )
39 )
40 from IPython.lib.inputhook import guis
40 from IPython.lib.inputhook import guis
41
41
42 #-----------------------------------------------------------------------------
42 #-----------------------------------------------------------------------------
43 # Aliases and Flags
43 # Aliases and Flags
44 #-----------------------------------------------------------------------------
44 #-----------------------------------------------------------------------------
45
45
46 gui_keys = tuple(sorted([ key for key in guis if key is not None ]))
46 gui_keys = tuple(sorted([ key for key in guis if key is not None ]))
47
47
48 backend_keys = sorted(pylabtools.backends.keys())
48 backend_keys = sorted(pylabtools.backends.keys())
49 backend_keys.insert(0, 'auto')
49 backend_keys.insert(0, 'auto')
50
50
51 shell_flags = {}
51 shell_flags = {}
52
52
53 addflag = lambda *args: shell_flags.update(boolean_flag(*args))
53 addflag = lambda *args: shell_flags.update(boolean_flag(*args))
54 addflag('autoindent', 'InteractiveShell.autoindent',
54 addflag('autoindent', 'InteractiveShell.autoindent',
55 'Turn on autoindenting.', 'Turn off autoindenting.'
55 'Turn on autoindenting.', 'Turn off autoindenting.'
56 )
56 )
57 addflag('automagic', 'InteractiveShell.automagic',
57 addflag('automagic', 'InteractiveShell.automagic',
58 """Turn on the auto calling of magic commands. Type %%magic at the
58 """Turn on the auto calling of magic commands. Type %%magic at the
59 IPython prompt for more information.""",
59 IPython prompt for more information.""",
60 'Turn off the auto calling of magic commands.'
60 'Turn off the auto calling of magic commands.'
61 )
61 )
62 addflag('pdb', 'InteractiveShell.pdb',
62 addflag('pdb', 'InteractiveShell.pdb',
63 "Enable auto calling the pdb debugger after every exception.",
63 "Enable auto calling the pdb debugger after every exception.",
64 "Disable auto calling the pdb debugger after every exception."
64 "Disable auto calling the pdb debugger after every exception."
65 )
65 )
66 # pydb flag doesn't do any config, as core.debugger switches on import,
66 # pydb flag doesn't do any config, as core.debugger switches on import,
67 # which is before parsing. This just allows the flag to be passed.
67 # which is before parsing. This just allows the flag to be passed.
68 shell_flags.update(dict(
68 shell_flags.update(dict(
69 pydb = ({},
69 pydb = ({},
70 """Use the third party 'pydb' package as debugger, instead of pdb.
70 """Use the third party 'pydb' package as debugger, instead of pdb.
71 Requires that pydb is installed."""
71 Requires that pydb is installed."""
72 )
72 )
73 ))
73 ))
74 addflag('pprint', 'PlainTextFormatter.pprint',
74 addflag('pprint', 'PlainTextFormatter.pprint',
75 "Enable auto pretty printing of results.",
75 "Enable auto pretty printing of results.",
76 "Disable auto pretty printing of results."
76 "Disable auto pretty printing of results."
77 )
77 )
78 addflag('color-info', 'InteractiveShell.color_info',
78 addflag('color-info', 'InteractiveShell.color_info',
79 """IPython can display information about objects via a set of func-
79 """IPython can display information about objects via a set of func-
80 tions, and optionally can use colors for this, syntax highlighting
80 tions, and optionally can use colors for this, syntax highlighting
81 source code and various other elements. However, because this
81 source code and various other elements. However, because this
82 information is passed through a pager (like 'less') and many pagers get
82 information is passed through a pager (like 'less') and many pagers get
83 confused with color codes, this option is off by default. You can test
83 confused with color codes, this option is off by default. You can test
84 it and turn it on permanently in your ipython_config.py file if it
84 it and turn it on permanently in your ipython_config.py file if it
85 works for you. Test it and turn it on permanently if it works with
85 works for you. Test it and turn it on permanently if it works with
86 your system. The magic function %%color_info allows you to toggle this
86 your system. The magic function %%color_info allows you to toggle this
87 interactively for testing.""",
87 interactively for testing.""",
88 "Disable using colors for info related things."
88 "Disable using colors for info related things."
89 )
89 )
90 addflag('deep-reload', 'InteractiveShell.deep_reload',
90 addflag('deep-reload', 'InteractiveShell.deep_reload',
91 """Enable deep (recursive) reloading by default. IPython can use the
91 """Enable deep (recursive) reloading by default. IPython can use the
92 deep_reload module which reloads changes in modules recursively (it
92 deep_reload module which reloads changes in modules recursively (it
93 replaces the reload() function, so you don't need to change anything to
93 replaces the reload() function, so you don't need to change anything to
94 use it). deep_reload() forces a full reload of modules whose code may
94 use it). deep_reload() forces a full reload of modules whose code may
95 have changed, which the default reload() function does not. When
95 have changed, which the default reload() function does not. When
96 deep_reload is off, IPython will use the normal reload(), but
96 deep_reload is off, IPython will use the normal reload(), but
97 deep_reload will still be available as dreload(). This feature is off
97 deep_reload will still be available as dreload(). This feature is off
98 by default [which means that you have both normal reload() and
98 by default [which means that you have both normal reload() and
99 dreload()].""",
99 dreload()].""",
100 "Disable deep (recursive) reloading by default."
100 "Disable deep (recursive) reloading by default."
101 )
101 )
102 nosep_config = Config()
102 nosep_config = Config()
103 nosep_config.InteractiveShell.separate_in = ''
103 nosep_config.InteractiveShell.separate_in = ''
104 nosep_config.InteractiveShell.separate_out = ''
104 nosep_config.InteractiveShell.separate_out = ''
105 nosep_config.InteractiveShell.separate_out2 = ''
105 nosep_config.InteractiveShell.separate_out2 = ''
106
106
107 shell_flags['nosep']=(nosep_config, "Eliminate all spacing between prompts.")
107 shell_flags['nosep']=(nosep_config, "Eliminate all spacing between prompts.")
108 shell_flags['pylab'] = (
108 shell_flags['pylab'] = (
109 {'InteractiveShellApp' : {'pylab' : 'auto'}},
109 {'InteractiveShellApp' : {'pylab' : 'auto'}},
110 """Pre-load matplotlib and numpy for interactive use with
110 """Pre-load matplotlib and numpy for interactive use with
111 the default matplotlib backend."""
111 the default matplotlib backend."""
112 )
112 )
113 shell_flags['matplotlib'] = (
113 shell_flags['matplotlib'] = (
114 {'InteractiveShellApp' : {'matplotlib' : 'auto'}},
114 {'InteractiveShellApp' : {'matplotlib' : 'auto'}},
115 """Configure matplotlib for interactive use with
115 """Configure matplotlib for interactive use with
116 the default matplotlib backend."""
116 the default matplotlib backend."""
117 )
117 )
118
118
119 # it's possible we don't want short aliases for *all* of these:
119 # it's possible we don't want short aliases for *all* of these:
120 shell_aliases = dict(
120 shell_aliases = dict(
121 autocall='InteractiveShell.autocall',
121 autocall='InteractiveShell.autocall',
122 colors='InteractiveShell.colors',
122 colors='InteractiveShell.colors',
123 logfile='InteractiveShell.logfile',
123 logfile='InteractiveShell.logfile',
124 logappend='InteractiveShell.logappend',
124 logappend='InteractiveShell.logappend',
125 c='InteractiveShellApp.code_to_run',
125 c='InteractiveShellApp.code_to_run',
126 m='InteractiveShellApp.module_to_run',
126 m='InteractiveShellApp.module_to_run',
127 ext='InteractiveShellApp.extra_extension',
127 ext='InteractiveShellApp.extra_extension',
128 gui='InteractiveShellApp.gui',
128 gui='InteractiveShellApp.gui',
129 pylab='InteractiveShellApp.pylab',
129 pylab='InteractiveShellApp.pylab',
130 matplotlib='InteractiveShellApp.matplotlib',
130 matplotlib='InteractiveShellApp.matplotlib',
131 )
131 )
132 shell_aliases['cache-size'] = 'InteractiveShell.cache_size'
132 shell_aliases['cache-size'] = 'InteractiveShell.cache_size'
133
133
134 #-----------------------------------------------------------------------------
134 #-----------------------------------------------------------------------------
135 # Main classes and functions
135 # Main classes and functions
136 #-----------------------------------------------------------------------------
136 #-----------------------------------------------------------------------------
137
137
138 class InteractiveShellApp(Configurable):
138 class InteractiveShellApp(Configurable):
139 """A Mixin for applications that start InteractiveShell instances.
139 """A Mixin for applications that start InteractiveShell instances.
140
140
141 Provides configurables for loading extensions and executing files
141 Provides configurables for loading extensions and executing files
142 as part of configuring a Shell environment.
142 as part of configuring a Shell environment.
143
143
144 The following methods should be called by the :meth:`initialize` method
144 The following methods should be called by the :meth:`initialize` method
145 of the subclass:
145 of the subclass:
146
146
147 - :meth:`init_path`
147 - :meth:`init_path`
148 - :meth:`init_shell` (to be implemented by the subclass)
148 - :meth:`init_shell` (to be implemented by the subclass)
149 - :meth:`init_gui_pylab`
149 - :meth:`init_gui_pylab`
150 - :meth:`init_extensions`
150 - :meth:`init_extensions`
151 - :meth:`init_code`
151 - :meth:`init_code`
152 """
152 """
153 extensions = List(Unicode, config=True,
153 extensions = List(Unicode, config=True,
154 help="A list of dotted module names of IPython extensions to load."
154 help="A list of dotted module names of IPython extensions to load."
155 )
155 )
156 extra_extension = Unicode('', config=True,
156 extra_extension = Unicode('', config=True,
157 help="dotted module name of an IPython extension to load."
157 help="dotted module name of an IPython extension to load."
158 )
158 )
159 def _extra_extension_changed(self, name, old, new):
159 def _extra_extension_changed(self, name, old, new):
160 if new:
160 if new:
161 # add to self.extensions
161 # add to self.extensions
162 self.extensions.append(new)
162 self.extensions.append(new)
163
163
164 # Extensions that are always loaded (not configurable)
164 # Extensions that are always loaded (not configurable)
165 default_extensions = List(Unicode, [u'storemagic'], config=False)
165 default_extensions = List(Unicode, [u'storemagic'], config=False)
166
166
167 hide_initial_ns = Bool(True, config=True,
167 hide_initial_ns = Bool(True, config=True,
168 help="""Should variables loaded at startup (by startup files, exec_lines, etc.)
168 help="""Should variables loaded at startup (by startup files, exec_lines, etc.)
169 be hidden from tools like %who?"""
169 be hidden from tools like %who?"""
170 )
170 )
171
171
172 exec_files = List(Unicode, config=True,
172 exec_files = List(Unicode, config=True,
173 help="""List of files to run at IPython startup."""
173 help="""List of files to run at IPython startup."""
174 )
174 )
175 exec_PYTHONSTARTUP = Bool(True, config=True,
175 exec_PYTHONSTARTUP = Bool(True, config=True,
176 help="""Run the file referenced by the PYTHONSTARTUP environment
176 help="""Run the file referenced by the PYTHONSTARTUP environment
177 variable at IPython startup."""
177 variable at IPython startup."""
178 )
178 )
179 file_to_run = Unicode('', config=True,
179 file_to_run = Unicode('', config=True,
180 help="""A file to be run""")
180 help="""A file to be run""")
181
181
182 exec_lines = List(Unicode, config=True,
182 exec_lines = List(Unicode, config=True,
183 help="""lines of code to run at IPython startup."""
183 help="""lines of code to run at IPython startup."""
184 )
184 )
185 code_to_run = Unicode('', config=True,
185 code_to_run = Unicode('', config=True,
186 help="Execute the given command string."
186 help="Execute the given command string."
187 )
187 )
188 module_to_run = Unicode('', config=True,
188 module_to_run = Unicode('', config=True,
189 help="Run the module as a script."
189 help="Run the module as a script."
190 )
190 )
191 gui = CaselessStrEnum(gui_keys, config=True,
191 gui = CaselessStrEnum(gui_keys, config=True,
192 help="Enable GUI event loop integration with any of {0}.".format(gui_keys)
192 help="Enable GUI event loop integration with any of {0}.".format(gui_keys)
193 )
193 )
194 matplotlib = CaselessStrEnum(backend_keys,
194 matplotlib = CaselessStrEnum(backend_keys,
195 config=True,
195 config=True,
196 help="""Configure matplotlib for interactive use with
196 help="""Configure matplotlib for interactive use with
197 the default matplotlib backend."""
197 the default matplotlib backend."""
198 )
198 )
199 pylab = CaselessStrEnum(backend_keys,
199 pylab = CaselessStrEnum(backend_keys,
200 config=True,
200 config=True,
201 help="""Pre-load matplotlib and numpy for interactive use,
201 help="""Pre-load matplotlib and numpy for interactive use,
202 selecting a particular matplotlib backend and loop integration.
202 selecting a particular matplotlib backend and loop integration.
203 """
203 """
204 )
204 )
205 pylab_import_all = Bool(True, config=True,
205 pylab_import_all = Bool(True, config=True,
206 help="""If true, IPython will populate the user namespace with numpy, pylab, etc.
206 help="""If true, IPython will populate the user namespace with numpy, pylab, etc.
207 and an ``import *`` is done from numpy and pylab, when using pylab mode.
207 and an ``import *`` is done from numpy and pylab, when using pylab mode.
208
208
209 When False, pylab mode should not import any names into the user namespace.
209 When False, pylab mode should not import any names into the user namespace.
210 """
210 """
211 )
211 )
212 shell = Instance('IPython.core.interactiveshell.InteractiveShellABC')
212 shell = Instance('IPython.core.interactiveshell.InteractiveShellABC')
213
213
214 user_ns = Instance(dict, args=None, allow_none=True)
214 user_ns = Instance(dict, args=None, allow_none=True)
215 def _user_ns_changed(self, name, old, new):
215 def _user_ns_changed(self, name, old, new):
216 if self.shell is not None:
216 if self.shell is not None:
217 self.shell.user_ns = new
217 self.shell.user_ns = new
218 self.shell.init_user_ns()
218 self.shell.init_user_ns()
219
219
220 def init_path(self):
220 def init_path(self):
221 """Add current working directory, '', to sys.path"""
221 """Add current working directory, '', to sys.path"""
222 if sys.path[0] != '':
222 if sys.path[0] != '':
223 sys.path.insert(0, '')
223 sys.path.insert(0, '')
224
224
225 def init_shell(self):
225 def init_shell(self):
226 raise NotImplementedError("Override in subclasses")
226 raise NotImplementedError("Override in subclasses")
227
227
228 def init_gui_pylab(self):
228 def init_gui_pylab(self):
229 """Enable GUI event loop integration, taking pylab into account."""
229 """Enable GUI event loop integration, taking pylab into account."""
230 enable = False
230 enable = False
231 shell = self.shell
231 shell = self.shell
232 if self.pylab:
232 if self.pylab:
233 enable = lambda key: shell.enable_pylab(key, import_all=self.pylab_import_all)
233 enable = lambda key: shell.enable_pylab(key, import_all=self.pylab_import_all)
234 key = self.pylab
234 key = self.pylab
235 elif self.matplotlib:
235 elif self.matplotlib:
236 enable = shell.enable_matplotlib
236 enable = shell.enable_matplotlib
237 key = self.matplotlib
237 key = self.matplotlib
238 elif self.gui:
238 elif self.gui:
239 enable = shell.enable_gui
239 enable = shell.enable_gui
240 key = self.gui
240 key = self.gui
241
241
242 if not enable:
242 if not enable:
243 return
243 return
244
244
245 try:
245 try:
246 r = enable(key)
246 r = enable(key)
247 except ImportError:
247 except ImportError:
248 self.log.warn("Eventloop or matplotlib integration failed. Is matplotlib installed?")
248 self.log.warn("Eventloop or matplotlib integration failed. Is matplotlib installed?")
249 self.shell.showtraceback()
249 self.shell.showtraceback()
250 return
250 return
251 except Exception:
251 except Exception:
252 self.log.warn("GUI event loop or pylab initialization failed")
252 self.log.warn("GUI event loop or pylab initialization failed")
253 self.shell.showtraceback()
253 self.shell.showtraceback()
254 return
254 return
255
255
256 if isinstance(r, tuple):
256 if isinstance(r, tuple):
257 gui, backend = r[:2]
257 gui, backend = r[:2]
258 self.log.info("Enabling GUI event loop integration, "
258 self.log.info("Enabling GUI event loop integration, "
259 "eventloop=%s, matplotlib=%s", gui, backend)
259 "eventloop=%s, matplotlib=%s", gui, backend)
260 if key == "auto":
260 if key == "auto":
261 print("Using matplotlib backend: %s" % backend)
261 print("Using matplotlib backend: %s" % backend)
262 else:
262 else:
263 gui = r
263 gui = r
264 self.log.info("Enabling GUI event loop integration, "
264 self.log.info("Enabling GUI event loop integration, "
265 "eventloop=%s", gui)
265 "eventloop=%s", gui)
266
266
267 def init_extensions(self):
267 def init_extensions(self):
268 """Load all IPython extensions in IPythonApp.extensions.
268 """Load all IPython extensions in IPythonApp.extensions.
269
269
270 This uses the :meth:`ExtensionManager.load_extensions` to load all
270 This uses the :meth:`ExtensionManager.load_extensions` to load all
271 the extensions listed in ``self.extensions``.
271 the extensions listed in ``self.extensions``.
272 """
272 """
273 try:
273 try:
274 self.log.debug("Loading IPython extensions...")
274 self.log.debug("Loading IPython extensions...")
275 extensions = self.default_extensions + self.extensions
275 extensions = self.default_extensions + self.extensions
276 for ext in extensions:
276 for ext in extensions:
277 try:
277 try:
278 self.log.info("Loading IPython extension: %s" % ext)
278 self.log.info("Loading IPython extension: %s" % ext)
279 self.shell.extension_manager.load_extension(ext)
279 self.shell.extension_manager.load_extension(ext)
280 except:
280 except:
281 self.log.warn("Error in loading extension: %s" % ext +
281 self.log.warn("Error in loading extension: %s" % ext +
282 "\nCheck your config files in %s" % self.profile_dir.location
282 "\nCheck your config files in %s" % self.profile_dir.location
283 )
283 )
284 self.shell.showtraceback()
284 self.shell.showtraceback()
285 except:
285 except:
286 self.log.warn("Unknown error in loading extensions:")
286 self.log.warn("Unknown error in loading extensions:")
287 self.shell.showtraceback()
287 self.shell.showtraceback()
288
288
289 def init_code(self):
289 def init_code(self):
290 """run the pre-flight code, specified via exec_lines"""
290 """run the pre-flight code, specified via exec_lines"""
291 self._run_startup_files()
291 self._run_startup_files()
292 self._run_exec_lines()
292 self._run_exec_lines()
293 self._run_exec_files()
293 self._run_exec_files()
294
294
295 # Hide variables defined here from %who etc.
295 # Hide variables defined here from %who etc.
296 if self.hide_initial_ns:
296 if self.hide_initial_ns:
297 self.shell.user_ns_hidden.update(self.shell.user_ns)
297 self.shell.user_ns_hidden.update(self.shell.user_ns)
298
298
299 # command-line execution (ipython -i script.py, ipython -m module)
299 # command-line execution (ipython -i script.py, ipython -m module)
300 # should *not* be excluded from %whos
300 # should *not* be excluded from %whos
301 self._run_cmd_line_code()
301 self._run_cmd_line_code()
302 self._run_module()
302 self._run_module()
303
303
304 # flush output, so itwon't be attached to the first cell
304 # flush output, so itwon't be attached to the first cell
305 sys.stdout.flush()
305 sys.stdout.flush()
306 sys.stderr.flush()
306 sys.stderr.flush()
307
307
308 def _run_exec_lines(self):
308 def _run_exec_lines(self):
309 """Run lines of code in IPythonApp.exec_lines in the user's namespace."""
309 """Run lines of code in IPythonApp.exec_lines in the user's namespace."""
310 if not self.exec_lines:
310 if not self.exec_lines:
311 return
311 return
312 try:
312 try:
313 self.log.debug("Running code from IPythonApp.exec_lines...")
313 self.log.debug("Running code from IPythonApp.exec_lines...")
314 for line in self.exec_lines:
314 for line in self.exec_lines:
315 try:
315 try:
316 self.log.info("Running code in user namespace: %s" %
316 self.log.info("Running code in user namespace: %s" %
317 line)
317 line)
318 self.shell.run_cell(line, store_history=False)
318 self.shell.run_cell(line, store_history=False)
319 except:
319 except:
320 self.log.warn("Error in executing line in user "
320 self.log.warn("Error in executing line in user "
321 "namespace: %s" % line)
321 "namespace: %s" % line)
322 self.shell.showtraceback()
322 self.shell.showtraceback()
323 except:
323 except:
324 self.log.warn("Unknown error in handling IPythonApp.exec_lines:")
324 self.log.warn("Unknown error in handling IPythonApp.exec_lines:")
325 self.shell.showtraceback()
325 self.shell.showtraceback()
326
326
327 def _exec_file(self, fname):
327 def _exec_file(self, fname):
328 try:
328 try:
329 full_filename = filefind(fname, [u'.', self.ipython_dir])
329 full_filename = filefind(fname, [u'.', self.ipython_dir])
330 except IOError as e:
330 except IOError as e:
331 self.log.warn("File not found: %r"%fname)
331 self.log.warn("File not found: %r"%fname)
332 return
332 return
333 # Make sure that the running script gets a proper sys.argv as if it
333 # Make sure that the running script gets a proper sys.argv as if it
334 # were run from a system shell.
334 # were run from a system shell.
335 save_argv = sys.argv
335 save_argv = sys.argv
336 sys.argv = [full_filename] + self.extra_args[1:]
336 sys.argv = [full_filename] + self.extra_args[1:]
337 # protect sys.argv from potential unicode strings on Python 2:
337 # protect sys.argv from potential unicode strings on Python 2:
338 if not py3compat.PY3:
338 if not py3compat.PY3:
339 sys.argv = [ py3compat.cast_bytes(a) for a in sys.argv ]
339 sys.argv = [ py3compat.cast_bytes(a) for a in sys.argv ]
340 try:
340 try:
341 if os.path.isfile(full_filename):
341 if os.path.isfile(full_filename):
342 self.log.info("Running file in user namespace: %s" %
342 self.log.info("Running file in user namespace: %s" %
343 full_filename)
343 full_filename)
344 # Ensure that __file__ is always defined to match Python
344 # Ensure that __file__ is always defined to match Python
345 # behavior.
345 # behavior.
346 with preserve_keys(self.shell.user_ns, '__file__'):
346 with preserve_keys(self.shell.user_ns, '__file__'):
347 self.shell.user_ns['__file__'] = fname
347 self.shell.user_ns['__file__'] = fname
348 if full_filename.endswith('.ipy'):
348 if full_filename.endswith('.ipy'):
349 self.shell.safe_execfile_ipy(full_filename)
349 self.shell.safe_execfile_ipy(full_filename)
350 else:
350 else:
351 # default to python, even without extension
351 # default to python, even without extension
352 self.shell.safe_execfile(full_filename,
352 self.shell.safe_execfile(full_filename,
353 self.shell.user_ns)
353 self.shell.user_ns)
354 finally:
354 finally:
355 sys.argv = save_argv
355 sys.argv = save_argv
356
356
357 def _run_startup_files(self):
357 def _run_startup_files(self):
358 """Run files from profile startup directory"""
358 """Run files from profile startup directory"""
359 startup_dir = self.profile_dir.startup_dir
359 startup_dir = self.profile_dir.startup_dir
360 startup_files = []
360 startup_files = []
361
361
362 if self.exec_PYTHONSTARTUP and os.environ.get('PYTHONSTARTUP', False) and \
362 if self.exec_PYTHONSTARTUP and os.environ.get('PYTHONSTARTUP', False) and \
363 not (self.file_to_run or self.code_to_run or self.module_to_run):
363 not (self.file_to_run or self.code_to_run or self.module_to_run):
364 python_startup = os.environ['PYTHONSTARTUP']
364 python_startup = os.environ['PYTHONSTARTUP']
365 self.log.debug("Running PYTHONSTARTUP file %s...", python_startup)
365 self.log.debug("Running PYTHONSTARTUP file %s...", python_startup)
366 try:
366 try:
367 self._exec_file(python_startup)
367 self._exec_file(python_startup)
368 except:
368 except:
369 self.log.warn("Unknown error in handling PYTHONSTARTUP file %s:", python_startup)
369 self.log.warn("Unknown error in handling PYTHONSTARTUP file %s:", python_startup)
370 self.shell.showtraceback()
370 self.shell.showtraceback()
371 finally:
371 finally:
372 # Many PYTHONSTARTUP files set up the readline completions,
372 # Many PYTHONSTARTUP files set up the readline completions,
373 # but this is often at odds with IPython's own completions.
373 # but this is often at odds with IPython's own completions.
374 # Do not allow PYTHONSTARTUP to set up readline.
374 # Do not allow PYTHONSTARTUP to set up readline.
375 if self.shell.has_readline:
375 if self.shell.has_readline:
376 self.shell.set_readline_completer()
376 self.shell.set_readline_completer()
377
377
378 startup_files += glob.glob(os.path.join(startup_dir, '*.py'))
378 startup_files += glob.glob(os.path.join(startup_dir, '*.py'))
379 startup_files += glob.glob(os.path.join(startup_dir, '*.ipy'))
379 startup_files += glob.glob(os.path.join(startup_dir, '*.ipy'))
380 if not startup_files:
380 if not startup_files:
381 return
381 return
382
382
383 self.log.debug("Running startup files from %s...", startup_dir)
383 self.log.debug("Running startup files from %s...", startup_dir)
384 try:
384 try:
385 for fname in sorted(startup_files):
385 for fname in sorted(startup_files):
386 self._exec_file(fname)
386 self._exec_file(fname)
387 except:
387 except:
388 self.log.warn("Unknown error in handling startup files:")
388 self.log.warn("Unknown error in handling startup files:")
389 self.shell.showtraceback()
389 self.shell.showtraceback()
390
390
391 def _run_exec_files(self):
391 def _run_exec_files(self):
392 """Run files from IPythonApp.exec_files"""
392 """Run files from IPythonApp.exec_files"""
393 if not self.exec_files:
393 if not self.exec_files:
394 return
394 return
395
395
396 self.log.debug("Running files in IPythonApp.exec_files...")
396 self.log.debug("Running files in IPythonApp.exec_files...")
397 try:
397 try:
398 for fname in self.exec_files:
398 for fname in self.exec_files:
399 self._exec_file(fname)
399 self._exec_file(fname)
400 except:
400 except:
401 self.log.warn("Unknown error in handling IPythonApp.exec_files:")
401 self.log.warn("Unknown error in handling IPythonApp.exec_files:")
402 self.shell.showtraceback()
402 self.shell.showtraceback()
403
403
404 def _run_cmd_line_code(self):
404 def _run_cmd_line_code(self):
405 """Run code or file specified at the command-line"""
405 """Run code or file specified at the command-line"""
406 if self.code_to_run:
406 if self.code_to_run:
407 line = self.code_to_run
407 line = self.code_to_run
408 try:
408 try:
409 self.log.info("Running code given at command line (c=): %s" %
409 self.log.info("Running code given at command line (c=): %s" %
410 line)
410 line)
411 self.shell.run_cell(line, store_history=False)
411 self.shell.run_cell(line, store_history=False)
412 except:
412 except:
413 self.log.warn("Error in executing line in user namespace: %s" %
413 self.log.warn("Error in executing line in user namespace: %s" %
414 line)
414 line)
415 self.shell.showtraceback()
415 self.shell.showtraceback()
416
416
417 # Like Python itself, ignore the second if the first of these is present
417 # Like Python itself, ignore the second if the first of these is present
418 elif self.file_to_run:
418 elif self.file_to_run:
419 fname = self.file_to_run
419 fname = self.file_to_run
420 try:
420 try:
421 self._exec_file(fname)
421 self._exec_file(fname)
422 except:
422 except:
423 self.log.warn("Error in executing file in user namespace: %s" %
423 self.log.warn("Error in executing file in user namespace: %s" %
424 fname)
424 fname)
425 self.shell.showtraceback()
425 self.shell.showtraceback()
426
426
427 def _run_module(self):
427 def _run_module(self):
428 """Run module specified at the command-line."""
428 """Run module specified at the command-line."""
429 if self.module_to_run:
429 if self.module_to_run:
430 # Make sure that the module gets a proper sys.argv as if it were
430 # Make sure that the module gets a proper sys.argv as if it were
431 # run using `python -m`.
431 # run using `python -m`.
432 save_argv = sys.argv
432 save_argv = sys.argv
433 sys.argv = [sys.executable] + self.extra_args
433 sys.argv = [sys.executable] + self.extra_args
434 try:
434 try:
435 self.shell.safe_run_module(self.module_to_run,
435 self.shell.safe_run_module(self.module_to_run,
436 self.shell.user_ns)
436 self.shell.user_ns)
437 finally:
437 finally:
438 sys.argv = save_argv
438 sys.argv = save_argv
@@ -1,84 +1,83 b''
1 """ Import Qt in a manner suitable for an IPython kernel.
1 """ Import Qt in a manner suitable for an IPython kernel.
2
2
3 This is the import used for the `gui=qt` or `matplotlib=qt` initialization.
3 This is the import used for the `gui=qt` or `matplotlib=qt` initialization.
4
4
5 Import Priority:
5 Import Priority:
6
6
7 if Qt4 has been imported anywhere else:
7 if Qt4 has been imported anywhere else:
8 use that
8 use that
9
9
10 if matplotlib has been imported and doesn't support v2 (<= 1.0.1):
10 if matplotlib has been imported and doesn't support v2 (<= 1.0.1):
11 use PyQt4 @v1
11 use PyQt4 @v1
12
12
13 Next, ask ETS' QT_API env variable
13 Next, ask ETS' QT_API env variable
14
14
15 if QT_API not set:
15 if QT_API not set:
16 ask matplotlib via rcParams['backend.qt4']
16 ask matplotlib via rcParams['backend.qt4']
17 if it said PyQt:
17 if it said PyQt:
18 use PyQt4 @v1
18 use PyQt4 @v1
19 elif it said PySide:
19 elif it said PySide:
20 use PySide
20 use PySide
21
21
22 else: (matplotlib said nothing)
22 else: (matplotlib said nothing)
23 # this is the default path - nobody told us anything
23 # this is the default path - nobody told us anything
24 try:
24 try:
25 PyQt @v1
25 PyQt @v1
26 except:
26 except:
27 fallback on PySide
27 fallback on PySide
28 else:
28 else:
29 use PyQt @v2 or PySide, depending on QT_API
29 use PyQt @v2 or PySide, depending on QT_API
30 because ETS doesn't work with PyQt @v1.
30 because ETS doesn't work with PyQt @v1.
31
31
32 """
32 """
33
33
34 import os
34 import os
35 import sys
35 import sys
36
36
37 from IPython.utils.warn import warn
38 from IPython.utils.version import check_version
37 from IPython.utils.version import check_version
39 from IPython.external.qt_loaders import (load_qt, QT_API_PYSIDE,
38 from IPython.external.qt_loaders import (load_qt, QT_API_PYSIDE,
40 QT_API_PYQT, QT_API_PYQT_DEFAULT,
39 QT_API_PYQT, QT_API_PYQT_DEFAULT,
41 loaded_api)
40 loaded_api)
42
41
43 #Constraints placed on an imported matplotlib
42 #Constraints placed on an imported matplotlib
44 def matplotlib_options(mpl):
43 def matplotlib_options(mpl):
45 if mpl is None:
44 if mpl is None:
46 return
45 return
47 mpqt = mpl.rcParams.get('backend.qt4', None)
46 mpqt = mpl.rcParams.get('backend.qt4', None)
48 if mpqt is None:
47 if mpqt is None:
49 return None
48 return None
50 if mpqt.lower() == 'pyside':
49 if mpqt.lower() == 'pyside':
51 return [QT_API_PYSIDE]
50 return [QT_API_PYSIDE]
52 elif mpqt.lower() == 'pyqt4':
51 elif mpqt.lower() == 'pyqt4':
53 return [QT_API_PYQT_DEFAULT]
52 return [QT_API_PYQT_DEFAULT]
54 raise ImportError("unhandled value for backend.qt4 from matplotlib: %r" %
53 raise ImportError("unhandled value for backend.qt4 from matplotlib: %r" %
55 mpqt)
54 mpqt)
56
55
57 def get_options():
56 def get_options():
58 """Return a list of acceptable QT APIs, in decreasing order of
57 """Return a list of acceptable QT APIs, in decreasing order of
59 preference
58 preference
60 """
59 """
61 #already imported Qt somewhere. Use that
60 #already imported Qt somewhere. Use that
62 loaded = loaded_api()
61 loaded = loaded_api()
63 if loaded is not None:
62 if loaded is not None:
64 return [loaded]
63 return [loaded]
65
64
66 mpl = sys.modules.get('matplotlib', None)
65 mpl = sys.modules.get('matplotlib', None)
67
66
68 if mpl is not None and not check_version(mpl.__version__, '1.0.2'):
67 if mpl is not None and not check_version(mpl.__version__, '1.0.2'):
69 #1.0.1 only supports PyQt4 v1
68 #1.0.1 only supports PyQt4 v1
70 return [QT_API_PYQT_DEFAULT]
69 return [QT_API_PYQT_DEFAULT]
71
70
72 if os.environ.get('QT_API', None) is None:
71 if os.environ.get('QT_API', None) is None:
73 #no ETS variable. Ask mpl, then use either
72 #no ETS variable. Ask mpl, then use either
74 return matplotlib_options(mpl) or [QT_API_PYQT_DEFAULT, QT_API_PYSIDE]
73 return matplotlib_options(mpl) or [QT_API_PYQT_DEFAULT, QT_API_PYSIDE]
75
74
76 #ETS variable present. Will fallback to external.qt
75 #ETS variable present. Will fallback to external.qt
77 return None
76 return None
78
77
79 api_opts = get_options()
78 api_opts = get_options()
80 if api_opts is not None:
79 if api_opts is not None:
81 QtCore, QtGui, QtSvg, QT_API = load_qt(api_opts)
80 QtCore, QtGui, QtSvg, QT_API = load_qt(api_opts)
82
81
83 else: # use ETS variable
82 else: # use ETS variable
84 from IPython.external.qt import QtCore, QtGui, QtSvg, QT_API
83 from IPython.external.qt import QtCore, QtGui, QtSvg, QT_API
@@ -1,140 +1,137 b''
1 import io
1 import io
2 import os
2 import os
3 import zipfile
3 import zipfile
4
4
5 from tornado import web
5 from tornado import web
6
6
7 from ..base.handlers import IPythonHandler, notebook_path_regex
7 from ..base.handlers import IPythonHandler, notebook_path_regex
8 from IPython.nbformat.current import to_notebook_json
8 from IPython.nbformat.current import to_notebook_json
9
9
10 from IPython.utils import tz
11 from IPython.utils.py3compat import cast_bytes
10 from IPython.utils.py3compat import cast_bytes
12
11
13 import sys
14
15 def find_resource_files(output_files_dir):
12 def find_resource_files(output_files_dir):
16 files = []
13 files = []
17 for dirpath, dirnames, filenames in os.walk(output_files_dir):
14 for dirpath, dirnames, filenames in os.walk(output_files_dir):
18 files.extend([os.path.join(dirpath, f) for f in filenames])
15 files.extend([os.path.join(dirpath, f) for f in filenames])
19 return files
16 return files
20
17
21 def respond_zip(handler, name, output, resources):
18 def respond_zip(handler, name, output, resources):
22 """Zip up the output and resource files and respond with the zip file.
19 """Zip up the output and resource files and respond with the zip file.
23
20
24 Returns True if it has served a zip file, False if there are no resource
21 Returns True if it has served a zip file, False if there are no resource
25 files, in which case we serve the plain output file.
22 files, in which case we serve the plain output file.
26 """
23 """
27 # Check if we have resource files we need to zip
24 # Check if we have resource files we need to zip
28 output_files = resources.get('outputs', None)
25 output_files = resources.get('outputs', None)
29 if not output_files:
26 if not output_files:
30 return False
27 return False
31
28
32 # Headers
29 # Headers
33 zip_filename = os.path.splitext(name)[0] + '.zip'
30 zip_filename = os.path.splitext(name)[0] + '.zip'
34 handler.set_header('Content-Disposition',
31 handler.set_header('Content-Disposition',
35 'attachment; filename="%s"' % zip_filename)
32 'attachment; filename="%s"' % zip_filename)
36 handler.set_header('Content-Type', 'application/zip')
33 handler.set_header('Content-Type', 'application/zip')
37
34
38 # Prepare the zip file
35 # Prepare the zip file
39 buffer = io.BytesIO()
36 buffer = io.BytesIO()
40 zipf = zipfile.ZipFile(buffer, mode='w', compression=zipfile.ZIP_DEFLATED)
37 zipf = zipfile.ZipFile(buffer, mode='w', compression=zipfile.ZIP_DEFLATED)
41 output_filename = os.path.splitext(name)[0] + '.' + resources['output_extension']
38 output_filename = os.path.splitext(name)[0] + '.' + resources['output_extension']
42 zipf.writestr(output_filename, cast_bytes(output, 'utf-8'))
39 zipf.writestr(output_filename, cast_bytes(output, 'utf-8'))
43 for filename, data in output_files.items():
40 for filename, data in output_files.items():
44 zipf.writestr(os.path.basename(filename), data)
41 zipf.writestr(os.path.basename(filename), data)
45 zipf.close()
42 zipf.close()
46
43
47 handler.finish(buffer.getvalue())
44 handler.finish(buffer.getvalue())
48 return True
45 return True
49
46
50 def get_exporter(format, **kwargs):
47 def get_exporter(format, **kwargs):
51 """get an exporter, raising appropriate errors"""
48 """get an exporter, raising appropriate errors"""
52 # if this fails, will raise 500
49 # if this fails, will raise 500
53 try:
50 try:
54 from IPython.nbconvert.exporters.export import exporter_map
51 from IPython.nbconvert.exporters.export import exporter_map
55 except ImportError as e:
52 except ImportError as e:
56 raise web.HTTPError(500, "Could not import nbconvert: %s" % e)
53 raise web.HTTPError(500, "Could not import nbconvert: %s" % e)
57
54
58 try:
55 try:
59 Exporter = exporter_map[format]
56 Exporter = exporter_map[format]
60 except KeyError:
57 except KeyError:
61 # should this be 400?
58 # should this be 400?
62 raise web.HTTPError(404, u"No exporter for format: %s" % format)
59 raise web.HTTPError(404, u"No exporter for format: %s" % format)
63
60
64 try:
61 try:
65 return Exporter(**kwargs)
62 return Exporter(**kwargs)
66 except Exception as e:
63 except Exception as e:
67 raise web.HTTPError(500, "Could not construct Exporter: %s" % e)
64 raise web.HTTPError(500, "Could not construct Exporter: %s" % e)
68
65
69 class NbconvertFileHandler(IPythonHandler):
66 class NbconvertFileHandler(IPythonHandler):
70
67
71 SUPPORTED_METHODS = ('GET',)
68 SUPPORTED_METHODS = ('GET',)
72
69
73 @web.authenticated
70 @web.authenticated
74 def get(self, format, path='', name=None):
71 def get(self, format, path='', name=None):
75
72
76 exporter = get_exporter(format, config=self.config, log=self.log)
73 exporter = get_exporter(format, config=self.config, log=self.log)
77
74
78 path = path.strip('/')
75 path = path.strip('/')
79 model = self.notebook_manager.get_notebook(name=name, path=path)
76 model = self.notebook_manager.get_notebook(name=name, path=path)
80
77
81 self.set_header('Last-Modified', model['last_modified'])
78 self.set_header('Last-Modified', model['last_modified'])
82
79
83 try:
80 try:
84 output, resources = exporter.from_notebook_node(model['content'])
81 output, resources = exporter.from_notebook_node(model['content'])
85 except Exception as e:
82 except Exception as e:
86 raise web.HTTPError(500, "nbconvert failed: %s" % e)
83 raise web.HTTPError(500, "nbconvert failed: %s" % e)
87
84
88 if respond_zip(self, name, output, resources):
85 if respond_zip(self, name, output, resources):
89 return
86 return
90
87
91 # Force download if requested
88 # Force download if requested
92 if self.get_argument('download', 'false').lower() == 'true':
89 if self.get_argument('download', 'false').lower() == 'true':
93 filename = os.path.splitext(name)[0] + '.' + resources['output_extension']
90 filename = os.path.splitext(name)[0] + '.' + resources['output_extension']
94 self.set_header('Content-Disposition',
91 self.set_header('Content-Disposition',
95 'attachment; filename="%s"' % filename)
92 'attachment; filename="%s"' % filename)
96
93
97 # MIME type
94 # MIME type
98 if exporter.output_mimetype:
95 if exporter.output_mimetype:
99 self.set_header('Content-Type',
96 self.set_header('Content-Type',
100 '%s; charset=utf-8' % exporter.output_mimetype)
97 '%s; charset=utf-8' % exporter.output_mimetype)
101
98
102 self.finish(output)
99 self.finish(output)
103
100
104 class NbconvertPostHandler(IPythonHandler):
101 class NbconvertPostHandler(IPythonHandler):
105 SUPPORTED_METHODS = ('POST',)
102 SUPPORTED_METHODS = ('POST',)
106
103
107 @web.authenticated
104 @web.authenticated
108 def post(self, format):
105 def post(self, format):
109 exporter = get_exporter(format, config=self.config)
106 exporter = get_exporter(format, config=self.config)
110
107
111 model = self.get_json_body()
108 model = self.get_json_body()
112 nbnode = to_notebook_json(model['content'])
109 nbnode = to_notebook_json(model['content'])
113
110
114 try:
111 try:
115 output, resources = exporter.from_notebook_node(nbnode)
112 output, resources = exporter.from_notebook_node(nbnode)
116 except Exception as e:
113 except Exception as e:
117 raise web.HTTPError(500, "nbconvert failed: %s" % e)
114 raise web.HTTPError(500, "nbconvert failed: %s" % e)
118
115
119 if respond_zip(self, nbnode.metadata.name, output, resources):
116 if respond_zip(self, nbnode.metadata.name, output, resources):
120 return
117 return
121
118
122 # MIME type
119 # MIME type
123 if exporter.output_mimetype:
120 if exporter.output_mimetype:
124 self.set_header('Content-Type',
121 self.set_header('Content-Type',
125 '%s; charset=utf-8' % exporter.output_mimetype)
122 '%s; charset=utf-8' % exporter.output_mimetype)
126
123
127 self.finish(output)
124 self.finish(output)
128
125
129 #-----------------------------------------------------------------------------
126 #-----------------------------------------------------------------------------
130 # URL to handler mappings
127 # URL to handler mappings
131 #-----------------------------------------------------------------------------
128 #-----------------------------------------------------------------------------
132
129
133 _format_regex = r"(?P<format>\w+)"
130 _format_regex = r"(?P<format>\w+)"
134
131
135
132
136 default_handlers = [
133 default_handlers = [
137 (r"/nbconvert/%s%s" % (_format_regex, notebook_path_regex),
134 (r"/nbconvert/%s%s" % (_format_regex, notebook_path_regex),
138 NbconvertFileHandler),
135 NbconvertFileHandler),
139 (r"/nbconvert/%s" % _format_regex, NbconvertPostHandler),
136 (r"/nbconvert/%s" % _format_regex, NbconvertPostHandler),
140 ]
137 ]
@@ -1,132 +1,130 b''
1 """A kernel manager relating notebooks and kernels
1 """A kernel manager relating notebooks and kernels
2
2
3 Authors:
3 Authors:
4
4
5 * Brian Granger
5 * Brian Granger
6 """
6 """
7
7
8 #-----------------------------------------------------------------------------
8 #-----------------------------------------------------------------------------
9 # Copyright (C) 2013 The IPython Development Team
9 # Copyright (C) 2013 The IPython Development Team
10 #
10 #
11 # Distributed under the terms of the BSD License. The full license is in
11 # Distributed under the terms of the BSD License. The full license is in
12 # the file COPYING, distributed as part of this software.
12 # the file COPYING, distributed as part of this software.
13 #-----------------------------------------------------------------------------
13 #-----------------------------------------------------------------------------
14
14
15 #-----------------------------------------------------------------------------
15 #-----------------------------------------------------------------------------
16 # Imports
16 # Imports
17 #-----------------------------------------------------------------------------
17 #-----------------------------------------------------------------------------
18
18
19 import os
19 import os
20
20
21 from tornado import web
21 from tornado import web
22
22
23 from IPython.kernel.multikernelmanager import MultiKernelManager
23 from IPython.kernel.multikernelmanager import MultiKernelManager
24 from IPython.utils.traitlets import (
24 from IPython.utils.traitlets import List, Unicode, TraitError
25 Dict, List, Unicode,
26 )
27
25
28 from IPython.html.utils import to_os_path
26 from IPython.html.utils import to_os_path
29 from IPython.utils.py3compat import getcwd
27 from IPython.utils.py3compat import getcwd
30
28
31 #-----------------------------------------------------------------------------
29 #-----------------------------------------------------------------------------
32 # Classes
30 # Classes
33 #-----------------------------------------------------------------------------
31 #-----------------------------------------------------------------------------
34
32
35
33
36 class MappingKernelManager(MultiKernelManager):
34 class MappingKernelManager(MultiKernelManager):
37 """A KernelManager that handles notebook mapping and HTTP error handling"""
35 """A KernelManager that handles notebook mapping and HTTP error handling"""
38
36
39 def _kernel_manager_class_default(self):
37 def _kernel_manager_class_default(self):
40 return "IPython.kernel.ioloop.IOLoopKernelManager"
38 return "IPython.kernel.ioloop.IOLoopKernelManager"
41
39
42 kernel_argv = List(Unicode)
40 kernel_argv = List(Unicode)
43
41
44 root_dir = Unicode(getcwd(), config=True)
42 root_dir = Unicode(getcwd(), config=True)
45
43
46 def _root_dir_changed(self, name, old, new):
44 def _root_dir_changed(self, name, old, new):
47 """Do a bit of validation of the root dir."""
45 """Do a bit of validation of the root dir."""
48 if not os.path.isabs(new):
46 if not os.path.isabs(new):
49 # If we receive a non-absolute path, make it absolute.
47 # If we receive a non-absolute path, make it absolute.
50 self.root_dir = os.path.abspath(new)
48 self.root_dir = os.path.abspath(new)
51 return
49 return
52 if not os.path.exists(new) or not os.path.isdir(new):
50 if not os.path.exists(new) or not os.path.isdir(new):
53 raise TraitError("kernel root dir %r is not a directory" % new)
51 raise TraitError("kernel root dir %r is not a directory" % new)
54
52
55 #-------------------------------------------------------------------------
53 #-------------------------------------------------------------------------
56 # Methods for managing kernels and sessions
54 # Methods for managing kernels and sessions
57 #-------------------------------------------------------------------------
55 #-------------------------------------------------------------------------
58
56
59 def _handle_kernel_died(self, kernel_id):
57 def _handle_kernel_died(self, kernel_id):
60 """notice that a kernel died"""
58 """notice that a kernel died"""
61 self.log.warn("Kernel %s died, removing from map.", kernel_id)
59 self.log.warn("Kernel %s died, removing from map.", kernel_id)
62 self.remove_kernel(kernel_id)
60 self.remove_kernel(kernel_id)
63
61
64 def cwd_for_path(self, path):
62 def cwd_for_path(self, path):
65 """Turn API path into absolute OS path."""
63 """Turn API path into absolute OS path."""
66 # short circuit for NotebookManagers that pass in absolute paths
64 # short circuit for NotebookManagers that pass in absolute paths
67 if os.path.exists(path):
65 if os.path.exists(path):
68 return path
66 return path
69
67
70 os_path = to_os_path(path, self.root_dir)
68 os_path = to_os_path(path, self.root_dir)
71 # in the case of notebooks and kernels not being on the same filesystem,
69 # in the case of notebooks and kernels not being on the same filesystem,
72 # walk up to root_dir if the paths don't exist
70 # walk up to root_dir if the paths don't exist
73 while not os.path.exists(os_path) and os_path != self.root_dir:
71 while not os.path.exists(os_path) and os_path != self.root_dir:
74 os_path = os.path.dirname(os_path)
72 os_path = os.path.dirname(os_path)
75 return os_path
73 return os_path
76
74
77 def start_kernel(self, kernel_id=None, path=None, **kwargs):
75 def start_kernel(self, kernel_id=None, path=None, **kwargs):
78 """Start a kernel for a session an return its kernel_id.
76 """Start a kernel for a session an return its kernel_id.
79
77
80 Parameters
78 Parameters
81 ----------
79 ----------
82 kernel_id : uuid
80 kernel_id : uuid
83 The uuid to associate the new kernel with. If this
81 The uuid to associate the new kernel with. If this
84 is not None, this kernel will be persistent whenever it is
82 is not None, this kernel will be persistent whenever it is
85 requested.
83 requested.
86 path : API path
84 path : API path
87 The API path (unicode, '/' delimited) for the cwd.
85 The API path (unicode, '/' delimited) for the cwd.
88 Will be transformed to an OS path relative to root_dir.
86 Will be transformed to an OS path relative to root_dir.
89 """
87 """
90 if kernel_id is None:
88 if kernel_id is None:
91 kwargs['extra_arguments'] = self.kernel_argv
89 kwargs['extra_arguments'] = self.kernel_argv
92 if path is not None:
90 if path is not None:
93 kwargs['cwd'] = self.cwd_for_path(path)
91 kwargs['cwd'] = self.cwd_for_path(path)
94 kernel_id = super(MappingKernelManager, self).start_kernel(**kwargs)
92 kernel_id = super(MappingKernelManager, self).start_kernel(**kwargs)
95 self.log.info("Kernel started: %s" % kernel_id)
93 self.log.info("Kernel started: %s" % kernel_id)
96 self.log.debug("Kernel args: %r" % kwargs)
94 self.log.debug("Kernel args: %r" % kwargs)
97 # register callback for failed auto-restart
95 # register callback for failed auto-restart
98 self.add_restart_callback(kernel_id,
96 self.add_restart_callback(kernel_id,
99 lambda : self._handle_kernel_died(kernel_id),
97 lambda : self._handle_kernel_died(kernel_id),
100 'dead',
98 'dead',
101 )
99 )
102 else:
100 else:
103 self._check_kernel_id(kernel_id)
101 self._check_kernel_id(kernel_id)
104 self.log.info("Using existing kernel: %s" % kernel_id)
102 self.log.info("Using existing kernel: %s" % kernel_id)
105 return kernel_id
103 return kernel_id
106
104
107 def shutdown_kernel(self, kernel_id, now=False):
105 def shutdown_kernel(self, kernel_id, now=False):
108 """Shutdown a kernel by kernel_id"""
106 """Shutdown a kernel by kernel_id"""
109 self._check_kernel_id(kernel_id)
107 self._check_kernel_id(kernel_id)
110 super(MappingKernelManager, self).shutdown_kernel(kernel_id, now=now)
108 super(MappingKernelManager, self).shutdown_kernel(kernel_id, now=now)
111
109
112 def kernel_model(self, kernel_id):
110 def kernel_model(self, kernel_id):
113 """Return a dictionary of kernel information described in the
111 """Return a dictionary of kernel information described in the
114 JSON standard model."""
112 JSON standard model."""
115 self._check_kernel_id(kernel_id)
113 self._check_kernel_id(kernel_id)
116 model = {"id":kernel_id}
114 model = {"id":kernel_id}
117 return model
115 return model
118
116
119 def list_kernels(self):
117 def list_kernels(self):
120 """Returns a list of kernel_id's of kernels running."""
118 """Returns a list of kernel_id's of kernels running."""
121 kernels = []
119 kernels = []
122 kernel_ids = super(MappingKernelManager, self).list_kernel_ids()
120 kernel_ids = super(MappingKernelManager, self).list_kernel_ids()
123 for kernel_id in kernel_ids:
121 for kernel_id in kernel_ids:
124 model = self.kernel_model(kernel_id)
122 model = self.kernel_model(kernel_id)
125 kernels.append(model)
123 kernels.append(model)
126 return kernels
124 return kernels
127
125
128 # override _check_kernel_id to raise 404 instead of KeyError
126 # override _check_kernel_id to raise 404 instead of KeyError
129 def _check_kernel_id(self, kernel_id):
127 def _check_kernel_id(self, kernel_id):
130 """Check a that a kernel_id exists and raise 404 if not."""
128 """Check a that a kernel_id exists and raise 404 if not."""
131 if kernel_id not in self:
129 if kernel_id not in self:
132 raise web.HTTPError(404, u'Kernel does not exist: %s' % kernel_id)
130 raise web.HTTPError(404, u'Kernel does not exist: %s' % kernel_id)
@@ -1,122 +1,118 b''
1 """Test the kernels service API."""
1 """Test the kernels service API."""
2
2
3
3
4 import os
5 import sys
6 import json
7
8 import requests
4 import requests
9
5
10 from IPython.html.utils import url_path_join
6 from IPython.html.utils import url_path_join
11 from IPython.html.tests.launchnotebook import NotebookTestBase, assert_http_error
7 from IPython.html.tests.launchnotebook import NotebookTestBase, assert_http_error
12
8
13 class KernelAPI(object):
9 class KernelAPI(object):
14 """Wrapper for kernel REST API requests"""
10 """Wrapper for kernel REST API requests"""
15 def __init__(self, base_url):
11 def __init__(self, base_url):
16 self.base_url = base_url
12 self.base_url = base_url
17
13
18 def _req(self, verb, path, body=None):
14 def _req(self, verb, path, body=None):
19 response = requests.request(verb,
15 response = requests.request(verb,
20 url_path_join(self.base_url, 'api/kernels', path), data=body)
16 url_path_join(self.base_url, 'api/kernels', path), data=body)
21
17
22 if 400 <= response.status_code < 600:
18 if 400 <= response.status_code < 600:
23 try:
19 try:
24 response.reason = response.json()['message']
20 response.reason = response.json()['message']
25 except:
21 except:
26 pass
22 pass
27 response.raise_for_status()
23 response.raise_for_status()
28
24
29 return response
25 return response
30
26
31 def list(self):
27 def list(self):
32 return self._req('GET', '')
28 return self._req('GET', '')
33
29
34 def get(self, id):
30 def get(self, id):
35 return self._req('GET', id)
31 return self._req('GET', id)
36
32
37 def start(self):
33 def start(self):
38 return self._req('POST', '')
34 return self._req('POST', '')
39
35
40 def shutdown(self, id):
36 def shutdown(self, id):
41 return self._req('DELETE', id)
37 return self._req('DELETE', id)
42
38
43 def interrupt(self, id):
39 def interrupt(self, id):
44 return self._req('POST', url_path_join(id, 'interrupt'))
40 return self._req('POST', url_path_join(id, 'interrupt'))
45
41
46 def restart(self, id):
42 def restart(self, id):
47 return self._req('POST', url_path_join(id, 'restart'))
43 return self._req('POST', url_path_join(id, 'restart'))
48
44
49 class KernelAPITest(NotebookTestBase):
45 class KernelAPITest(NotebookTestBase):
50 """Test the kernels web service API"""
46 """Test the kernels web service API"""
51 def setUp(self):
47 def setUp(self):
52 self.kern_api = KernelAPI(self.base_url())
48 self.kern_api = KernelAPI(self.base_url())
53
49
54 def tearDown(self):
50 def tearDown(self):
55 for k in self.kern_api.list().json():
51 for k in self.kern_api.list().json():
56 self.kern_api.shutdown(k['id'])
52 self.kern_api.shutdown(k['id'])
57
53
58 def test__no_kernels(self):
54 def test__no_kernels(self):
59 """Make sure there are no kernels running at the start"""
55 """Make sure there are no kernels running at the start"""
60 kernels = self.kern_api.list().json()
56 kernels = self.kern_api.list().json()
61 self.assertEqual(kernels, [])
57 self.assertEqual(kernels, [])
62
58
63 def test_main_kernel_handler(self):
59 def test_main_kernel_handler(self):
64 # POST request
60 # POST request
65 r = self.kern_api.start()
61 r = self.kern_api.start()
66 kern1 = r.json()
62 kern1 = r.json()
67 self.assertEqual(r.headers['location'], '/api/kernels/' + kern1['id'])
63 self.assertEqual(r.headers['location'], '/api/kernels/' + kern1['id'])
68 self.assertEqual(r.status_code, 201)
64 self.assertEqual(r.status_code, 201)
69 self.assertIsInstance(kern1, dict)
65 self.assertIsInstance(kern1, dict)
70
66
71 # GET request
67 # GET request
72 r = self.kern_api.list()
68 r = self.kern_api.list()
73 self.assertEqual(r.status_code, 200)
69 self.assertEqual(r.status_code, 200)
74 assert isinstance(r.json(), list)
70 assert isinstance(r.json(), list)
75 self.assertEqual(r.json()[0]['id'], kern1['id'])
71 self.assertEqual(r.json()[0]['id'], kern1['id'])
76
72
77 # create another kernel and check that they both are added to the
73 # create another kernel and check that they both are added to the
78 # list of kernels from a GET request
74 # list of kernels from a GET request
79 kern2 = self.kern_api.start().json()
75 kern2 = self.kern_api.start().json()
80 assert isinstance(kern2, dict)
76 assert isinstance(kern2, dict)
81 r = self.kern_api.list()
77 r = self.kern_api.list()
82 kernels = r.json()
78 kernels = r.json()
83 self.assertEqual(r.status_code, 200)
79 self.assertEqual(r.status_code, 200)
84 assert isinstance(kernels, list)
80 assert isinstance(kernels, list)
85 self.assertEqual(len(kernels), 2)
81 self.assertEqual(len(kernels), 2)
86
82
87 # Interrupt a kernel
83 # Interrupt a kernel
88 r = self.kern_api.interrupt(kern2['id'])
84 r = self.kern_api.interrupt(kern2['id'])
89 self.assertEqual(r.status_code, 204)
85 self.assertEqual(r.status_code, 204)
90
86
91 # Restart a kernel
87 # Restart a kernel
92 r = self.kern_api.restart(kern2['id'])
88 r = self.kern_api.restart(kern2['id'])
93 self.assertEqual(r.headers['Location'], '/api/kernels/'+kern2['id'])
89 self.assertEqual(r.headers['Location'], '/api/kernels/'+kern2['id'])
94 rekern = r.json()
90 rekern = r.json()
95 self.assertEqual(rekern['id'], kern2['id'])
91 self.assertEqual(rekern['id'], kern2['id'])
96
92
97 def test_kernel_handler(self):
93 def test_kernel_handler(self):
98 # GET kernel with given id
94 # GET kernel with given id
99 kid = self.kern_api.start().json()['id']
95 kid = self.kern_api.start().json()['id']
100 r = self.kern_api.get(kid)
96 r = self.kern_api.get(kid)
101 kern1 = r.json()
97 kern1 = r.json()
102 self.assertEqual(r.status_code, 200)
98 self.assertEqual(r.status_code, 200)
103 assert isinstance(kern1, dict)
99 assert isinstance(kern1, dict)
104 self.assertIn('id', kern1)
100 self.assertIn('id', kern1)
105 self.assertEqual(kern1['id'], kid)
101 self.assertEqual(kern1['id'], kid)
106
102
107 # Request a bad kernel id and check that a JSON
103 # Request a bad kernel id and check that a JSON
108 # message is returned!
104 # message is returned!
109 bad_id = '111-111-111-111-111'
105 bad_id = '111-111-111-111-111'
110 with assert_http_error(404, 'Kernel does not exist: ' + bad_id):
106 with assert_http_error(404, 'Kernel does not exist: ' + bad_id):
111 self.kern_api.get(bad_id)
107 self.kern_api.get(bad_id)
112
108
113 # DELETE kernel with id
109 # DELETE kernel with id
114 r = self.kern_api.shutdown(kid)
110 r = self.kern_api.shutdown(kid)
115 self.assertEqual(r.status_code, 204)
111 self.assertEqual(r.status_code, 204)
116 kernels = self.kern_api.list().json()
112 kernels = self.kern_api.list().json()
117 self.assertEqual(kernels, [])
113 self.assertEqual(kernels, [])
118
114
119 # Request to delete a non-existent kernel id
115 # Request to delete a non-existent kernel id
120 bad_id = '111-111-111-111-111'
116 bad_id = '111-111-111-111-111'
121 with assert_http_error(404, 'Kernel does not exist: ' + bad_id):
117 with assert_http_error(404, 'Kernel does not exist: ' + bad_id):
122 self.kern_api.shutdown(bad_id)
118 self.kern_api.shutdown(bad_id)
@@ -1,103 +1,101 b''
1 """Tornado handlers for the tree view.
1 """Tornado handlers for the tree view.
2
2
3 Authors:
3 Authors:
4
4
5 * Brian Granger
5 * Brian Granger
6 """
6 """
7
7
8 #-----------------------------------------------------------------------------
8 #-----------------------------------------------------------------------------
9 # Copyright (C) 2011 The IPython Development Team
9 # Copyright (C) 2011 The IPython Development Team
10 #
10 #
11 # Distributed under the terms of the BSD License. The full license is in
11 # Distributed under the terms of the BSD License. The full license is in
12 # the file COPYING, distributed as part of this software.
12 # the file COPYING, distributed as part of this software.
13 #-----------------------------------------------------------------------------
13 #-----------------------------------------------------------------------------
14
14
15 #-----------------------------------------------------------------------------
15 #-----------------------------------------------------------------------------
16 # Imports
16 # Imports
17 #-----------------------------------------------------------------------------
17 #-----------------------------------------------------------------------------
18 import os
19
20 from tornado import web
18 from tornado import web
21 from ..base.handlers import IPythonHandler, notebook_path_regex, path_regex
19 from ..base.handlers import IPythonHandler, notebook_path_regex, path_regex
22 from ..utils import url_path_join, path2url, url2path, url_escape, is_hidden
20 from ..utils import url_path_join, url_escape
23
21
24 #-----------------------------------------------------------------------------
22 #-----------------------------------------------------------------------------
25 # Handlers
23 # Handlers
26 #-----------------------------------------------------------------------------
24 #-----------------------------------------------------------------------------
27
25
28
26
29 class TreeHandler(IPythonHandler):
27 class TreeHandler(IPythonHandler):
30 """Render the tree view, listing notebooks, clusters, etc."""
28 """Render the tree view, listing notebooks, clusters, etc."""
31
29
32 def generate_breadcrumbs(self, path):
30 def generate_breadcrumbs(self, path):
33 breadcrumbs = [(url_escape(url_path_join(self.base_url, 'tree')), '')]
31 breadcrumbs = [(url_escape(url_path_join(self.base_url, 'tree')), '')]
34 comps = path.split('/')
32 comps = path.split('/')
35 ncomps = len(comps)
33 ncomps = len(comps)
36 for i in range(ncomps):
34 for i in range(ncomps):
37 if comps[i]:
35 if comps[i]:
38 link = url_escape(url_path_join(self.base_url, 'tree', *comps[0:i+1]))
36 link = url_escape(url_path_join(self.base_url, 'tree', *comps[0:i+1]))
39 breadcrumbs.append((link, comps[i]))
37 breadcrumbs.append((link, comps[i]))
40 return breadcrumbs
38 return breadcrumbs
41
39
42 def generate_page_title(self, path):
40 def generate_page_title(self, path):
43 comps = path.split('/')
41 comps = path.split('/')
44 if len(comps) > 3:
42 if len(comps) > 3:
45 for i in range(len(comps)-2):
43 for i in range(len(comps)-2):
46 comps.pop(0)
44 comps.pop(0)
47 page_title = url_escape(url_path_join(*comps))
45 page_title = url_escape(url_path_join(*comps))
48 if page_title:
46 if page_title:
49 return page_title+'/'
47 return page_title+'/'
50 else:
48 else:
51 return 'Home'
49 return 'Home'
52
50
53 @web.authenticated
51 @web.authenticated
54 def get(self, path='', name=None):
52 def get(self, path='', name=None):
55 path = path.strip('/')
53 path = path.strip('/')
56 nbm = self.notebook_manager
54 nbm = self.notebook_manager
57 if name is not None:
55 if name is not None:
58 # is a notebook, redirect to notebook handler
56 # is a notebook, redirect to notebook handler
59 url = url_escape(url_path_join(
57 url = url_escape(url_path_join(
60 self.base_url, 'notebooks', path, name
58 self.base_url, 'notebooks', path, name
61 ))
59 ))
62 self.log.debug("Redirecting %s to %s", self.request.path, url)
60 self.log.debug("Redirecting %s to %s", self.request.path, url)
63 self.redirect(url)
61 self.redirect(url)
64 else:
62 else:
65 if not nbm.path_exists(path=path):
63 if not nbm.path_exists(path=path):
66 # Directory is hidden or does not exist.
64 # Directory is hidden or does not exist.
67 raise web.HTTPError(404)
65 raise web.HTTPError(404)
68 elif nbm.is_hidden(path):
66 elif nbm.is_hidden(path):
69 self.log.info("Refusing to serve hidden directory, via 404 Error")
67 self.log.info("Refusing to serve hidden directory, via 404 Error")
70 raise web.HTTPError(404)
68 raise web.HTTPError(404)
71 breadcrumbs = self.generate_breadcrumbs(path)
69 breadcrumbs = self.generate_breadcrumbs(path)
72 page_title = self.generate_page_title(path)
70 page_title = self.generate_page_title(path)
73 self.write(self.render_template('tree.html',
71 self.write(self.render_template('tree.html',
74 project=self.project_dir,
72 project=self.project_dir,
75 page_title=page_title,
73 page_title=page_title,
76 notebook_path=path,
74 notebook_path=path,
77 breadcrumbs=breadcrumbs
75 breadcrumbs=breadcrumbs
78 ))
76 ))
79
77
80
78
81 class TreeRedirectHandler(IPythonHandler):
79 class TreeRedirectHandler(IPythonHandler):
82 """Redirect a request to the corresponding tree URL"""
80 """Redirect a request to the corresponding tree URL"""
83
81
84 @web.authenticated
82 @web.authenticated
85 def get(self, path=''):
83 def get(self, path=''):
86 url = url_escape(url_path_join(
84 url = url_escape(url_path_join(
87 self.base_url, 'tree', path.strip('/')
85 self.base_url, 'tree', path.strip('/')
88 ))
86 ))
89 self.log.debug("Redirecting %s to %s", self.request.path, url)
87 self.log.debug("Redirecting %s to %s", self.request.path, url)
90 self.redirect(url)
88 self.redirect(url)
91
89
92
90
93 #-----------------------------------------------------------------------------
91 #-----------------------------------------------------------------------------
94 # URL to handler mappings
92 # URL to handler mappings
95 #-----------------------------------------------------------------------------
93 #-----------------------------------------------------------------------------
96
94
97
95
98 default_handlers = [
96 default_handlers = [
99 (r"/tree%s" % notebook_path_regex, TreeHandler),
97 (r"/tree%s" % notebook_path_regex, TreeHandler),
100 (r"/tree%s" % path_regex, TreeHandler),
98 (r"/tree%s" % path_regex, TreeHandler),
101 (r"/tree", TreeHandler),
99 (r"/tree", TreeHandler),
102 (r"/", TreeRedirectHandler),
100 (r"/", TreeRedirectHandler),
103 ]
101 ]
@@ -1,312 +1,311 b''
1 #!/usr/bin/env python
1 #!/usr/bin/env python
2 """NbConvert is a utility for conversion of .ipynb files.
2 """NbConvert is a utility for conversion of .ipynb files.
3
3
4 Command-line interface for the NbConvert conversion utility.
4 Command-line interface for the NbConvert conversion utility.
5 """
5 """
6
6
7 # Copyright (c) IPython Development Team.
7 # Copyright (c) IPython Development Team.
8 # Distributed under the terms of the Modified BSD License.
8 # Distributed under the terms of the Modified BSD License.
9
9
10 from __future__ import print_function
10 from __future__ import print_function
11
11
12 import logging
12 import logging
13 import sys
13 import sys
14 import os
14 import os
15 import glob
15 import glob
16
16
17 from IPython.core.application import BaseIPythonApplication, base_aliases, base_flags
17 from IPython.core.application import BaseIPythonApplication, base_aliases, base_flags
18 from IPython.core.profiledir import ProfileDir
18 from IPython.core.profiledir import ProfileDir
19 from IPython.config import catch_config_error, Configurable
19 from IPython.config import catch_config_error, Configurable
20 from IPython.utils.traitlets import (
20 from IPython.utils.traitlets import (
21 Unicode, List, Instance, DottedObjectName, Type, CaselessStrEnum,
21 Unicode, List, Instance, DottedObjectName, Type, CaselessStrEnum,
22 )
22 )
23 from IPython.utils.importstring import import_item
23 from IPython.utils.importstring import import_item
24 from IPython.utils.text import dedent
25
24
26 from .exporters.export import get_export_names, exporter_map
25 from .exporters.export import get_export_names, exporter_map
27 from IPython.nbconvert import exporters, preprocessors, writers, postprocessors
26 from IPython.nbconvert import exporters, preprocessors, writers, postprocessors
28 from .utils.base import NbConvertBase
27 from .utils.base import NbConvertBase
29 from .utils.exceptions import ConversionException
28 from .utils.exceptions import ConversionException
30
29
31 #-----------------------------------------------------------------------------
30 #-----------------------------------------------------------------------------
32 #Classes and functions
31 #Classes and functions
33 #-----------------------------------------------------------------------------
32 #-----------------------------------------------------------------------------
34
33
35 class DottedOrNone(DottedObjectName):
34 class DottedOrNone(DottedObjectName):
36 """
35 """
37 A string holding a valid dotted object name in Python, such as A.b3._c
36 A string holding a valid dotted object name in Python, such as A.b3._c
38 Also allows for None type."""
37 Also allows for None type."""
39
38
40 default_value = u''
39 default_value = u''
41
40
42 def validate(self, obj, value):
41 def validate(self, obj, value):
43 if value is not None and len(value) > 0:
42 if value is not None and len(value) > 0:
44 return super(DottedOrNone, self).validate(obj, value)
43 return super(DottedOrNone, self).validate(obj, value)
45 else:
44 else:
46 return value
45 return value
47
46
48 nbconvert_aliases = {}
47 nbconvert_aliases = {}
49 nbconvert_aliases.update(base_aliases)
48 nbconvert_aliases.update(base_aliases)
50 nbconvert_aliases.update({
49 nbconvert_aliases.update({
51 'to' : 'NbConvertApp.export_format',
50 'to' : 'NbConvertApp.export_format',
52 'template' : 'TemplateExporter.template_file',
51 'template' : 'TemplateExporter.template_file',
53 'writer' : 'NbConvertApp.writer_class',
52 'writer' : 'NbConvertApp.writer_class',
54 'post': 'NbConvertApp.postprocessor_class',
53 'post': 'NbConvertApp.postprocessor_class',
55 'output': 'NbConvertApp.output_base',
54 'output': 'NbConvertApp.output_base',
56 'reveal-prefix': 'RevealHelpPreprocessor.url_prefix',
55 'reveal-prefix': 'RevealHelpPreprocessor.url_prefix',
57 })
56 })
58
57
59 nbconvert_flags = {}
58 nbconvert_flags = {}
60 nbconvert_flags.update(base_flags)
59 nbconvert_flags.update(base_flags)
61 nbconvert_flags.update({
60 nbconvert_flags.update({
62 'stdout' : (
61 'stdout' : (
63 {'NbConvertApp' : {'writer_class' : "StdoutWriter"}},
62 {'NbConvertApp' : {'writer_class' : "StdoutWriter"}},
64 "Write notebook output to stdout instead of files."
63 "Write notebook output to stdout instead of files."
65 )
64 )
66 })
65 })
67
66
68
67
69 class NbConvertApp(BaseIPythonApplication):
68 class NbConvertApp(BaseIPythonApplication):
70 """Application used to convert from notebook file type (``*.ipynb``)"""
69 """Application used to convert from notebook file type (``*.ipynb``)"""
71
70
72 name = 'ipython-nbconvert'
71 name = 'ipython-nbconvert'
73 aliases = nbconvert_aliases
72 aliases = nbconvert_aliases
74 flags = nbconvert_flags
73 flags = nbconvert_flags
75
74
76 def _log_level_default(self):
75 def _log_level_default(self):
77 return logging.INFO
76 return logging.INFO
78
77
79 def _classes_default(self):
78 def _classes_default(self):
80 classes = [NbConvertBase, ProfileDir]
79 classes = [NbConvertBase, ProfileDir]
81 for pkg in (exporters, preprocessors, writers, postprocessors):
80 for pkg in (exporters, preprocessors, writers, postprocessors):
82 for name in dir(pkg):
81 for name in dir(pkg):
83 cls = getattr(pkg, name)
82 cls = getattr(pkg, name)
84 if isinstance(cls, type) and issubclass(cls, Configurable):
83 if isinstance(cls, type) and issubclass(cls, Configurable):
85 classes.append(cls)
84 classes.append(cls)
86
85
87 return classes
86 return classes
88
87
89 description = Unicode(
88 description = Unicode(
90 u"""This application is used to convert notebook files (*.ipynb)
89 u"""This application is used to convert notebook files (*.ipynb)
91 to various other formats.
90 to various other formats.
92
91
93 WARNING: THE COMMANDLINE INTERFACE MAY CHANGE IN FUTURE RELEASES.""")
92 WARNING: THE COMMANDLINE INTERFACE MAY CHANGE IN FUTURE RELEASES.""")
94
93
95 output_base = Unicode('', config=True, help='''overwrite base name use for output files.
94 output_base = Unicode('', config=True, help='''overwrite base name use for output files.
96 can only be use when converting one notebook at a time.
95 can only be use when converting one notebook at a time.
97 ''')
96 ''')
98
97
99 examples = Unicode(u"""
98 examples = Unicode(u"""
100 The simplest way to use nbconvert is
99 The simplest way to use nbconvert is
101
100
102 > ipython nbconvert mynotebook.ipynb
101 > ipython nbconvert mynotebook.ipynb
103
102
104 which will convert mynotebook.ipynb to the default format (probably HTML).
103 which will convert mynotebook.ipynb to the default format (probably HTML).
105
104
106 You can specify the export format with `--to`.
105 You can specify the export format with `--to`.
107 Options include {0}
106 Options include {0}
108
107
109 > ipython nbconvert --to latex mynotebook.ipynb
108 > ipython nbconvert --to latex mynotebook.ipynb
110
109
111 Both HTML and LaTeX support multiple output templates. LaTeX includes
110 Both HTML and LaTeX support multiple output templates. LaTeX includes
112 'basic', 'book', and 'article'. HTML includes 'basic' and 'full'. You
111 'basic', 'book', and 'article'. HTML includes 'basic' and 'full'. You
113 can specify the flavor of the format used.
112 can specify the flavor of the format used.
114
113
115 > ipython nbconvert --to html --template basic mynotebook.ipynb
114 > ipython nbconvert --to html --template basic mynotebook.ipynb
116
115
117 You can also pipe the output to stdout, rather than a file
116 You can also pipe the output to stdout, rather than a file
118
117
119 > ipython nbconvert mynotebook.ipynb --stdout
118 > ipython nbconvert mynotebook.ipynb --stdout
120
119
121 PDF is generated via latex
120 PDF is generated via latex
122
121
123 > ipython nbconvert mynotebook.ipynb --to pdf
122 > ipython nbconvert mynotebook.ipynb --to pdf
124
123
125 You can get (and serve) a Reveal.js-powered slideshow
124 You can get (and serve) a Reveal.js-powered slideshow
126
125
127 > ipython nbconvert myslides.ipynb --to slides --post serve
126 > ipython nbconvert myslides.ipynb --to slides --post serve
128
127
129 Multiple notebooks can be given at the command line in a couple of
128 Multiple notebooks can be given at the command line in a couple of
130 different ways:
129 different ways:
131
130
132 > ipython nbconvert notebook*.ipynb
131 > ipython nbconvert notebook*.ipynb
133 > ipython nbconvert notebook1.ipynb notebook2.ipynb
132 > ipython nbconvert notebook1.ipynb notebook2.ipynb
134
133
135 or you can specify the notebooks list in a config file, containing::
134 or you can specify the notebooks list in a config file, containing::
136
135
137 c.NbConvertApp.notebooks = ["my_notebook.ipynb"]
136 c.NbConvertApp.notebooks = ["my_notebook.ipynb"]
138
137
139 > ipython nbconvert --config mycfg.py
138 > ipython nbconvert --config mycfg.py
140 """.format(get_export_names()))
139 """.format(get_export_names()))
141
140
142 # Writer specific variables
141 # Writer specific variables
143 writer = Instance('IPython.nbconvert.writers.base.WriterBase',
142 writer = Instance('IPython.nbconvert.writers.base.WriterBase',
144 help="""Instance of the writer class used to write the
143 help="""Instance of the writer class used to write the
145 results of the conversion.""")
144 results of the conversion.""")
146 writer_class = DottedObjectName('FilesWriter', config=True,
145 writer_class = DottedObjectName('FilesWriter', config=True,
147 help="""Writer class used to write the
146 help="""Writer class used to write the
148 results of the conversion""")
147 results of the conversion""")
149 writer_aliases = {'fileswriter': 'IPython.nbconvert.writers.files.FilesWriter',
148 writer_aliases = {'fileswriter': 'IPython.nbconvert.writers.files.FilesWriter',
150 'debugwriter': 'IPython.nbconvert.writers.debug.DebugWriter',
149 'debugwriter': 'IPython.nbconvert.writers.debug.DebugWriter',
151 'stdoutwriter': 'IPython.nbconvert.writers.stdout.StdoutWriter'}
150 'stdoutwriter': 'IPython.nbconvert.writers.stdout.StdoutWriter'}
152 writer_factory = Type()
151 writer_factory = Type()
153
152
154 def _writer_class_changed(self, name, old, new):
153 def _writer_class_changed(self, name, old, new):
155 if new.lower() in self.writer_aliases:
154 if new.lower() in self.writer_aliases:
156 new = self.writer_aliases[new.lower()]
155 new = self.writer_aliases[new.lower()]
157 self.writer_factory = import_item(new)
156 self.writer_factory = import_item(new)
158
157
159 # Post-processor specific variables
158 # Post-processor specific variables
160 postprocessor = Instance('IPython.nbconvert.postprocessors.base.PostProcessorBase',
159 postprocessor = Instance('IPython.nbconvert.postprocessors.base.PostProcessorBase',
161 help="""Instance of the PostProcessor class used to write the
160 help="""Instance of the PostProcessor class used to write the
162 results of the conversion.""")
161 results of the conversion.""")
163
162
164 postprocessor_class = DottedOrNone(config=True,
163 postprocessor_class = DottedOrNone(config=True,
165 help="""PostProcessor class used to write the
164 help="""PostProcessor class used to write the
166 results of the conversion""")
165 results of the conversion""")
167 postprocessor_aliases = {'serve': 'IPython.nbconvert.postprocessors.serve.ServePostProcessor'}
166 postprocessor_aliases = {'serve': 'IPython.nbconvert.postprocessors.serve.ServePostProcessor'}
168 postprocessor_factory = Type()
167 postprocessor_factory = Type()
169
168
170 def _postprocessor_class_changed(self, name, old, new):
169 def _postprocessor_class_changed(self, name, old, new):
171 if new.lower() in self.postprocessor_aliases:
170 if new.lower() in self.postprocessor_aliases:
172 new = self.postprocessor_aliases[new.lower()]
171 new = self.postprocessor_aliases[new.lower()]
173 if new:
172 if new:
174 self.postprocessor_factory = import_item(new)
173 self.postprocessor_factory = import_item(new)
175
174
176
175
177 # Other configurable variables
176 # Other configurable variables
178 export_format = CaselessStrEnum(get_export_names(),
177 export_format = CaselessStrEnum(get_export_names(),
179 default_value="html",
178 default_value="html",
180 config=True,
179 config=True,
181 help="""The export format to be used."""
180 help="""The export format to be used."""
182 )
181 )
183
182
184 notebooks = List([], config=True, help="""List of notebooks to convert.
183 notebooks = List([], config=True, help="""List of notebooks to convert.
185 Wildcards are supported.
184 Wildcards are supported.
186 Filenames passed positionally will be added to the list.
185 Filenames passed positionally will be added to the list.
187 """)
186 """)
188
187
189 @catch_config_error
188 @catch_config_error
190 def initialize(self, argv=None):
189 def initialize(self, argv=None):
191 self.init_syspath()
190 self.init_syspath()
192 super(NbConvertApp, self).initialize(argv)
191 super(NbConvertApp, self).initialize(argv)
193 self.init_notebooks()
192 self.init_notebooks()
194 self.init_writer()
193 self.init_writer()
195 self.init_postprocessor()
194 self.init_postprocessor()
196
195
197
196
198
197
199 def init_syspath(self):
198 def init_syspath(self):
200 """
199 """
201 Add the cwd to the sys.path ($PYTHONPATH)
200 Add the cwd to the sys.path ($PYTHONPATH)
202 """
201 """
203 sys.path.insert(0, os.getcwd())
202 sys.path.insert(0, os.getcwd())
204
203
205
204
206 def init_notebooks(self):
205 def init_notebooks(self):
207 """Construct the list of notebooks.
206 """Construct the list of notebooks.
208 If notebooks are passed on the command-line,
207 If notebooks are passed on the command-line,
209 they override notebooks specified in config files.
208 they override notebooks specified in config files.
210 Glob each notebook to replace notebook patterns with filenames.
209 Glob each notebook to replace notebook patterns with filenames.
211 """
210 """
212
211
213 # Specifying notebooks on the command-line overrides (rather than adds)
212 # Specifying notebooks on the command-line overrides (rather than adds)
214 # the notebook list
213 # the notebook list
215 if self.extra_args:
214 if self.extra_args:
216 patterns = self.extra_args
215 patterns = self.extra_args
217 else:
216 else:
218 patterns = self.notebooks
217 patterns = self.notebooks
219
218
220 # Use glob to replace all the notebook patterns with filenames.
219 # Use glob to replace all the notebook patterns with filenames.
221 filenames = []
220 filenames = []
222 for pattern in patterns:
221 for pattern in patterns:
223
222
224 # Use glob to find matching filenames. Allow the user to convert
223 # Use glob to find matching filenames. Allow the user to convert
225 # notebooks without having to type the extension.
224 # notebooks without having to type the extension.
226 globbed_files = glob.glob(pattern)
225 globbed_files = glob.glob(pattern)
227 globbed_files.extend(glob.glob(pattern + '.ipynb'))
226 globbed_files.extend(glob.glob(pattern + '.ipynb'))
228 if not globbed_files:
227 if not globbed_files:
229 self.log.warn("pattern %r matched no files", pattern)
228 self.log.warn("pattern %r matched no files", pattern)
230
229
231 for filename in globbed_files:
230 for filename in globbed_files:
232 if not filename in filenames:
231 if not filename in filenames:
233 filenames.append(filename)
232 filenames.append(filename)
234 self.notebooks = filenames
233 self.notebooks = filenames
235
234
236 def init_writer(self):
235 def init_writer(self):
237 """
236 """
238 Initialize the writer (which is stateless)
237 Initialize the writer (which is stateless)
239 """
238 """
240 self._writer_class_changed(None, self.writer_class, self.writer_class)
239 self._writer_class_changed(None, self.writer_class, self.writer_class)
241 self.writer = self.writer_factory(parent=self)
240 self.writer = self.writer_factory(parent=self)
242
241
243 def init_postprocessor(self):
242 def init_postprocessor(self):
244 """
243 """
245 Initialize the postprocessor (which is stateless)
244 Initialize the postprocessor (which is stateless)
246 """
245 """
247 self._postprocessor_class_changed(None, self.postprocessor_class,
246 self._postprocessor_class_changed(None, self.postprocessor_class,
248 self.postprocessor_class)
247 self.postprocessor_class)
249 if self.postprocessor_factory:
248 if self.postprocessor_factory:
250 self.postprocessor = self.postprocessor_factory(parent=self)
249 self.postprocessor = self.postprocessor_factory(parent=self)
251
250
252 def start(self):
251 def start(self):
253 """
252 """
254 Ran after initialization completed
253 Ran after initialization completed
255 """
254 """
256 super(NbConvertApp, self).start()
255 super(NbConvertApp, self).start()
257 self.convert_notebooks()
256 self.convert_notebooks()
258
257
259 def convert_notebooks(self):
258 def convert_notebooks(self):
260 """
259 """
261 Convert the notebooks in the self.notebook traitlet
260 Convert the notebooks in the self.notebook traitlet
262 """
261 """
263 # Export each notebook
262 # Export each notebook
264 conversion_success = 0
263 conversion_success = 0
265
264
266 if self.output_base != '' and len(self.notebooks) > 1:
265 if self.output_base != '' and len(self.notebooks) > 1:
267 self.log.error(
266 self.log.error(
268 """UsageError: --output flag or `NbConvertApp.output_base` config option
267 """UsageError: --output flag or `NbConvertApp.output_base` config option
269 cannot be used when converting multiple notebooks.
268 cannot be used when converting multiple notebooks.
270 """)
269 """)
271 self.exit(1)
270 self.exit(1)
272
271
273 exporter = exporter_map[self.export_format](config=self.config)
272 exporter = exporter_map[self.export_format](config=self.config)
274
273
275 for notebook_filename in self.notebooks:
274 for notebook_filename in self.notebooks:
276 self.log.info("Converting notebook %s to %s", notebook_filename, self.export_format)
275 self.log.info("Converting notebook %s to %s", notebook_filename, self.export_format)
277
276
278 # Get a unique key for the notebook and set it in the resources object.
277 # Get a unique key for the notebook and set it in the resources object.
279 basename = os.path.basename(notebook_filename)
278 basename = os.path.basename(notebook_filename)
280 notebook_name = basename[:basename.rfind('.')]
279 notebook_name = basename[:basename.rfind('.')]
281 if self.output_base:
280 if self.output_base:
282 notebook_name = self.output_base
281 notebook_name = self.output_base
283 resources = {}
282 resources = {}
284 resources['unique_key'] = notebook_name
283 resources['unique_key'] = notebook_name
285 resources['output_files_dir'] = '%s_files' % notebook_name
284 resources['output_files_dir'] = '%s_files' % notebook_name
286 self.log.info("Support files will be in %s", os.path.join(resources['output_files_dir'], ''))
285 self.log.info("Support files will be in %s", os.path.join(resources['output_files_dir'], ''))
287
286
288 # Try to export
287 # Try to export
289 try:
288 try:
290 output, resources = exporter.from_filename(notebook_filename, resources=resources)
289 output, resources = exporter.from_filename(notebook_filename, resources=resources)
291 except ConversionException as e:
290 except ConversionException as e:
292 self.log.error("Error while converting '%s'", notebook_filename,
291 self.log.error("Error while converting '%s'", notebook_filename,
293 exc_info=True)
292 exc_info=True)
294 self.exit(1)
293 self.exit(1)
295 else:
294 else:
296 write_resultes = self.writer.write(output, resources, notebook_name=notebook_name)
295 write_resultes = self.writer.write(output, resources, notebook_name=notebook_name)
297
296
298 #Post-process if post processor has been defined.
297 #Post-process if post processor has been defined.
299 if hasattr(self, 'postprocessor') and self.postprocessor:
298 if hasattr(self, 'postprocessor') and self.postprocessor:
300 self.postprocessor(write_resultes)
299 self.postprocessor(write_resultes)
301 conversion_success += 1
300 conversion_success += 1
302
301
303 # If nothing was converted successfully, help the user.
302 # If nothing was converted successfully, help the user.
304 if conversion_success == 0:
303 if conversion_success == 0:
305 self.print_help()
304 self.print_help()
306 sys.exit(-1)
305 sys.exit(-1)
307
306
308 #-----------------------------------------------------------------------------
307 #-----------------------------------------------------------------------------
309 # Main entry point
308 # Main entry point
310 #-----------------------------------------------------------------------------
309 #-----------------------------------------------------------------------------
311
310
312 launch_new_instance = NbConvertApp.launch_instance
311 launch_new_instance = NbConvertApp.launch_instance
@@ -1,72 +1,70 b''
1 """API for converting notebooks between versions.
1 """API for converting notebooks between versions.
2
2
3 Authors:
3 Authors:
4
4
5 * Jonathan Frederic
5 * Jonathan Frederic
6 """
6 """
7
7
8 #-----------------------------------------------------------------------------
8 #-----------------------------------------------------------------------------
9 # Copyright (C) 2013 The IPython Development Team
9 # Copyright (C) 2013 The IPython Development Team
10 #
10 #
11 # Distributed under the terms of the BSD License. The full license is in
11 # Distributed under the terms of the BSD License. The full license is in
12 # the file COPYING, distributed as part of this software.
12 # the file COPYING, distributed as part of this software.
13 #-----------------------------------------------------------------------------
13 #-----------------------------------------------------------------------------
14
14
15 #-----------------------------------------------------------------------------
15 #-----------------------------------------------------------------------------
16 # Imports
16 # Imports
17 #-----------------------------------------------------------------------------
17 #-----------------------------------------------------------------------------
18
18
19 import re
20
21 from .reader import get_version, versions
19 from .reader import get_version, versions
22
20
23 #-----------------------------------------------------------------------------
21 #-----------------------------------------------------------------------------
24 # Functions
22 # Functions
25 #-----------------------------------------------------------------------------
23 #-----------------------------------------------------------------------------
26
24
27 def convert(nb, to_version):
25 def convert(nb, to_version):
28 """Convert a notebook node object to a specific version. Assumes that
26 """Convert a notebook node object to a specific version. Assumes that
29 all the versions starting from 1 to the latest major X are implemented.
27 all the versions starting from 1 to the latest major X are implemented.
30 In other words, there should never be a case where v1 v2 v3 v5 exist without
28 In other words, there should never be a case where v1 v2 v3 v5 exist without
31 a v4. Also assumes that all conversions can be made in one step increments
29 a v4. Also assumes that all conversions can be made in one step increments
32 between major versions and ignores minor revisions.
30 between major versions and ignores minor revisions.
33
31
34 Parameters
32 Parameters
35 ----------
33 ----------
36 nb : NotebookNode
34 nb : NotebookNode
37 to_version : int
35 to_version : int
38 Major revision to convert the notebook to. Can either be an upgrade or
36 Major revision to convert the notebook to. Can either be an upgrade or
39 a downgrade.
37 a downgrade.
40 """
38 """
41
39
42 # Get input notebook version.
40 # Get input notebook version.
43 (version, version_minor) = get_version(nb)
41 (version, version_minor) = get_version(nb)
44
42
45 # Check if destination is current version, if so return contents
43 # Check if destination is current version, if so return contents
46 if version == to_version:
44 if version == to_version:
47 return nb
45 return nb
48
46
49 # If the version exist, try to convert to it one step at a time.
47 # If the version exist, try to convert to it one step at a time.
50 elif to_version in versions:
48 elif to_version in versions:
51
49
52 # Get the the version that this recursion will convert to as a step
50 # Get the the version that this recursion will convert to as a step
53 # closer to the final revision. Make sure the newer of the conversion
51 # closer to the final revision. Make sure the newer of the conversion
54 # functions is used to perform the conversion.
52 # functions is used to perform the conversion.
55 if to_version > version:
53 if to_version > version:
56 step_version = version + 1
54 step_version = version + 1
57 convert_function = versions[step_version].upgrade
55 convert_function = versions[step_version].upgrade
58 else:
56 else:
59 step_version = version - 1
57 step_version = version - 1
60 convert_function = versions[version].downgrade
58 convert_function = versions[version].downgrade
61
59
62 # Convert and make sure version changed during conversion.
60 # Convert and make sure version changed during conversion.
63 converted = convert_function(nb)
61 converted = convert_function(nb)
64 if converted.get('nbformat', 1) == version:
62 if converted.get('nbformat', 1) == version:
65 raise Exception("Cannot convert notebook from v%d to v%d. Operation" \
63 raise Exception("Cannot convert notebook from v%d to v%d. Operation" \
66 "failed silently." % (version, step_version))
64 "failed silently." % (version, step_version))
67
65
68 # Recursively convert until target version is reached.
66 # Recursively convert until target version is reached.
69 return convert(converted, to_version)
67 return convert(converted, to_version)
70 else:
68 else:
71 raise Exception("Cannot convert notebook to v%d because that " \
69 raise Exception("Cannot convert notebook to v%d because that " \
72 "version doesn't exist" % (to_version))
70 "version doesn't exist" % (to_version))
@@ -1,81 +1,73 b''
1 """Base config factories.
1 """Base config factories.
2
2
3 Authors:
3 Authors:
4
4
5 * Min RK
5 * Min RK
6 """
6 """
7
7
8 #-----------------------------------------------------------------------------
8 #-----------------------------------------------------------------------------
9 # Copyright (C) 2010-2011 The IPython Development Team
9 # Copyright (C) 2010-2011 The IPython Development Team
10 #
10 #
11 # Distributed under the terms of the BSD License. The full license is in
11 # Distributed under the terms of the BSD License. The full license is in
12 # the file COPYING, distributed as part of this software.
12 # the file COPYING, distributed as part of this software.
13 #-----------------------------------------------------------------------------
13 #-----------------------------------------------------------------------------
14
14
15 #-----------------------------------------------------------------------------
15 #-----------------------------------------------------------------------------
16 # Imports
16 # Imports
17 #-----------------------------------------------------------------------------
17 #-----------------------------------------------------------------------------
18
18
19
20 import logging
21 import os
22
23 import zmq
24 from zmq.eventloop.ioloop import IOLoop
25
26 from IPython.config.configurable import Configurable
27 from IPython.utils.localinterfaces import localhost
19 from IPython.utils.localinterfaces import localhost
28 from IPython.utils.traitlets import Integer, Instance, Unicode
20 from IPython.utils.traitlets import Integer, Unicode
29
21
30 from IPython.parallel.util import select_random_ports
22 from IPython.parallel.util import select_random_ports
31 from IPython.kernel.zmq.session import Session, SessionFactory
23 from IPython.kernel.zmq.session import SessionFactory
32
24
33 #-----------------------------------------------------------------------------
25 #-----------------------------------------------------------------------------
34 # Classes
26 # Classes
35 #-----------------------------------------------------------------------------
27 #-----------------------------------------------------------------------------
36
28
37
29
38 class RegistrationFactory(SessionFactory):
30 class RegistrationFactory(SessionFactory):
39 """The Base Configurable for objects that involve registration."""
31 """The Base Configurable for objects that involve registration."""
40
32
41 url = Unicode('', config=True,
33 url = Unicode('', config=True,
42 help="""The 0MQ url used for registration. This sets transport, ip, and port
34 help="""The 0MQ url used for registration. This sets transport, ip, and port
43 in one variable. For example: url='tcp://127.0.0.1:12345' or
35 in one variable. For example: url='tcp://127.0.0.1:12345' or
44 url='epgm://*:90210'"""
36 url='epgm://*:90210'"""
45 ) # url takes precedence over ip,regport,transport
37 ) # url takes precedence over ip,regport,transport
46 transport = Unicode('tcp', config=True,
38 transport = Unicode('tcp', config=True,
47 help="""The 0MQ transport for communications. This will likely be
39 help="""The 0MQ transport for communications. This will likely be
48 the default of 'tcp', but other values include 'ipc', 'epgm', 'inproc'.""")
40 the default of 'tcp', but other values include 'ipc', 'epgm', 'inproc'.""")
49 ip = Unicode(config=True,
41 ip = Unicode(config=True,
50 help="""The IP address for registration. This is generally either
42 help="""The IP address for registration. This is generally either
51 '127.0.0.1' for loopback only or '*' for all interfaces.
43 '127.0.0.1' for loopback only or '*' for all interfaces.
52 """)
44 """)
53 def _ip_default(self):
45 def _ip_default(self):
54 return localhost()
46 return localhost()
55 regport = Integer(config=True,
47 regport = Integer(config=True,
56 help="""The port on which the Hub listens for registration.""")
48 help="""The port on which the Hub listens for registration.""")
57 def _regport_default(self):
49 def _regport_default(self):
58 return select_random_ports(1)[0]
50 return select_random_ports(1)[0]
59
51
60 def __init__(self, **kwargs):
52 def __init__(self, **kwargs):
61 super(RegistrationFactory, self).__init__(**kwargs)
53 super(RegistrationFactory, self).__init__(**kwargs)
62 self._propagate_url()
54 self._propagate_url()
63 self._rebuild_url()
55 self._rebuild_url()
64 self.on_trait_change(self._propagate_url, 'url')
56 self.on_trait_change(self._propagate_url, 'url')
65 self.on_trait_change(self._rebuild_url, 'ip')
57 self.on_trait_change(self._rebuild_url, 'ip')
66 self.on_trait_change(self._rebuild_url, 'transport')
58 self.on_trait_change(self._rebuild_url, 'transport')
67 self.on_trait_change(self._rebuild_url, 'regport')
59 self.on_trait_change(self._rebuild_url, 'regport')
68
60
69 def _rebuild_url(self):
61 def _rebuild_url(self):
70 self.url = "%s://%s:%i"%(self.transport, self.ip, self.regport)
62 self.url = "%s://%s:%i"%(self.transport, self.ip, self.regport)
71
63
72 def _propagate_url(self):
64 def _propagate_url(self):
73 """Ensure self.url contains full transport://interface:port"""
65 """Ensure self.url contains full transport://interface:port"""
74 if self.url:
66 if self.url:
75 iface = self.url.split('://',1)
67 iface = self.url.split('://',1)
76 if len(iface) == 2:
68 if len(iface) == 2:
77 self.transport,iface = iface
69 self.transport,iface = iface
78 iface = iface.split(':')
70 iface = iface.split(':')
79 self.ip = iface[0]
71 self.ip = iface[0]
80 if iface[1]:
72 if iface[1]:
81 self.regport = int(iface[1])
73 self.regport = int(iface[1])
@@ -1,390 +1,388 b''
1 """Some generic utilities for dealing with classes, urls, and serialization."""
1 """Some generic utilities for dealing with classes, urls, and serialization."""
2
2
3 # Copyright (c) IPython Development Team.
3 # Copyright (c) IPython Development Team.
4 # Distributed under the terms of the Modified BSD License.
4 # Distributed under the terms of the Modified BSD License.
5
5
6 import logging
6 import logging
7 import os
7 import os
8 import re
8 import re
9 import stat
9 import stat
10 import socket
10 import socket
11 import sys
11 import sys
12 import warnings
12 import warnings
13 from signal import signal, SIGINT, SIGABRT, SIGTERM
13 from signal import signal, SIGINT, SIGABRT, SIGTERM
14 try:
14 try:
15 from signal import SIGKILL
15 from signal import SIGKILL
16 except ImportError:
16 except ImportError:
17 SIGKILL=None
17 SIGKILL=None
18 from types import FunctionType
18 from types import FunctionType
19
19
20 try:
20 try:
21 import cPickle
21 import cPickle
22 pickle = cPickle
22 pickle = cPickle
23 except:
23 except:
24 cPickle = None
24 cPickle = None
25 import pickle
25 import pickle
26
26
27 import zmq
27 import zmq
28 from zmq.log import handlers
28 from zmq.log import handlers
29
29
30 from IPython.external.decorator import decorator
30 from IPython.external.decorator import decorator
31
31
32 from IPython.config.application import Application
32 from IPython.config.application import Application
33 from IPython.utils.localinterfaces import localhost, is_public_ip, public_ips
33 from IPython.utils.localinterfaces import localhost, is_public_ip, public_ips
34 from IPython.utils.py3compat import string_types, iteritems, itervalues
34 from IPython.utils.py3compat import string_types, iteritems, itervalues
35 from IPython.kernel.zmq.log import EnginePUBHandler
35 from IPython.kernel.zmq.log import EnginePUBHandler
36 from IPython.kernel.zmq.serialize import (
36
37 unserialize_object, serialize_object, pack_apply_message, unpack_apply_message
38 )
39
37
40 #-----------------------------------------------------------------------------
38 #-----------------------------------------------------------------------------
41 # Classes
39 # Classes
42 #-----------------------------------------------------------------------------
40 #-----------------------------------------------------------------------------
43
41
44 class Namespace(dict):
42 class Namespace(dict):
45 """Subclass of dict for attribute access to keys."""
43 """Subclass of dict for attribute access to keys."""
46
44
47 def __getattr__(self, key):
45 def __getattr__(self, key):
48 """getattr aliased to getitem"""
46 """getattr aliased to getitem"""
49 if key in self:
47 if key in self:
50 return self[key]
48 return self[key]
51 else:
49 else:
52 raise NameError(key)
50 raise NameError(key)
53
51
54 def __setattr__(self, key, value):
52 def __setattr__(self, key, value):
55 """setattr aliased to setitem, with strict"""
53 """setattr aliased to setitem, with strict"""
56 if hasattr(dict, key):
54 if hasattr(dict, key):
57 raise KeyError("Cannot override dict keys %r"%key)
55 raise KeyError("Cannot override dict keys %r"%key)
58 self[key] = value
56 self[key] = value
59
57
60
58
61 class ReverseDict(dict):
59 class ReverseDict(dict):
62 """simple double-keyed subset of dict methods."""
60 """simple double-keyed subset of dict methods."""
63
61
64 def __init__(self, *args, **kwargs):
62 def __init__(self, *args, **kwargs):
65 dict.__init__(self, *args, **kwargs)
63 dict.__init__(self, *args, **kwargs)
66 self._reverse = dict()
64 self._reverse = dict()
67 for key, value in iteritems(self):
65 for key, value in iteritems(self):
68 self._reverse[value] = key
66 self._reverse[value] = key
69
67
70 def __getitem__(self, key):
68 def __getitem__(self, key):
71 try:
69 try:
72 return dict.__getitem__(self, key)
70 return dict.__getitem__(self, key)
73 except KeyError:
71 except KeyError:
74 return self._reverse[key]
72 return self._reverse[key]
75
73
76 def __setitem__(self, key, value):
74 def __setitem__(self, key, value):
77 if key in self._reverse:
75 if key in self._reverse:
78 raise KeyError("Can't have key %r on both sides!"%key)
76 raise KeyError("Can't have key %r on both sides!"%key)
79 dict.__setitem__(self, key, value)
77 dict.__setitem__(self, key, value)
80 self._reverse[value] = key
78 self._reverse[value] = key
81
79
82 def pop(self, key):
80 def pop(self, key):
83 value = dict.pop(self, key)
81 value = dict.pop(self, key)
84 self._reverse.pop(value)
82 self._reverse.pop(value)
85 return value
83 return value
86
84
87 def get(self, key, default=None):
85 def get(self, key, default=None):
88 try:
86 try:
89 return self[key]
87 return self[key]
90 except KeyError:
88 except KeyError:
91 return default
89 return default
92
90
93 #-----------------------------------------------------------------------------
91 #-----------------------------------------------------------------------------
94 # Functions
92 # Functions
95 #-----------------------------------------------------------------------------
93 #-----------------------------------------------------------------------------
96
94
97 @decorator
95 @decorator
98 def log_errors(f, self, *args, **kwargs):
96 def log_errors(f, self, *args, **kwargs):
99 """decorator to log unhandled exceptions raised in a method.
97 """decorator to log unhandled exceptions raised in a method.
100
98
101 For use wrapping on_recv callbacks, so that exceptions
99 For use wrapping on_recv callbacks, so that exceptions
102 do not cause the stream to be closed.
100 do not cause the stream to be closed.
103 """
101 """
104 try:
102 try:
105 return f(self, *args, **kwargs)
103 return f(self, *args, **kwargs)
106 except Exception:
104 except Exception:
107 self.log.error("Uncaught exception in %r" % f, exc_info=True)
105 self.log.error("Uncaught exception in %r" % f, exc_info=True)
108
106
109
107
110 def is_url(url):
108 def is_url(url):
111 """boolean check for whether a string is a zmq url"""
109 """boolean check for whether a string is a zmq url"""
112 if '://' not in url:
110 if '://' not in url:
113 return False
111 return False
114 proto, addr = url.split('://', 1)
112 proto, addr = url.split('://', 1)
115 if proto.lower() not in ['tcp','pgm','epgm','ipc','inproc']:
113 if proto.lower() not in ['tcp','pgm','epgm','ipc','inproc']:
116 return False
114 return False
117 return True
115 return True
118
116
119 def validate_url(url):
117 def validate_url(url):
120 """validate a url for zeromq"""
118 """validate a url for zeromq"""
121 if not isinstance(url, string_types):
119 if not isinstance(url, string_types):
122 raise TypeError("url must be a string, not %r"%type(url))
120 raise TypeError("url must be a string, not %r"%type(url))
123 url = url.lower()
121 url = url.lower()
124
122
125 proto_addr = url.split('://')
123 proto_addr = url.split('://')
126 assert len(proto_addr) == 2, 'Invalid url: %r'%url
124 assert len(proto_addr) == 2, 'Invalid url: %r'%url
127 proto, addr = proto_addr
125 proto, addr = proto_addr
128 assert proto in ['tcp','pgm','epgm','ipc','inproc'], "Invalid protocol: %r"%proto
126 assert proto in ['tcp','pgm','epgm','ipc','inproc'], "Invalid protocol: %r"%proto
129
127
130 # domain pattern adapted from http://www.regexlib.com/REDetails.aspx?regexp_id=391
128 # domain pattern adapted from http://www.regexlib.com/REDetails.aspx?regexp_id=391
131 # author: Remi Sabourin
129 # author: Remi Sabourin
132 pat = re.compile(r'^([\w\d]([\w\d\-]{0,61}[\w\d])?\.)*[\w\d]([\w\d\-]{0,61}[\w\d])?$')
130 pat = re.compile(r'^([\w\d]([\w\d\-]{0,61}[\w\d])?\.)*[\w\d]([\w\d\-]{0,61}[\w\d])?$')
133
131
134 if proto == 'tcp':
132 if proto == 'tcp':
135 lis = addr.split(':')
133 lis = addr.split(':')
136 assert len(lis) == 2, 'Invalid url: %r'%url
134 assert len(lis) == 2, 'Invalid url: %r'%url
137 addr,s_port = lis
135 addr,s_port = lis
138 try:
136 try:
139 port = int(s_port)
137 port = int(s_port)
140 except ValueError:
138 except ValueError:
141 raise AssertionError("Invalid port %r in url: %r"%(port, url))
139 raise AssertionError("Invalid port %r in url: %r"%(port, url))
142
140
143 assert addr == '*' or pat.match(addr) is not None, 'Invalid url: %r'%url
141 assert addr == '*' or pat.match(addr) is not None, 'Invalid url: %r'%url
144
142
145 else:
143 else:
146 # only validate tcp urls currently
144 # only validate tcp urls currently
147 pass
145 pass
148
146
149 return True
147 return True
150
148
151
149
152 def validate_url_container(container):
150 def validate_url_container(container):
153 """validate a potentially nested collection of urls."""
151 """validate a potentially nested collection of urls."""
154 if isinstance(container, string_types):
152 if isinstance(container, string_types):
155 url = container
153 url = container
156 return validate_url(url)
154 return validate_url(url)
157 elif isinstance(container, dict):
155 elif isinstance(container, dict):
158 container = itervalues(container)
156 container = itervalues(container)
159
157
160 for element in container:
158 for element in container:
161 validate_url_container(element)
159 validate_url_container(element)
162
160
163
161
164 def split_url(url):
162 def split_url(url):
165 """split a zmq url (tcp://ip:port) into ('tcp','ip','port')."""
163 """split a zmq url (tcp://ip:port) into ('tcp','ip','port')."""
166 proto_addr = url.split('://')
164 proto_addr = url.split('://')
167 assert len(proto_addr) == 2, 'Invalid url: %r'%url
165 assert len(proto_addr) == 2, 'Invalid url: %r'%url
168 proto, addr = proto_addr
166 proto, addr = proto_addr
169 lis = addr.split(':')
167 lis = addr.split(':')
170 assert len(lis) == 2, 'Invalid url: %r'%url
168 assert len(lis) == 2, 'Invalid url: %r'%url
171 addr,s_port = lis
169 addr,s_port = lis
172 return proto,addr,s_port
170 return proto,addr,s_port
173
171
174
172
175 def disambiguate_ip_address(ip, location=None):
173 def disambiguate_ip_address(ip, location=None):
176 """turn multi-ip interfaces '0.0.0.0' and '*' into a connectable address
174 """turn multi-ip interfaces '0.0.0.0' and '*' into a connectable address
177
175
178 Explicit IP addresses are returned unmodified.
176 Explicit IP addresses are returned unmodified.
179
177
180 Parameters
178 Parameters
181 ----------
179 ----------
182
180
183 ip : IP address
181 ip : IP address
184 An IP address, or the special values 0.0.0.0, or *
182 An IP address, or the special values 0.0.0.0, or *
185 location: IP address, optional
183 location: IP address, optional
186 A public IP of the target machine.
184 A public IP of the target machine.
187 If location is an IP of the current machine,
185 If location is an IP of the current machine,
188 localhost will be returned,
186 localhost will be returned,
189 otherwise location will be returned.
187 otherwise location will be returned.
190 """
188 """
191 if ip in {'0.0.0.0', '*'}:
189 if ip in {'0.0.0.0', '*'}:
192 if not location:
190 if not location:
193 # unspecified location, localhost is the only choice
191 # unspecified location, localhost is the only choice
194 ip = localhost()
192 ip = localhost()
195 elif is_public_ip(location):
193 elif is_public_ip(location):
196 # location is a public IP on this machine, use localhost
194 # location is a public IP on this machine, use localhost
197 ip = localhost()
195 ip = localhost()
198 elif not public_ips():
196 elif not public_ips():
199 # this machine's public IPs cannot be determined,
197 # this machine's public IPs cannot be determined,
200 # assume `location` is not this machine
198 # assume `location` is not this machine
201 warnings.warn("IPython could not determine public IPs", RuntimeWarning)
199 warnings.warn("IPython could not determine public IPs", RuntimeWarning)
202 ip = location
200 ip = location
203 else:
201 else:
204 # location is not this machine, do not use loopback
202 # location is not this machine, do not use loopback
205 ip = location
203 ip = location
206 return ip
204 return ip
207
205
208
206
209 def disambiguate_url(url, location=None):
207 def disambiguate_url(url, location=None):
210 """turn multi-ip interfaces '0.0.0.0' and '*' into connectable
208 """turn multi-ip interfaces '0.0.0.0' and '*' into connectable
211 ones, based on the location (default interpretation is localhost).
209 ones, based on the location (default interpretation is localhost).
212
210
213 This is for zeromq urls, such as ``tcp://*:10101``.
211 This is for zeromq urls, such as ``tcp://*:10101``.
214 """
212 """
215 try:
213 try:
216 proto,ip,port = split_url(url)
214 proto,ip,port = split_url(url)
217 except AssertionError:
215 except AssertionError:
218 # probably not tcp url; could be ipc, etc.
216 # probably not tcp url; could be ipc, etc.
219 return url
217 return url
220
218
221 ip = disambiguate_ip_address(ip,location)
219 ip = disambiguate_ip_address(ip,location)
222
220
223 return "%s://%s:%s"%(proto,ip,port)
221 return "%s://%s:%s"%(proto,ip,port)
224
222
225
223
226 #--------------------------------------------------------------------------
224 #--------------------------------------------------------------------------
227 # helpers for implementing old MEC API via view.apply
225 # helpers for implementing old MEC API via view.apply
228 #--------------------------------------------------------------------------
226 #--------------------------------------------------------------------------
229
227
230 def interactive(f):
228 def interactive(f):
231 """decorator for making functions appear as interactively defined.
229 """decorator for making functions appear as interactively defined.
232 This results in the function being linked to the user_ns as globals()
230 This results in the function being linked to the user_ns as globals()
233 instead of the module globals().
231 instead of the module globals().
234 """
232 """
235
233
236 # build new FunctionType, so it can have the right globals
234 # build new FunctionType, so it can have the right globals
237 # interactive functions never have closures, that's kind of the point
235 # interactive functions never have closures, that's kind of the point
238 if isinstance(f, FunctionType):
236 if isinstance(f, FunctionType):
239 mainmod = __import__('__main__')
237 mainmod = __import__('__main__')
240 f = FunctionType(f.__code__, mainmod.__dict__,
238 f = FunctionType(f.__code__, mainmod.__dict__,
241 f.__name__, f.__defaults__,
239 f.__name__, f.__defaults__,
242 )
240 )
243 # associate with __main__ for uncanning
241 # associate with __main__ for uncanning
244 f.__module__ = '__main__'
242 f.__module__ = '__main__'
245 return f
243 return f
246
244
247 @interactive
245 @interactive
248 def _push(**ns):
246 def _push(**ns):
249 """helper method for implementing `client.push` via `client.apply`"""
247 """helper method for implementing `client.push` via `client.apply`"""
250 user_ns = globals()
248 user_ns = globals()
251 tmp = '_IP_PUSH_TMP_'
249 tmp = '_IP_PUSH_TMP_'
252 while tmp in user_ns:
250 while tmp in user_ns:
253 tmp = tmp + '_'
251 tmp = tmp + '_'
254 try:
252 try:
255 for name, value in ns.items():
253 for name, value in ns.items():
256 user_ns[tmp] = value
254 user_ns[tmp] = value
257 exec("%s = %s" % (name, tmp), user_ns)
255 exec("%s = %s" % (name, tmp), user_ns)
258 finally:
256 finally:
259 user_ns.pop(tmp, None)
257 user_ns.pop(tmp, None)
260
258
261 @interactive
259 @interactive
262 def _pull(keys):
260 def _pull(keys):
263 """helper method for implementing `client.pull` via `client.apply`"""
261 """helper method for implementing `client.pull` via `client.apply`"""
264 if isinstance(keys, (list,tuple, set)):
262 if isinstance(keys, (list,tuple, set)):
265 return [eval(key, globals()) for key in keys]
263 return [eval(key, globals()) for key in keys]
266 else:
264 else:
267 return eval(keys, globals())
265 return eval(keys, globals())
268
266
269 @interactive
267 @interactive
270 def _execute(code):
268 def _execute(code):
271 """helper method for implementing `client.execute` via `client.apply`"""
269 """helper method for implementing `client.execute` via `client.apply`"""
272 exec(code, globals())
270 exec(code, globals())
273
271
274 #--------------------------------------------------------------------------
272 #--------------------------------------------------------------------------
275 # extra process management utilities
273 # extra process management utilities
276 #--------------------------------------------------------------------------
274 #--------------------------------------------------------------------------
277
275
278 _random_ports = set()
276 _random_ports = set()
279
277
280 def select_random_ports(n):
278 def select_random_ports(n):
281 """Selects and return n random ports that are available."""
279 """Selects and return n random ports that are available."""
282 ports = []
280 ports = []
283 for i in range(n):
281 for i in range(n):
284 sock = socket.socket()
282 sock = socket.socket()
285 sock.bind(('', 0))
283 sock.bind(('', 0))
286 while sock.getsockname()[1] in _random_ports:
284 while sock.getsockname()[1] in _random_ports:
287 sock.close()
285 sock.close()
288 sock = socket.socket()
286 sock = socket.socket()
289 sock.bind(('', 0))
287 sock.bind(('', 0))
290 ports.append(sock)
288 ports.append(sock)
291 for i, sock in enumerate(ports):
289 for i, sock in enumerate(ports):
292 port = sock.getsockname()[1]
290 port = sock.getsockname()[1]
293 sock.close()
291 sock.close()
294 ports[i] = port
292 ports[i] = port
295 _random_ports.add(port)
293 _random_ports.add(port)
296 return ports
294 return ports
297
295
298 def signal_children(children):
296 def signal_children(children):
299 """Relay interupt/term signals to children, for more solid process cleanup."""
297 """Relay interupt/term signals to children, for more solid process cleanup."""
300 def terminate_children(sig, frame):
298 def terminate_children(sig, frame):
301 log = Application.instance().log
299 log = Application.instance().log
302 log.critical("Got signal %i, terminating children..."%sig)
300 log.critical("Got signal %i, terminating children..."%sig)
303 for child in children:
301 for child in children:
304 child.terminate()
302 child.terminate()
305
303
306 sys.exit(sig != SIGINT)
304 sys.exit(sig != SIGINT)
307 # sys.exit(sig)
305 # sys.exit(sig)
308 for sig in (SIGINT, SIGABRT, SIGTERM):
306 for sig in (SIGINT, SIGABRT, SIGTERM):
309 signal(sig, terminate_children)
307 signal(sig, terminate_children)
310
308
311 def generate_exec_key(keyfile):
309 def generate_exec_key(keyfile):
312 import uuid
310 import uuid
313 newkey = str(uuid.uuid4())
311 newkey = str(uuid.uuid4())
314 with open(keyfile, 'w') as f:
312 with open(keyfile, 'w') as f:
315 # f.write('ipython-key ')
313 # f.write('ipython-key ')
316 f.write(newkey+'\n')
314 f.write(newkey+'\n')
317 # set user-only RW permissions (0600)
315 # set user-only RW permissions (0600)
318 # this will have no effect on Windows
316 # this will have no effect on Windows
319 os.chmod(keyfile, stat.S_IRUSR|stat.S_IWUSR)
317 os.chmod(keyfile, stat.S_IRUSR|stat.S_IWUSR)
320
318
321
319
322 def integer_loglevel(loglevel):
320 def integer_loglevel(loglevel):
323 try:
321 try:
324 loglevel = int(loglevel)
322 loglevel = int(loglevel)
325 except ValueError:
323 except ValueError:
326 if isinstance(loglevel, str):
324 if isinstance(loglevel, str):
327 loglevel = getattr(logging, loglevel)
325 loglevel = getattr(logging, loglevel)
328 return loglevel
326 return loglevel
329
327
330 def connect_logger(logname, context, iface, root="ip", loglevel=logging.DEBUG):
328 def connect_logger(logname, context, iface, root="ip", loglevel=logging.DEBUG):
331 logger = logging.getLogger(logname)
329 logger = logging.getLogger(logname)
332 if any([isinstance(h, handlers.PUBHandler) for h in logger.handlers]):
330 if any([isinstance(h, handlers.PUBHandler) for h in logger.handlers]):
333 # don't add a second PUBHandler
331 # don't add a second PUBHandler
334 return
332 return
335 loglevel = integer_loglevel(loglevel)
333 loglevel = integer_loglevel(loglevel)
336 lsock = context.socket(zmq.PUB)
334 lsock = context.socket(zmq.PUB)
337 lsock.connect(iface)
335 lsock.connect(iface)
338 handler = handlers.PUBHandler(lsock)
336 handler = handlers.PUBHandler(lsock)
339 handler.setLevel(loglevel)
337 handler.setLevel(loglevel)
340 handler.root_topic = root
338 handler.root_topic = root
341 logger.addHandler(handler)
339 logger.addHandler(handler)
342 logger.setLevel(loglevel)
340 logger.setLevel(loglevel)
343
341
344 def connect_engine_logger(context, iface, engine, loglevel=logging.DEBUG):
342 def connect_engine_logger(context, iface, engine, loglevel=logging.DEBUG):
345 logger = logging.getLogger()
343 logger = logging.getLogger()
346 if any([isinstance(h, handlers.PUBHandler) for h in logger.handlers]):
344 if any([isinstance(h, handlers.PUBHandler) for h in logger.handlers]):
347 # don't add a second PUBHandler
345 # don't add a second PUBHandler
348 return
346 return
349 loglevel = integer_loglevel(loglevel)
347 loglevel = integer_loglevel(loglevel)
350 lsock = context.socket(zmq.PUB)
348 lsock = context.socket(zmq.PUB)
351 lsock.connect(iface)
349 lsock.connect(iface)
352 handler = EnginePUBHandler(engine, lsock)
350 handler = EnginePUBHandler(engine, lsock)
353 handler.setLevel(loglevel)
351 handler.setLevel(loglevel)
354 logger.addHandler(handler)
352 logger.addHandler(handler)
355 logger.setLevel(loglevel)
353 logger.setLevel(loglevel)
356 return logger
354 return logger
357
355
358 def local_logger(logname, loglevel=logging.DEBUG):
356 def local_logger(logname, loglevel=logging.DEBUG):
359 loglevel = integer_loglevel(loglevel)
357 loglevel = integer_loglevel(loglevel)
360 logger = logging.getLogger(logname)
358 logger = logging.getLogger(logname)
361 if any([isinstance(h, logging.StreamHandler) for h in logger.handlers]):
359 if any([isinstance(h, logging.StreamHandler) for h in logger.handlers]):
362 # don't add a second StreamHandler
360 # don't add a second StreamHandler
363 return
361 return
364 handler = logging.StreamHandler()
362 handler = logging.StreamHandler()
365 handler.setLevel(loglevel)
363 handler.setLevel(loglevel)
366 formatter = logging.Formatter("%(asctime)s.%(msecs).03d [%(name)s] %(message)s",
364 formatter = logging.Formatter("%(asctime)s.%(msecs).03d [%(name)s] %(message)s",
367 datefmt="%Y-%m-%d %H:%M:%S")
365 datefmt="%Y-%m-%d %H:%M:%S")
368 handler.setFormatter(formatter)
366 handler.setFormatter(formatter)
369
367
370 logger.addHandler(handler)
368 logger.addHandler(handler)
371 logger.setLevel(loglevel)
369 logger.setLevel(loglevel)
372 return logger
370 return logger
373
371
374 def set_hwm(sock, hwm=0):
372 def set_hwm(sock, hwm=0):
375 """set zmq High Water Mark on a socket
373 """set zmq High Water Mark on a socket
376
374
377 in a way that always works for various pyzmq / libzmq versions.
375 in a way that always works for various pyzmq / libzmq versions.
378 """
376 """
379 import zmq
377 import zmq
380
378
381 for key in ('HWM', 'SNDHWM', 'RCVHWM'):
379 for key in ('HWM', 'SNDHWM', 'RCVHWM'):
382 opt = getattr(zmq, key, None)
380 opt = getattr(zmq, key, None)
383 if opt is None:
381 if opt is None:
384 continue
382 continue
385 try:
383 try:
386 sock.setsockopt(opt, hwm)
384 sock.setsockopt(opt, hwm)
387 except zmq.ZMQError:
385 except zmq.ZMQError:
388 pass
386 pass
389
387
390
388
@@ -1,395 +1,393 b''
1 #!/usr/bin/env python
1 #!/usr/bin/env python
2 # encoding: utf-8
2 # encoding: utf-8
3 """
3 """
4 The :class:`~IPython.core.application.Application` object for the command
4 The :class:`~IPython.core.application.Application` object for the command
5 line :command:`ipython` program.
5 line :command:`ipython` program.
6
6
7 Authors
7 Authors
8 -------
8 -------
9
9
10 * Brian Granger
10 * Brian Granger
11 * Fernando Perez
11 * Fernando Perez
12 * Min Ragan-Kelley
12 * Min Ragan-Kelley
13 """
13 """
14
14
15 #-----------------------------------------------------------------------------
15 #-----------------------------------------------------------------------------
16 # Copyright (C) 2008-2011 The IPython Development Team
16 # Copyright (C) 2008-2011 The IPython Development Team
17 #
17 #
18 # Distributed under the terms of the BSD License. The full license is in
18 # Distributed under the terms of the BSD License. The full license is in
19 # the file COPYING, distributed as part of this software.
19 # the file COPYING, distributed as part of this software.
20 #-----------------------------------------------------------------------------
20 #-----------------------------------------------------------------------------
21
21
22 #-----------------------------------------------------------------------------
22 #-----------------------------------------------------------------------------
23 # Imports
23 # Imports
24 #-----------------------------------------------------------------------------
24 #-----------------------------------------------------------------------------
25
25
26 from __future__ import absolute_import
26 from __future__ import absolute_import
27 from __future__ import print_function
27 from __future__ import print_function
28
28
29 import logging
29 import logging
30 import os
30 import os
31 import sys
31 import sys
32
32
33 from IPython.config.loader import (
33 from IPython.config.loader import Config
34 Config, PyFileConfigLoader, ConfigFileNotFound
35 )
36 from IPython.config.application import boolean_flag, catch_config_error, Application
34 from IPython.config.application import boolean_flag, catch_config_error, Application
37 from IPython.core import release
35 from IPython.core import release
38 from IPython.core import usage
36 from IPython.core import usage
39 from IPython.core.completer import IPCompleter
37 from IPython.core.completer import IPCompleter
40 from IPython.core.crashhandler import CrashHandler
38 from IPython.core.crashhandler import CrashHandler
41 from IPython.core.formatters import PlainTextFormatter
39 from IPython.core.formatters import PlainTextFormatter
42 from IPython.core.history import HistoryManager
40 from IPython.core.history import HistoryManager
43 from IPython.core.prompts import PromptManager
41 from IPython.core.prompts import PromptManager
44 from IPython.core.application import (
42 from IPython.core.application import (
45 ProfileDir, BaseIPythonApplication, base_flags, base_aliases
43 ProfileDir, BaseIPythonApplication, base_flags, base_aliases
46 )
44 )
47 from IPython.core.magics import ScriptMagics
45 from IPython.core.magics import ScriptMagics
48 from IPython.core.shellapp import (
46 from IPython.core.shellapp import (
49 InteractiveShellApp, shell_flags, shell_aliases
47 InteractiveShellApp, shell_flags, shell_aliases
50 )
48 )
51 from IPython.extensions.storemagic import StoreMagics
49 from IPython.extensions.storemagic import StoreMagics
52 from IPython.terminal.interactiveshell import TerminalInteractiveShell
50 from IPython.terminal.interactiveshell import TerminalInteractiveShell
53 from IPython.utils import warn
51 from IPython.utils import warn
54 from IPython.utils.path import get_ipython_dir, check_for_old_config
52 from IPython.utils.path import get_ipython_dir, check_for_old_config
55 from IPython.utils.traitlets import (
53 from IPython.utils.traitlets import (
56 Bool, List, Dict,
54 Bool, List, Dict,
57 )
55 )
58
56
59 #-----------------------------------------------------------------------------
57 #-----------------------------------------------------------------------------
60 # Globals, utilities and helpers
58 # Globals, utilities and helpers
61 #-----------------------------------------------------------------------------
59 #-----------------------------------------------------------------------------
62
60
63 _examples = """
61 _examples = """
64 ipython --matplotlib # enable matplotlib integration
62 ipython --matplotlib # enable matplotlib integration
65 ipython --matplotlib=qt # enable matplotlib integration with qt4 backend
63 ipython --matplotlib=qt # enable matplotlib integration with qt4 backend
66
64
67 ipython --log-level=DEBUG # set logging to DEBUG
65 ipython --log-level=DEBUG # set logging to DEBUG
68 ipython --profile=foo # start with profile foo
66 ipython --profile=foo # start with profile foo
69
67
70 ipython qtconsole # start the qtconsole GUI application
68 ipython qtconsole # start the qtconsole GUI application
71 ipython help qtconsole # show the help for the qtconsole subcmd
69 ipython help qtconsole # show the help for the qtconsole subcmd
72
70
73 ipython console # start the terminal-based console application
71 ipython console # start the terminal-based console application
74 ipython help console # show the help for the console subcmd
72 ipython help console # show the help for the console subcmd
75
73
76 ipython notebook # start the IPython notebook
74 ipython notebook # start the IPython notebook
77 ipython help notebook # show the help for the notebook subcmd
75 ipython help notebook # show the help for the notebook subcmd
78
76
79 ipython profile create foo # create profile foo w/ default config files
77 ipython profile create foo # create profile foo w/ default config files
80 ipython help profile # show the help for the profile subcmd
78 ipython help profile # show the help for the profile subcmd
81
79
82 ipython locate # print the path to the IPython directory
80 ipython locate # print the path to the IPython directory
83 ipython locate profile foo # print the path to the directory for profile `foo`
81 ipython locate profile foo # print the path to the directory for profile `foo`
84
82
85 ipython nbconvert # convert notebooks to/from other formats
83 ipython nbconvert # convert notebooks to/from other formats
86 """
84 """
87
85
88 #-----------------------------------------------------------------------------
86 #-----------------------------------------------------------------------------
89 # Crash handler for this application
87 # Crash handler for this application
90 #-----------------------------------------------------------------------------
88 #-----------------------------------------------------------------------------
91
89
92 class IPAppCrashHandler(CrashHandler):
90 class IPAppCrashHandler(CrashHandler):
93 """sys.excepthook for IPython itself, leaves a detailed report on disk."""
91 """sys.excepthook for IPython itself, leaves a detailed report on disk."""
94
92
95 def __init__(self, app):
93 def __init__(self, app):
96 contact_name = release.author
94 contact_name = release.author
97 contact_email = release.author_email
95 contact_email = release.author_email
98 bug_tracker = 'https://github.com/ipython/ipython/issues'
96 bug_tracker = 'https://github.com/ipython/ipython/issues'
99 super(IPAppCrashHandler,self).__init__(
97 super(IPAppCrashHandler,self).__init__(
100 app, contact_name, contact_email, bug_tracker
98 app, contact_name, contact_email, bug_tracker
101 )
99 )
102
100
103 def make_report(self,traceback):
101 def make_report(self,traceback):
104 """Return a string containing a crash report."""
102 """Return a string containing a crash report."""
105
103
106 sec_sep = self.section_sep
104 sec_sep = self.section_sep
107 # Start with parent report
105 # Start with parent report
108 report = [super(IPAppCrashHandler, self).make_report(traceback)]
106 report = [super(IPAppCrashHandler, self).make_report(traceback)]
109 # Add interactive-specific info we may have
107 # Add interactive-specific info we may have
110 rpt_add = report.append
108 rpt_add = report.append
111 try:
109 try:
112 rpt_add(sec_sep+"History of session input:")
110 rpt_add(sec_sep+"History of session input:")
113 for line in self.app.shell.user_ns['_ih']:
111 for line in self.app.shell.user_ns['_ih']:
114 rpt_add(line)
112 rpt_add(line)
115 rpt_add('\n*** Last line of input (may not be in above history):\n')
113 rpt_add('\n*** Last line of input (may not be in above history):\n')
116 rpt_add(self.app.shell._last_input_line+'\n')
114 rpt_add(self.app.shell._last_input_line+'\n')
117 except:
115 except:
118 pass
116 pass
119
117
120 return ''.join(report)
118 return ''.join(report)
121
119
122 #-----------------------------------------------------------------------------
120 #-----------------------------------------------------------------------------
123 # Aliases and Flags
121 # Aliases and Flags
124 #-----------------------------------------------------------------------------
122 #-----------------------------------------------------------------------------
125 flags = dict(base_flags)
123 flags = dict(base_flags)
126 flags.update(shell_flags)
124 flags.update(shell_flags)
127 frontend_flags = {}
125 frontend_flags = {}
128 addflag = lambda *args: frontend_flags.update(boolean_flag(*args))
126 addflag = lambda *args: frontend_flags.update(boolean_flag(*args))
129 addflag('autoedit-syntax', 'TerminalInteractiveShell.autoedit_syntax',
127 addflag('autoedit-syntax', 'TerminalInteractiveShell.autoedit_syntax',
130 'Turn on auto editing of files with syntax errors.',
128 'Turn on auto editing of files with syntax errors.',
131 'Turn off auto editing of files with syntax errors.'
129 'Turn off auto editing of files with syntax errors.'
132 )
130 )
133 addflag('banner', 'TerminalIPythonApp.display_banner',
131 addflag('banner', 'TerminalIPythonApp.display_banner',
134 "Display a banner upon starting IPython.",
132 "Display a banner upon starting IPython.",
135 "Don't display a banner upon starting IPython."
133 "Don't display a banner upon starting IPython."
136 )
134 )
137 addflag('confirm-exit', 'TerminalInteractiveShell.confirm_exit',
135 addflag('confirm-exit', 'TerminalInteractiveShell.confirm_exit',
138 """Set to confirm when you try to exit IPython with an EOF (Control-D
136 """Set to confirm when you try to exit IPython with an EOF (Control-D
139 in Unix, Control-Z/Enter in Windows). By typing 'exit' or 'quit',
137 in Unix, Control-Z/Enter in Windows). By typing 'exit' or 'quit',
140 you can force a direct exit without any confirmation.""",
138 you can force a direct exit without any confirmation.""",
141 "Don't prompt the user when exiting."
139 "Don't prompt the user when exiting."
142 )
140 )
143 addflag('term-title', 'TerminalInteractiveShell.term_title',
141 addflag('term-title', 'TerminalInteractiveShell.term_title',
144 "Enable auto setting the terminal title.",
142 "Enable auto setting the terminal title.",
145 "Disable auto setting the terminal title."
143 "Disable auto setting the terminal title."
146 )
144 )
147 classic_config = Config()
145 classic_config = Config()
148 classic_config.InteractiveShell.cache_size = 0
146 classic_config.InteractiveShell.cache_size = 0
149 classic_config.PlainTextFormatter.pprint = False
147 classic_config.PlainTextFormatter.pprint = False
150 classic_config.PromptManager.in_template = '>>> '
148 classic_config.PromptManager.in_template = '>>> '
151 classic_config.PromptManager.in2_template = '... '
149 classic_config.PromptManager.in2_template = '... '
152 classic_config.PromptManager.out_template = ''
150 classic_config.PromptManager.out_template = ''
153 classic_config.InteractiveShell.separate_in = ''
151 classic_config.InteractiveShell.separate_in = ''
154 classic_config.InteractiveShell.separate_out = ''
152 classic_config.InteractiveShell.separate_out = ''
155 classic_config.InteractiveShell.separate_out2 = ''
153 classic_config.InteractiveShell.separate_out2 = ''
156 classic_config.InteractiveShell.colors = 'NoColor'
154 classic_config.InteractiveShell.colors = 'NoColor'
157 classic_config.InteractiveShell.xmode = 'Plain'
155 classic_config.InteractiveShell.xmode = 'Plain'
158
156
159 frontend_flags['classic']=(
157 frontend_flags['classic']=(
160 classic_config,
158 classic_config,
161 "Gives IPython a similar feel to the classic Python prompt."
159 "Gives IPython a similar feel to the classic Python prompt."
162 )
160 )
163 # # log doesn't make so much sense this way anymore
161 # # log doesn't make so much sense this way anymore
164 # paa('--log','-l',
162 # paa('--log','-l',
165 # action='store_true', dest='InteractiveShell.logstart',
163 # action='store_true', dest='InteractiveShell.logstart',
166 # help="Start logging to the default log file (./ipython_log.py).")
164 # help="Start logging to the default log file (./ipython_log.py).")
167 #
165 #
168 # # quick is harder to implement
166 # # quick is harder to implement
169 frontend_flags['quick']=(
167 frontend_flags['quick']=(
170 {'TerminalIPythonApp' : {'quick' : True}},
168 {'TerminalIPythonApp' : {'quick' : True}},
171 "Enable quick startup with no config files."
169 "Enable quick startup with no config files."
172 )
170 )
173
171
174 frontend_flags['i'] = (
172 frontend_flags['i'] = (
175 {'TerminalIPythonApp' : {'force_interact' : True}},
173 {'TerminalIPythonApp' : {'force_interact' : True}},
176 """If running code from the command line, become interactive afterwards.
174 """If running code from the command line, become interactive afterwards.
177 Note: can also be given simply as '-i'."""
175 Note: can also be given simply as '-i'."""
178 )
176 )
179 flags.update(frontend_flags)
177 flags.update(frontend_flags)
180
178
181 aliases = dict(base_aliases)
179 aliases = dict(base_aliases)
182 aliases.update(shell_aliases)
180 aliases.update(shell_aliases)
183
181
184 #-----------------------------------------------------------------------------
182 #-----------------------------------------------------------------------------
185 # Main classes and functions
183 # Main classes and functions
186 #-----------------------------------------------------------------------------
184 #-----------------------------------------------------------------------------
187
185
188
186
189 class LocateIPythonApp(BaseIPythonApplication):
187 class LocateIPythonApp(BaseIPythonApplication):
190 description = """print the path to the IPython dir"""
188 description = """print the path to the IPython dir"""
191 subcommands = Dict(dict(
189 subcommands = Dict(dict(
192 profile=('IPython.core.profileapp.ProfileLocate',
190 profile=('IPython.core.profileapp.ProfileLocate',
193 "print the path to an IPython profile directory",
191 "print the path to an IPython profile directory",
194 ),
192 ),
195 ))
193 ))
196 def start(self):
194 def start(self):
197 if self.subapp is not None:
195 if self.subapp is not None:
198 return self.subapp.start()
196 return self.subapp.start()
199 else:
197 else:
200 print(self.ipython_dir)
198 print(self.ipython_dir)
201
199
202
200
203 class TerminalIPythonApp(BaseIPythonApplication, InteractiveShellApp):
201 class TerminalIPythonApp(BaseIPythonApplication, InteractiveShellApp):
204 name = u'ipython'
202 name = u'ipython'
205 description = usage.cl_usage
203 description = usage.cl_usage
206 crash_handler_class = IPAppCrashHandler
204 crash_handler_class = IPAppCrashHandler
207 examples = _examples
205 examples = _examples
208
206
209 flags = Dict(flags)
207 flags = Dict(flags)
210 aliases = Dict(aliases)
208 aliases = Dict(aliases)
211 classes = List()
209 classes = List()
212 def _classes_default(self):
210 def _classes_default(self):
213 """This has to be in a method, for TerminalIPythonApp to be available."""
211 """This has to be in a method, for TerminalIPythonApp to be available."""
214 return [
212 return [
215 InteractiveShellApp, # ShellApp comes before TerminalApp, because
213 InteractiveShellApp, # ShellApp comes before TerminalApp, because
216 self.__class__, # it will also affect subclasses (e.g. QtConsole)
214 self.__class__, # it will also affect subclasses (e.g. QtConsole)
217 TerminalInteractiveShell,
215 TerminalInteractiveShell,
218 PromptManager,
216 PromptManager,
219 HistoryManager,
217 HistoryManager,
220 ProfileDir,
218 ProfileDir,
221 PlainTextFormatter,
219 PlainTextFormatter,
222 IPCompleter,
220 IPCompleter,
223 ScriptMagics,
221 ScriptMagics,
224 StoreMagics,
222 StoreMagics,
225 ]
223 ]
226
224
227 subcommands = dict(
225 subcommands = dict(
228 qtconsole=('IPython.qt.console.qtconsoleapp.IPythonQtConsoleApp',
226 qtconsole=('IPython.qt.console.qtconsoleapp.IPythonQtConsoleApp',
229 """Launch the IPython Qt Console."""
227 """Launch the IPython Qt Console."""
230 ),
228 ),
231 notebook=('IPython.html.notebookapp.NotebookApp',
229 notebook=('IPython.html.notebookapp.NotebookApp',
232 """Launch the IPython HTML Notebook Server."""
230 """Launch the IPython HTML Notebook Server."""
233 ),
231 ),
234 profile = ("IPython.core.profileapp.ProfileApp",
232 profile = ("IPython.core.profileapp.ProfileApp",
235 "Create and manage IPython profiles."
233 "Create and manage IPython profiles."
236 ),
234 ),
237 kernel = ("IPython.kernel.zmq.kernelapp.IPKernelApp",
235 kernel = ("IPython.kernel.zmq.kernelapp.IPKernelApp",
238 "Start a kernel without an attached frontend."
236 "Start a kernel without an attached frontend."
239 ),
237 ),
240 console=('IPython.terminal.console.app.ZMQTerminalIPythonApp',
238 console=('IPython.terminal.console.app.ZMQTerminalIPythonApp',
241 """Launch the IPython terminal-based Console."""
239 """Launch the IPython terminal-based Console."""
242 ),
240 ),
243 locate=('IPython.terminal.ipapp.LocateIPythonApp',
241 locate=('IPython.terminal.ipapp.LocateIPythonApp',
244 LocateIPythonApp.description
242 LocateIPythonApp.description
245 ),
243 ),
246 history=('IPython.core.historyapp.HistoryApp',
244 history=('IPython.core.historyapp.HistoryApp',
247 "Manage the IPython history database."
245 "Manage the IPython history database."
248 ),
246 ),
249 nbconvert=('IPython.nbconvert.nbconvertapp.NbConvertApp',
247 nbconvert=('IPython.nbconvert.nbconvertapp.NbConvertApp',
250 "Convert notebooks to/from other formats."
248 "Convert notebooks to/from other formats."
251 ),
249 ),
252 trust=('IPython.nbformat.sign.TrustNotebookApp',
250 trust=('IPython.nbformat.sign.TrustNotebookApp',
253 "Sign notebooks to trust their potentially unsafe contents at load."
251 "Sign notebooks to trust their potentially unsafe contents at load."
254 ),
252 ),
255 )
253 )
256 subcommands['install-nbextension'] = (
254 subcommands['install-nbextension'] = (
257 "IPython.html.nbextensions.NBExtensionApp",
255 "IPython.html.nbextensions.NBExtensionApp",
258 "Install IPython notebook extension files"
256 "Install IPython notebook extension files"
259 )
257 )
260
258
261 # *do* autocreate requested profile, but don't create the config file.
259 # *do* autocreate requested profile, but don't create the config file.
262 auto_create=Bool(True)
260 auto_create=Bool(True)
263 # configurables
261 # configurables
264 ignore_old_config=Bool(False, config=True,
262 ignore_old_config=Bool(False, config=True,
265 help="Suppress warning messages about legacy config files"
263 help="Suppress warning messages about legacy config files"
266 )
264 )
267 quick = Bool(False, config=True,
265 quick = Bool(False, config=True,
268 help="""Start IPython quickly by skipping the loading of config files."""
266 help="""Start IPython quickly by skipping the loading of config files."""
269 )
267 )
270 def _quick_changed(self, name, old, new):
268 def _quick_changed(self, name, old, new):
271 if new:
269 if new:
272 self.load_config_file = lambda *a, **kw: None
270 self.load_config_file = lambda *a, **kw: None
273 self.ignore_old_config=True
271 self.ignore_old_config=True
274
272
275 display_banner = Bool(True, config=True,
273 display_banner = Bool(True, config=True,
276 help="Whether to display a banner upon starting IPython."
274 help="Whether to display a banner upon starting IPython."
277 )
275 )
278
276
279 # if there is code of files to run from the cmd line, don't interact
277 # if there is code of files to run from the cmd line, don't interact
280 # unless the --i flag (App.force_interact) is true.
278 # unless the --i flag (App.force_interact) is true.
281 force_interact = Bool(False, config=True,
279 force_interact = Bool(False, config=True,
282 help="""If a command or file is given via the command-line,
280 help="""If a command or file is given via the command-line,
283 e.g. 'ipython foo.py', start an interactive shell after executing the
281 e.g. 'ipython foo.py', start an interactive shell after executing the
284 file or command."""
282 file or command."""
285 )
283 )
286 def _force_interact_changed(self, name, old, new):
284 def _force_interact_changed(self, name, old, new):
287 if new:
285 if new:
288 self.interact = True
286 self.interact = True
289
287
290 def _file_to_run_changed(self, name, old, new):
288 def _file_to_run_changed(self, name, old, new):
291 if new:
289 if new:
292 self.something_to_run = True
290 self.something_to_run = True
293 if new and not self.force_interact:
291 if new and not self.force_interact:
294 self.interact = False
292 self.interact = False
295 _code_to_run_changed = _file_to_run_changed
293 _code_to_run_changed = _file_to_run_changed
296 _module_to_run_changed = _file_to_run_changed
294 _module_to_run_changed = _file_to_run_changed
297
295
298 # internal, not-configurable
296 # internal, not-configurable
299 interact=Bool(True)
297 interact=Bool(True)
300 something_to_run=Bool(False)
298 something_to_run=Bool(False)
301
299
302 def parse_command_line(self, argv=None):
300 def parse_command_line(self, argv=None):
303 """override to allow old '-pylab' flag with deprecation warning"""
301 """override to allow old '-pylab' flag with deprecation warning"""
304
302
305 argv = sys.argv[1:] if argv is None else argv
303 argv = sys.argv[1:] if argv is None else argv
306
304
307 if '-pylab' in argv:
305 if '-pylab' in argv:
308 # deprecated `-pylab` given,
306 # deprecated `-pylab` given,
309 # warn and transform into current syntax
307 # warn and transform into current syntax
310 argv = argv[:] # copy, don't clobber
308 argv = argv[:] # copy, don't clobber
311 idx = argv.index('-pylab')
309 idx = argv.index('-pylab')
312 warn.warn("`-pylab` flag has been deprecated.\n"
310 warn.warn("`-pylab` flag has been deprecated.\n"
313 " Use `--matplotlib <backend>` and import pylab manually.")
311 " Use `--matplotlib <backend>` and import pylab manually.")
314 argv[idx] = '--pylab'
312 argv[idx] = '--pylab'
315
313
316 return super(TerminalIPythonApp, self).parse_command_line(argv)
314 return super(TerminalIPythonApp, self).parse_command_line(argv)
317
315
318 @catch_config_error
316 @catch_config_error
319 def initialize(self, argv=None):
317 def initialize(self, argv=None):
320 """Do actions after construct, but before starting the app."""
318 """Do actions after construct, but before starting the app."""
321 super(TerminalIPythonApp, self).initialize(argv)
319 super(TerminalIPythonApp, self).initialize(argv)
322 if self.subapp is not None:
320 if self.subapp is not None:
323 # don't bother initializing further, starting subapp
321 # don't bother initializing further, starting subapp
324 return
322 return
325 if not self.ignore_old_config:
323 if not self.ignore_old_config:
326 check_for_old_config(self.ipython_dir)
324 check_for_old_config(self.ipython_dir)
327 # print self.extra_args
325 # print self.extra_args
328 if self.extra_args and not self.something_to_run:
326 if self.extra_args and not self.something_to_run:
329 self.file_to_run = self.extra_args[0]
327 self.file_to_run = self.extra_args[0]
330 self.init_path()
328 self.init_path()
331 # create the shell
329 # create the shell
332 self.init_shell()
330 self.init_shell()
333 # and draw the banner
331 # and draw the banner
334 self.init_banner()
332 self.init_banner()
335 # Now a variety of things that happen after the banner is printed.
333 # Now a variety of things that happen after the banner is printed.
336 self.init_gui_pylab()
334 self.init_gui_pylab()
337 self.init_extensions()
335 self.init_extensions()
338 self.init_code()
336 self.init_code()
339
337
340 def init_shell(self):
338 def init_shell(self):
341 """initialize the InteractiveShell instance"""
339 """initialize the InteractiveShell instance"""
342 # Create an InteractiveShell instance.
340 # Create an InteractiveShell instance.
343 # shell.display_banner should always be False for the terminal
341 # shell.display_banner should always be False for the terminal
344 # based app, because we call shell.show_banner() by hand below
342 # based app, because we call shell.show_banner() by hand below
345 # so the banner shows *before* all extension loading stuff.
343 # so the banner shows *before* all extension loading stuff.
346 self.shell = TerminalInteractiveShell.instance(parent=self,
344 self.shell = TerminalInteractiveShell.instance(parent=self,
347 display_banner=False, profile_dir=self.profile_dir,
345 display_banner=False, profile_dir=self.profile_dir,
348 ipython_dir=self.ipython_dir, user_ns=self.user_ns)
346 ipython_dir=self.ipython_dir, user_ns=self.user_ns)
349 self.shell.configurables.append(self)
347 self.shell.configurables.append(self)
350
348
351 def init_banner(self):
349 def init_banner(self):
352 """optionally display the banner"""
350 """optionally display the banner"""
353 if self.display_banner and self.interact:
351 if self.display_banner and self.interact:
354 self.shell.show_banner()
352 self.shell.show_banner()
355 # Make sure there is a space below the banner.
353 # Make sure there is a space below the banner.
356 if self.log_level <= logging.INFO: print()
354 if self.log_level <= logging.INFO: print()
357
355
358 def _pylab_changed(self, name, old, new):
356 def _pylab_changed(self, name, old, new):
359 """Replace --pylab='inline' with --pylab='auto'"""
357 """Replace --pylab='inline' with --pylab='auto'"""
360 if new == 'inline':
358 if new == 'inline':
361 warn.warn("'inline' not available as pylab backend, "
359 warn.warn("'inline' not available as pylab backend, "
362 "using 'auto' instead.")
360 "using 'auto' instead.")
363 self.pylab = 'auto'
361 self.pylab = 'auto'
364
362
365 def start(self):
363 def start(self):
366 if self.subapp is not None:
364 if self.subapp is not None:
367 return self.subapp.start()
365 return self.subapp.start()
368 # perform any prexec steps:
366 # perform any prexec steps:
369 if self.interact:
367 if self.interact:
370 self.log.debug("Starting IPython's mainloop...")
368 self.log.debug("Starting IPython's mainloop...")
371 self.shell.mainloop()
369 self.shell.mainloop()
372 else:
370 else:
373 self.log.debug("IPython not interactive...")
371 self.log.debug("IPython not interactive...")
374
372
375 def load_default_config(ipython_dir=None):
373 def load_default_config(ipython_dir=None):
376 """Load the default config file from the default ipython_dir.
374 """Load the default config file from the default ipython_dir.
377
375
378 This is useful for embedded shells.
376 This is useful for embedded shells.
379 """
377 """
380 if ipython_dir is None:
378 if ipython_dir is None:
381 ipython_dir = get_ipython_dir()
379 ipython_dir = get_ipython_dir()
382
380
383 profile_dir = os.path.join(ipython_dir, 'profile_default')
381 profile_dir = os.path.join(ipython_dir, 'profile_default')
384
382
385 config = Config()
383 config = Config()
386 for cf in Application._load_config_files("ipython_config", path=profile_dir):
384 for cf in Application._load_config_files("ipython_config", path=profile_dir):
387 config.update(cf)
385 config.update(cf)
388
386
389 return config
387 return config
390
388
391 launch_new_instance = TerminalIPythonApp.launch_instance
389 launch_new_instance = TerminalIPythonApp.launch_instance
392
390
393
391
394 if __name__ == '__main__':
392 if __name__ == '__main__':
395 launch_new_instance()
393 launch_new_instance()
@@ -1,325 +1,325 b''
1 #!/usr/bin/env python
1 #!/usr/bin/env python
2
2
3 """ PickleShare - a small 'shelve' like datastore with concurrency support
3 """ PickleShare - a small 'shelve' like datastore with concurrency support
4
4
5 Like shelve, a PickleShareDB object acts like a normal dictionary. Unlike
5 Like shelve, a PickleShareDB object acts like a normal dictionary. Unlike
6 shelve, many processes can access the database simultaneously. Changing a
6 shelve, many processes can access the database simultaneously. Changing a
7 value in database is immediately visible to other processes accessing the
7 value in database is immediately visible to other processes accessing the
8 same database.
8 same database.
9
9
10 Concurrency is possible because the values are stored in separate files. Hence
10 Concurrency is possible because the values are stored in separate files. Hence
11 the "database" is a directory where *all* files are governed by PickleShare.
11 the "database" is a directory where *all* files are governed by PickleShare.
12
12
13 Example usage::
13 Example usage::
14
14
15 from pickleshare import *
15 from pickleshare import *
16 db = PickleShareDB('~/testpickleshare')
16 db = PickleShareDB('~/testpickleshare')
17 db.clear()
17 db.clear()
18 print "Should be empty:",db.items()
18 print "Should be empty:",db.items()
19 db['hello'] = 15
19 db['hello'] = 15
20 db['aku ankka'] = [1,2,313]
20 db['aku ankka'] = [1,2,313]
21 db['paths/are/ok/key'] = [1,(5,46)]
21 db['paths/are/ok/key'] = [1,(5,46)]
22 print db.keys()
22 print db.keys()
23 del db['aku ankka']
23 del db['aku ankka']
24
24
25 This module is certainly not ZODB, but can be used for low-load
25 This module is certainly not ZODB, but can be used for low-load
26 (non-mission-critical) situations where tiny code size trumps the
26 (non-mission-critical) situations where tiny code size trumps the
27 advanced features of a "real" object database.
27 advanced features of a "real" object database.
28
28
29 Installation guide: easy_install pickleshare
29 Installation guide: easy_install pickleshare
30
30
31 Author: Ville Vainio <vivainio@gmail.com>
31 Author: Ville Vainio <vivainio@gmail.com>
32 License: MIT open source license.
32 License: MIT open source license.
33
33
34 """
34 """
35 from __future__ import print_function
35 from __future__ import print_function
36
36
37 from IPython.external.path import path as Path
37 from IPython.external.path import path as Path
38 import os,stat,time
38 import stat, time
39 import collections
39 import collections
40 try:
40 try:
41 import cPickle as pickle
41 import cPickle as pickle
42 except ImportError:
42 except ImportError:
43 import pickle
43 import pickle
44 import glob
44 import glob
45
45
46 def gethashfile(key):
46 def gethashfile(key):
47 return ("%02x" % abs(hash(key) % 256))[-2:]
47 return ("%02x" % abs(hash(key) % 256))[-2:]
48
48
49 _sentinel = object()
49 _sentinel = object()
50
50
51 class PickleShareDB(collections.MutableMapping):
51 class PickleShareDB(collections.MutableMapping):
52 """ The main 'connection' object for PickleShare database """
52 """ The main 'connection' object for PickleShare database """
53 def __init__(self,root):
53 def __init__(self,root):
54 """ Return a db object that will manage the specied directory"""
54 """ Return a db object that will manage the specied directory"""
55 self.root = Path(root).expanduser().abspath()
55 self.root = Path(root).expanduser().abspath()
56 if not self.root.isdir():
56 if not self.root.isdir():
57 self.root.makedirs()
57 self.root.makedirs()
58 # cache has { 'key' : (obj, orig_mod_time) }
58 # cache has { 'key' : (obj, orig_mod_time) }
59 self.cache = {}
59 self.cache = {}
60
60
61
61
62 def __getitem__(self,key):
62 def __getitem__(self,key):
63 """ db['key'] reading """
63 """ db['key'] reading """
64 fil = self.root / key
64 fil = self.root / key
65 try:
65 try:
66 mtime = (fil.stat()[stat.ST_MTIME])
66 mtime = (fil.stat()[stat.ST_MTIME])
67 except OSError:
67 except OSError:
68 raise KeyError(key)
68 raise KeyError(key)
69
69
70 if fil in self.cache and mtime == self.cache[fil][1]:
70 if fil in self.cache and mtime == self.cache[fil][1]:
71 return self.cache[fil][0]
71 return self.cache[fil][0]
72 try:
72 try:
73 # The cached item has expired, need to read
73 # The cached item has expired, need to read
74 with fil.open("rb") as f:
74 with fil.open("rb") as f:
75 obj = pickle.loads(f.read())
75 obj = pickle.loads(f.read())
76 except:
76 except:
77 raise KeyError(key)
77 raise KeyError(key)
78
78
79 self.cache[fil] = (obj,mtime)
79 self.cache[fil] = (obj,mtime)
80 return obj
80 return obj
81
81
82 def __setitem__(self,key,value):
82 def __setitem__(self,key,value):
83 """ db['key'] = 5 """
83 """ db['key'] = 5 """
84 fil = self.root / key
84 fil = self.root / key
85 parent = fil.parent
85 parent = fil.parent
86 if parent and not parent.isdir():
86 if parent and not parent.isdir():
87 parent.makedirs()
87 parent.makedirs()
88 # We specify protocol 2, so that we can mostly go between Python 2
88 # We specify protocol 2, so that we can mostly go between Python 2
89 # and Python 3. We can upgrade to protocol 3 when Python 2 is obsolete.
89 # and Python 3. We can upgrade to protocol 3 when Python 2 is obsolete.
90 with fil.open('wb') as f:
90 with fil.open('wb') as f:
91 pickled = pickle.dump(value, f, protocol=2)
91 pickled = pickle.dump(value, f, protocol=2)
92 try:
92 try:
93 self.cache[fil] = (value,fil.mtime)
93 self.cache[fil] = (value,fil.mtime)
94 except OSError as e:
94 except OSError as e:
95 if e.errno != 2:
95 if e.errno != 2:
96 raise
96 raise
97
97
98 def hset(self, hashroot, key, value):
98 def hset(self, hashroot, key, value):
99 """ hashed set """
99 """ hashed set """
100 hroot = self.root / hashroot
100 hroot = self.root / hashroot
101 if not hroot.isdir():
101 if not hroot.isdir():
102 hroot.makedirs()
102 hroot.makedirs()
103 hfile = hroot / gethashfile(key)
103 hfile = hroot / gethashfile(key)
104 d = self.get(hfile, {})
104 d = self.get(hfile, {})
105 d.update( {key : value})
105 d.update( {key : value})
106 self[hfile] = d
106 self[hfile] = d
107
107
108
108
109
109
110 def hget(self, hashroot, key, default = _sentinel, fast_only = True):
110 def hget(self, hashroot, key, default = _sentinel, fast_only = True):
111 """ hashed get """
111 """ hashed get """
112 hroot = self.root / hashroot
112 hroot = self.root / hashroot
113 hfile = hroot / gethashfile(key)
113 hfile = hroot / gethashfile(key)
114
114
115 d = self.get(hfile, _sentinel )
115 d = self.get(hfile, _sentinel )
116 #print "got dict",d,"from",hfile
116 #print "got dict",d,"from",hfile
117 if d is _sentinel:
117 if d is _sentinel:
118 if fast_only:
118 if fast_only:
119 if default is _sentinel:
119 if default is _sentinel:
120 raise KeyError(key)
120 raise KeyError(key)
121
121
122 return default
122 return default
123
123
124 # slow mode ok, works even after hcompress()
124 # slow mode ok, works even after hcompress()
125 d = self.hdict(hashroot)
125 d = self.hdict(hashroot)
126
126
127 return d.get(key, default)
127 return d.get(key, default)
128
128
129 def hdict(self, hashroot):
129 def hdict(self, hashroot):
130 """ Get all data contained in hashed category 'hashroot' as dict """
130 """ Get all data contained in hashed category 'hashroot' as dict """
131 hfiles = self.keys(hashroot + "/*")
131 hfiles = self.keys(hashroot + "/*")
132 hfiles.sort()
132 hfiles.sort()
133 last = len(hfiles) and hfiles[-1] or ''
133 last = len(hfiles) and hfiles[-1] or ''
134 if last.endswith('xx'):
134 if last.endswith('xx'):
135 # print "using xx"
135 # print "using xx"
136 hfiles = [last] + hfiles[:-1]
136 hfiles = [last] + hfiles[:-1]
137
137
138 all = {}
138 all = {}
139
139
140 for f in hfiles:
140 for f in hfiles:
141 # print "using",f
141 # print "using",f
142 try:
142 try:
143 all.update(self[f])
143 all.update(self[f])
144 except KeyError:
144 except KeyError:
145 print("Corrupt",f,"deleted - hset is not threadsafe!")
145 print("Corrupt",f,"deleted - hset is not threadsafe!")
146 del self[f]
146 del self[f]
147
147
148 self.uncache(f)
148 self.uncache(f)
149
149
150 return all
150 return all
151
151
152 def hcompress(self, hashroot):
152 def hcompress(self, hashroot):
153 """ Compress category 'hashroot', so hset is fast again
153 """ Compress category 'hashroot', so hset is fast again
154
154
155 hget will fail if fast_only is True for compressed items (that were
155 hget will fail if fast_only is True for compressed items (that were
156 hset before hcompress).
156 hset before hcompress).
157
157
158 """
158 """
159 hfiles = self.keys(hashroot + "/*")
159 hfiles = self.keys(hashroot + "/*")
160 all = {}
160 all = {}
161 for f in hfiles:
161 for f in hfiles:
162 # print "using",f
162 # print "using",f
163 all.update(self[f])
163 all.update(self[f])
164 self.uncache(f)
164 self.uncache(f)
165
165
166 self[hashroot + '/xx'] = all
166 self[hashroot + '/xx'] = all
167 for f in hfiles:
167 for f in hfiles:
168 p = self.root / f
168 p = self.root / f
169 if p.basename() == 'xx':
169 if p.basename() == 'xx':
170 continue
170 continue
171 p.remove()
171 p.remove()
172
172
173
173
174
174
175 def __delitem__(self,key):
175 def __delitem__(self,key):
176 """ del db["key"] """
176 """ del db["key"] """
177 fil = self.root / key
177 fil = self.root / key
178 self.cache.pop(fil,None)
178 self.cache.pop(fil,None)
179 try:
179 try:
180 fil.remove()
180 fil.remove()
181 except OSError:
181 except OSError:
182 # notfound and permission denied are ok - we
182 # notfound and permission denied are ok - we
183 # lost, the other process wins the conflict
183 # lost, the other process wins the conflict
184 pass
184 pass
185
185
186 def _normalized(self, p):
186 def _normalized(self, p):
187 """ Make a key suitable for user's eyes """
187 """ Make a key suitable for user's eyes """
188 return str(self.root.relpathto(p)).replace('\\','/')
188 return str(self.root.relpathto(p)).replace('\\','/')
189
189
190 def keys(self, globpat = None):
190 def keys(self, globpat = None):
191 """ All keys in DB, or all keys matching a glob"""
191 """ All keys in DB, or all keys matching a glob"""
192
192
193 if globpat is None:
193 if globpat is None:
194 files = self.root.walkfiles()
194 files = self.root.walkfiles()
195 else:
195 else:
196 files = [Path(p) for p in glob.glob(self.root/globpat)]
196 files = [Path(p) for p in glob.glob(self.root/globpat)]
197 return [self._normalized(p) for p in files if p.isfile()]
197 return [self._normalized(p) for p in files if p.isfile()]
198
198
199 def __iter__(self):
199 def __iter__(self):
200 return iter(self.keys())
200 return iter(self.keys())
201
201
202 def __len__(self):
202 def __len__(self):
203 return len(self.keys())
203 return len(self.keys())
204
204
205 def uncache(self,*items):
205 def uncache(self,*items):
206 """ Removes all, or specified items from cache
206 """ Removes all, or specified items from cache
207
207
208 Use this after reading a large amount of large objects
208 Use this after reading a large amount of large objects
209 to free up memory, when you won't be needing the objects
209 to free up memory, when you won't be needing the objects
210 for a while.
210 for a while.
211
211
212 """
212 """
213 if not items:
213 if not items:
214 self.cache = {}
214 self.cache = {}
215 for it in items:
215 for it in items:
216 self.cache.pop(it,None)
216 self.cache.pop(it,None)
217
217
218 def waitget(self,key, maxwaittime = 60 ):
218 def waitget(self,key, maxwaittime = 60 ):
219 """ Wait (poll) for a key to get a value
219 """ Wait (poll) for a key to get a value
220
220
221 Will wait for `maxwaittime` seconds before raising a KeyError.
221 Will wait for `maxwaittime` seconds before raising a KeyError.
222 The call exits normally if the `key` field in db gets a value
222 The call exits normally if the `key` field in db gets a value
223 within the timeout period.
223 within the timeout period.
224
224
225 Use this for synchronizing different processes or for ensuring
225 Use this for synchronizing different processes or for ensuring
226 that an unfortunately timed "db['key'] = newvalue" operation
226 that an unfortunately timed "db['key'] = newvalue" operation
227 in another process (which causes all 'get' operation to cause a
227 in another process (which causes all 'get' operation to cause a
228 KeyError for the duration of pickling) won't screw up your program
228 KeyError for the duration of pickling) won't screw up your program
229 logic.
229 logic.
230 """
230 """
231
231
232 wtimes = [0.2] * 3 + [0.5] * 2 + [1]
232 wtimes = [0.2] * 3 + [0.5] * 2 + [1]
233 tries = 0
233 tries = 0
234 waited = 0
234 waited = 0
235 while 1:
235 while 1:
236 try:
236 try:
237 val = self[key]
237 val = self[key]
238 return val
238 return val
239 except KeyError:
239 except KeyError:
240 pass
240 pass
241
241
242 if waited > maxwaittime:
242 if waited > maxwaittime:
243 raise KeyError(key)
243 raise KeyError(key)
244
244
245 time.sleep(wtimes[tries])
245 time.sleep(wtimes[tries])
246 waited+=wtimes[tries]
246 waited+=wtimes[tries]
247 if tries < len(wtimes) -1:
247 if tries < len(wtimes) -1:
248 tries+=1
248 tries+=1
249
249
250 def getlink(self,folder):
250 def getlink(self,folder):
251 """ Get a convenient link for accessing items """
251 """ Get a convenient link for accessing items """
252 return PickleShareLink(self, folder)
252 return PickleShareLink(self, folder)
253
253
254 def __repr__(self):
254 def __repr__(self):
255 return "PickleShareDB('%s')" % self.root
255 return "PickleShareDB('%s')" % self.root
256
256
257
257
258
258
259 class PickleShareLink:
259 class PickleShareLink:
260 """ A shortdand for accessing nested PickleShare data conveniently.
260 """ A shortdand for accessing nested PickleShare data conveniently.
261
261
262 Created through PickleShareDB.getlink(), example::
262 Created through PickleShareDB.getlink(), example::
263
263
264 lnk = db.getlink('myobjects/test')
264 lnk = db.getlink('myobjects/test')
265 lnk.foo = 2
265 lnk.foo = 2
266 lnk.bar = lnk.foo + 5
266 lnk.bar = lnk.foo + 5
267
267
268 """
268 """
269 def __init__(self, db, keydir ):
269 def __init__(self, db, keydir ):
270 self.__dict__.update(locals())
270 self.__dict__.update(locals())
271
271
272 def __getattr__(self,key):
272 def __getattr__(self,key):
273 return self.__dict__['db'][self.__dict__['keydir']+'/' + key]
273 return self.__dict__['db'][self.__dict__['keydir']+'/' + key]
274 def __setattr__(self,key,val):
274 def __setattr__(self,key,val):
275 self.db[self.keydir+'/' + key] = val
275 self.db[self.keydir+'/' + key] = val
276 def __repr__(self):
276 def __repr__(self):
277 db = self.__dict__['db']
277 db = self.__dict__['db']
278 keys = db.keys( self.__dict__['keydir'] +"/*")
278 keys = db.keys( self.__dict__['keydir'] +"/*")
279 return "<PickleShareLink '%s': %s>" % (
279 return "<PickleShareLink '%s': %s>" % (
280 self.__dict__['keydir'],
280 self.__dict__['keydir'],
281 ";".join([Path(k).basename() for k in keys]))
281 ";".join([Path(k).basename() for k in keys]))
282
282
283 def main():
283 def main():
284 import textwrap
284 import textwrap
285 usage = textwrap.dedent("""\
285 usage = textwrap.dedent("""\
286 pickleshare - manage PickleShare databases
286 pickleshare - manage PickleShare databases
287
287
288 Usage:
288 Usage:
289
289
290 pickleshare dump /path/to/db > dump.txt
290 pickleshare dump /path/to/db > dump.txt
291 pickleshare load /path/to/db < dump.txt
291 pickleshare load /path/to/db < dump.txt
292 pickleshare test /path/to/db
292 pickleshare test /path/to/db
293 """)
293 """)
294 DB = PickleShareDB
294 DB = PickleShareDB
295 import sys
295 import sys
296 if len(sys.argv) < 2:
296 if len(sys.argv) < 2:
297 print(usage)
297 print(usage)
298 return
298 return
299
299
300 cmd = sys.argv[1]
300 cmd = sys.argv[1]
301 args = sys.argv[2:]
301 args = sys.argv[2:]
302 if cmd == 'dump':
302 if cmd == 'dump':
303 if not args: args= ['.']
303 if not args: args= ['.']
304 db = DB(args[0])
304 db = DB(args[0])
305 import pprint
305 import pprint
306 pprint.pprint(db.items())
306 pprint.pprint(db.items())
307 elif cmd == 'load':
307 elif cmd == 'load':
308 cont = sys.stdin.read()
308 cont = sys.stdin.read()
309 db = DB(args[0])
309 db = DB(args[0])
310 data = eval(cont)
310 data = eval(cont)
311 db.clear()
311 db.clear()
312 for k,v in db.items():
312 for k,v in db.items():
313 db[k] = v
313 db[k] = v
314 elif cmd == 'testwait':
314 elif cmd == 'testwait':
315 db = DB(args[0])
315 db = DB(args[0])
316 db.clear()
316 db.clear()
317 print(db.waitget('250'))
317 print(db.waitget('250'))
318 elif cmd == 'test':
318 elif cmd == 'test':
319 test()
319 test()
320 stress()
320 stress()
321
321
322 if __name__== "__main__":
322 if __name__== "__main__":
323 main()
323 main()
324
324
325
325
@@ -1,124 +1,123 b''
1 # encoding: utf-8
1 # encoding: utf-8
2 """
2 """
3 Utilities for working with external processes.
3 Utilities for working with external processes.
4 """
4 """
5
5
6 #-----------------------------------------------------------------------------
6 #-----------------------------------------------------------------------------
7 # Copyright (C) 2008-2011 The IPython Development Team
7 # Copyright (C) 2008-2011 The IPython Development Team
8 #
8 #
9 # Distributed under the terms of the BSD License. The full license is in
9 # Distributed under the terms of the BSD License. The full license is in
10 # the file COPYING, distributed as part of this software.
10 # the file COPYING, distributed as part of this software.
11 #-----------------------------------------------------------------------------
11 #-----------------------------------------------------------------------------
12
12
13 #-----------------------------------------------------------------------------
13 #-----------------------------------------------------------------------------
14 # Imports
14 # Imports
15 #-----------------------------------------------------------------------------
15 #-----------------------------------------------------------------------------
16 from __future__ import print_function
16 from __future__ import print_function
17
17
18 # Stdlib
18 # Stdlib
19 import os
19 import os
20 import sys
20 import sys
21 import shlex
22
21
23 # Our own
22 # Our own
24 if sys.platform == 'win32':
23 if sys.platform == 'win32':
25 from ._process_win32 import _find_cmd, system, getoutput, arg_split
24 from ._process_win32 import _find_cmd, system, getoutput, arg_split
26 elif sys.platform == 'cli':
25 elif sys.platform == 'cli':
27 from ._process_cli import _find_cmd, system, getoutput, arg_split
26 from ._process_cli import _find_cmd, system, getoutput, arg_split
28 else:
27 else:
29 from ._process_posix import _find_cmd, system, getoutput, arg_split
28 from ._process_posix import _find_cmd, system, getoutput, arg_split
30
29
31 from ._process_common import getoutputerror, get_output_error_code, process_handler
30 from ._process_common import getoutputerror, get_output_error_code, process_handler
32 from . import py3compat
31 from . import py3compat
33
32
34 #-----------------------------------------------------------------------------
33 #-----------------------------------------------------------------------------
35 # Code
34 # Code
36 #-----------------------------------------------------------------------------
35 #-----------------------------------------------------------------------------
37
36
38
37
39 class FindCmdError(Exception):
38 class FindCmdError(Exception):
40 pass
39 pass
41
40
42
41
43 def find_cmd(cmd):
42 def find_cmd(cmd):
44 """Find absolute path to executable cmd in a cross platform manner.
43 """Find absolute path to executable cmd in a cross platform manner.
45
44
46 This function tries to determine the full path to a command line program
45 This function tries to determine the full path to a command line program
47 using `which` on Unix/Linux/OS X and `win32api` on Windows. Most of the
46 using `which` on Unix/Linux/OS X and `win32api` on Windows. Most of the
48 time it will use the version that is first on the users `PATH`.
47 time it will use the version that is first on the users `PATH`.
49
48
50 Warning, don't use this to find IPython command line programs as there
49 Warning, don't use this to find IPython command line programs as there
51 is a risk you will find the wrong one. Instead find those using the
50 is a risk you will find the wrong one. Instead find those using the
52 following code and looking for the application itself::
51 following code and looking for the application itself::
53
52
54 from IPython.utils.path import get_ipython_module_path
53 from IPython.utils.path import get_ipython_module_path
55 from IPython.utils.process import pycmd2argv
54 from IPython.utils.process import pycmd2argv
56 argv = pycmd2argv(get_ipython_module_path('IPython.terminal.ipapp'))
55 argv = pycmd2argv(get_ipython_module_path('IPython.terminal.ipapp'))
57
56
58 Parameters
57 Parameters
59 ----------
58 ----------
60 cmd : str
59 cmd : str
61 The command line program to look for.
60 The command line program to look for.
62 """
61 """
63 try:
62 try:
64 path = _find_cmd(cmd).rstrip()
63 path = _find_cmd(cmd).rstrip()
65 except OSError:
64 except OSError:
66 raise FindCmdError('command could not be found: %s' % cmd)
65 raise FindCmdError('command could not be found: %s' % cmd)
67 # which returns empty if not found
66 # which returns empty if not found
68 if path == '':
67 if path == '':
69 raise FindCmdError('command could not be found: %s' % cmd)
68 raise FindCmdError('command could not be found: %s' % cmd)
70 return os.path.abspath(path)
69 return os.path.abspath(path)
71
70
72
71
73 def is_cmd_found(cmd):
72 def is_cmd_found(cmd):
74 """Check whether executable `cmd` exists or not and return a bool."""
73 """Check whether executable `cmd` exists or not and return a bool."""
75 try:
74 try:
76 find_cmd(cmd)
75 find_cmd(cmd)
77 return True
76 return True
78 except FindCmdError:
77 except FindCmdError:
79 return False
78 return False
80
79
81
80
82 def pycmd2argv(cmd):
81 def pycmd2argv(cmd):
83 r"""Take the path of a python command and return a list (argv-style).
82 r"""Take the path of a python command and return a list (argv-style).
84
83
85 This only works on Python based command line programs and will find the
84 This only works on Python based command line programs and will find the
86 location of the ``python`` executable using ``sys.executable`` to make
85 location of the ``python`` executable using ``sys.executable`` to make
87 sure the right version is used.
86 sure the right version is used.
88
87
89 For a given path ``cmd``, this returns [cmd] if cmd's extension is .exe,
88 For a given path ``cmd``, this returns [cmd] if cmd's extension is .exe,
90 .com or .bat, and [, cmd] otherwise.
89 .com or .bat, and [, cmd] otherwise.
91
90
92 Parameters
91 Parameters
93 ----------
92 ----------
94 cmd : string
93 cmd : string
95 The path of the command.
94 The path of the command.
96
95
97 Returns
96 Returns
98 -------
97 -------
99 argv-style list.
98 argv-style list.
100 """
99 """
101 ext = os.path.splitext(cmd)[1]
100 ext = os.path.splitext(cmd)[1]
102 if ext in ['.exe', '.com', '.bat']:
101 if ext in ['.exe', '.com', '.bat']:
103 return [cmd]
102 return [cmd]
104 else:
103 else:
105 return [sys.executable, cmd]
104 return [sys.executable, cmd]
106
105
107
106
108 def abbrev_cwd():
107 def abbrev_cwd():
109 """ Return abbreviated version of cwd, e.g. d:mydir """
108 """ Return abbreviated version of cwd, e.g. d:mydir """
110 cwd = py3compat.getcwd().replace('\\','/')
109 cwd = py3compat.getcwd().replace('\\','/')
111 drivepart = ''
110 drivepart = ''
112 tail = cwd
111 tail = cwd
113 if sys.platform == 'win32':
112 if sys.platform == 'win32':
114 if len(cwd) < 4:
113 if len(cwd) < 4:
115 return cwd
114 return cwd
116 drivepart,tail = os.path.splitdrive(cwd)
115 drivepart,tail = os.path.splitdrive(cwd)
117
116
118
117
119 parts = tail.split('/')
118 parts = tail.split('/')
120 if len(parts) > 2:
119 if len(parts) > 2:
121 tail = '/'.join(parts[-2:])
120 tail = '/'.join(parts[-2:])
122
121
123 return (drivepart + (
122 return (drivepart + (
124 cwd == '/' and '/' or tail))
123 cwd == '/' and '/' or tail))
@@ -1,134 +1,133 b''
1 #!/usr/bin/env python
1 #!/usr/bin/env python
2 """Script to commit the doc build outputs into the github-pages repo.
2 """Script to commit the doc build outputs into the github-pages repo.
3
3
4 Use:
4 Use:
5
5
6 gh-pages.py [tag]
6 gh-pages.py [tag]
7
7
8 If no tag is given, the current output of 'git describe' is used. If given,
8 If no tag is given, the current output of 'git describe' is used. If given,
9 that is how the resulting directory will be named.
9 that is how the resulting directory will be named.
10
10
11 In practice, you should use either actual clean tags from a current build or
11 In practice, you should use either actual clean tags from a current build or
12 something like 'current' as a stable URL for the most current version of the """
12 something like 'current' as a stable URL for the most current version of the """
13
13
14 #-----------------------------------------------------------------------------
14 #-----------------------------------------------------------------------------
15 # Imports
15 # Imports
16 #-----------------------------------------------------------------------------
16 #-----------------------------------------------------------------------------
17 import os
17 import os
18 import re
19 import shutil
18 import shutil
20 import sys
19 import sys
21 from os import chdir as cd
20 from os import chdir as cd
22 from os.path import join as pjoin
21 from os.path import join as pjoin
23
22
24 from subprocess import Popen, PIPE, CalledProcessError, check_call
23 from subprocess import Popen, PIPE, CalledProcessError, check_call
25
24
26 #-----------------------------------------------------------------------------
25 #-----------------------------------------------------------------------------
27 # Globals
26 # Globals
28 #-----------------------------------------------------------------------------
27 #-----------------------------------------------------------------------------
29
28
30 pages_dir = 'gh-pages'
29 pages_dir = 'gh-pages'
31 html_dir = 'build/html'
30 html_dir = 'build/html'
32 pdf_dir = 'build/latex'
31 pdf_dir = 'build/latex'
33 pages_repo = 'git@github.com:ipython/ipython-doc.git'
32 pages_repo = 'git@github.com:ipython/ipython-doc.git'
34
33
35 #-----------------------------------------------------------------------------
34 #-----------------------------------------------------------------------------
36 # Functions
35 # Functions
37 #-----------------------------------------------------------------------------
36 #-----------------------------------------------------------------------------
38 def sh(cmd):
37 def sh(cmd):
39 """Execute command in a subshell, return status code."""
38 """Execute command in a subshell, return status code."""
40 return check_call(cmd, shell=True)
39 return check_call(cmd, shell=True)
41
40
42
41
43 def sh2(cmd):
42 def sh2(cmd):
44 """Execute command in a subshell, return stdout.
43 """Execute command in a subshell, return stdout.
45
44
46 Stderr is unbuffered from the subshell.x"""
45 Stderr is unbuffered from the subshell.x"""
47 p = Popen(cmd, stdout=PIPE, shell=True)
46 p = Popen(cmd, stdout=PIPE, shell=True)
48 out = p.communicate()[0]
47 out = p.communicate()[0]
49 retcode = p.returncode
48 retcode = p.returncode
50 if retcode:
49 if retcode:
51 raise CalledProcessError(retcode, cmd)
50 raise CalledProcessError(retcode, cmd)
52 else:
51 else:
53 return out.rstrip()
52 return out.rstrip()
54
53
55
54
56 def sh3(cmd):
55 def sh3(cmd):
57 """Execute command in a subshell, return stdout, stderr
56 """Execute command in a subshell, return stdout, stderr
58
57
59 If anything appears in stderr, print it out to sys.stderr"""
58 If anything appears in stderr, print it out to sys.stderr"""
60 p = Popen(cmd, stdout=PIPE, stderr=PIPE, shell=True)
59 p = Popen(cmd, stdout=PIPE, stderr=PIPE, shell=True)
61 out, err = p.communicate()
60 out, err = p.communicate()
62 retcode = p.returncode
61 retcode = p.returncode
63 if retcode:
62 if retcode:
64 raise CalledProcessError(retcode, cmd)
63 raise CalledProcessError(retcode, cmd)
65 else:
64 else:
66 return out.rstrip(), err.rstrip()
65 return out.rstrip(), err.rstrip()
67
66
68
67
69 def init_repo(path):
68 def init_repo(path):
70 """clone the gh-pages repo if we haven't already."""
69 """clone the gh-pages repo if we haven't already."""
71 sh("git clone %s %s"%(pages_repo, path))
70 sh("git clone %s %s"%(pages_repo, path))
72 here = os.getcwdu()
71 here = os.getcwdu()
73 cd(path)
72 cd(path)
74 sh('git checkout gh-pages')
73 sh('git checkout gh-pages')
75 cd(here)
74 cd(here)
76
75
77 #-----------------------------------------------------------------------------
76 #-----------------------------------------------------------------------------
78 # Script starts
77 # Script starts
79 #-----------------------------------------------------------------------------
78 #-----------------------------------------------------------------------------
80 if __name__ == '__main__':
79 if __name__ == '__main__':
81 # The tag can be given as a positional argument
80 # The tag can be given as a positional argument
82 try:
81 try:
83 tag = sys.argv[1]
82 tag = sys.argv[1]
84 except IndexError:
83 except IndexError:
85 tag = "dev"
84 tag = "dev"
86
85
87 startdir = os.getcwdu()
86 startdir = os.getcwdu()
88 if not os.path.exists(pages_dir):
87 if not os.path.exists(pages_dir):
89 # init the repo
88 # init the repo
90 init_repo(pages_dir)
89 init_repo(pages_dir)
91 else:
90 else:
92 # ensure up-to-date before operating
91 # ensure up-to-date before operating
93 cd(pages_dir)
92 cd(pages_dir)
94 sh('git checkout gh-pages')
93 sh('git checkout gh-pages')
95 sh('git pull')
94 sh('git pull')
96 cd(startdir)
95 cd(startdir)
97
96
98 dest = pjoin(pages_dir, tag)
97 dest = pjoin(pages_dir, tag)
99
98
100 # don't `make html` here, because gh-pages already depends on html in Makefile
99 # don't `make html` here, because gh-pages already depends on html in Makefile
101 # sh('make html')
100 # sh('make html')
102 if tag != 'dev':
101 if tag != 'dev':
103 # only build pdf for non-dev targets
102 # only build pdf for non-dev targets
104 #sh2('make pdf')
103 #sh2('make pdf')
105 pass
104 pass
106
105
107 # This is pretty unforgiving: we unconditionally nuke the destination
106 # This is pretty unforgiving: we unconditionally nuke the destination
108 # directory, and then copy the html tree in there
107 # directory, and then copy the html tree in there
109 shutil.rmtree(dest, ignore_errors=True)
108 shutil.rmtree(dest, ignore_errors=True)
110 shutil.copytree(html_dir, dest)
109 shutil.copytree(html_dir, dest)
111 if tag != 'dev':
110 if tag != 'dev':
112 #shutil.copy(pjoin(pdf_dir, 'ipython.pdf'), pjoin(dest, 'ipython.pdf'))
111 #shutil.copy(pjoin(pdf_dir, 'ipython.pdf'), pjoin(dest, 'ipython.pdf'))
113 pass
112 pass
114
113
115 try:
114 try:
116 cd(pages_dir)
115 cd(pages_dir)
117 branch = sh2('git rev-parse --abbrev-ref HEAD').strip()
116 branch = sh2('git rev-parse --abbrev-ref HEAD').strip()
118 if branch != 'gh-pages':
117 if branch != 'gh-pages':
119 e = 'On %r, git branch is %r, MUST be "gh-pages"' % (pages_dir,
118 e = 'On %r, git branch is %r, MUST be "gh-pages"' % (pages_dir,
120 branch)
119 branch)
121 raise RuntimeError(e)
120 raise RuntimeError(e)
122
121
123 sh('git add -A %s' % tag)
122 sh('git add -A %s' % tag)
124 sh('git commit -m"Updated doc release: %s"' % tag)
123 sh('git commit -m"Updated doc release: %s"' % tag)
125 print
124 print
126 print 'Most recent 3 commits:'
125 print 'Most recent 3 commits:'
127 sys.stdout.flush()
126 sys.stdout.flush()
128 sh('git --no-pager log --oneline HEAD~3..')
127 sh('git --no-pager log --oneline HEAD~3..')
129 finally:
128 finally:
130 cd(startdir)
129 cd(startdir)
131
130
132 print
131 print
133 print 'Now verify the build in: %r' % dest
132 print 'Now verify the build in: %r' % dest
134 print "If everything looks good, 'git push'"
133 print "If everything looks good, 'git push'"
@@ -1,99 +1,99 b''
1 """A simple example of how to use IPython.config.application.Application.
1 """A simple example of how to use IPython.config.application.Application.
2
2
3 This should serve as a simple example that shows how the IPython config
3 This should serve as a simple example that shows how the IPython config
4 system works. The main classes are:
4 system works. The main classes are:
5
5
6 * IPython.config.configurable.Configurable
6 * IPython.config.configurable.Configurable
7 * IPython.config.configurable.SingletonConfigurable
7 * IPython.config.configurable.SingletonConfigurable
8 * IPython.config.loader.Config
8 * IPython.config.loader.Config
9 * IPython.config.application.Application
9 * IPython.config.application.Application
10
10
11 To see the command line option help, run this program from the command line::
11 To see the command line option help, run this program from the command line::
12
12
13 $ python appconfig.py -h
13 $ python appconfig.py -h
14
14
15 To make one of your classes configurable (from the command line and config
15 To make one of your classes configurable (from the command line and config
16 files) inherit from Configurable and declare class attributes as traits (see
16 files) inherit from Configurable and declare class attributes as traits (see
17 classes Foo and Bar below). To make the traits configurable, you will need
17 classes Foo and Bar below). To make the traits configurable, you will need
18 to set the following options:
18 to set the following options:
19
19
20 * ``config``: set to ``True`` to make the attribute configurable.
20 * ``config``: set to ``True`` to make the attribute configurable.
21 * ``shortname``: by default, configurable attributes are set using the syntax
21 * ``shortname``: by default, configurable attributes are set using the syntax
22 "Classname.attributename". At the command line, this is a bit verbose, so
22 "Classname.attributename". At the command line, this is a bit verbose, so
23 we allow "shortnames" to be declared. Setting a shortname is optional, but
23 we allow "shortnames" to be declared. Setting a shortname is optional, but
24 when you do this, you can set the option at the command line using the
24 when you do this, you can set the option at the command line using the
25 syntax: "shortname=value".
25 syntax: "shortname=value".
26 * ``help``: set the help string to display a help message when the ``-h``
26 * ``help``: set the help string to display a help message when the ``-h``
27 option is given at the command line. The help string should be valid ReST.
27 option is given at the command line. The help string should be valid ReST.
28
28
29 When the config attribute of an Application is updated, it will fire all of
29 When the config attribute of an Application is updated, it will fire all of
30 the trait's events for all of the config=True attributes.
30 the trait's events for all of the config=True attributes.
31 """
31 """
32
32
33 from IPython.config.configurable import Configurable
33 from IPython.config.configurable import Configurable
34 from IPython.config.application import Application
34 from IPython.config.application import Application
35 from IPython.utils.traitlets import (
35 from IPython.utils.traitlets import (
36 Bool, Unicode, Int, Float, List, Dict
36 Bool, Unicode, Int, List, Dict
37 )
37 )
38
38
39
39
40 class Foo(Configurable):
40 class Foo(Configurable):
41 """A class that has configurable, typed attributes.
41 """A class that has configurable, typed attributes.
42
42
43 """
43 """
44
44
45 i = Int(0, config=True, help="The integer i.")
45 i = Int(0, config=True, help="The integer i.")
46 j = Int(1, config=True, help="The integer j.")
46 j = Int(1, config=True, help="The integer j.")
47 name = Unicode(u'Brian', config=True, help="First name.")
47 name = Unicode(u'Brian', config=True, help="First name.")
48
48
49
49
50 class Bar(Configurable):
50 class Bar(Configurable):
51
51
52 enabled = Bool(True, config=True, help="Enable bar.")
52 enabled = Bool(True, config=True, help="Enable bar.")
53
53
54
54
55 class MyApp(Application):
55 class MyApp(Application):
56
56
57 name = Unicode(u'myapp')
57 name = Unicode(u'myapp')
58 running = Bool(False, config=True,
58 running = Bool(False, config=True,
59 help="Is the app running?")
59 help="Is the app running?")
60 classes = List([Bar, Foo])
60 classes = List([Bar, Foo])
61 config_file = Unicode(u'', config=True,
61 config_file = Unicode(u'', config=True,
62 help="Load this config file")
62 help="Load this config file")
63
63
64 aliases = Dict(dict(i='Foo.i',j='Foo.j',name='Foo.name', running='MyApp.running',
64 aliases = Dict(dict(i='Foo.i',j='Foo.j',name='Foo.name', running='MyApp.running',
65 enabled='Bar.enabled', log_level='MyApp.log_level'))
65 enabled='Bar.enabled', log_level='MyApp.log_level'))
66
66
67 flags = Dict(dict(enable=({'Bar': {'enabled' : True}}, "Enable Bar"),
67 flags = Dict(dict(enable=({'Bar': {'enabled' : True}}, "Enable Bar"),
68 disable=({'Bar': {'enabled' : False}}, "Disable Bar"),
68 disable=({'Bar': {'enabled' : False}}, "Disable Bar"),
69 debug=({'MyApp':{'log_level':10}}, "Set loglevel to DEBUG")
69 debug=({'MyApp':{'log_level':10}}, "Set loglevel to DEBUG")
70 ))
70 ))
71
71
72 def init_foo(self):
72 def init_foo(self):
73 # Pass config to other classes for them to inherit the config.
73 # Pass config to other classes for them to inherit the config.
74 self.foo = Foo(config=self.config)
74 self.foo = Foo(config=self.config)
75
75
76 def init_bar(self):
76 def init_bar(self):
77 # Pass config to other classes for them to inherit the config.
77 # Pass config to other classes for them to inherit the config.
78 self.bar = Bar(config=self.config)
78 self.bar = Bar(config=self.config)
79
79
80 def initialize(self, argv=None):
80 def initialize(self, argv=None):
81 self.parse_command_line(argv)
81 self.parse_command_line(argv)
82 if self.config_file:
82 if self.config_file:
83 self.load_config_file(self.config_file)
83 self.load_config_file(self.config_file)
84 self.init_foo()
84 self.init_foo()
85 self.init_bar()
85 self.init_bar()
86
86
87 def start(self):
87 def start(self):
88 print("app.config:")
88 print("app.config:")
89 print(self.config)
89 print(self.config)
90
90
91
91
92 def main():
92 def main():
93 app = MyApp()
93 app = MyApp()
94 app.initialize()
94 app.initialize()
95 app.start()
95 app.start()
96
96
97
97
98 if __name__ == "__main__":
98 if __name__ == "__main__":
99 main()
99 main()
@@ -1,177 +1,177 b''
1 # encoding: utf-8
1 # encoding: utf-8
2 from __future__ import print_function
2 from __future__ import print_function
3
3
4 __docformat__ = "restructuredtext en"
4 __docformat__ = "restructuredtext en"
5
5
6 #-------------------------------------------------------------------------------
6 #-------------------------------------------------------------------------------
7 # Copyright (C) 2008 The IPython Development Team
7 # Copyright (C) 2008 The IPython Development Team
8 #
8 #
9 # Distributed under the terms of the BSD License. The full license is in
9 # Distributed under the terms of the BSD License. The full license is in
10 # the file COPYING, distributed as part of this software.
10 # the file COPYING, distributed as part of this software.
11 #-------------------------------------------------------------------------------
11 #-------------------------------------------------------------------------------
12
12
13 #-------------------------------------------------------------------------------
13 #-------------------------------------------------------------------------------
14 # Imports
14 # Imports
15 #-------------------------------------------------------------------------------
15 #-------------------------------------------------------------------------------
16
16
17 import sys, os
17 import sys
18 from textwrap import fill
18 from textwrap import fill
19
19
20 display_status=True
20 display_status=True
21
21
22 def check_display(f):
22 def check_display(f):
23 """decorator to allow display methods to be muted by mod.display_status"""
23 """decorator to allow display methods to be muted by mod.display_status"""
24 def maybe_display(*args, **kwargs):
24 def maybe_display(*args, **kwargs):
25 if display_status:
25 if display_status:
26 return f(*args, **kwargs)
26 return f(*args, **kwargs)
27 return maybe_display
27 return maybe_display
28
28
29 @check_display
29 @check_display
30 def print_line(char='='):
30 def print_line(char='='):
31 print(char * 76)
31 print(char * 76)
32
32
33 @check_display
33 @check_display
34 def print_status(package, status):
34 def print_status(package, status):
35 initial_indent = "%22s: " % package
35 initial_indent = "%22s: " % package
36 indent = ' ' * 24
36 indent = ' ' * 24
37 print(fill(str(status), width=76,
37 print(fill(str(status), width=76,
38 initial_indent=initial_indent,
38 initial_indent=initial_indent,
39 subsequent_indent=indent))
39 subsequent_indent=indent))
40
40
41 @check_display
41 @check_display
42 def print_message(message):
42 def print_message(message):
43 indent = ' ' * 24 + "* "
43 indent = ' ' * 24 + "* "
44 print(fill(str(message), width=76,
44 print(fill(str(message), width=76,
45 initial_indent=indent,
45 initial_indent=indent,
46 subsequent_indent=indent))
46 subsequent_indent=indent))
47
47
48 @check_display
48 @check_display
49 def print_raw(section):
49 def print_raw(section):
50 print(section)
50 print(section)
51
51
52 #-------------------------------------------------------------------------------
52 #-------------------------------------------------------------------------------
53 # Tests for specific packages
53 # Tests for specific packages
54 #-------------------------------------------------------------------------------
54 #-------------------------------------------------------------------------------
55
55
56 def check_for_ipython():
56 def check_for_ipython():
57 try:
57 try:
58 import IPython
58 import IPython
59 except ImportError:
59 except ImportError:
60 print_status("IPython", "Not found")
60 print_status("IPython", "Not found")
61 return False
61 return False
62 else:
62 else:
63 print_status("IPython", IPython.__version__)
63 print_status("IPython", IPython.__version__)
64 return True
64 return True
65
65
66 def check_for_sphinx():
66 def check_for_sphinx():
67 try:
67 try:
68 import sphinx
68 import sphinx
69 except ImportError:
69 except ImportError:
70 print_status('sphinx', "Not found (required for docs and nbconvert)")
70 print_status('sphinx', "Not found (required for docs and nbconvert)")
71 return False
71 return False
72 else:
72 else:
73 print_status('sphinx', sphinx.__version__)
73 print_status('sphinx', sphinx.__version__)
74 return True
74 return True
75
75
76 def check_for_pygments():
76 def check_for_pygments():
77 try:
77 try:
78 import pygments
78 import pygments
79 except ImportError:
79 except ImportError:
80 print_status('pygments', "Not found (required for docs and nbconvert)")
80 print_status('pygments', "Not found (required for docs and nbconvert)")
81 return False
81 return False
82 else:
82 else:
83 print_status('pygments', pygments.__version__)
83 print_status('pygments', pygments.__version__)
84 return True
84 return True
85
85
86 def check_for_jinja2():
86 def check_for_jinja2():
87 try:
87 try:
88 import jinja2
88 import jinja2
89 except ImportError:
89 except ImportError:
90 print_status('jinja2', "Not found (required for notebook and nbconvert)")
90 print_status('jinja2', "Not found (required for notebook and nbconvert)")
91 return False
91 return False
92 else:
92 else:
93 print_status('jinja2', jinja2.__version__)
93 print_status('jinja2', jinja2.__version__)
94 return True
94 return True
95
95
96 def check_for_nose():
96 def check_for_nose():
97 try:
97 try:
98 import nose
98 import nose
99 except ImportError:
99 except ImportError:
100 print_status('nose', "Not found (required for running the test suite)")
100 print_status('nose', "Not found (required for running the test suite)")
101 return False
101 return False
102 else:
102 else:
103 print_status('nose', nose.__version__)
103 print_status('nose', nose.__version__)
104 return True
104 return True
105
105
106 def check_for_pexpect():
106 def check_for_pexpect():
107 try:
107 try:
108 import pexpect
108 import pexpect
109 except ImportError:
109 except ImportError:
110 print_status("pexpect", "no (will use bundled version in IPython.external)")
110 print_status("pexpect", "no (will use bundled version in IPython.external)")
111 return False
111 return False
112 else:
112 else:
113 print_status("pexpect", pexpect.__version__)
113 print_status("pexpect", pexpect.__version__)
114 return True
114 return True
115
115
116 def check_for_pyzmq():
116 def check_for_pyzmq():
117 try:
117 try:
118 import zmq
118 import zmq
119 except ImportError:
119 except ImportError:
120 print_status('pyzmq', "no (required for qtconsole, notebook, and parallel computing capabilities)")
120 print_status('pyzmq', "no (required for qtconsole, notebook, and parallel computing capabilities)")
121 return False
121 return False
122 else:
122 else:
123 # pyzmq 2.1.10 adds pyzmq_version_info funtion for returning
123 # pyzmq 2.1.10 adds pyzmq_version_info funtion for returning
124 # version as a tuple
124 # version as a tuple
125 if hasattr(zmq, 'pyzmq_version_info') and zmq.pyzmq_version_info() >= (2,1,11):
125 if hasattr(zmq, 'pyzmq_version_info') and zmq.pyzmq_version_info() >= (2,1,11):
126 print_status("pyzmq", zmq.__version__)
126 print_status("pyzmq", zmq.__version__)
127 return True
127 return True
128 else:
128 else:
129 print_status('pyzmq', "no (have %s, but require >= 2.1.11 for"
129 print_status('pyzmq', "no (have %s, but require >= 2.1.11 for"
130 " qtconsole, notebook, and parallel computing capabilities)" % zmq.__version__)
130 " qtconsole, notebook, and parallel computing capabilities)" % zmq.__version__)
131 return False
131 return False
132
132
133 def check_for_tornado():
133 def check_for_tornado():
134 try:
134 try:
135 import tornado
135 import tornado
136 except ImportError:
136 except ImportError:
137 print_status('tornado', "no (required for notebook)")
137 print_status('tornado', "no (required for notebook)")
138 return False
138 return False
139 else:
139 else:
140 if getattr(tornado, 'version_info', (0,)) < (3,1):
140 if getattr(tornado, 'version_info', (0,)) < (3,1):
141 print_status('tornado', "no (have %s, but require >= 3.1.0)" % tornado.version)
141 print_status('tornado', "no (have %s, but require >= 3.1.0)" % tornado.version)
142 return False
142 return False
143 else:
143 else:
144 print_status('tornado', tornado.version)
144 print_status('tornado', tornado.version)
145 return True
145 return True
146
146
147 def check_for_readline():
147 def check_for_readline():
148 from distutils.version import LooseVersion
148 from distutils.version import LooseVersion
149 readline = None
149 readline = None
150 try:
150 try:
151 import gnureadline as readline
151 import gnureadline as readline
152 except ImportError:
152 except ImportError:
153 pass
153 pass
154 if readline is None:
154 if readline is None:
155 try:
155 try:
156 import readline
156 import readline
157 except ImportError:
157 except ImportError:
158 pass
158 pass
159 if readline is None:
159 if readline is None:
160 try:
160 try:
161 import pyreadline
161 import pyreadline
162 vs = pyreadline.release.version
162 vs = pyreadline.release.version
163 except (ImportError, AttributeError):
163 except (ImportError, AttributeError):
164 print_status('readline', "no (required for good interactive behavior)")
164 print_status('readline', "no (required for good interactive behavior)")
165 return False
165 return False
166 if LooseVersion(vs).version >= [1,7,1]:
166 if LooseVersion(vs).version >= [1,7,1]:
167 print_status('readline', "yes pyreadline-" + vs)
167 print_status('readline', "yes pyreadline-" + vs)
168 return True
168 return True
169 else:
169 else:
170 print_status('readline', "no pyreadline-%s < 1.7.1" % vs)
170 print_status('readline', "no pyreadline-%s < 1.7.1" % vs)
171 return False
171 return False
172 else:
172 else:
173 if sys.platform == 'darwin' and 'libedit' in readline.__doc__:
173 if sys.platform == 'darwin' and 'libedit' in readline.__doc__:
174 print_status('readline', "no (libedit detected)")
174 print_status('readline', "no (libedit detected)")
175 return False
175 return False
176 print_status('readline', "yes")
176 print_status('readline', "yes")
177 return True
177 return True
@@ -1,213 +1,211 b''
1 #!/usr/bin/env python
1 #!/usr/bin/env python
2 """Simple tools to query github.com and gather stats about issues.
2 """Simple tools to query github.com and gather stats about issues.
3
3
4 To generate a report for IPython 2.0, run:
4 To generate a report for IPython 2.0, run:
5
5
6 python github_stats.py --milestone 2.0 --since-tag rel-1.0.0
6 python github_stats.py --milestone 2.0 --since-tag rel-1.0.0
7 """
7 """
8 #-----------------------------------------------------------------------------
8 #-----------------------------------------------------------------------------
9 # Imports
9 # Imports
10 #-----------------------------------------------------------------------------
10 #-----------------------------------------------------------------------------
11
11
12 from __future__ import print_function
12 from __future__ import print_function
13
13
14 import codecs
14 import codecs
15 import json
16 import re
17 import sys
15 import sys
18
16
19 from argparse import ArgumentParser
17 from argparse import ArgumentParser
20 from datetime import datetime, timedelta
18 from datetime import datetime, timedelta
21 from subprocess import check_output
19 from subprocess import check_output
22 from gh_api import (
20 from gh_api import (
23 get_paged_request, make_auth_header, get_pull_request, is_pull_request,
21 get_paged_request, make_auth_header, get_pull_request, is_pull_request,
24 get_milestone_id, get_issues_list,
22 get_milestone_id, get_issues_list,
25 )
23 )
26 #-----------------------------------------------------------------------------
24 #-----------------------------------------------------------------------------
27 # Globals
25 # Globals
28 #-----------------------------------------------------------------------------
26 #-----------------------------------------------------------------------------
29
27
30 ISO8601 = "%Y-%m-%dT%H:%M:%SZ"
28 ISO8601 = "%Y-%m-%dT%H:%M:%SZ"
31 PER_PAGE = 100
29 PER_PAGE = 100
32
30
33 #-----------------------------------------------------------------------------
31 #-----------------------------------------------------------------------------
34 # Functions
32 # Functions
35 #-----------------------------------------------------------------------------
33 #-----------------------------------------------------------------------------
36
34
37 def round_hour(dt):
35 def round_hour(dt):
38 return dt.replace(minute=0,second=0,microsecond=0)
36 return dt.replace(minute=0,second=0,microsecond=0)
39
37
40 def _parse_datetime(s):
38 def _parse_datetime(s):
41 """Parse dates in the format returned by the Github API."""
39 """Parse dates in the format returned by the Github API."""
42 if s:
40 if s:
43 return datetime.strptime(s, ISO8601)
41 return datetime.strptime(s, ISO8601)
44 else:
42 else:
45 return datetime.fromtimestamp(0)
43 return datetime.fromtimestamp(0)
46
44
47 def issues2dict(issues):
45 def issues2dict(issues):
48 """Convert a list of issues to a dict, keyed by issue number."""
46 """Convert a list of issues to a dict, keyed by issue number."""
49 idict = {}
47 idict = {}
50 for i in issues:
48 for i in issues:
51 idict[i['number']] = i
49 idict[i['number']] = i
52 return idict
50 return idict
53
51
54 def split_pulls(all_issues, project="ipython/ipython"):
52 def split_pulls(all_issues, project="ipython/ipython"):
55 """split a list of closed issues into non-PR Issues and Pull Requests"""
53 """split a list of closed issues into non-PR Issues and Pull Requests"""
56 pulls = []
54 pulls = []
57 issues = []
55 issues = []
58 for i in all_issues:
56 for i in all_issues:
59 if is_pull_request(i):
57 if is_pull_request(i):
60 pull = get_pull_request(project, i['number'], auth=True)
58 pull = get_pull_request(project, i['number'], auth=True)
61 pulls.append(pull)
59 pulls.append(pull)
62 else:
60 else:
63 issues.append(i)
61 issues.append(i)
64 return issues, pulls
62 return issues, pulls
65
63
66
64
67 def issues_closed_since(period=timedelta(days=365), project="ipython/ipython", pulls=False):
65 def issues_closed_since(period=timedelta(days=365), project="ipython/ipython", pulls=False):
68 """Get all issues closed since a particular point in time. period
66 """Get all issues closed since a particular point in time. period
69 can either be a datetime object, or a timedelta object. In the
67 can either be a datetime object, or a timedelta object. In the
70 latter case, it is used as a time before the present.
68 latter case, it is used as a time before the present.
71 """
69 """
72
70
73 which = 'pulls' if pulls else 'issues'
71 which = 'pulls' if pulls else 'issues'
74
72
75 if isinstance(period, timedelta):
73 if isinstance(period, timedelta):
76 since = round_hour(datetime.utcnow() - period)
74 since = round_hour(datetime.utcnow() - period)
77 else:
75 else:
78 since = period
76 since = period
79 url = "https://api.github.com/repos/%s/%s?state=closed&sort=updated&since=%s&per_page=%i" % (project, which, since.strftime(ISO8601), PER_PAGE)
77 url = "https://api.github.com/repos/%s/%s?state=closed&sort=updated&since=%s&per_page=%i" % (project, which, since.strftime(ISO8601), PER_PAGE)
80 allclosed = get_paged_request(url, headers=make_auth_header())
78 allclosed = get_paged_request(url, headers=make_auth_header())
81
79
82 filtered = [ i for i in allclosed if _parse_datetime(i['closed_at']) > since ]
80 filtered = [ i for i in allclosed if _parse_datetime(i['closed_at']) > since ]
83 if pulls:
81 if pulls:
84 filtered = [ i for i in filtered if _parse_datetime(i['merged_at']) > since ]
82 filtered = [ i for i in filtered if _parse_datetime(i['merged_at']) > since ]
85 # filter out PRs not against master (backports)
83 # filter out PRs not against master (backports)
86 filtered = [ i for i in filtered if i['base']['ref'] == 'master' ]
84 filtered = [ i for i in filtered if i['base']['ref'] == 'master' ]
87 else:
85 else:
88 filtered = [ i for i in filtered if not is_pull_request(i) ]
86 filtered = [ i for i in filtered if not is_pull_request(i) ]
89
87
90 return filtered
88 return filtered
91
89
92
90
93 def sorted_by_field(issues, field='closed_at', reverse=False):
91 def sorted_by_field(issues, field='closed_at', reverse=False):
94 """Return a list of issues sorted by closing date date."""
92 """Return a list of issues sorted by closing date date."""
95 return sorted(issues, key = lambda i:i[field], reverse=reverse)
93 return sorted(issues, key = lambda i:i[field], reverse=reverse)
96
94
97
95
98 def report(issues, show_urls=False):
96 def report(issues, show_urls=False):
99 """Summary report about a list of issues, printing number and title.
97 """Summary report about a list of issues, printing number and title.
100 """
98 """
101 # titles may have unicode in them, so we must encode everything below
99 # titles may have unicode in them, so we must encode everything below
102 if show_urls:
100 if show_urls:
103 for i in issues:
101 for i in issues:
104 role = 'ghpull' if 'merged_at' in i else 'ghissue'
102 role = 'ghpull' if 'merged_at' in i else 'ghissue'
105 print('* :%s:`%d`: %s' % (role, i['number'],
103 print('* :%s:`%d`: %s' % (role, i['number'],
106 i['title'].encode('utf-8')))
104 i['title'].encode('utf-8')))
107 else:
105 else:
108 for i in issues:
106 for i in issues:
109 print('* %d: %s' % (i['number'], i['title'].encode('utf-8')))
107 print('* %d: %s' % (i['number'], i['title'].encode('utf-8')))
110
108
111 #-----------------------------------------------------------------------------
109 #-----------------------------------------------------------------------------
112 # Main script
110 # Main script
113 #-----------------------------------------------------------------------------
111 #-----------------------------------------------------------------------------
114
112
115 if __name__ == "__main__":
113 if __name__ == "__main__":
116 # deal with unicode
114 # deal with unicode
117 sys.stdout = codecs.getwriter('utf8')(sys.stdout)
115 sys.stdout = codecs.getwriter('utf8')(sys.stdout)
118
116
119 # Whether to add reST urls for all issues in printout.
117 # Whether to add reST urls for all issues in printout.
120 show_urls = True
118 show_urls = True
121
119
122 parser = ArgumentParser()
120 parser = ArgumentParser()
123 parser.add_argument('--since-tag', type=str,
121 parser.add_argument('--since-tag', type=str,
124 help="The git tag to use for the starting point (typically the last major release)."
122 help="The git tag to use for the starting point (typically the last major release)."
125 )
123 )
126 parser.add_argument('--milestone', type=str,
124 parser.add_argument('--milestone', type=str,
127 help="The GitHub milestone to use for filtering issues [optional]."
125 help="The GitHub milestone to use for filtering issues [optional]."
128 )
126 )
129 parser.add_argument('--days', type=int,
127 parser.add_argument('--days', type=int,
130 help="The number of days of data to summarize (use this or --since-tag)."
128 help="The number of days of data to summarize (use this or --since-tag)."
131 )
129 )
132 parser.add_argument('--project', type=str, default="ipython/ipython",
130 parser.add_argument('--project', type=str, default="ipython/ipython",
133 help="The project to summarize."
131 help="The project to summarize."
134 )
132 )
135
133
136 opts = parser.parse_args()
134 opts = parser.parse_args()
137 tag = opts.since_tag
135 tag = opts.since_tag
138
136
139 # set `since` from days or git tag
137 # set `since` from days or git tag
140 if opts.days:
138 if opts.days:
141 since = datetime.utcnow() - timedelta(days=opts.days)
139 since = datetime.utcnow() - timedelta(days=opts.days)
142 else:
140 else:
143 if not tag:
141 if not tag:
144 tag = check_output(['git', 'describe', '--abbrev=0']).strip()
142 tag = check_output(['git', 'describe', '--abbrev=0']).strip()
145 cmd = ['git', 'log', '-1', '--format=%ai', tag]
143 cmd = ['git', 'log', '-1', '--format=%ai', tag]
146 tagday, tz = check_output(cmd).strip().rsplit(' ', 1)
144 tagday, tz = check_output(cmd).strip().rsplit(' ', 1)
147 since = datetime.strptime(tagday, "%Y-%m-%d %H:%M:%S")
145 since = datetime.strptime(tagday, "%Y-%m-%d %H:%M:%S")
148 h = int(tz[1:3])
146 h = int(tz[1:3])
149 m = int(tz[3:])
147 m = int(tz[3:])
150 td = timedelta(hours=h, minutes=m)
148 td = timedelta(hours=h, minutes=m)
151 if tz[0] == '-':
149 if tz[0] == '-':
152 since += td
150 since += td
153 else:
151 else:
154 since -= td
152 since -= td
155
153
156 since = round_hour(since)
154 since = round_hour(since)
157
155
158 milestone = opts.milestone
156 milestone = opts.milestone
159 project = opts.project
157 project = opts.project
160
158
161 print("fetching GitHub stats since %s (tag: %s, milestone: %s)" % (since, tag, milestone), file=sys.stderr)
159 print("fetching GitHub stats since %s (tag: %s, milestone: %s)" % (since, tag, milestone), file=sys.stderr)
162 if milestone:
160 if milestone:
163 milestone_id = get_milestone_id(project=project, milestone=milestone,
161 milestone_id = get_milestone_id(project=project, milestone=milestone,
164 auth=True)
162 auth=True)
165 issues = get_issues_list(project=project,
163 issues = get_issues_list(project=project,
166 milestone=milestone_id,
164 milestone=milestone_id,
167 state='closed',
165 state='closed',
168 auth=True,
166 auth=True,
169 )
167 )
170 else:
168 else:
171 issues = issues_closed_since(since, project=project, pulls=False)
169 issues = issues_closed_since(since, project=project, pulls=False)
172 pulls = issues_closed_since(since, project=project, pulls=True)
170 pulls = issues_closed_since(since, project=project, pulls=True)
173
171
174 # For regular reports, it's nice to show them in reverse chronological order
172 # For regular reports, it's nice to show them in reverse chronological order
175 issues = sorted_by_field(issues, reverse=True)
173 issues = sorted_by_field(issues, reverse=True)
176 pulls = sorted_by_field(pulls, reverse=True)
174 pulls = sorted_by_field(pulls, reverse=True)
177
175
178 n_issues, n_pulls = map(len, (issues, pulls))
176 n_issues, n_pulls = map(len, (issues, pulls))
179 n_total = n_issues + n_pulls
177 n_total = n_issues + n_pulls
180
178
181 # Print summary report we can directly include into release notes.
179 # Print summary report we can directly include into release notes.
182
180
183 print()
181 print()
184 since_day = since.strftime("%Y/%m/%d")
182 since_day = since.strftime("%Y/%m/%d")
185 today = datetime.today().strftime("%Y/%m/%d")
183 today = datetime.today().strftime("%Y/%m/%d")
186 print("GitHub stats for %s - %s (tag: %s)" % (since_day, today, tag))
184 print("GitHub stats for %s - %s (tag: %s)" % (since_day, today, tag))
187 print()
185 print()
188 print("These lists are automatically generated, and may be incomplete or contain duplicates.")
186 print("These lists are automatically generated, and may be incomplete or contain duplicates.")
189 print()
187 print()
190 if tag:
188 if tag:
191 # print git info, in addition to GitHub info:
189 # print git info, in addition to GitHub info:
192 since_tag = tag+'..'
190 since_tag = tag+'..'
193 cmd = ['git', 'log', '--oneline', since_tag]
191 cmd = ['git', 'log', '--oneline', since_tag]
194 ncommits = len(check_output(cmd).splitlines())
192 ncommits = len(check_output(cmd).splitlines())
195
193
196 author_cmd = ['git', 'log', '--use-mailmap', "--format=* %aN", since_tag]
194 author_cmd = ['git', 'log', '--use-mailmap', "--format=* %aN", since_tag]
197 all_authors = check_output(author_cmd).decode('utf-8', 'replace').splitlines()
195 all_authors = check_output(author_cmd).decode('utf-8', 'replace').splitlines()
198 unique_authors = sorted(set(all_authors), key=lambda s: s.lower())
196 unique_authors = sorted(set(all_authors), key=lambda s: s.lower())
199 print("The following %i authors contributed %i commits." % (len(unique_authors), ncommits))
197 print("The following %i authors contributed %i commits." % (len(unique_authors), ncommits))
200 print()
198 print()
201 print('\n'.join(unique_authors))
199 print('\n'.join(unique_authors))
202 print()
200 print()
203
201
204 print()
202 print()
205 print("We closed a total of %d issues, %d pull requests and %d regular issues;\n"
203 print("We closed a total of %d issues, %d pull requests and %d regular issues;\n"
206 "this is the full list (generated with the script \n"
204 "this is the full list (generated with the script \n"
207 ":file:`tools/github_stats.py`):" % (n_total, n_pulls, n_issues))
205 ":file:`tools/github_stats.py`):" % (n_total, n_pulls, n_issues))
208 print()
206 print()
209 print('Pull Requests (%d):\n' % n_pulls)
207 print('Pull Requests (%d):\n' % n_pulls)
210 report(pulls, show_urls)
208 report(pulls, show_urls)
211 print()
209 print()
212 print('Issues (%d):\n' % n_issues)
210 print('Issues (%d):\n' % n_issues)
213 report(issues, show_urls)
211 report(issues, show_urls)
@@ -1,25 +1,23 b''
1 #!/usr/bin/env python
1 #!/usr/bin/env python
2 """Simple script to create a tarball with proper git info.
2 """Simple script to create a tarball with proper git info.
3 """
3 """
4
4
5 import commands
5 import commands
6 import os
6 import os
7 import sys
8 import shutil
9
7
10 from toollib import *
8 from toollib import cd, sh
11
9
12 tag = commands.getoutput('git describe --tags')
10 tag = commands.getoutput('git describe --tags')
13 base_name = 'ipython-%s' % tag
11 base_name = 'ipython-%s' % tag
14 tar_name = '%s.tgz' % base_name
12 tar_name = '%s.tgz' % base_name
15
13
16 # git archive is weird: Even if I give it a specific path, it still won't
14 # git archive is weird: Even if I give it a specific path, it still won't
17 # archive the whole tree. It seems the only way to get the whole tree is to cd
15 # archive the whole tree. It seems the only way to get the whole tree is to cd
18 # to the top of the tree. There are long threads (since 2007) on the git list
16 # to the top of the tree. There are long threads (since 2007) on the git list
19 # about this and it still doesn't work in a sensible way...
17 # about this and it still doesn't work in a sensible way...
20
18
21 start_dir = os.getcwdu()
19 start_dir = os.getcwdu()
22 cd('..')
20 cd('..')
23 git_tpl = 'git archive --format=tar --prefix={0}/ HEAD | gzip > {1}'
21 git_tpl = 'git archive --format=tar --prefix={0}/ HEAD | gzip > {1}'
24 sh(git_tpl.format(base_name, tar_name))
22 sh(git_tpl.format(base_name, tar_name))
25 sh('mv {0} tools/'.format(tar_name))
23 sh('mv {0} tools/'.format(tar_name))
@@ -1,79 +1,77 b''
1 """Various utilities common to IPython release and maintenance tools.
1 """Various utilities common to IPython release and maintenance tools.
2 """
2 """
3 from __future__ import print_function
3 from __future__ import print_function
4
4
5 # Library imports
5 # Library imports
6 import os
6 import os
7 import sys
7 import sys
8
8
9 from distutils.dir_util import remove_tree
10
11 # Useful shorthands
9 # Useful shorthands
12 pjoin = os.path.join
10 pjoin = os.path.join
13 cd = os.chdir
11 cd = os.chdir
14
12
15 # Constants
13 # Constants
16
14
17 # SSH root address of the archive site
15 # SSH root address of the archive site
18 archive_user = 'ipython@archive.ipython.org'
16 archive_user = 'ipython@archive.ipython.org'
19 archive_dir = 'archive.ipython.org'
17 archive_dir = 'archive.ipython.org'
20 archive = '%s:%s' % (archive_user, archive_dir)
18 archive = '%s:%s' % (archive_user, archive_dir)
21
19
22 # Build commands
20 # Build commands
23 # Source dists
21 # Source dists
24 sdists = './setup.py sdist --formats=gztar,zip'
22 sdists = './setup.py sdist --formats=gztar,zip'
25 # Eggs
23 # Eggs
26 eggs = './setupegg.py bdist_egg'
24 eggs = './setupegg.py bdist_egg'
27
25
28 # Windows builds.
26 # Windows builds.
29 # We do them separately, so that the extra Windows scripts don't get pulled
27 # We do them separately, so that the extra Windows scripts don't get pulled
30 # into Unix builds (setup.py has code which checks for bdist_wininst). Note
28 # into Unix builds (setup.py has code which checks for bdist_wininst). Note
31 # that the install scripts args are added to the main distutils call in
29 # that the install scripts args are added to the main distutils call in
32 # setup.py, so they don't need to be passed here.
30 # setup.py, so they don't need to be passed here.
33 #
31 #
34 # The Windows 64-bit installer can't be built by a Linux/Mac Python because ofa
32 # The Windows 64-bit installer can't be built by a Linux/Mac Python because ofa
35 # bug in distutils: http://bugs.python.org/issue6792.
33 # bug in distutils: http://bugs.python.org/issue6792.
36 # So we have to build it with a wine-installed native Windows Python...
34 # So we have to build it with a wine-installed native Windows Python...
37 win_builds = ["python setup.py bdist_wininst "
35 win_builds = ["python setup.py bdist_wininst "
38 "--install-script=ipython_win_post_install.py",
36 "--install-script=ipython_win_post_install.py",
39 r"%s/.wine/dosdevices/c\:/Python32/python.exe setup.py build "
37 r"%s/.wine/dosdevices/c\:/Python32/python.exe setup.py build "
40 "--plat-name=win-amd64 bdist_wininst "
38 "--plat-name=win-amd64 bdist_wininst "
41 "--install-script=ipython_win_post_install.py" %
39 "--install-script=ipython_win_post_install.py" %
42 os.environ['HOME'] ]
40 os.environ['HOME'] ]
43
41
44 # Utility functions
42 # Utility functions
45 def sh(cmd):
43 def sh(cmd):
46 """Run system command in shell, raise SystemExit if it returns an error."""
44 """Run system command in shell, raise SystemExit if it returns an error."""
47 print("$", cmd)
45 print("$", cmd)
48 stat = os.system(cmd)
46 stat = os.system(cmd)
49 #stat = 0 # Uncomment this and comment previous to run in debug mode
47 #stat = 0 # Uncomment this and comment previous to run in debug mode
50 if stat:
48 if stat:
51 raise SystemExit("Command %s failed with code: %s" % (cmd, stat))
49 raise SystemExit("Command %s failed with code: %s" % (cmd, stat))
52
50
53 # Backwards compatibility
51 # Backwards compatibility
54 c = sh
52 c = sh
55
53
56 def get_ipdir():
54 def get_ipdir():
57 """Get IPython directory from command line, or assume it's the one above."""
55 """Get IPython directory from command line, or assume it's the one above."""
58
56
59 # Initialize arguments and check location
57 # Initialize arguments and check location
60 try:
58 try:
61 ipdir = sys.argv[1]
59 ipdir = sys.argv[1]
62 except IndexError:
60 except IndexError:
63 ipdir = '..'
61 ipdir = '..'
64
62
65 ipdir = os.path.abspath(ipdir)
63 ipdir = os.path.abspath(ipdir)
66
64
67 cd(ipdir)
65 cd(ipdir)
68 if not os.path.isdir('IPython') and os.path.isfile('setup.py'):
66 if not os.path.isdir('IPython') and os.path.isfile('setup.py'):
69 raise SystemExit('Invalid ipython directory: %s' % ipdir)
67 raise SystemExit('Invalid ipython directory: %s' % ipdir)
70 return ipdir
68 return ipdir
71
69
72
70
73 def compile_tree():
71 def compile_tree():
74 """Compile all Python files below current directory."""
72 """Compile all Python files below current directory."""
75 stat = os.system('python -m compileall .')
73 stat = os.system('python -m compileall .')
76 if stat:
74 if stat:
77 msg = '*** ERROR: Some Python files in tree do NOT compile! ***\n'
75 msg = '*** ERROR: Some Python files in tree do NOT compile! ***\n'
78 msg += 'See messages above for the actual file that produced it.\n'
76 msg += 'See messages above for the actual file that produced it.\n'
79 raise SystemExit(msg)
77 raise SystemExit(msg)
General Comments 0
You need to be logged in to leave comments. Login now